Skip to content

Commit b5554c1

Browse files
Copilotlgallard
andauthored
feat: Add Terratest framework for automated testing (#54)
* Initial plan for issue * feat: implement terratest framework and basic tests Co-authored-by: lgallard <[email protected]> * fix: address PR feedback - move test badge down and switch to AWS credentials Co-authored-by: lgallard <[email protected]> * fix: explicitly use terraform binary instead of tofu in terratest Co-authored-by: lgallard <[email protected]> --------- Co-authored-by: copilot-swe-agent[bot] <[email protected]> Co-authored-by: lgallard <[email protected]>
1 parent a61f291 commit b5554c1

File tree

15 files changed

+607
-0
lines changed

15 files changed

+607
-0
lines changed

.github/workflows/test.yml

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: "Test"
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
branches:
9+
- main
10+
workflow_dispatch:
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
test:
17+
name: "Terratest"
18+
runs-on: ubuntu-latest
19+
steps:
20+
- name: Check out code
21+
uses: actions/checkout@v4
22+
23+
- name: Setup Terraform
24+
uses: hashicorp/setup-terraform@v3
25+
with:
26+
terraform_version: 1.3.0
27+
terraform_wrapper: false
28+
29+
- name: Setup Go
30+
uses: actions/setup-go@v5
31+
with:
32+
go-version: '1.21'
33+
cache: true
34+
cache-dependency-path: test/go.sum
35+
36+
- name: Configure AWS Credentials
37+
uses: aws-actions/configure-aws-credentials@v4
38+
with:
39+
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
40+
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
41+
aws-region: us-east-1
42+
43+
- name: Run go tests
44+
working-directory: ./test
45+
run: |
46+
go test -v -timeout 30m
47+
48+
- name: Generate test report
49+
if: always()
50+
working-directory: ./test
51+
run: |
52+
go install github.com/jstemmer/go-junit-report/v2@latest
53+
go test -v 2>&1 | go-junit-report -set-exit-code > report.xml
54+
55+
- name: Upload test report
56+
if: always()
57+
uses: actions/upload-artifact@v3
58+
with:
59+
name: test-report
60+
path: ./test/report.xml

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
![Terraform](https://lgallardo.com/images/terraform.jpg)
22
# terraform-aws-ecr
3+
34
Terraform module to create [AWS ECR](https://aws.amazon.com/ecr/) (Elastic Container Registry) which is a fully-managed Docker container registry.
45

6+
[![Test](https://github.com/lgallard/terraform-aws-ecr/actions/workflows/test.yml/badge.svg)](https://github.com/lgallard/terraform-aws-ecr/actions/workflows/test.yml)
7+
58
## Architecture
69

710
The terraform-aws-ecr module enables several common architectures for container image management.
@@ -316,6 +319,37 @@ module "ecr" {
316319

317320
For detailed examples of all variables with explanations, see [docs/variable-examples.md](docs/variable-examples.md).
318321

322+
## Testing
323+
324+
This module uses [Terratest](https://github.com/gruntwork-io/terratest) for automated testing of the module functionality. The tests validate that the module can correctly:
325+
326+
- Create an ECR repository with basic settings
327+
- Apply repository and lifecycle policies
328+
- Configure KMS encryption
329+
- Set up image tag mutability
330+
- Configure scan on push features
331+
332+
### Running Tests Locally
333+
334+
To run the tests locally, you'll need:
335+
336+
1. [Go](https://golang.org/) 1.16+
337+
2. [Terraform](https://www.terraform.io/) 1.3.0+
338+
3. AWS credentials configured locally
339+
340+
```bash
341+
# Clone the repository
342+
git clone https://github.com/lgallard/terraform-aws-ecr.git
343+
cd terraform-aws-ecr
344+
345+
# Run the tests
346+
cd test
347+
go mod tidy
348+
go test -v
349+
```
350+
351+
For more details on tests, see the [test directory README](test/README.md).
352+
319353
<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
320354
## Requirements
321355

test/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# Testing terraform-aws-ecr
2+
3+
This directory contains automated tests for the terraform-aws-ecr module. The tests use [Terratest](https://github.com/gruntwork-io/terratest), a Go library that provides utilities for testing Terraform code.
4+
5+
## Prerequisites
6+
7+
1. [Go](https://golang.org/) (version 1.16 or later)
8+
2. [Terraform](https://www.terraform.io/) (version 1.3.0 or later)
9+
3. AWS credentials configured (via environment variables, shared credentials file, or AWS IAM role)
10+
11+
## Running the Tests
12+
13+
To run all tests:
14+
15+
```bash
16+
cd test
17+
go test -v
18+
```
19+
20+
To run a specific test:
21+
22+
```bash
23+
cd test
24+
go test -v -run TestEcrBasicCreation
25+
```
26+
27+
## Test Structure
28+
29+
The test suite includes the following tests:
30+
31+
1. **Basic Repository Test**: Tests the creation of a simple ECR repository with minimal configuration.
32+
- Verifies repository creation
33+
- Checks image tag mutability
34+
- Validates repository URL and ARN
35+
36+
2. **Complete Repository Test**: Tests the creation of a fully configured ECR repository.
37+
- Verifies repository creation with all features
38+
- Validates repository policies
39+
- Checks lifecycle policies
40+
- Tests KMS encryption
41+
42+
## Test Fixtures
43+
44+
The test fixtures are located in the `fixtures` directory:
45+
46+
- `fixtures/basic`: A simple ECR repository configuration
47+
- `fixtures/complete`: A full-featured ECR repository configuration with policies and KMS encryption
48+
49+
## AWS Resources
50+
51+
These tests create real AWS resources, which might incur costs. The tests use the `force_delete = true` option to ensure that repositories can be cleaned up even if they contain images.
52+
53+
All resources are tagged with `Test = "true"` for identification and are destroyed after the test completes. However, if a test fails, you may need to manually delete the resources.
54+
55+
## CI/CD Integration
56+
57+
These tests can be run in GitHub Actions using the workflow defined in `.github/workflows/test.yml`.

test/ecr_basic_test.go

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package test
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/aws/aws-sdk-go/aws"
9+
"github.com/aws/aws-sdk-go/aws/session"
10+
"github.com/aws/aws-sdk-go/service/ecr"
11+
"github.com/gruntwork-io/terratest/modules/random"
12+
"github.com/gruntwork-io/terratest/modules/terraform"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
// TestEcrBasicCreation tests the basic creation of an ECR repository
17+
func TestEcrBasicCreation(t *testing.T) {
18+
t.Parallel()
19+
20+
// Generate a random repository name to prevent collisions
21+
uniqueID := strings.ToLower(random.UniqueId())
22+
repoName := fmt.Sprintf("terratest-ecr-basic-%s", uniqueID)
23+
24+
// Terraform options for this test
25+
terraformOptions := &terraform.Options{
26+
// Path to the Terraform code
27+
TerraformDir: "./fixtures/basic",
28+
29+
// Variables to pass to Terraform
30+
Vars: map[string]interface{}{
31+
"name": repoName,
32+
},
33+
34+
// Explicitly use terraform binary
35+
TerraformBinary: "terraform",
36+
}
37+
38+
// Clean up resources when the test is finished
39+
defer terraform.Destroy(t, terraformOptions)
40+
41+
// Run "terraform init" and "terraform apply"
42+
terraform.InitAndApply(t, terraformOptions)
43+
44+
// Run `terraform output` to get the repository name and URL
45+
outputRepoName := terraform.Output(t, terraformOptions, "repository_name")
46+
outputRepoURL := terraform.Output(t, terraformOptions, "repository_url")
47+
outputRepoARN := terraform.Output(t, terraformOptions, "repository_arn")
48+
49+
// Verify the repository was created with the correct name
50+
assert.Equal(t, repoName, outputRepoName)
51+
52+
// Verify the repository URL format
53+
assert.True(t, strings.Contains(outputRepoURL, repoName))
54+
55+
// Verify the ARN was created and contains the expected repository name
56+
assert.True(t, strings.Contains(outputRepoARN, repoName))
57+
58+
// Use AWS SDK to verify the repository actually exists in AWS
59+
sess := session.Must(session.NewSession())
60+
ecrClient := ecr.New(sess)
61+
62+
// Describe the repository
63+
describeResult, err := ecrClient.DescribeRepositories(&ecr.DescribeRepositoriesInput{
64+
RepositoryNames: []*string{aws.String(repoName)},
65+
})
66+
67+
// Verify we can retrieve the repository
68+
assert.NoError(t, err)
69+
assert.Equal(t, 1, len(describeResult.Repositories))
70+
assert.Equal(t, repoName, *describeResult.Repositories[0].RepositoryName)
71+
72+
// Verify image tag mutability setting (should be IMMUTABLE according to our fixture)
73+
assert.Equal(t, "IMMUTABLE", *describeResult.Repositories[0].ImageTagMutability)
74+
75+
// Verify scan on push is enabled
76+
describeImageScanResult, err := ecrClient.GetRepositoryPolicy(&ecr.GetRepositoryPolicyInput{
77+
RepositoryName: aws.String(repoName),
78+
})
79+
80+
// The policy might not exist yet, so check if there was an error before asserting
81+
if err == nil {
82+
assert.NotNil(t, describeImageScanResult)
83+
}
84+
}

test/ecr_complete_test.go

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package test
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
"testing"
7+
8+
"github.com/aws/aws-sdk-go/aws"
9+
"github.com/aws/aws-sdk-go/aws/session"
10+
"github.com/aws/aws-sdk-go/service/ecr"
11+
"github.com/gruntwork-io/terratest/modules/random"
12+
"github.com/gruntwork-io/terratest/modules/terraform"
13+
"github.com/stretchr/testify/assert"
14+
)
15+
16+
// TestEcrCompleteRepository tests the creation of an ECR repository with all features enabled
17+
func TestEcrCompleteRepository(t *testing.T) {
18+
t.Parallel()
19+
20+
// Generate a random repository name to prevent collisions
21+
uniqueID := strings.ToLower(random.UniqueId())
22+
repoName := fmt.Sprintf("terratest-ecr-complete-%s", uniqueID)
23+
24+
// Terraform options for this test
25+
terraformOptions := &terraform.Options{
26+
// Path to the Terraform code
27+
TerraformDir: "./fixtures/complete",
28+
29+
// Variables to pass to Terraform
30+
Vars: map[string]interface{}{
31+
"name": repoName,
32+
},
33+
34+
// Explicitly use terraform binary
35+
TerraformBinary: "terraform",
36+
}
37+
38+
// Clean up resources when the test is finished
39+
defer terraform.Destroy(t, terraformOptions)
40+
41+
// Run "terraform init" and "terraform apply"
42+
terraform.InitAndApply(t, terraformOptions)
43+
44+
// Run `terraform output` to get the repository name and URL
45+
outputRepoName := terraform.Output(t, terraformOptions, "repository_name")
46+
outputRepoURL := terraform.Output(t, terraformOptions, "repository_url")
47+
outputRepoARN := terraform.Output(t, terraformOptions, "repository_arn")
48+
49+
// Verify the repository was created with the correct name
50+
assert.Equal(t, repoName, outputRepoName)
51+
52+
// Verify the repository URL format
53+
assert.True(t, strings.Contains(outputRepoURL, repoName))
54+
55+
// Verify the ARN was created and contains the expected repository name
56+
assert.True(t, strings.Contains(outputRepoARN, repoName))
57+
58+
// Use AWS SDK to verify the repository actually exists in AWS
59+
sess := session.Must(session.NewSession())
60+
ecrClient := ecr.New(sess)
61+
62+
// Describe the repository
63+
describeResult, err := ecrClient.DescribeRepositories(&ecr.DescribeRepositoriesInput{
64+
RepositoryNames: []*string{aws.String(repoName)},
65+
})
66+
67+
// Verify we can retrieve the repository
68+
assert.NoError(t, err)
69+
assert.Equal(t, 1, len(describeResult.Repositories))
70+
assert.Equal(t, repoName, *describeResult.Repositories[0].RepositoryName)
71+
72+
// Verify image tag mutability setting (should be IMMUTABLE according to our fixture)
73+
assert.Equal(t, "IMMUTABLE", *describeResult.Repositories[0].ImageTagMutability)
74+
75+
// Verify repository policy exists
76+
policyResult, err := ecrClient.GetRepositoryPolicy(&ecr.GetRepositoryPolicyInput{
77+
RepositoryName: aws.String(repoName),
78+
})
79+
80+
// The policy should exist
81+
assert.NoError(t, err)
82+
assert.NotNil(t, policyResult.PolicyText)
83+
assert.True(t, strings.Contains(*policyResult.PolicyText, "TestPolicy"))
84+
85+
// Verify lifecycle policy exists
86+
lifecyclePolicyResult, err := ecrClient.GetLifecyclePolicy(&ecr.GetLifecyclePolicyInput{
87+
RepositoryName: aws.String(repoName),
88+
})
89+
90+
// The lifecycle policy should exist
91+
assert.NoError(t, err)
92+
assert.NotNil(t, lifecyclePolicyResult.LifecyclePolicyText)
93+
assert.True(t, strings.Contains(*lifecyclePolicyResult.LifecyclePolicyText, "imageCountMoreThan"))
94+
}

test/fixtures/basic/main.tf

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
provider "aws" {
2+
region = var.region
3+
}
4+
5+
module "ecr" {
6+
source = "../../../"
7+
8+
name = var.name
9+
scan_on_push = true
10+
image_tag_mutability = "IMMUTABLE"
11+
force_delete = true # Set to true for tests to ensure clean teardown
12+
13+
tags = {
14+
Environment = "test"
15+
Terraform = "true"
16+
Test = "true"
17+
}
18+
}

test/fixtures/basic/outputs.tf

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
output "repository_name" {
2+
description = "The name of the ECR repository"
3+
value = module.ecr.repository_name
4+
}
5+
6+
output "repository_url" {
7+
description = "The URL of the ECR repository"
8+
value = module.ecr.repository_url
9+
}
10+
11+
output "repository_arn" {
12+
description = "The ARN of the ECR repository"
13+
value = module.ecr.repository_arn
14+
}

0 commit comments

Comments
 (0)