Amazonka 1.0 Released

After 4 months, nearly 900 commits and an inordinate number of ExitFailure (-9) build errors, version 1.0 of the Haskell Amazonka AWS SDK has been released.

Some of the features include significant changes to the underlying generation mechanisms, along with changes to the external surface APIs which are outlined below.

Looking back at the initial commits for Amazonka show that it’s taken 2 years and nearly 3,300 commits reach this milestone. The entire suite now consists of 55 libraries over 200K LOC and is in use by a diverse set of individuals and companies.

I’d like to thank everybody who contributed to the release. If you have feedback or encounter any problems, please open a GitHub issue, reach out via the maintainer email located in the cabal files, or join the freshly minted Gitter chat.

A whirlwind summary of some of the changes you can find in 1.0 follows, in no particular order.

Contents

Errors

Previously the individual services either had a service-specific error type such as EC2Error, a generated type, or shared one of the RESTError or XMLError types.

In place of these, there is now a single unified Error type containing HTTP, serialisation and service specific errors.

In addition to this change to the underlying errors, changes have also been made to the exposed interfaces in amazonka, which commonly had signatures such as Either Error (Rs a) and in turn the AWST transformer lifted this result into an internal ExceptT.

Since the previous approach was not amenable to composition due to the concrete Error, functional dependencies and instances MonadError/MonadReader, the library still passes around Either Error a internally, but externally it exposes a MonadThrow constraint and I recommend using Control.Exception.Lens and the various Prisms available from AsError to catch/handle specific errors.

For example:

trying _Error (send $ ListObjects "bucket-name")
    :: Either Error ListObjectsResponse

trying _TransportError (send $ ListObjects "bucket-name")
    :: Either HttpException ListObjectsResponse

trying _SerializeError (send $ ListObjects "bucket-name")
    :: Either SerializeError ListObjectsResponse

trying _ServiceError (send $ ListObjects "bucket-name")
    :: Either ServiceError ListObjectsResponse

The individual service libraries now generate error matchers compatible with the above idiom. For example, the amazonka-dynamodb library contains the following generated error matcher:

_InternalServerError :: AsError a => Getting (First ServiceError) a ServiceError
_InternalServerError = _ServiceError . hasCode "InternalServerError"

Which can be used in the same fashion as the previous example. Check out the individual library’s main service interface Network.AWS.<ServiceName> to see what error matchers are available.

Free Monad

The core logic of sending requests, retrieving EC2 metadata and presigning are now provided by interpretations for a free monad. This works by the regular functions exposed from Network.AWS and Control.Monad.Trans.AWS constructing layers of a FreeT Command AST which will be interpreted by using runAWS or runAWST.

This allows for mocking AWS logic in your program by replacing any runAWS or runAWST call with a custom interpretation of the FreeT Command AST.

Network.AWS vs Control.Monad.Trans.AWS

Due to the previously mentioned changes to Error and ExceptT usage, the surface API for the main modules offered by the amazonka library have changed somewhat.

Firstly, you’ll now need to manually call runResourceT to unwrap any ResourceT actions, whereas previously it was internalised into the AWST stack.

Secondly, errors now need to be explicitly caught and handled via the aforementioned error/exception mechanisms.

The primary use case for Network.AWS is the fact that since AWS is simply AWST specialised to IO, a MonadAWS type class is provided to automatically lift the functions from Network.AWS without having to lift . lift ... through an encompassing application monad stack.

But that said, Network.AWS is simply built upon Control.Monad.Trans.AWS, which in turn is built upon Network.AWS.Free. All of these modules are exposed and most of the functions compose with respect to MonadFree Command m constraints.

Authentication

The mechanisms for supplying AuthN/AuthZ information have minor changes to make the library consistent with the official AWS SDKs.

For example, when retrieving credentials from the environment the following variables are used:

export AWS_ACCESS_KEY_ID = "*****"
export AWS_SECRET_ACCESS_KEY = "*****"
export AWS_SESSION_TOKEN = "*****"

With AWS_SESSION_TOKEN being optional.

A credentials file is now also supported. It is located in $HOME/.aws/credentials on Linux/OSX and C:\\Users\<user>\.aws\credentials on Windows.

It is INI-formatted and can contain the following keys per [profile] heading:

[default]
aws_access_key_id     = *****
aws_secret_access_key = *****
aws_session_token     = *****

Multiple [profile] sections can co-exist and the selected profile is determined by arguments to getAuth, with [default] being used for Discover.

You can read more information about the standard AWS credential mechanisms on the AWS security blog.

Configuring Requests

Service configuration such as endpoints or timeouts can be overridden per request via the *With suffixed functions. For example, changing the timeout to 10 seconds for a particular request:

sendWith (svcTimeout ?~ 10) (getObject "bucket-name" "object-key")

In fact, since modifying timeouts and retry logic is so common, functions are provided to do this for one or more actions in the form of:

Field Naming

The way lens prefixes are generated has been completely re-implemented. This is for a number of reasons such as stability of ordering, stability of a historically selected prefix with regards to introduced fields and a desire to reduce the number of suffixed ordinals that needed to be introduced to disambiguate fields.

Additionally, casing mechanisms now universally treat an acronym such as Vpc into the form of VPC. This is pervasive and consistent through naming of operations, types, module namespaces, etc.

Both of these are breaking changes, but are considerably more future proof than the previous implementation.

Generator

The previous generator predominantly used textual template rendering to emit Haskell declarations and a fair amount of logic was tied up in templating code. The new(er) generator now constructs a Haskell AST and then pretty prints code declarations. Actual layout, spacing and comments are still done by templates.

This results in less code, including templating logic and defers any sort of formatting to tools like hindent and stylish-haskell.

As an artifact of these changes, it is now considerably slower. :)

Additional Services

Since the initial public release of Amazonka, an additional 12 libraries have been added to the suite, consisting of:

Many of these libraries have only been tested for trivial cases (as in, operations that won’t cost me anything) and feedback is needed from users to continue to improve the APIs.

Miscellaneous Changes

Supported GHC Versions

The currently supported GHC versions are 7.8.4 and 7.10, built against stackage lts-2.* and nightly-* respectively. The libraries will probably work on 7.6.3 as well, but active testing is not done for reasons of personal scale.

Cabal vs Stack

In place of cabal sandbox, stack is now used for all development due to the multi-lib nature of the project. This has been a huge improvement to my development workflow, but because of this testing with cabal-install has become somewhat limited. For now, if you’re trying to build the project from git, I suggest sticking to stack and using the supplied stack-*.yml configurations.