From 8623f614006219a5485c9b948dcf9bf9fc664d96 Mon Sep 17 00:00:00 2001 From: Rak Siva Date: Mon, 19 Aug 2024 16:46:47 -0600 Subject: [PATCH 1/3] add terratest example --- dictionary.txt | 1 + src/nav.config.ts | 4 + src/pages/guides/terraform.mdx | 6 + src/pages/guides/terraform/terratest.mdx | 260 +++++++++++++++++++++++ 4 files changed, 271 insertions(+) create mode 100644 src/pages/guides/terraform/terratest.mdx diff --git a/dictionary.txt b/dictionary.txt index 16a8caf28..a7ec05149 100644 --- a/dictionary.txt +++ b/dictionary.txt @@ -201,6 +201,7 @@ monorepos decrypts deploytf href +init ^.+[-:_]\w+$ [a-z]+([A-Z0-9]|[A-Z0-9]\w+) diff --git a/src/nav.config.ts b/src/nav.config.ts index cd8a72c0c..20d1c606b 100644 --- a/src/nav.config.ts +++ b/src/nav.config.ts @@ -586,6 +586,10 @@ const fullNav: FullNav = { title: 'AWS API Gateway Throttle', href: '/guides/terraform/api-gateway-throttle', }, + { + title: 'Testing AWS resources with Terratest', + href: '/guides/terraform/terratest', + }, ], }, ], diff --git a/src/pages/guides/terraform.mdx b/src/pages/guides/terraform.mdx index e67bbb981..4237e7d96 100644 --- a/src/pages/guides/terraform.mdx +++ b/src/pages/guides/terraform.mdx @@ -27,4 +27,10 @@ export const description = 'How to work with the Terraform Providers' name="AWS API Gateway Throttle" description="Extend the Nitric AWS Terraform provider to set API Gateway throttling limits" /> + diff --git a/src/pages/guides/terraform/terratest.mdx b/src/pages/guides/terraform/terratest.mdx new file mode 100644 index 000000000..2f0356425 --- /dev/null +++ b/src/pages/guides/terraform/terratest.mdx @@ -0,0 +1,260 @@ +export const description = + 'Use Terratest to validate the infrastructure of a Nitric GO project deployed with Terraform' + +# Testing AWS resources with Terratest + +This guide will walk you through adding Terratest to a Nitric project. + +## How Terratest works + +Terratest is designed to automate the entire process of testing your Terraform code with the following steps: + +- Initialize: Terratest will automatically run terraform init to initialize the Terraform working directory. +- Apply: It will then run terraform apply to deploy the infrastructure as defined in your Terraform code. +- Assert: The test script will then run assertions to check that the infrastructure was created as expected. +- Teardown: Finally, it will run terraform destroy to tear down the infrastructure after the test completes. + +## What we'll be doing + +1. Create and set up your application. +2. Deploying to AWS with a Terraform provider. +3. Add and execute Terratest + +## Create and set up your application. + +Our sample project creates a real-time communication service using websockets and a key-value store for connections. + +We intend to deploy to AWS and will use Terratest to ensure that the following: + +- **API Gateway WebSocket**: Confirm that the webSocket endpoint is correctly configured for real-time communication. +- **DynamoDB Table**: Verify that the key-value store for connections is created and operational. +- **IAM Roles**: Ensure that permissions for interacting with AWS services are correctly set up. + +Let's start by creating a new project for our application. + +```bash +nitric new my-websocket-app go-starter +``` + +### Application code + +Replace the contents of `services\hello.go` with our websockets application. + +```go +package main + +import ( + "context" + "fmt" + + "github.com/nitrictech/go-sdk/handler" + "github.com/nitrictech/go-sdk/nitric" +) + +func main() { + // Create a WebSocket endpoint named "public". + ws, err := nitric.NewWebsocket("public") + if err != nil { + fmt.Println("Error creating WebSocket:", err) + return + } + + // Initialize a KV store named "connections" with Get, Set, and Delete permissions. + connections, err := nitric.NewKv("connections").Allow(nitric.KvStoreGet, nitric.KvStoreSet, nitric.KvStoreDelete) + if err != nil { + fmt.Println("Error creating KV store:", err) + return + } + + // Handle new WebSocket connections by storing the connection ID in the KV store. + ws.On(handler.WebsocketConnect, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { + err := connections.Set(context.TODO(), ctx.Request.ConnectionID(), map[string]interface{}{ + "connectionId": ctx.Request.ConnectionID(), + }) + if err != nil { + return ctx, err + } + + return next(ctx) + }) + + // Handle WebSocket disconnections by removing the connection ID from the KV store. + ws.On(handler.WebsocketDisconnect, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { + err := connections.Delete(context.TODO(), ctx.Request.ConnectionID()) + if err != nil { + return ctx, err + } + + return next(ctx) + }) + + // Handle incoming messages by broadcasting them to all other connections. + ws.On(handler.WebsocketMessage, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { + connectionStream, err := connections.Keys(context.TODO()) + if err != nil { + return ctx, err + } + + senderId := ctx.Request.ConnectionID() + + for { + connectionId, err := connectionStream.Recv() + if err != nil { + break + } + + if connectionId == senderId { + continue + } + + message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message()) + err = ws.Send(context.TODO(), connectionId, []byte(message)) + if err != nil { + return ctx, err + } + } + + return next(ctx) + }) + + // Start the Nitric service to handle WebSocket events. + if err := nitric.Run(); err != nil { + fmt.Println("Error running Nitric service:", err) + } +} +``` + +## Deploying to AWS with a Terraform provider + +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). + +``` +nitric stack new dev aws-tf +``` + +Update this newly created stack file to include your target region: + +```yaml +# The nitric provider to use +provider: nitric/awstf@1.11.6 + +# The target aws region to deploy to +region: us-east-2 +``` + +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: + +``` +preview: + - beta-providers +``` + +Once you've created your Nitric stack, you can generate the Terraform code by running the following command: + +``` +nitric up +``` + +This will generate the Terraform code for your Nitric application into a folder named cdktf.out by default. + +## Add and execute Terratest + +Add the necessary dependencies for Terratest: + +```bash +go get github.com/gruntwork-io/terratest/modules/terraform +go get github.com/stretchr/testify/assert +go get github.com/aws/aws-sdk-go/aws +go get github.com/aws/aws-sdk-go/aws/session +go get github.com/aws/aws-sdk-go/service/apigatewayv2 +go get github.com/aws/aws-sdk-go/service/dynamodb +go get github.com/aws/aws-sdk-go/service/iam +``` + +### Create the Test File + +Create a new file named `test_terraform_resources.go` in your project’s test directory: + +```bash +mkdir -p test +touch test/test_terraform_resources.go +``` + +Add the following code to `test_terraform_resources.go`: + +```go +package test + +import ( + "testing" + "github.com/aws/aws-sdk-go/aws" + "github.com/aws/aws-sdk-go/aws/session" + "github.com/aws/aws-sdk-go/service/apigatewayv2" + "github.com/aws/aws-sdk-go/service/dynamodb" + "github.com/aws/aws-sdk-go/service/iam" + "github.com/gruntwork-io/terratest/modules/terraform" + "github.com/stretchr/testify/assert" +) + +func TestTerraformResources(t *testing.T) { + // Set Terraform options, specifying the directory with your Terraform configuration + terraformOptions := &terraform.Options{ + TerraformDir: "../cdktf.out/stacks/go-realtime-dev", + } + + // Ensure resources are destroyed after test completion + defer terraform.Destroy(t, terraformOptions) + + // Initialize and apply the Terraform configuration + terraform.InitAndApply(t, terraformOptions) + + // Initialize AWS session for interacting with AWS services + sess := session.Must(session.NewSession(&aws.Config{Region: aws.String("us-east-2")})) + + // Test DynamoDB table creation (key-value store) + dynamoClient := dynamodb.New(sess) + tableName := "connections" // Name of the DynamoDB table to check + _, err := dynamoClient.DescribeTable(&dynamodb.DescribeTableInput{ + TableName: aws.String(tableName), + }) + assert.NoError(t, err, "Expected DynamoDB table 'connections' to be created") + + // Test IAM role creation + iamClient := iam.New(sess) + roleName := "go-realtime_services-hello" // Name of the IAM role to check + _, err = iamClient.GetRole(&iam.GetRoleInput{ + RoleName: aws.String(roleName), + }) + assert.NoError(t, err, "Expected IAM role 'go-realtime_services-hello' to be created") + + // Test API gateway webSocket creation + apiClient := apigatewayv2.New(sess) + apiName := "public" // Name of the API Gateway WebSocket to check + result, err := apiClient.GetApis(&apigatewayv2.GetApisInput{}) + assert.NoError(t, err) + found := false + for _, api := range result.Items { + if *api.Name == apiName { + found = true + break + } + } + assert.True(t, found, "Expected API Gateway WebSocket 'public' to be created") +} +``` + +### Run the tests + +To run the tests, navigate to your project’s root directory and execute the Go test command: + +```bash +go test -v ./test +``` + +This will: + +1. Deploy the infrastructure using Terraform. +2. Validate the creation of the DynamoDB table, IAM role, and API Gateway WebSocket. +3. Clean up the infrastructure after testing. + +The output should confirm the successful creation and validation of each resource. From 2bb8392b96eb11fe6ac04d2552aaf59c19a9c3c9 Mon Sep 17 00:00:00 2001 From: Rak Siva Date: Fri, 30 Aug 2024 07:33:41 -0600 Subject: [PATCH 2/3] Apply suggestions from code review Co-authored-by: David Moore <4121492+davemooreuws@users.noreply.github.com> --- src/nav.config.ts | 2 +- src/pages/guides/terraform/terratest.mdx | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/nav.config.ts b/src/nav.config.ts index 20d1c606b..4f382fadf 100644 --- a/src/nav.config.ts +++ b/src/nav.config.ts @@ -587,7 +587,7 @@ const fullNav: FullNav = { href: '/guides/terraform/api-gateway-throttle', }, { - title: 'Testing AWS resources with Terratest', + title: 'Terratest for Testing AWS Resources', href: '/guides/terraform/terratest', }, ], diff --git a/src/pages/guides/terraform/terratest.mdx b/src/pages/guides/terraform/terratest.mdx index 2f0356425..03bb6c138 100644 --- a/src/pages/guides/terraform/terratest.mdx +++ b/src/pages/guides/terraform/terratest.mdx @@ -3,30 +3,30 @@ export const description = # Testing AWS resources with Terratest -This guide will walk you through adding Terratest to a Nitric project. +This guide will walk you through adding [Terratest](https://terratest.gruntwork.io/) to a Nitric project. ## How Terratest works Terratest is designed to automate the entire process of testing your Terraform code with the following steps: -- Initialize: Terratest will automatically run terraform init to initialize the Terraform working directory. -- Apply: It will then run terraform apply to deploy the infrastructure as defined in your Terraform code. -- Assert: The test script will then run assertions to check that the infrastructure was created as expected. -- Teardown: Finally, it will run terraform destroy to tear down the infrastructure after the test completes. +- **Initialize**: Terratest will automatically run terraform init to initialize the Terraform working directory. +- **Apply**: It will then run terraform apply to deploy the infrastructure as defined in your Terraform code. +- **Assert**: The test script will then run assertions to check that the infrastructure was created as expected. +- **Teardown**: Finally, it will run terraform destroy to tear down the infrastructure after the test completes. ## What we'll be doing 1. Create and set up your application. 2. Deploying to AWS with a Terraform provider. -3. Add and execute Terratest +3. Add and execute Terratest. ## Create and set up your application. -Our sample project creates a real-time communication service using websockets and a key-value store for connections. +Our sample project creates a real-time communication service using [websockets](/websockets) and a [key-value store](/keyvalue) for connections. We intend to deploy to AWS and will use Terratest to ensure that the following: -- **API Gateway WebSocket**: Confirm that the webSocket endpoint is correctly configured for real-time communication. +- **API Gateway WebSocket**: Confirm that the websocket endpoint is correctly configured for real-time communication. - **DynamoDB Table**: Verify that the key-value store for connections is created and operational. - **IAM Roles**: Ensure that permissions for interacting with AWS services are correctly set up. @@ -142,7 +142,7 @@ provider: nitric/awstf@1.11.6 region: us-east-2 ``` -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: +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: ``` preview: @@ -155,7 +155,7 @@ Once you've created your Nitric stack, you can generate the Terraform code by ru nitric up ``` -This will generate the Terraform code for your Nitric application into a folder named cdktf.out by default. +This will generate the Terraform code for your Nitric application into a folder named `cdktf.out` by default. ## Add and execute Terratest From 5e50d39d0b16d54dbe6da20f14d22f59b525597d Mon Sep 17 00:00:00 2001 From: Rak Siva Date: Mon, 14 Oct 2024 17:41:00 -0600 Subject: [PATCH 3/3] fix: update code for new go SDK fix: go imports --- src/pages/guides/terraform/terratest.mdx | 49 ++++++++---------------- 1 file changed, 16 insertions(+), 33 deletions(-) diff --git a/src/pages/guides/terraform/terratest.mdx b/src/pages/guides/terraform/terratest.mdx index 03bb6c138..192fb63fe 100644 --- a/src/pages/guides/terraform/terratest.mdx +++ b/src/pages/guides/terraform/terratest.mdx @@ -47,54 +47,42 @@ import ( "context" "fmt" - "github.com/nitrictech/go-sdk/handler" "github.com/nitrictech/go-sdk/nitric" + "github.com/nitrictech/go-sdk/nitric/keyvalue" + "github.com/nitrictech/go-sdk/nitric/websockets" ) func main() { // Create a WebSocket endpoint named "public". - ws, err := nitric.NewWebsocket("public") - if err != nil { - fmt.Println("Error creating WebSocket:", err) - return - } + ws := nitric.NewWebsocket("public") // Initialize a KV store named "connections" with Get, Set, and Delete permissions. - connections, err := nitric.NewKv("connections").Allow(nitric.KvStoreGet, nitric.KvStoreSet, nitric.KvStoreDelete) - if err != nil { - fmt.Println("Error creating KV store:", err) - return - } + connections := nitric.NewKv("connections").Allow(keyvalue.KvStoreGet, keyvalue.KvStoreSet, keyvalue.KvStoreDelete) // Handle new WebSocket connections by storing the connection ID in the KV store. - ws.On(handler.WebsocketConnect, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { + ws.On(websockets.EventType_Connect, func(ctx *websockets.Ctx) { err := connections.Set(context.TODO(), ctx.Request.ConnectionID(), map[string]interface{}{ "connectionId": ctx.Request.ConnectionID(), }) if err != nil { - return ctx, err + return } - - return next(ctx) }) // Handle WebSocket disconnections by removing the connection ID from the KV store. - ws.On(handler.WebsocketDisconnect, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { + ws.On(websockets.EventType_Disconnect, func(ctx *websockets.Ctx) { err := connections.Delete(context.TODO(), ctx.Request.ConnectionID()) if err != nil { - return ctx, err + return } - - return next(ctx) }) // Handle incoming messages by broadcasting them to all other connections. - ws.On(handler.WebsocketMessage, func(ctx *handler.WebsocketContext, next handler.WebsocketHandler) (*handler.WebsocketContext, error) { + ws.On(websockets.EventType_Message, func(ctx *websockets.Ctx) { connectionStream, err := connections.Keys(context.TODO()) if err != nil { - return ctx, err + return } - senderId := ctx.Request.ConnectionID() for { @@ -110,17 +98,12 @@ func main() { message := fmt.Sprintf("%s: %s", senderId, ctx.Request.Message()) err = ws.Send(context.TODO(), connectionId, []byte(message)) if err != nil { - return ctx, err + return } } - - return next(ctx) }) - // Start the Nitric service to handle WebSocket events. - if err := nitric.Run(); err != nil { - fmt.Println("Error running Nitric service:", err) - } + nitric.Run() } ``` @@ -173,14 +156,14 @@ go get github.com/aws/aws-sdk-go/service/iam ### Create the Test File -Create a new file named `test_terraform_resources.go` in your project’s test directory: +Create a new file named `terraform_resources_test.go` in your project’s test directory: ```bash mkdir -p test -touch test/test_terraform_resources.go +touch test/terraform_resources_test.go ``` -Add the following code to `test_terraform_resources.go`: +Add the following code to `terraform_resources_test.go`: ```go package test @@ -199,7 +182,7 @@ import ( func TestTerraformResources(t *testing.T) { // Set Terraform options, specifying the directory with your Terraform configuration terraformOptions := &terraform.Options{ - TerraformDir: "../cdktf.out/stacks/go-realtime-dev", + TerraformDir: "../cdktf.out/stacks/my-websocket-app-dev", } // Ensure resources are destroyed after test completion