Dhall AWS CloudFormation
dhall-aws-cloudformation
contains Dhall bindings to AWS CloudFormation, so you can generate CloudFormation template from Dhall expressions. This will let you easily typecheck, template and modularize your CloudFormation definitions.
π References
π‘ Examples
π Usage
Use resource schema
AWS Cloudformation has massive amount of specifications, to load all package.dhall
remotely will be very slow
It is recommended to just import the only resources you need
optionaly, if you really need all resources in
package.dhall
, load the binary cache to local first
let Function =
-- import Lambda Function type definition
https://github.com/jcouyang/dhall-aws-cloudformation/raw/0.9.69/cloudformation/AWS::Lambda::Function.dhall
sha256:60937fd655917883d994e8593155453b823258e801792b0878b828b372946836
let Fn =
-- Intrinsic functions
https://github.com/jcouyang/dhall-aws-cloudformation/raw/0.9.69/Fn.dhall
sha256:b2cf7212998902c44ba1bf1670a8e0bc40562542b9b525587cd044f317644e47
let S =
{-
Each AWS String field can be either a String or a Intrinsic function, we can use `Fn.renderText "abc"` to create static string
Or `Fn.render (Ref "abc")` to create a function that ref to a string
-} Fn.renderText
let render =
-- function can be nested `render (Fn.Ref (Fn.GetAtt (Fn.String "abc.property")))`
Fn.render
let example0 =
{ Resources.HelloWorldFunction
= Function.Resources::{
, Properties = Function.Properties::{
, Handler = Some (S "index.handler")
, Code = Function.Code::{
, S3Bucket = Some (S "lambda-functions")
, S3Key = Some (S "amilookup.zip")
}
, Runtime = Some (S "nodejs12.x")
, Role = render (Fn.Ref "role logical id")
, Timeout = Some +25
, TracingConfig = Some { Mode = Some (S "Active") }
}
}
}
in example0
to convert to CloudFormation JSON file just
dhall-to-json < ./template.dhall > ./template.json
generates
{
"Resources": {
"HelloWorldFunction": {
"Properties": {
"Code": {
"S3Bucket": "lambda-functions",
"S3Key": "amilookup.zip"
},
"Handler": "index.handler",
"Role": {
"Ref": "role logical id"
},
"Runtime": "nodejs12.x",
"Timeout": 25,
"TracingConfig": {
"Mode": "Active"
}
},
"Type": "AWS::Lambda::Function"
}
}
}
Intrinsic Function
The following intrinsic functions are implemented, you can find examples of using intrinsic function in Fn.dhall document
- Fn::Base64
- Fn::Cidr
- Condition functions
- Fn::FindInMap
- Fn::GetAtt
- Fn::GetAZs
- Fn::ImportValue
- Fn::Join
- Fn::Select
- Fn::Split
- Fn::Sub
- Fn::Transform
- Ref
Type Safe Fn::GetAttr
Instead of manually looking for AWS documents to make sure the resource has what attributes, we can just use <Resource>.GetAttr.<attribute name>
:
render (Role.GetAttr.Arn "HelloWorldFunctionRole")
So the compiler can just help you find the correct attributes available.
Sam Policy Templates
Cloudformation's Policy document is loosy type as just JSON, it is hard to get the policy right and too many boilerplates to create a Dhall JSON data
Thanks to AWS SAM there are some common policy documents we can laverage
All these templates are translated into Dhall functions, so you don't need to use SAM to be able to use these policy documents.
let Policy = https://github.com/jcouyang/dhall-aws-cloudformation/raw/0.9.69/cloudformation/AWS::IAM::Role/Policy.dhall
let Sam/Policy = https://github.com/jcouyang/dhall-aws-cloudformation/raw/0.9.69/sam/policy-template/package.dhall
...
Policies = Some [Policy::{
, PolicyDocument = Sam/Policy.DynamoDBReadPolicy (Fn.String "DBName")
, PolicyName = s "dynamo read only"
}]
...
will generates
{
"Policies": [
{
"PolicyDocument": {
"Statement": [
{
"Action": [
"dynamodb:GetItem",
"dynamodb:Scan",
"dynamodb:Query",
"dynamodb:BatchGetItem",
"dynamodb:DescribeTable"
],
"Effect": "Allow",
"Resource": [
{
"Fn::Sub": [
"arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}",
{
"tableName": "DBName"
}
]
},
{
"Fn::Sub": [
"arn:${AWS::Partition}:dynamodb:${AWS::Region}:${AWS::AccountId}:table/${tableName}/index/*",
{
"tableName": "DBName"
}
]
}
]
}
]
},
"PolicyName": "dynamo read only"
}
]
}
β Contribute
Build and Test
> nix-shell
$ stack build
$ stack test
Generate Type Definitions
Type definitions are generated from config file ./config.dhall
which contains specifications used by AWS CDK as well:
To regenerate types definition files, simply run
$ stack run
Or if you just want to regenerate dhall files without setting up haskell dev environment, just
docker run --rm -v $(pwd):/data -w /data ghcr.io/jcouyang/dhall-aws-cloudformation
β οΈ Known Issue
The following CloudFormation definitions will raise assertion error due to invalid type definition such as empty type or cyclic import
AWS::EMR::Cluster
AWS::EMR::InstanceGroupConfig
AWS::EMR::InstanceFleetConfig
AWS::Macie::FindingsFilter
AWS::DataBrew::Recipe
AWS::FIS::ExperimentTemplate
AWS::SageMaker::ModelBiasJobDefinition
AWS::SageMaker::ModelQualityJobDefinition
AWS::SageMaker::MonitoringSchedule
AWS::SageMaker::DataQualityJobDefinition
AWS::SageMaker::ModelExplainabilityJobDefinition
AWS::S3::StorageLens
AWS::StepFunctions::StateMachine
AWS::MWAA::Environment
AWS::WAFv2::RuleGroup
AWS::WAFv2::WebACL
AWS::ServiceDiscovery::PrivateDnsNamespace
AWS::ServiceDiscovery::PublicDnsNamespace
AWS::AmplifyUIBuilder::Component
AWS::AmplifyUIBuilder::Theme