2021-06-15 15:01:18 +02:00
---
2021-07-16 18:37:29 +02:00
slug: /1008/aws-cloudformation/
2021-06-15 15:01:18 +02:00
---
2021-06-14 16:35:23 +02:00
2021-07-16 18:37:29 +02:00
# Provision infrastructure with Dagger and AWS CloudFormation
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
In this guide, you will learn how to automatically [provision infrastructure ](https://dzone.com/articles/infrastructure-provisioning-– ) on AWS by integrating [Amazon Cloudformation ](https://aws.amazon.com/cloudformation/ ) in your Dagger environment.
2021-06-15 15:01:18 +02:00
2021-06-21 16:31:15 +02:00
We will start with something simple: provisioning a new bucket on [Amazon S3 ](https://en.wikipedia.org/wiki/Amazon_S3 ). But Cloudformation can provision almost any AWS resource, and Dagger can integrate with the full Cloudformation API.
2021-06-14 16:35:23 +02:00
## Prerequisites
### Reminder
#### Guidelines
2021-06-21 16:31:15 +02:00
The provisioning strategy detailed below follows S3 best practices. However, to remain agnostic of your current AWS level, it profoundly relies on S3 and Cloudformation documentation.
2021-06-14 16:35:23 +02:00
#### Relays
2021-07-22 13:34:53 +02:00
The first thing to consider when developing a plan based on relays is to read their universe reference: it summarizes the expected inputs and their corresponding formats. [Here ](../reference/universe/aws/cloudformation.md ) is the Cloudformation one.
2021-06-14 16:35:23 +02:00
2021-06-16 18:57:19 +02:00
## Initialize a Dagger Workspace and Environment
2021-06-14 16:35:23 +02:00
2021-06-16 18:57:19 +02:00
### (optional) Setup example app
You will need the local copy of the [Dagger examples repository ](https://github.com/dagger/examples ) used in previous guides
```shell
git clone https://github.com/dagger/examples
```
2021-06-21 16:31:15 +02:00
Make sure to run all commands from the todoapp directory:
2021-06-16 18:57:19 +02:00
```shell
cd examples/todoapp
```
### (optional) Initialize a Cue module
2021-06-21 16:31:15 +02:00
This guide will use the same directory as the root of the Dagger workspace and the root of the Cue module, but you can create your Cue module anywhere inside the Dagger workspace.
2021-06-14 16:35:23 +02:00
2021-06-16 15:48:49 +02:00
```shell
2021-06-16 18:57:19 +02:00
cue mod init
2021-06-14 16:35:23 +02:00
```
2021-06-16 18:57:19 +02:00
### Organize your package
Let's create a new directory for our Cue package:
2021-06-14 16:35:23 +02:00
2021-06-16 15:48:49 +02:00
```shell
2021-07-02 03:57:43 +02:00
mkdir cloudformation
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
## Create a basic plan
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Let's implement the Cloudformation template and convert it to a Cue definition for further flexibility.
2021-06-16 18:57:19 +02:00
2021-06-21 16:31:15 +02:00
### Setup the template and the environment
2021-06-16 18:57:19 +02:00
2021-06-21 16:31:15 +02:00
#### Setup the template
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
The idea here is to follow best practices in [S3 buckets ](https://docs.aws.amazon.com/AmazonS3/latest/userguide/HostingWebsiteOnS3Setup.html ) provisioning. Thankfully, the AWS documentation contains a working [Cloudformation template ](https://docs.aws.amazon.com/fr_fr/AWSCloudFormation/latest/UserGuide/quickref-s3.html#scenario-s3-bucket-website ) that fits 95% of our needs.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
##### 1. Tweaking the template: output bucket name only
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Create a file named `template.cue` and add the following configuration to it.
2021-06-14 16:35:23 +02:00
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/template.cue title="todoapp/cloudformation/template.cue"
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
##### 2. Cloudformation relay
2021-06-14 16:35:23 +02:00
2021-07-22 13:34:53 +02:00
As our plan relies on [Cloudformation's relay ](../reference/universe/aws/cloudformation.md ), let's dissect the expected inputs by gradually incorporating them into our plan.
2021-06-14 16:35:23 +02:00
2021-06-16 15:48:49 +02:00
```shell
2021-06-27 15:07:52 +02:00
dagger doc alpha.dagger.io/aws/cloudformation
2021-06-21 16:31:15 +02:00
# Inputs:
# config.region string AWS region
# config.accessKey dagger.#Secret AWS access key
# config.secretKey dagger.#Secret AWS secret key
# source string Source is the Cloudformation template (JSON/YAML…
# stackName string Stackname is the cloudformation stack
# parameters struct Stack parameters
# onFailure *"DO_NOTHING" | "ROLLBACK" | "DELETE" Behavior when failure to create/update the Stack
# timeout *10 | >=0 & int Maximum waiting time until stack creation/update…
# neverUpdate *false | true Never update the stack if already exists
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
###### 1. General insights
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
As seen above in the documentation, values starting with `*` are default values. However, as a plan developer, we may need to add default values to inputs from relays without one: Cue gives you this flexibility.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
###### 2. The config value
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
The config values are all part of the `aws` relay. Regarding this package, as you can see above, five of the required inputs miss default options (`parameters` field is optional):
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
> - _config.region_
> - _config.accessKey_
> - _config.secretKey_
> - _source_
> - _stackName_
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Let's implement the first step, use the `aws.#Config` relay, and request its first inputs: the region to deploy and the AWS credentials.
2021-06-14 16:35:23 +02:00
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/source-begin.cue title="todoapp/cloudformation/source.cue"
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
This defines:
2021-06-14 16:35:23 +02:00
2021-06-27 15:07:52 +02:00
- `awsConfig` : AWS CLI Configuration step using the package `alpha.dagger.io/aws` . It takes three user inputs: a `region` , an `accessKey` , and a `secretKey`
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
#### Setup the environment
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
##### 1. Create a new environment
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Now that the Cue package is ready, let's create an environment to run it:
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
```shell
2021-07-10 17:03:25 +02:00
dagger new 'cloudformation' -p ./cloudformation
2021-06-14 16:35:23 +02:00
```
2021-06-28 19:38:29 +02:00
##### 2. Check plan
2021-06-14 18:36:29 +02:00
2021-06-21 16:31:15 +02:00
_Pro tips_: To check whether it worked or not, these three commands might help
2021-06-14 16:35:23 +02:00
2021-06-16 15:48:49 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger input list -e cloudformation # List our personal plan's inputs
2021-06-21 16:31:15 +02:00
# Input Value Set by user Description
# awsConfig.region string false AWS region
# awsConfig.accessKey dagger.#Secret false AWS access key
# awsConfig.secretKey dagger.#Secret false AWS secret key
2021-06-14 16:35:23 +02:00
2021-06-28 19:38:29 +02:00
dagger query -e cloudformation # Query values / inspect default values (Instrumental in case of conflict)
2021-06-21 16:31:15 +02:00
# {}
2021-06-14 16:35:23 +02:00
2021-06-28 19:38:29 +02:00
dagger up -e cloudformation # Try to run the plan. As expected, we encounter a failure because some user inputs haven't been set
2021-06-21 16:31:15 +02:00
# 4:11PM ERR system | required input is missing input=awsConfig.region
# 4:11PM ERR system | required input is missing input=awsConfig.accessKey
# 4:11PM ERR system | required input is missing input=awsConfig.secretKey
# 4:11PM FTL system | some required inputs are not set, please re-run with `--force` if you think it's a mistake missing=0s
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
#### Finish template setup
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Now that we have the `config` definition properly configured, let's modify the Cloudformation one:
2021-06-14 16:35:23 +02:00
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/source-end.cue title="todoapp/cloudformation/source.cue"
2021-06-21 16:31:15 +02:00
```
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
This defines:
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
- `suffix` : random suffix leveraging the `random` relay. It doesn't have a seed because we don't care about predictability
- `cfnStackName` : Name of the stack, either a default value `stack-suffix` or user input
- `cfnStack` : Cloudformation relay with `AWS config` , `stackName` and `JSON template` as inputs
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
### Configure the environment
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Before bringing up the deployment, we need to provide the `cfnStack` inputs declared in the configuration. Otherwise, Dagger will complain about missing inputs.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger up -e cloudformation
2021-06-21 16:31:15 +02:00
# 3:34PM ERR system | required input is missing input=awsConfig.region
# 3:34PM ERR system | required input is missing input=awsConfig.accessKey
# 3:34PM ERR system | required input is missing input=awsConfig.secretKey
# 3:34PM FTL system | some required inputs are not set, please re-run with `--force` if you think it's a mistake missing=0s
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
You can inspect the list of inputs (both required and optional) using dagger input list:
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger input list -e cloudformation
2021-06-21 16:31:15 +02:00
# Input Value Set by user Description
# awsConfig.region string false AWS region
# awsConfig.accessKey dagger.#Secret false AWS access key
# awsConfig.secretKey dagger.#Secret false AWS secret key
# suffix.length *12 | number false length of the string
# cfnStack.onFailure *"DO_NOTHING" | "ROLLBACK" | "DELETE" false Behavior when failure to create/update the Stack
# cfnStack.timeout *10 | >=0 & int false Maximum waiting time until stack creation/update (in minutes)
# cfnStack.neverUpdate *false | true false Never update the stack if already exists
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
Let's provide the missing inputs:
2021-06-16 18:57:19 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger input text awsConfig.region us-east-2 -e cloudformation
dagger input secret awsConfig.accessKey yourAccessKey -e cloudformation
dagger input secret awsConfig.secretKey yourSecretKey -e cloudformation
2021-06-16 18:57:19 +02:00
```
2021-06-21 16:31:15 +02:00
### Deploying
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Finally ! We now have a working template ready to be used to provision S3 infrastructures. Let's deploy it:
2021-06-14 16:35:23 +02:00
< Tabs
2021-06-21 16:31:15 +02:00
defaultValue="nd"
2021-06-14 16:35:23 +02:00
values={[
2021-06-21 16:31:15 +02:00
{ label: 'Normal deploy', value: 'nd', },
{ label: 'Debug deploy', value: 'dd', },
2021-06-14 16:35:23 +02:00
]
}>
2021-06-21 16:31:15 +02:00
< TabItem value = "nd" >
2021-06-14 16:35:23 +02:00
2021-06-16 15:48:49 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger up -e cloudformation
2021-06-21 16:31:15 +02:00
#2:22PM INF suffix.out | computing
#2:22PM INF suffix.out | completed duration=200ms
#2:22PM INF cfnStack.outputs | computing
#2:22PM INF cfnStack.outputs | #15 1.304 {
#2:22PM INF cfnStack.outputs | #15 1.304 "Parameters": []
#2:22PM INF cfnStack.outputs | #15 1.304 }
#2:22PM INF cfnStack.outputs | #15 2.948 {
#2:22PM INF cfnStack.outputs | #15 2.948 "StackId": "arn:aws:cloudformation:us-east-2:817126022176:stack/stack-emktqcfwksng/207d29a0-cd0b-11eb-aafd-0a6bae5481b4"
#2:22PM INF cfnStack.outputs | #15 2.948 }
#2:22PM INF cfnStack.outputs | completed duration=35s
2021-06-14 16:35:23 +02:00
2021-06-28 19:38:29 +02:00
dagger output list -e cloudformation
2021-06-21 16:31:15 +02:00
# Output Value Description
# suffix.out "emktqcfwksng" generated random string
# cfnStack.outputs.Name "arn:aws:s3:::stack-emktqcfwksng-s3bucket-9eiowjs1jab4" -
2021-06-14 16:35:23 +02:00
```
< / TabItem >
2021-06-21 16:31:15 +02:00
< TabItem value = "dd" >
2021-06-14 16:35:23 +02:00
2021-06-16 15:48:49 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger up -l debug -e cloudformation
2021-06-21 16:31:15 +02:00
#Output:
# 3:50PM DBG system | detected buildkit version version=v0.8.3
# 3:50PM DBG system | spawning buildkit job localdirs={
# "/tmp/infra-provisioning/.dagger/env/infra/plan": "/tmp/infra-provisioning/.dagger/env/infra/plan"
# } attrs=null
# 3:50PM DBG system | loading configuration
# ... Lots of logs ... :-D
# Output Value Description
# suffix.out "abnyiemsoqbm" generated random string
# cfnStack.outputs.Name "arn:aws:s3:::stack-abnyiemsoqbm-s3bucket-9eiowjs1jab4" -
2021-06-28 19:38:29 +02:00
dagger output list -e cloudformation
2021-06-21 16:31:15 +02:00
# Output Value Description
# suffix.out "abnyiemsoqbm" generated random string
# cfnStack.outputs.Name "arn:aws:s3:::stack-abnyiemsoqbm-s3bucket-9eiowjs1jab4" -
2021-06-14 16:35:23 +02:00
```
< / TabItem >
< / Tabs >
2021-06-21 16:31:15 +02:00
The deployment went well!
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
In case of a failure, the `Debug deploy` tab shows the command to get more information.
The name of the provisioned S3 instance lies in the `cfnStack.outputs.Name` output key, without `arn:aws:s3:::`
2021-06-28 19:38:29 +02:00
> With this provisioning infrastructure, your dev team will easily be able to instantiate aws infrastructures: all they need to know is `dagger input list -e cloudformation` and `dagger up -e cloudformation` isn't that awesome? :-D
2021-06-21 16:31:15 +02:00
## Cue Cloudformation template
This section will convert the inlined JSON template to CUE to take advantage of the language features.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
To do so quickly, we will first transform the template from JSON format to Cue format, then optimize it to leverage Cue's forces.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
### 1. Create convert.cue
We will create a new `convert.cue` file to process the conversion
import Tabs from "@theme/Tabs";
import TabItem from "@theme/TabItem";
2021-06-14 16:35:23 +02:00
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/template/convert.cue title="todoapp/cloudformation/convert.cue"
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
This defines:
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
- `s3Template` : contains the unmarshalled template.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
You need to empty the plan and copy the `convert.cue` file to the plan for Dagger to reference it
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
```shell
2021-07-10 17:03:25 +02:00
mv cloudformation/source.cue ~/tmp/
2021-06-21 16:31:15 +02:00
```
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
### 2. Retrieve the Unmarshalled JSON
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Then, still in the same folder, query the `s3Template` value to retrieve the Unmarshalled result of `s3` :
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger query s3Template -e cloudformation
2021-06-21 16:31:15 +02:00
# {
# "AWSTemplateFormatVersion": "2010-09-09",
# "Outputs": {
# "Name": {
# "Description": "Name S3 Bucket",
# "Value": {
# "Fn::GetAtt": [
# "S3Bucket",
# "Arn"
# ...
2021-06-14 16:35:23 +02:00
```
2021-06-28 19:38:29 +02:00
The commented output above is the cue version of the JSON Template, copy it
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
### 3. Remove convert.cue
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
```shell
2021-07-02 03:57:43 +02:00
rm cloudformation/convert.cue
2021-06-21 16:31:15 +02:00
```
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
### 4. Store the output
2021-06-14 16:35:23 +02:00
2021-06-28 19:38:29 +02:00
Open `cloudformation/template.cue` and append below elements with copied Cue definition of the JSON:
2021-06-14 16:35:23 +02:00
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/template/template-begin.cue title="todoapp/cloudformation/template.cue"
2021-06-14 16:35:23 +02:00
```
2021-06-21 16:31:15 +02:00
We're using the built-in `json.Marshal` function to convert CUE back to JSON, so Cloudformation still receives the same template.
2021-06-14 16:35:23 +02:00
2021-06-28 19:38:29 +02:00
You can inspect the configuration using `dagger query -e cloudformation` to verify it produces the same manifest:
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger query template -f text -e cloudformation
2021-06-21 16:31:15 +02:00
```
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Now that the template is defined in CUE, we can use the language to add more flexibility to our template.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
Let's define a re-usable `#Deployment` definition in `todoapp/cloudformation/deployment.cue` :
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/template/deployment.cue title="todoapp/cloudformation/deployment.cue"
2021-06-21 16:31:15 +02:00
```
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
`template.cue` can be rewritten as follows:
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/template/template-end.cue title="todoapp/cloudformation/template.cue"
2021-06-21 16:31:15 +02:00
```
Verify template
Double-checks at the template level can be done with manual uploads on Cloudformation's web interface or by executing the below command locally:
```shell
2021-06-28 19:38:29 +02:00
tmpfile=$(mktemp ./tmp.XXXXXX) & & dagger query template -f text -e cloudformation > "$tmpfile" & & aws cloudformation validate-template --template-body file://"$tmpfile" ; rm "$tmpfile"
2021-06-21 16:31:15 +02:00
```
Let's make sure it yields the same result:
```shell
2021-06-28 19:38:29 +02:00
dagger query template -f text -e cloudformation
2021-06-21 16:31:15 +02:00
# {
# "description": "Name S3 Bucket",
# "indexDocument": "index.html",
# "errorDocument": "error.html",
# "version": "2012-10-17",
# "deletionPolicy": "Retain",
# "accessControl": "PublicRead",
# "template": {
# "AWSTemplateFormatVersion": "2010-09-09",
# "Outputs": {
# "Name": {
# "Description": "Name S3 Bucket",
# "Value": {
```
2021-08-13 04:18:56 +02:00
Reimplement `source.cue` :
2021-07-10 17:03:25 +02:00
2021-08-13 04:18:56 +02:00
```cue file=./tests/cloudformation/source-end.cue title="todoapp/cloudformation/source.cue"
2021-07-10 17:03:25 +02:00
```
2021-06-21 16:31:15 +02:00
And we can now deploy it:
```shell
2021-06-28 19:38:29 +02:00
dagger up -e cloudformation
2021-06-14 16:35:23 +02:00
#2:22PM INF suffix.out | computing
#2:22PM INF suffix.out | completed duration=200ms
#2:22PM INF cfnStack.outputs | computing
#2:22PM INF cfnStack.outputs | #15 1.304 {
#2:22PM INF cfnStack.outputs | #15 1.304 "Parameters": []
#2:22PM INF cfnStack.outputs | #15 1.304 }
#2:22PM INF cfnStack.outputs | #15 2.948 {
#2:22PM INF cfnStack.outputs | #15 2.948 "StackId": "arn:aws:cloudformation:us-east-2:817126022176:stack/stack-emktqcfwksng/207d29a0-cd0b-11eb-aafd-0a6bae5481b4"
#2:22PM INF cfnStack.outputs | #15 2.948 }
#2:22PM INF cfnStack.outputs | completed duration=35s
```
2021-06-21 16:31:15 +02:00
Name of the deployed bucket:
2021-06-14 16:35:23 +02:00
2021-06-16 15:48:49 +02:00
```shell
2021-06-28 19:38:29 +02:00
dagger output list -e cloudformation
2021-06-14 16:35:23 +02:00
# Output Value Description
2021-06-21 16:31:15 +02:00
# suffix.out "ucwcecwwshdl" generated random string
# cfnStack.outputs.Name "arn:aws:s3:::stack-ucwcecwwshdl-s3bucket-gaqmj8rzsl08" -
2021-06-14 16:35:23 +02:00
```
The name of the provisioned S3 instance lies in the `cfnStack.outputs.Name` output key, without `arn:aws:s3:::`
2021-06-21 16:31:15 +02:00
PS: This plan could be further extended with the AWS S3 example. It could provide infrastructure and quickly deploy it.
2021-06-14 16:35:23 +02:00
2021-06-21 16:31:15 +02:00
PS1: As it could be an excellent first exercise for you, this won't be detailed here. However, we're interested in your imagination: let us know your implementations :-)