Check for CDK Version 2
cdk --version
If version is 1, goto Chapter CDK with GO start and install V2
Create a directory and create the skeleton in it.
mkdir vpc
cd vpc
cdk init app --language=go
Now you have a base setup - which would create a SNS topic.
The init app creates a monolithic setup, so we create modules. If you are unsure about GO modules, read Chapter GO modules.
The skeleton does not use modules. For very small apps thats ok, but if you have more complex setups modules are better.
mkdir main
cp vpc.go main/main.go
package main
import (
"github.com/aws/aws-cdk-go/awscdk/v2"
"vpc"
)
func main() {
app := awscdk.NewApp(nil)
vpc.NewVpcStack(app, "basevpc", &vpc.VpcStackProps{
awscdk.StackProps{
Env: env(),
},
})
app.Synth(nil)
}
func env() *awscdk.Environment {
return nil
}
1 package main
2
3 import (
4 "github.com/aws/aws-cdk-go/awscdk/v2"
5 "vpc"
6 )
7
8 func main() {
9 app := awscdk.NewApp(nil)
10
11 vpc.NewVpcStack(app, "basevpc", &vpc.VpcStackProps{
12 awscdk.StackProps{
13 Env: env(),
14 },
15 })
16
17 app.Synth(nil)
18 }
19
20 func env() *awscdk.Environment {
21 return nil
22
23 }
Update main/main.go in the directory vpc/main as shown above.
The changes are:
Now the main file is done.
package vpc
import (
"github.com/aws/aws-cdk-go/awscdk/v2"
"github.com/aws/aws-cdk-go/awscdk/v2/awsec2"
"github.com/aws/aws-cdk-go/awscdk/v2/awsssm"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/aws/constructs-go/constructs/v10"
)
type VpcStackProps struct {
awscdk.StackProps
}
func NewVpcStack(scope constructs.Construct, id string, props *VpcStackProps) awscdk.Stack {
var sprops awscdk.StackProps
if props != nil {
sprops = props.StackProps
}
stack := awscdk.NewStack(scope, &id, &sprops)
myVpc := awsec2.NewVpc(stack, aws.String("basevpc"),
&awsec2.VpcProps{
Cidr: aws.String("10.0.0.0/16"),
MaxAzs: aws.Float64(1),
},
)
awsssm.NewStringParameter(stack, aws.String("basevpc-parm"),
&awsssm.StringParameterProps{
Description: aws.String("Created VPC"),
ParameterName: aws.String("/network/basevpc"),
StringValue: myVpc.VpcId(),
},
)
return stack
}
1 package vpc
2
3 import (
4 "github.com/aws/aws-cdk-go/awscdk/v2"
5 "github.com/aws/aws-cdk-go/awscdk/v2/awsec2"
6 "github.com/aws/aws-cdk-go/awscdk/v2/awsssm"
7 "github.com/aws/aws-sdk-go-v2/aws"
8 "github.com/aws/constructs-go/constructs/v10"
9 )
10
11 type VpcStackProps struct {
12 awscdk.StackProps
13 }
14
15 func NewVpcStack(scope constructs.Construct, id string, props *VpcStackProps) awscdk.Stack {
16 var sprops awscdk.StackProps
17 if props != nil {
18 sprops = props.StackProps
19 }
20 stack := awscdk.NewStack(scope, &id, &sprops)
21
22 myVpc := awsec2.NewVpc(stack, aws.String("basevpc"),
23 &awsec2.VpcProps{
24 Cidr: aws.String("10.0.0.0/16"),
25 MaxAzs: aws.Float64(1),
26 },
27 )
28
29 awsssm.NewStringParameter(stack, aws.String("basevpc-parm"),
30 &awsssm.StringParameterProps{
31 Description: aws.String("Created VPC"),
32 ParameterName: aws.String("/network/basevpc"),
33 StringValue: myVpc.VpcId(),
34 },
35 )
36
37 return stack
38 }
Update vpc.go in the directory vpc (the base directory) as shown above.
The changes are:
A word about SSM parameters. When working with different stacks, there are several method to feed outputs of one stack into another stack. You may just use attributes and return values inside the language. Thats leads to a coupling of the stacks. That means, if you deploy one stack, all dependent stacks will be deployed, if something is changed there. Thats fine for environments which may be updated all-stacks-at-once. If you want to decouple stacks, working with parameters is the way to go.
Now the app itself is done. Last step is the test file.
Before that we have to synth the template once:
cdk synth
If everything is ok, the file cdk.out/basevpc.template.json is generated. The firs lines are:
{
"Resources": {
"basevpc24F855EE": {
"Type": "AWS::EC2::VPC",
"Properties": {
"CidrBlock": "10.0.0.0/16",
In this example basevpc24F855EE is the CloudFormation logical name of the vpc resource. We need this name for the unit tests in the next step. `
package vpc_test
import (
"encoding/json"
"testing"
"vpc"
"github.com/aws/aws-cdk-go/awscdk/v2"
"github.com/stretchr/testify/assert"
"github.com/tidwall/gjson"
)
func TestVpcStack(t *testing.T) {
// GIVEN
app := awscdk.NewApp(nil)
// WHEN
stack := vpc.NewVpcStack(app, "MyStack", nil)
// THEN
bytes, err := json.Marshal(app.Synth(nil).GetStackArtifact(stack.ArtifactId()).Template())
if err != nil {
t.Error(err)
}
template := gjson.ParseBytes(bytes)
cidr := template.Get("Resources.basevpc24F855EE.Properties.CidrBlock").String()
assert.Equal(t, "10.0.0.0/16", cidr)
}
1 package vpc_test
2
3 import (
4 "encoding/json"
5 "testing"
6
7 "vpc"
8
9 "github.com/aws/aws-cdk-go/awscdk/v2"
10 "github.com/stretchr/testify/assert"
11 "github.com/tidwall/gjson"
12 )
13
14 func TestVpcStack(t *testing.T) {
15 // GIVEN
16 app := awscdk.NewApp(nil)
17
18 // WHEN
19 stack := vpc.NewVpcStack(app, "MyStack", nil)
20
21 // THEN
22 bytes, err := json.Marshal(app.Synth(nil).GetStackArtifact(stack.ArtifactId()).Template())
23 if err != nil {
24 t.Error(err)
25 }
26
27 template := gjson.ParseBytes(bytes)
28 cidr := template.Get("Resources.basevpc24F855EE.Properties.CidrBlock").String()
29 assert.Equal(t, "10.0.0.0/16", cidr)
30 }
Update vpc_test.go as shown above.
The changes are:
Short version
go test
Output:
PASS
ok vpc 4.737s
Long version
go test -v
Output
=== RUN TestVpcStack
--- PASS: TestVpcStack (4.39s)
PASS
ok vpc 4.905s
Why do these tests, if we have to put in the logical name manually?
With the test you can now check changes and of the CloudFormation template is generated ok. So changing something, like the cidr range would mean:
With this simple vpc this seems trivial and not worth the effort. But when programs become more complex, the test give you a safety net. Especially when you start using computed cidr ranges e.g. out of a database (or an excel file), some tests really make your life better.
cdk deploy
Output
basevpc: deploying...
[0%] start: Publishing 989bae47474159e1edd440abd0dc1a557dba752fc4c2d7faf96a00fdc817043c:current_account-current_region
[100%] success: Published 989bae47474159e1edd440abd0dc1a557dba752fc4c2d7faf96a00fdc817043c:current_account-current_region
basevpc: creating CloudFormation changeset...
✅ basevpc
Stack ARN:
arn:aws:cloudformation:eu-central-1:795048271754:stack/basevpc/df55f150-3240-11ec-94f8-065e3dcecf82
You may check the resources in the AWS console, or with cdkstat
Create stacks.csv
cdk ls >stacks.csv
Check deployment status of the stack vpc:
cdkstat
Output before deployment:
Name Status Description
---- ------ -----------
basevpc LOCAL_ONLY
Output after deployment:
Name Status Description
---- ------ -----------
basevpc CREATE_COMPLETE -
Challenge: Add an description to the stack!
To check all created resources:
cdkstat basevpc
Which shows you:
Logical ID Pysical ID Type Status
---------- ---------- ----------- -----------
CDKMetadata 564824a0-324a-11ec-91fe-0a953ea AWS::CDK::Metadata CREATE_COMPLETE
basevpc24F855EE vpc-0d50cd3c702d69630 AWS::EC2::VPC CREATE_COMPLETE
basevpcIGWF722C55C igw-0cb1ef7dbfce83191 AWS::EC2::InternetGateway CREATE_COMPLETE
basevpcPrivateSubnet1DefaultRou basev-basev-19L6M1CE07F51 AWS::EC2::Route CREATE_COMPLETE
basevpcPrivateSubnet1RouteTable rtb-063a9d28de07fdfaa AWS::EC2::RouteTable CREATE_COMPLETE
basevpcPrivateSubnet1RouteTable rtbassoc-0b43ef72b20773cbb AWS::EC2::SubnetRouteTableAssoc CREATE_COMPLETE
basevpcPrivateSubnet1SubnetC819 subnet-071d24ec8fda79fb0 AWS::EC2::Subnet CREATE_COMPLETE
basevpcPublicSubnet1DefaultRout basev-basev-ND0H47E84NRF AWS::EC2::Route CREATE_COMPLETE
basevpcPublicSubnet1EIPED7F596D 52.58.21.103 AWS::EC2::EIP CREATE_COMPLETE
basevpcPublicSubnet1NATGateway8 nat-0a167548993037b0b AWS::EC2::NatGateway CREATE_COMPLETE
basevpcPublicSubnet1RouteTableA rtbassoc-0d59a3d802cffb161 AWS::EC2::SubnetRouteTableAssoc CREATE_COMPLETE
basevpcPublicSubnet1RouteTableB rtb-0cd438b9d8a2e0045 AWS::EC2::RouteTable CREATE_COMPLETE
basevpcPublicSubnet1Subnet86B77 subnet-030211e874aeab999 AWS::EC2::Subnet CREATE_COMPLETE
basevpcVPCGW69B42E41 basev-basev-OHGDOS7NEFL3 AWS::EC2::VPCGatewayAttachment CREATE_COMPLETE
basevpcparm4B69C157 /network/basevpc AWS::SSM::Parameter CREATE_COMPLETE
When evaluating or learning the creation of AWS resources, it saves money of you clean up often. So we destroy the vpc if we are not using it.
cdk destroy
If you dont want to be asked or if you use cdk in a CI/CD pipeline, you may skip the question:
cdk destroy --force
Output
basevpc: destroying...
✅ basevpc: destroyed
cdkstat
Make sure you check the right region, cdkstat or the aws cli commands only show you the current region.
Regions can be configured in your AWS profile or in the environment.
Checking the env for AWS region:
env|grep REGION
Example output:
AWS_REGION=eu-central-1
AWS_DEFAULT_REGION=eu-central-1
See the full source on github.