Maintainer's Corner

Readme for configuration-tools-0.2.10

Overview

This package provides a collection of utilities on top of the packages
optparse-applicative,
aeson, and
yaml for configuring libraries and
applications in a composable way.

The main features are

configuration management through integration of command line option
parsing and configuration files,

a Setup.hs file that generates a PkgInfo module for each component
of a package that provide information about the package and the build, and

a set of types for configuration of HTTP services and clients along
with aeson instances and command line option parsers.

The ultimate goal for this package is a general framework for
compositional configuration management for software components.
Instead of designing such a framework from scratch the approach of this
package is to first explore design and implementation patterns based
on practical examples and by gluing together existing technology.

Therefor at the current state this package mostly provides operators and
coding patterns for writing stylish boilerplate code.

Once we feel that the developed patterns cover a sufficient portion of
real world requirements we plan to rewrite this package such that
the boilerplate is hidden behind a clean and simple DSL.

Installation

Assuming that you have a recent version version of GHC and Cabal
installed in your system this package can be install from Hackage
via

cabal install configuration-tools

If you don't need support for remote configuration files this package
can be build with a much smaller set of dependencies via

Configuration Management

The goal of this package is to make management of configurations easy by
providing an idiomatic style of defining and deploying configurations.

For each data type that is used as a configuration type the following must be
provided:

a default value,

a FromJSON instance that yields a function that takes a value and
updates that value with the parsed values,

a ToJSON instance, and

an options parser that yields a function that takes a value and updates
that value with the values provided as command line options.

Optionally, a function for validating the configuration value may be
provided.

The package provides operators and functions that make the implementation of
these requisites easy for the common case that the configuration is encoded
mainly through nested records.

In addition to the user defined command line options the following
options are recognized by the application:

--config-file, -c
parses the given file as a --possibly partial-- configuration in YAML or
JSON format. The file location can be provided either as a local file
system path or as a remote HTTP or HTTPS URL. In addition a list of static
configuration file locations can be defined in the code.

If this option is provided more than a single time the configuration
files are loaded in the order as the respective options appear
on the command line, where settings that are loaded later have
precedence over earlier settings. Files from static locations are
loaded before files that are specified on the command line.

print-config, -p
configures the application and prints the configuration in YAML format
to standard out and exits. The printed configuration is exactly the
configuration that otherwise would be used to run the application.

--help, -h
prints a help message and exits.

As long as the package wasn't build with -f-remote-configs the following
two options are available. They affect how configuration files
are loaded from remote URLs.

--config-https-insecure=true|false
Bypass certificate validation for all HTTPS
connections to all services.

--config-https-allow-cert=HOSTNAME:PORT:FINGERPRINT
Unconditionally trust the certificate for connecting
to the service.

The operators provided in this package assume that
lenses are provided for field of the
configuration record types.

An complete usage example can be found in the file
example/Example.hs
of the cabal package.

Usage Example

Remark: there are unicode equivalents for some operators available in
Configuration.Utils that lead to better aligned and more readable code.

Next we define the types that are used for the configuration of our application.
In this contrived example these types define a simplified version of HTTP URLs.

data Auth = Auth
{ _user :: !String
, _pwd :: !String
}

We have to define lenses for the configuration types. Here we do it explicitly.
Alternatively one could have used TemplateHaskell along with makeLenses from
the module Control.Lens from the lens
package.

(Note, that the module Configuration.Utils defines its own type synonyms for
lenses. If you import Control.Lens you should hide Lens and Lens' from
either module.)

We must provide a default value. If there is no reasonable default the
respective value could, for instance, be wrapped into Maybe. Here we
use the monoid identity value of the type.

defaultAuth :: Auth
defaultAuth = Auth
{ _user = ""
, _pwd = ""
}

Now we define an aesonFromJSON
instance that yields a function that updates a given Auth value with the
values from the parsed JSON value. The <*< operator is functional composition
lifted for applicative functors and % is a version of $ with a different
precedence that helps to reduce the use of parenthesis in applicative style
code.

Finally we define a command line option parser using the machinery from
the optparse-applicative
package. Similar to the FromJSON instance the parser does not yield a value
directly but instead yields a function that updates a given Auth value with
the value from the command line.

