Rewriting to Haskell–Configuration
Rewriting to Haskell (Series)
Rewriting to Haskell–Intro Rewriting to Haskell–Project Setup Rewriting to Haskell–Deployment Rewriting to Haskell–Automatic Formatting Rewriting to Haskell–Configuration Rewriting to Haskell–Standing on the shoulders of Rails Rewriting to Haskell–Making GHC More Nitpicky Rewriting to Haskell–Testing Rewriting to Haskell–Linting Rewriting to Haskell–Parsing Query Params Rewriting to Haskell–Parsing Query Params, Again Rewriting to Haskell–ErrorsComing from Rails we are used to employing yaml files to configure a web application. This is why we decided to do the same with Servant. As a matter of fact, we now have a configuration.yml
file:
database:
username: stream
database: stream_development
password: ""
application:
aws_s3_access_key: "ABCD1234"
aws_s3_secret_key: "EFGH5678"
aws_s3_region: us-east-1
aws_s3_bucket_name: stream-demo-bucket
That is great for development but how can we run test against the test database? Turns out that the package we use to parse the yaml file allows the use of ENV variables:
database:
username: stream
database: _env:DATABASE:stream_development
password: ""
application:
aws_s3_access_key: "ABCD1234"
aws_s3_secret_key: "EFGH5678"
aws_s3_region: us-east-1
aws_s3_bucket_name: stream-demo-bucket
That is, now we can just run DATABASE=stream_test stack test
!
In the repository we actually keep a configuration.yml.example
file and git ignore configuration.yml
to avoid leaking credentials:
database:
username: stream
database: _env:DATABASE:stream_development
password: ""
application:
aws_s3_access_key: "REPLACE_ME"
aws_s3_secret_key: "REPLACE_ME"
aws_s3_region: us-east-1
aws_s3_bucket_name: ll-stream-demo
For production we use Ansible (with Ansible Vault) to put in place the correct configuration.yml
. Plus, we instruct Hapistrano to make that file available for each deployment:
linked_files:
- haskell/configuration.yml
To read the configuration inside the Servant application we use loadYamlSettings
from the yaml package:
loadYamlSettings :: FromJSON settings
=> [FilePath] -- ^ run time config files to use, earlier files have precedence
-> [Value] -- ^ any other values to use, usually from compile time config. overridden by files
-> EnvUsage
-> IO settings
In other words, given a type settings
that is an instance of FromJSON
we can decode yaml files into a value of that type. And this is how we do it for Stream:
data Configuration
= Configuration
configurationDatabaseUser :: String,
{ configurationDatabaseDatabase :: String,
configurationDatabasePassword :: String,
configurationApplicationAwsS3AccessKey :: AccessKey,
configurationApplicationAwsS3SecretKey :: SecretKey,
configurationApplicationAwsS3Region :: Region,
configurationApplicationAwsS3BucketName :: BucketName
}
instance FromJSON Configuration where
Object x) = do
parseJSON (<- x .: "database"
database <- x .: "application"
application Configuration
<$> database .: "username"
<*> database .: "database"
<*> database .: "password"
<*> application .: "aws_s3_access_key"
<*> application .: "aws_s3_secret_key"
<*> application .: "aws_s3_region"
<*> application .: "aws_s3_bucket_name"
loadConfiguration :: IO Configuration
=
loadConfiguration "./configuration.yml"] [] useEnv loadYamlSettings [