|
| 1 | +export const description = |
| 2 | + 'Use Terratest to validate the infrastructure of a Nitric GO project deployed with Terraform' |
| 3 | + |
| 4 | +# Testing AWS resources with Terratest |
| 5 | + |
| 6 | +This guide will walk you through adding Terratest to a Nitric project. |
| 7 | + |
| 8 | +## How Terratest works |
| 9 | + |
| 10 | +Terratest is designed to automate the entire process of testing your Terraform code with the following steps: |
| 11 | + |
| 12 | +- Initialize: Terratest will automatically run terraform init to initialize the Terraform working directory. |
| 13 | +- Apply: It will then run terraform apply to deploy the infrastructure as defined in your Terraform code. |
| 14 | +- Assert: The test script will then run assertions to check that the infrastructure was created as expected. |
| 15 | +- Teardown: Finally, it will run terraform destroy to tear down the infrastructure after the test completes. |
| 16 | + |
| 17 | +## What we'll be doing |
| 18 | + |
| 19 | +1. Create and set up your application. |
| 20 | +2. Deploying to AWS with a Terraform provider. |
| 21 | +3. Add and execute Terratest |
| 22 | + |
| 23 | +## Create and set up your application. |
| 24 | + |
| 25 | +Our sample project creates a real-time communication service using websockets and a key-value store for connections. |
| 26 | + |
| 27 | +We intend to deploy to AWS and will use Terratest to ensure that the following: |
| 28 | + |
| 29 | +- **API Gateway WebSocket**: Confirm that the webSocket endpoint is correctly configured for real-time communication. |
| 30 | +- **DynamoDB Table**: Verify that the key-value store for connections is created and operational. |
| 31 | +- **IAM Roles**: Ensure that permissions for interacting with AWS services are correctly set up. |
| 32 | + |
| 33 | +Let's start by creating a new project for our application. |
| 34 | + |
| 35 | +```bash |
| 36 | +nitric new my-websocket-app go-starter |
| 37 | +``` |
| 38 | + |
| 39 | +### Application code |
| 40 | + |
| 41 | +Replace the contents of `services\hello.go` with our websockets application. |
| 42 | + |
| 43 | +```go |
| 44 | +package main |
| 45 | + |
| 46 | +import ( |
| 47 | + "context" |
| 48 | + "fmt" |
| 49 | + |
| 50 | + "github.com/nitrictech/go-sdk/handler" |
| 51 | + "github.com/nitrictech/go-sdk/nitric" |
| 52 | +) |
| 53 | + |
| 54 | +func main() { |
| 55 | + // Create a WebSocket endpoint named "public". |
| 56 | + ws, err := nitric.NewWebsocket("public") |
| 57 | + if err != nil { |
| 58 | + fmt.Println("Error creating WebSocket:", err) |
| 59 | + return |
| 60 | + } |
| 61 | + |
| 62 | + // Initialize a KV store named "connections" with Get, Set, and Delete permissions. |
| 63 | + connections, err := nitric.NewKv("connections").Allow(nitric.KvStoreGet, nitric.KvStoreSet, nitric.KvStoreDelete) |
| 64 | + if err != nil { |
| 65 | + fmt.Println("Error creating KV store:", err) |
| 66 | + return |
| 67 | + } |
| 68 | + |
| 69 | + // Handle new WebSocket connections by storing the connection ID in the KV store. |
| 70 | + ws.On(handler.WebsocketConnect, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { |
| 71 | + err := connections.Set(context.TODO(), ctx.Request.ConnectionID(), map[string]interface{}{ |
| 72 | + "connectionId": ctx.Request.ConnectionID(), |
| 73 | + }) |
| 74 | + if err != nil { |
| 75 | + return ctx, err |
| 76 | + } |
| 77 | + |
| 78 | + return next(ctx) |
| 79 | + }) |
| 80 | + |
| 81 | + // Handle WebSocket disconnections by removing the connection ID from the KV store. |
| 82 | + ws.On(handler.WebsocketDisconnect, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { |
| 83 | + err := connections.Delete(context.TODO(), ctx.Request.ConnectionID()) |
| 84 | + if err != nil { |
| 85 | + return ctx, err |
| 86 | + } |
| 87 | + |
| 88 | + return next(ctx) |
| 89 | + }) |
| 90 | + |
| 91 | + // Handle incoming messages by broadcasting them to all other connections. |
| 92 | + ws.On(handler.WebsocketMessage, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { |
| 93 | + connectionStream, err := connections.Keys(context.TODO()) |
| 94 | + if err != nil { |
| 95 | + return ctx, err |
| 96 | + } |
| 97 | + |
| 98 | + senderId := ctx.Request.ConnectionID() |
| 99 | + |
| 100 | + for { |
| 101 | + connectionId, err := connectionStream.Recv() |
| 102 | + if err != nil { |
| 103 | + break |
| 104 | + } |
| 105 | + |
| 106 | + if connectionId == senderId { |
| 107 | + continue |
| 108 | + } |
| 109 | + |
| 110 | + message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message()) |
| 111 | + err = ws.Send(context.TODO(), connectionId, []byte(message)) |
| 112 | + if err != nil { |
| 113 | + return ctx, err |
| 114 | + } |
| 115 | + } |
| 116 | + |
| 117 | + return next(ctx) |
| 118 | + }) |
| 119 | + |
| 120 | + // Start the Nitric service to handle WebSocket events. |
| 121 | + if err := nitric.Run(); err != nil { |
| 122 | + fmt.Println("Error running Nitric service:", err) |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +## Deploying to AWS with a Terraform provider |
| 128 | + |
| 129 | +To deploy your application with Terraform you'll need to use Nitric's Terraform providers. You can learn more about using Nitric with Terraform [here](/reference/providers/terraform). |
| 130 | + |
| 131 | +``` |
| 132 | +nitric stack new dev aws-tf |
| 133 | +``` |
| 134 | + |
| 135 | +Update this newly created stack file to include your target region: |
| 136 | + |
| 137 | +```yaml |
| 138 | +# The nitric provider to use |
| 139 | +provider: nitric/[email protected] |
| 140 | + |
| 141 | +# The target aws region to deploy to |
| 142 | +region: us-east-2 |
| 143 | +``` |
| 144 | +
|
| 145 | +The Nitric Terraform providers are currently in preview, to enable them you'll need to enable beta-providers in your Nitric project. You can do this by adding the following to your project's nitric.yaml file: |
| 146 | +
|
| 147 | +``` |
| 148 | +preview: |
| 149 | + - beta-providers |
| 150 | +``` |
| 151 | +
|
| 152 | +Once you've created your Nitric stack, you can generate the Terraform code by running the following command: |
| 153 | +
|
| 154 | +``` |
| 155 | +nitric up |
| 156 | +``` |
| 157 | + |
| 158 | +This will generate the Terraform code for your Nitric application into a folder named cdktf.out by default. |
| 159 | + |
| 160 | +## Add and execute Terratest |
| 161 | + |
| 162 | +Add the necessary dependencies for Terratest: |
| 163 | + |
| 164 | +```bash |
| 165 | +go get github.com/gruntwork-io/terratest/modules/terraform |
| 166 | +go get github.com/stretchr/testify/assert |
| 167 | +go get github.com/aws/aws-sdk-go/aws |
| 168 | +go get github.com/aws/aws-sdk-go/aws/session |
| 169 | +go get github.com/aws/aws-sdk-go/service/apigatewayv2 |
| 170 | +go get github.com/aws/aws-sdk-go/service/dynamodb |
| 171 | +go get github.com/aws/aws-sdk-go/service/iam |
| 172 | +``` |
| 173 | + |
| 174 | +### Create the Test File |
| 175 | + |
| 176 | +Create a new file named `test_terraform_resources.go` in your project’s test directory: |
| 177 | + |
| 178 | +```bash |
| 179 | +mkdir -p test |
| 180 | +touch test/test_terraform_resources.go |
| 181 | +``` |
| 182 | + |
| 183 | +Add the following code to `test_terraform_resources.go`: |
| 184 | + |
| 185 | +```go |
| 186 | +package test |
| 187 | + |
| 188 | +import ( |
| 189 | + "testing" |
| 190 | + "github.com/aws/aws-sdk-go/aws" |
| 191 | + "github.com/aws/aws-sdk-go/aws/session" |
| 192 | + "github.com/aws/aws-sdk-go/service/apigatewayv2" |
| 193 | + "github.com/aws/aws-sdk-go/service/dynamodb" |
| 194 | + "github.com/aws/aws-sdk-go/service/iam" |
| 195 | + "github.com/gruntwork-io/terratest/modules/terraform" |
| 196 | + "github.com/stretchr/testify/assert" |
| 197 | +) |
| 198 | + |
| 199 | +func TestTerraformResources(t *testing.T) { |
| 200 | + // Set Terraform options, specifying the directory with your Terraform configuration |
| 201 | + terraformOptions := &terraform.Options{ |
| 202 | + TerraformDir: "../cdktf.out/stacks/go-realtime-dev", |
| 203 | + } |
| 204 | + |
| 205 | + // Ensure resources are destroyed after test completion |
| 206 | + defer terraform.Destroy(t, terraformOptions) |
| 207 | + |
| 208 | + // Initialize and apply the Terraform configuration |
| 209 | + terraform.InitAndApply(t, terraformOptions) |
| 210 | + |
| 211 | + // Initialize AWS session for interacting with AWS services |
| 212 | + sess := session.Must(session.NewSession(&aws.Config{Region: aws.String("us-east-2")})) |
| 213 | + |
| 214 | + // Test DynamoDB table creation (key-value store) |
| 215 | + dynamoClient := dynamodb.New(sess) |
| 216 | + tableName := "connections" // Name of the DynamoDB table to check |
| 217 | + _, err := dynamoClient.DescribeTable(&dynamodb.DescribeTableInput{ |
| 218 | + TableName: aws.String(tableName), |
| 219 | + }) |
| 220 | + assert.NoError(t, err, "Expected DynamoDB table 'connections' to be created") |
| 221 | + |
| 222 | + // Test IAM role creation |
| 223 | + iamClient := iam.New(sess) |
| 224 | + roleName := "go-realtime_services-hello" // Name of the IAM role to check |
| 225 | + _, err = iamClient.GetRole(&iam.GetRoleInput{ |
| 226 | + RoleName: aws.String(roleName), |
| 227 | + }) |
| 228 | + assert.NoError(t, err, "Expected IAM role 'go-realtime_services-hello' to be created") |
| 229 | + |
| 230 | + // Test API gateway webSocket creation |
| 231 | + apiClient := apigatewayv2.New(sess) |
| 232 | + apiName := "public" // Name of the API Gateway WebSocket to check |
| 233 | + result, err := apiClient.GetApis(&apigatewayv2.GetApisInput{}) |
| 234 | + assert.NoError(t, err) |
| 235 | + found := false |
| 236 | + for _, api := range result.Items { |
| 237 | + if *api.Name == apiName { |
| 238 | + found = true |
| 239 | + break |
| 240 | + } |
| 241 | + } |
| 242 | + assert.True(t, found, "Expected API Gateway WebSocket 'public' to be created") |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +### Run the tests |
| 247 | + |
| 248 | +To run the tests, navigate to your project’s root directory and execute the Go test command: |
| 249 | + |
| 250 | +```bash |
| 251 | +go test -v ./test |
| 252 | +``` |
| 253 | + |
| 254 | +This will: |
| 255 | + |
| 256 | +1. Deploy the infrastructure using Terraform. |
| 257 | +2. Validate the creation of the DynamoDB table, IAM role, and API Gateway WebSocket. |
| 258 | +3. Clean up the infrastructure after testing. |
| 259 | + |
| 260 | +The output should confirm the successful creation and validation of each resource. |
0 commit comments