You may consult the documentation of the
optparse-applicative
package for further information on how to define command line options.

The following definitions for the HttpURL are similar to definitions for
the Auth type above. In addition it is demonstrated how to deal with nested
configuration types. Mainly the usage of ..: is replaced by %.: and
.:: is replaced by %::.

Now that everything is set up the configuration can be used to create a
ProgramInfo value. The ProgramInfo value is than use with the
runWithConfiguratin function to wrap a main function that takes an HttpURL
argument with configuration file and command line parsing.

Using Sum Types as Configuration Types

Sum types can not be used as configuration types in the same way as product types.
The reason is that the nondeterminism in the choice of a term for the type is
not restricted to the chosen constructor arguments but in addition there
is non-determinism in the choice of the constructor, too.

An update function for a product type can be defined point-wise as a mapping from
constructor parameters to values. An update for a sum type must take the
constructor context into account. Moreover, when applied to a given default
value the function may not be applicable at all if the default value uses a
different constructor context than what the update assumes.

For the future we plan to provide a general solution for configurations of sum
types which would be based on the possibility to define default values for more
than a single constructor. For now one must restrict configurations of sum types
to yield constant values instead of point-wise (partial) updates. In practice
this means that for a type a one has to provide an FromJSON instance for a
and use the ..: operator. Similarly for the option parser one has to define a
parser that yields an a and use it with the .:: operator.

The module Configuration.Utils.Maybe provides tools for dealing with
Maybe values.

Package and Build Information

The module Configuration.Utils.Setup an example Setup.hs script that hooks
into the cabal build process at the end of the configuration phase and generates
a module with package information for each component of the cabal package.

The modules are created in the autogen build directory where also the Path_
module is created by cabal's simple build setup. This is usually the directory
./dist/build/autogen.

For a library component the module is named just PkgInfo. For all
other components the module is named PkgInfo_COMPONENT_NAME where
COMPONENT_NAME is the name of the component with - characters replaced by
_.

For instance, if a cabal package contains a library and an executable that
is called my-app, the following modules are created: PkgInfo
and PkgInfo_my_app.

Usage as Setup Script

There are two ways how this module can be used:

Copy the code of this module into a file called Setup.hs in the root
directory of your package.

If the configuration-tools package is already installed in the system
where the build is done, following code can be used as Setup.hs script:

module Main (main) where
import Configuration.Utils.Setup

With both methods the field Build-Type in the package description (cabal) file
must be set to Custom:

Build-Type: Custom

Integration With Configuration.Utils

You can integrate the information provided by the PkgInfo modules with the
command line interface of an application by importing the respective module for
the component and using the runWithPkgInfoConfiguration function from the
module Configuration.Utils as show in the following example:

With that the resulting application supports the following additional command
line options:

--version, -v
prints the version of the application and exits.

--info, -i
prints a short info message for the application and exits.

--long-info
print a detailed info message for the application and exits.
Beside component name, package name, version, revision, and copyright
the message also contain information about the compiler that
was used for the build, the build architecture, build flags,
the author, the license type, and a list of all direct and
indirect dependencies along with their licenses and copyrights.

--license
prints the text of the license of the application and exits.

Here is the example output of --long-info for the example
examples/Trivial.hs from this package:

Configuration Types for HTTP Services and Clients

The module Configuration.Utils.Http contains some types for configuring HTTP
services and clients. Currently these types only provide the most basic
configuration settings. This will probably be extended in the future. Feel free
to submit patches for missing settings.

TODO

This package is in an early stage of development and more features
are planned.

Simplify specification of Configuration data types by
integrating the aeson instances and the option parser.

Come up with a story for sum types. We may use the following approach: The
definition of the default should include alternate values for each
constructor. Effectively, this means to map the sum type onto a product type
by interpreting the summands as factors. For mapping back from the product
type to the original sum type one has to provide a choice of the
constructor. Intuitively, a sum type can be represented as a tree where the
leafs partition the type into classes of value with the same constructors.
By providing a default value for each such class partial configurations that
are defined through point-wise updates can always be applied in a meaningful
way.

Include help text as comments in YAML serialization of configuration
values.