diff --git a/docs/learn/1008-aws-cloudformation.md b/docs/learn/1008-aws-cloudformation.md index 63ef450e..7884bcb4 100644 --- a/docs/learn/1008-aws-cloudformation.md +++ b/docs/learn/1008-aws-cloudformation.md @@ -66,68 +66,7 @@ The idea here is to follow best practices in [S3 buckets](https://docs.aws.amazo Create a file named `template.cue` and add the following configuration to it. -```cue title="todoapp/cloudformation/template.cue" -package cloudformation - -// inlined s3 cloudformation template as a string -template: """ - { - "AWSTemplateFormatVersion": "2010-09-09", - "Resources": { - "S3Bucket": { - "Type": "AWS::S3::Bucket", - "Properties": { - "AccessControl": "PublicRead", - "WebsiteConfiguration": { - "IndexDocument": "index.html", - "ErrorDocument": "error.html" - } - }, - "DeletionPolicy": "Retain" - }, - "BucketPolicy": { - "Type": "AWS::S3::BucketPolicy", - "Properties": { - "PolicyDocument": { - "Id": "MyPolicy", - "Version": "2012-10-17", - "Statement": [ - { - "Sid": "PublicReadForGetBucketObjects", - "Effect": "Allow", - "Principal": "*", - "Action": "s3:GetObject", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - { - "Ref": "S3Bucket" - }, - "/*" - ] - ] - } - } - ] - }, - "Bucket": { - "Ref": "S3Bucket" - } - } - } - }, - "Outputs": { - "Name": { - "Value": { - "Fn::GetAtt": ["S3Bucket", "Arn"] - }, - "Description": "Name S3 Bucket" - } - } - } -""" +```cue file=./tests/cloudformation/template.cue title="todoapp/cloudformation/template.cue" ``` ##### 2. Cloudformation relay @@ -164,15 +103,7 @@ The config values are all part of the `aws` relay. Regarding this package, as yo Let's implement the first step, use the `aws.#Config` relay, and request its first inputs: the region to deploy and the AWS credentials. -```cue title="todoapp/cloudformation/source.cue" -package cloudformation - -import ( - "alpha.dagger.io/aws" -) - -// AWS account: credentials and region -awsConfig: aws.#Config +```cue file=./tests/cloudformation/source-begin.cue title="todoapp/cloudformation/source.cue" ``` This defines: @@ -214,33 +145,7 @@ dagger up -e cloudformation # Try to run the plan. As expected, we encounter a f Now that we have the `config` definition properly configured, let's modify the Cloudformation one: -```cue title="todoapp/cloudformation/source.cue" -package cloudformation - -import ( - "alpha.dagger.io/aws" - "alpha.dagger.io/dagger" - "alpha.dagger.io/random" - "alpha.dagger.io/aws/cloudformation" -) - -// AWS account: credentials and region -awsConfig: aws.#Config - -// Create a random suffix -suffix: random.#String & { - seed: "" -} - -// Query the Cloudformation stackname, or create one with a random suffix to keep unicity -cfnStackName: *"stack-\(suffix.out)" | string & dagger.#Input - -// AWS Cloudformation stdlib -cfnStack: cloudformation.#Stack & { - config: awsConfig - stackName: cfnStackName - source: template -} +```cue file=./tests/cloudformation/source-end.cue title="todoapp/cloudformation/source.cue" ``` This defines: @@ -359,35 +264,9 @@ We will create a new `convert.cue` file to process the conversion import Tabs from "@theme/Tabs"; import TabItem from "@theme/TabItem"; - - - -```cue title="todoapp/cloudformation/convert.cue" -package cloudformation -import "encoding/json" - -s3Template: json.Unmarshal(template) +```cue file=./tests/cloudformation/template/convert.cue title="todoapp/cloudformation/convert.cue" ``` - - - -```cue title="todoapp/cloudformation/convert.cue" -package cloudformation -import "encoding/yaml" - -s3Template: yaml.Unmarshal(template) -``` - - - - This defines: - `s3Template`: contains the unmarshalled template. @@ -428,74 +307,7 @@ rm cloudformation/convert.cue Open `cloudformation/template.cue` and append below elements with copied Cue definition of the JSON: -```cue title="todoapp/cloudformation/template.cue" -// Add this line, to make it part to the cloudformation template -package cloudformation -import "encoding/json" - -// Wrap exported Cue in previous point inside the `s3` value -s3: { - "AWSTemplateFormatVersion": "2010-09-09", - "Outputs": { - "Name": { - "Description": "Name S3 Bucket", - "Value": { - "Fn::GetAtt": [ - "S3Bucket", - "Arn" - ] - } - } - }, - "Resources": { - "BucketPolicy": { - "Properties": { - "Bucket": { - "Ref": "S3Bucket" - }, - "PolicyDocument": { - "Id": "MyPolicy", - "Statement": [ - { - "Action": "s3:GetObject", - "Effect": "Allow", - "Principal": "*", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - { - "Ref": "S3Bucket" - }, - "/*" - ] - ] - }, - "Sid": "PublicReadForGetBucketObjects" - } - ], - "Version": "2012-10-17" - } - }, - "Type": "AWS::S3::BucketPolicy" - }, - "S3Bucket": { - "DeletionPolicy": "Retain", - "Properties": { - "AccessControl": "PublicRead", - "WebsiteConfiguration": { - "ErrorDocument": "error.html", - "IndexDocument": "index.html" - } - }, - "Type": "AWS::S3::Bucket" - } - } -} - -// Template contains the marshalled value of the s3 template -template: json.Marshal(s3) +```cue file=./tests/cloudformation/template/template-begin.cue title="todoapp/cloudformation/template.cue" ``` We're using the built-in `json.Marshal` function to convert CUE back to JSON, so Cloudformation still receives the same template. @@ -510,104 +322,12 @@ Now that the template is defined in CUE, we can use the language to add more fle Let's define a re-usable `#Deployment` definition in `todoapp/cloudformation/deployment.cue`: -```cue title="todoapp/cloudformation/deployment.cue" -package cloudformation - -#Deployment: { - - // Bucket's output description - description: string - - // index file - indexDocument: *"index.html" | string - - // error file - errorDocument: *"error.html" | string - - // Bucket policy version - version: *"2012-10-17" | string - - // Retain as default deletion policy. Delete is also accepted but requires the s3 bucket to be empty - deletionPolicy: *"Retain" | "Delete" - - // Canned access control list (ACL) that grants predefined permissions to the bucket - accessControl: *"PublicRead" | "Private" | "PublicReadWrite" | "AuthenticatedRead" | "LogDeliveryWrite" | "BucketOwnerRead" | "BucketOwnerFullControl" | "AwsExecRead" - - // Modified copy of s3 value in `todoapp/cloudformation/template.cue` - template: { - "AWSTemplateFormatVersion": "2010-09-09", - "Outputs": { - "Name": { - "Description": description, - "Value": { - "Fn::GetAtt": [ - "S3Bucket", - "Arn" - ] - } - } - }, - "Resources": { - "BucketPolicy": { - "Properties": { - "Bucket": { - "Ref": "S3Bucket" - }, - "PolicyDocument": { - "Id": "MyPolicy", - "Statement": [ - { - "Action": "s3:GetObject", - "Effect": "Allow", - "Principal": "*", - "Resource": { - "Fn::Join": [ - "", - [ - "arn:aws:s3:::", - { - "Ref": "S3Bucket" - }, - "/*" - ] - ] - }, - "Sid": "PublicReadForGetBucketObjects" - } - ], - "Version": version - } - }, - "Type": "AWS::S3::BucketPolicy" - }, - "S3Bucket": { - "DeletionPolicy": deletionPolicy, - "Properties": { - "AccessControl": "PublicRead", - "WebsiteConfiguration": { - "ErrorDocument": errorDocument, - "IndexDocument": indexDocument - } - }, - "Type": "AWS::S3::Bucket" - } - } - } -} +```cue file=./tests/cloudformation/template/deployment.cue title="todoapp/cloudformation/deployment.cue" ``` `template.cue` can be rewritten as follows: -```cue title="todoapp/cloudformation/template.cue" -package cloudformation -import "encoding/json" - -s3: #Deployment & { - description: "Name S3 Bucket" -} - -// Template contains the marshalled value of the s3 template -template: json.Marshal(s3.template) +```cue file=./tests/cloudformation/template/template-end.cue title="todoapp/cloudformation/template.cue" ``` Verify template @@ -637,10 +357,9 @@ dagger query template -f text -e cloudformation # "Value": { ``` -You need to move back the `source.cue` for Dagger to instanciate a bucket: +Reimplement `source.cue`: -```shell -mv ~/tmp/source.cue cloudformation/source.cue +```cue file=./tests/cloudformation/source-end.cue title="todoapp/cloudformation/source.cue" ``` And we can now deploy it: diff --git a/docs/learn/tests/.dagger/env/cloudformation/.gitignore b/docs/learn/tests/.dagger/env/cloudformation/.gitignore new file mode 100644 index 00000000..01ec19b0 --- /dev/null +++ b/docs/learn/tests/.dagger/env/cloudformation/.gitignore @@ -0,0 +1,2 @@ +# dagger state +state/** diff --git a/docs/learn/tests/.dagger/env/cloudformation/values.yaml b/docs/learn/tests/.dagger/env/cloudformation/values.yaml new file mode 100644 index 00000000..985f9aa9 --- /dev/null +++ b/docs/learn/tests/.dagger/env/cloudformation/values.yaml @@ -0,0 +1,32 @@ +plan: + package: ./cloudformation +name: cloudformation +inputs: + awsConfig.accessKey: + secret: ENC[AES256_GCM,data:MJSSjaXpMawrpM5trPQzZR2Cg/M=,iv:D8Ff3Uy8hpzFPKg+okFkVM5DRam9Dyk31GjIizklvP0=,tag:J/7/+xjHdy94mYTQmB0sUw==,type:str] + awsConfig.region: + text: us-east-2 + awsConfig.secretKey: + secret: ENC[AES256_GCM,data:BnBc29SIbLj3DDWwpem7mcLQSPoP6a/opqIYxLZyQPdSJojL/Jy9jA==,iv:nK3mj7jOgJdAwcO2i5OYj1FxHsYnoodyXMih9eqLnOQ=,tag:1zaXCkwL8d8ilyxCGzPN6Q==,type:str] + cfnStack.onFailure: + text: DELETE +sops: + kms: [] + gcp_kms: [] + azure_kv: [] + hc_vault: [] + age: + - recipient: age1gxwmtwahzwdmrskhf90ppwlnze30lgpm056kuesrxzeuyclrwvpsupwtpk + enc: | + -----BEGIN AGE ENCRYPTED FILE----- + YWdlLWVuY3J5cHRpb24ub3JnL3YxCi0+IFgyNTUxOSB0TW4xNVMzQkZueGY1NC91 + WUplK09CRXpPSGMyTGhZYjZubDI1Sk96YVRFClJhVlNGT2VMMEtjeTcxbThaMHNq + MTgwdlFObTc3VVYxU3FFZ1lpMDAxVWcKLS0tIFJMQ2F6bWo4OEY2VnZoajJMMDVq + Lzd0bU1qMXF0dzIzTG05cmlzZURxUncKp2wNXD/HKXgGv16pM/yvaYfWL5lsQyWo + CDsT4rHHMZ21XtS9W+7oL9IqMMmDUQf6RZgDgoqLVmTjdBeA8yB2yg== + -----END AGE ENCRYPTED FILE----- + lastmodified: "2021-08-13T02:12:09Z" + mac: ENC[AES256_GCM,data:tZ35vWWlepyQOuKtzO1pau51YQZZ+Lg2LKK9PlSJvJZYzp+QaUVMCsoTAkZXgMH12C9ced6Jo1pfV9kQjxG1/g+5JmjgClFCWCMTIRa8Z9iI3hGu35SjxG4KVRlmeZsjstEW+78JBOekBUWYzkX0vAlKq44qLLug3FDc+sxFetQ=,iv:yB85C7b5X4y/koB3zN3ktUoYrYdB7N07FvJigwjxao0=,tag:3PSTd39bLyPRa4uZzHycjg==,type:str] + pgp: [] + encrypted_suffix: secret + version: 3.7.1 diff --git a/docs/learn/tests/cloudformation/deletion.cue b/docs/learn/tests/cloudformation/deletion.cue new file mode 100644 index 00000000..ed57a23a --- /dev/null +++ b/docs/learn/tests/cloudformation/deletion.cue @@ -0,0 +1,24 @@ +package main + +import ( + "alpha.dagger.io/os" + "alpha.dagger.io/aws" + "alpha.dagger.io/dagger" +) + +// Remove Cloudformation Stack +stackRemoval: { + // Cloudformation Stackname + stackName: string & dagger.#Input + + ctr: os.#Container & { + image: aws.#CLI & { + config: awsConfig + } + always: true + env: STACK_NAME: stackName + command: """ + aws cloudformation delete-stack --stack-name $STACK_NAME + """ + } +} diff --git a/docs/learn/tests/cloudformation/source-begin.cue b/docs/learn/tests/cloudformation/source-begin.cue new file mode 100644 index 00000000..7cede99c --- /dev/null +++ b/docs/learn/tests/cloudformation/source-begin.cue @@ -0,0 +1,8 @@ +package main + +import ( + "alpha.dagger.io/aws" +) + +// AWS account: credentials and region +awsConfig: aws.#Config diff --git a/docs/learn/tests/cloudformation/source-end.cue b/docs/learn/tests/cloudformation/source-end.cue new file mode 100644 index 00000000..769331d5 --- /dev/null +++ b/docs/learn/tests/cloudformation/source-end.cue @@ -0,0 +1,26 @@ +package main + +import ( + "alpha.dagger.io/aws" + "alpha.dagger.io/dagger" + "alpha.dagger.io/random" + "alpha.dagger.io/aws/cloudformation" +) + +// AWS account: credentials and region +awsConfig: aws.#Config + +// Create a random suffix +suffix: random.#String & { + seed: "" +} + +// Query the Cloudformation stackname, or create one with a random suffix to keep unicity +cfnStackName: *"stack-\(suffix.out)" | string & dagger.#Input + +// AWS Cloudformation stdlib +cfnStack: cloudformation.#Stack & { + config: awsConfig + stackName: cfnStackName + source: template +} diff --git a/docs/learn/tests/cloudformation/template.cue b/docs/learn/tests/cloudformation/template.cue new file mode 100644 index 00000000..827ab6c0 --- /dev/null +++ b/docs/learn/tests/cloudformation/template.cue @@ -0,0 +1,61 @@ +package main + +// inlined s3 cloudformation template as a string +template: """ + { + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "S3Bucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "AccessControl": "PublicRead", + "WebsiteConfiguration": { + "IndexDocument": "index.html", + "ErrorDocument": "error.html" + } + }, + "DeletionPolicy": "Retain" + }, + "BucketPolicy": { + "Type": "AWS::S3::BucketPolicy", + "Properties": { + "PolicyDocument": { + "Id": "MyPolicy", + "Version": "2012-10-17", + "Statement": [ + { + "Sid": "PublicReadForGetBucketObjects", + "Effect": "Allow", + "Principal": "*", + "Action": "s3:GetObject", + "Resource": { + "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + "Ref": "S3Bucket" + }, + "/*" + ] + ] + } + } + ] + }, + "Bucket": { + "Ref": "S3Bucket" + } + } + } + }, + "Outputs": { + "Name": { + "Value": { + "Fn::GetAtt": ["S3Bucket", "Arn"] + }, + "Description": "Name S3 Bucket" + } + } + } + """ diff --git a/docs/learn/tests/cloudformation/template/convert.cue b/docs/learn/tests/cloudformation/template/convert.cue new file mode 100644 index 00000000..a08c9210 --- /dev/null +++ b/docs/learn/tests/cloudformation/template/convert.cue @@ -0,0 +1,5 @@ +package main + +import "encoding/json" + +s3Template: json.Unmarshal(template) diff --git a/docs/learn/tests/cloudformation/template/deployment.cue b/docs/learn/tests/cloudformation/template/deployment.cue new file mode 100644 index 00000000..be7ea2c9 --- /dev/null +++ b/docs/learn/tests/cloudformation/template/deployment.cue @@ -0,0 +1,75 @@ +package main + +#Deployment: { + + // Bucket's output description + description: string + + // index file + indexDocument: *"index.html" | string + + // error file + errorDocument: *"error.html" | string + + // Bucket policy version + version: *"2012-10-17" | string + + // Retain as default deletion policy. Delete is also accepted but requires the s3 bucket to be empty + deletionPolicy: *"Retain" | "Delete" + + // Canned access control list (ACL) that grants predefined permissions to the bucket + accessControl: *"PublicRead" | "Private" | "PublicReadWrite" | "AuthenticatedRead" | "LogDeliveryWrite" | "BucketOwnerRead" | "BucketOwnerFullControl" | "AwsExecRead" + + // Modified copy of s3 value in `todoapp/cloudformation/template.cue` + template: { + AWSTemplateFormatVersion: "2010-09-09" + Outputs: Name: { + Description: description + Value: "Fn::GetAtt": [ + "S3Bucket", + "Arn", + ] + } + Resources: { + BucketPolicy: { + Properties: { + Bucket: Ref: "S3Bucket" + PolicyDocument: { + Id: "MyPolicy" + Statement: [ + { + Action: "s3:GetObject" + Effect: "Allow" + Principal: "*" + Resource: "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + Ref: "S3Bucket" + }, + "/*", + ], + ] + Sid: "PublicReadForGetBucketObjects" + }, + ] + Version: version + } + } + Type: "AWS::S3::BucketPolicy" + } + S3Bucket: { + DeletionPolicy: deletionPolicy + Properties: { + AccessControl: "PublicRead" + WebsiteConfiguration: { + ErrorDocument: errorDocument + IndexDocument: indexDocument + } + } + Type: "AWS::S3::Bucket" + } + } + } +} diff --git a/docs/learn/tests/cloudformation/template/template-begin.cue b/docs/learn/tests/cloudformation/template/template-begin.cue new file mode 100644 index 00000000..490ac179 --- /dev/null +++ b/docs/learn/tests/cloudformation/template/template-begin.cue @@ -0,0 +1,60 @@ +// Add this line, to make it part to the cloudformation template +package main + +import "encoding/json" + +// Wrap exported Cue in previous point inside the `s3` value +s3: { + AWSTemplateFormatVersion: "2010-09-09" + Outputs: Name: { + Description: "Name S3 Bucket" + Value: "Fn::GetAtt": [ + "S3Bucket", + "Arn", + ] + } + Resources: { + BucketPolicy: { + Properties: { + Bucket: Ref: "S3Bucket" + PolicyDocument: { + Id: "MyPolicy" + Statement: [ + { + Action: "s3:GetObject" + Effect: "Allow" + Principal: "*" + Resource: "Fn::Join": [ + "", + [ + "arn:aws:s3:::", + { + Ref: "S3Bucket" + }, + "/*", + ], + ] + Sid: "PublicReadForGetBucketObjects" + }, + ] + Version: "2012-10-17" + } + } + Type: "AWS::S3::BucketPolicy" + } + S3Bucket: { + DeletionPolicy: "Retain" + Properties: { + AccessControl: "PublicRead" + WebsiteConfiguration: { + ErrorDocument: "error.html" + IndexDocument: "index.html" + } + } + Type: "AWS::S3::Bucket" + } + } +} + +// Template contains the marshalled value of the s3 template +template: json.Marshal(s3) diff --git a/docs/learn/tests/cloudformation/template/template-end.cue b/docs/learn/tests/cloudformation/template/template-end.cue new file mode 100644 index 00000000..c59fa5a4 --- /dev/null +++ b/docs/learn/tests/cloudformation/template/template-end.cue @@ -0,0 +1,10 @@ +package main + +import "encoding/json" + +s3: #Deployment & { + description: "Name S3 Bucket" +} + +// Template contains the marshalled value of the s3 template +template: json.Marshal(s3.template) diff --git a/docs/learn/tests/doc.bats b/docs/learn/tests/doc.bats index 498b5b4a..cf139ea1 100644 --- a/docs/learn/tests/doc.bats +++ b/docs/learn/tests/doc.bats @@ -77,4 +77,71 @@ setup() { # Run test run dagger -e gcpcloudrun up assert_success +} + +@test "doc-1008-aws-cloudformation" { + setup_example_sandbox "doc" + + ### Create a basic plan + ## Construct + mkdir cloudformation + cp $CODEBLOC_SRC/cloudformation/template.cue cloudformation + + # Cloudformation relay + dagger doc alpha.dagger.io/aws/cloudformation + cp $CODEBLOC_SRC/cloudformation/source-begin.cue cloudformation/source.cue + + # Initialize new env + dagger new 'cloudformation' -p cloudformation + + # Finish template setup + cp $CODEBLOC_SRC/cloudformation/source-end.cue cloudformation/source.cue + # Copy corresponding env + cp -r $CODEBLOC_SRC/.dagger/env/cloudformation .dagger/env/ + + # Run test + dagger -e cloudformation up + stackName=$(dagger -e cloudformation query cfnStackName -f text) + + ## Cleanup + # Place back empty source + cp $CODEBLOC_SRC/cloudformation/source-begin.cue cloudformation/source.cue + cp $CODEBLOC_SRC/cloudformation/deletion.cue cloudformation/deletion.cue + # Prepare and run cloudformation cleanup + dagger -e cloudformation input text stackRemoval.stackName $stackName + dagger -e cloudformation up + + ### Template part + ## Create convert.cue + cp $CODEBLOC_SRC/cloudformation/template/convert.cue cloudformation/convert.cue + rm cloudformation/source.cue cloudformation/deletion.cue + + ## Retrieve Unmarshalled JSON + dagger query -e cloudformation s3Template + + ## Remove convert.cue + rm cloudformation/convert.cue + ## Store the output + cp $CODEBLOC_SRC/cloudformation/template/template-begin.cue cloudformation/template.cue + # Inspect conf + dagger query -e cloudformation template -f text + + cp $CODEBLOC_SRC/cloudformation/template/deployment.cue cloudformation/deployment.cue + cp $CODEBLOC_SRC/cloudformation/template/template-end.cue cloudformation/template.cue + cp $CODEBLOC_SRC/cloudformation/source-end.cue cloudformation/source.cue + + # Deploy again + dagger -e cloudformation query template -f text + dagger -e cloudformation up + dagger -e cloudformation output list + + ## Cleanup again + stackName=$(dagger -e cloudformation query cfnStackName -f text) + rm -rf cloudformation/* + # Place back empty source + cp $CODEBLOC_SRC/cloudformation/source-begin.cue cloudformation/source.cue + cp $CODEBLOC_SRC/cloudformation/deletion.cue cloudformation/deletion.cue + # Prepare and run cloudformation cleanup + dagger -e cloudformation input text stackRemoval.stackName $stackName + dagger -e cloudformation up } \ No newline at end of file