diff --git a/.circleci/config.yml b/.circleci/config.yml
index 51a73881a..3d003e027 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -7,19 +7,18 @@ env: &env
PACKER_VERSION: 1.7.4
TERRAGRUNT_VERSION: v0.36.0
OPA_VERSION: v0.33.1
- GO_VERSION: 1.17
+ GO_VERSION: 1.18
GO111MODULE: auto
K8S_VERSION: v1.18.20 # Same as EKS
MINIKUBE_VERSION: v1.22.0
HELM_VERSION: v3.8.0
KUBECONFIG: /home/circleci/.kube/config
BIN_BUILD_PARALLELISM: 3
- DOCKER_COMPOSE_VERSION: v2.5.0
defaults: &defaults
machine:
enabled: true
- image: ubuntu-2004:202111-02
+ image: ubuntu-2004:2022.10.1
<<: *env
setup_minikube: &setup_minikube
@@ -75,16 +74,6 @@ install_docker_buildx: &install_docker_buildx
# Verify buildx is available
docker buildx create --use
-# Installation script for the docker compose plugin. See: https://docs.docker.com/compose/install/#install-compose-on-linux-systems
-install_docker_compose: &install_docker_compose
- name: install docker compose
- command: |
- curl -sLO https://github.com/docker/compose/releases/download/${DOCKER_COMPOSE_VERSION}/docker-compose-linux-x86_64
- mkdir -p ~/.docker/cli-plugins
- mv docker-compose-linux-x86_64 ~/.docker/cli-plugins/docker-compose
- chmod a+x ~/.docker/cli-plugins/docker-compose
- docker compose version
-
configure_environment_for_gcp: &configure_environment_for_gcp
name: configure environment for gcp
command: |
@@ -180,8 +169,6 @@ jobs:
<<: *install_gruntwork_utils
- run:
<<: *install_docker_buildx
- - run:
- <<: *install_docker_compose
# The weird way you have to set PATH in Circle 2.0
- run: |
diff --git a/docs/_docs/01_getting-started/quick-start.md b/docs/_docs/01_getting-started/quick-start.md
index a641a9518..2aaaadefa 100644
--- a/docs/_docs/01_getting-started/quick-start.md
+++ b/docs/_docs/01_getting-started/quick-start.md
@@ -16,7 +16,7 @@ custom_js:
Terratest uses the Go testing framework. To use Terratest, you need to install:
-- [Go](https://golang.org/) (requires version >=1.17)
+- [Go](https://golang.org/) (requires version >=1.18)
## Setting up your project
diff --git a/docs/_includes/navbar.html b/docs/_includes/navbar.html
index c7ee8ef2a..7516f3a8e 100644
--- a/docs/_includes/navbar.html
+++ b/docs/_includes/navbar.html
@@ -14,6 +14,7 @@
+ - NEWWe're Hiring!
- Quick Start
- Docs
- Examples
diff --git a/docs/assets/css/global.scss b/docs/assets/css/global.scss
index d4cee9afe..4d9bfbecc 100644
--- a/docs/assets/css/global.scss
+++ b/docs/assets/css/global.scss
@@ -733,6 +733,27 @@ footer hr {
border-left: 2px solid #194C5F;
}
+.navbar-nav {
+ li {
+ &:first-child {
+ a {
+ display: flex;
+ span {
+ margin-right: 12px;
+ padding: 0;
+ background: linear-gradient(101.84deg, #FE3162 2.31%, #FF4F47 98.56%);
+ border-radius: 3.5px;
+ font-weight: 600;
+ min-width: 52px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ }
+ }
+ }
+ }
+}
+
@media (min-width: 768px) {
.dropdown:hover .dropdown-menu {
display: block;
diff --git a/docs/assets/css/pages/home.scss b/docs/assets/css/pages/home.scss
index 42bfee09f..7285c2b5c 100644
--- a/docs/assets/css/pages/home.scss
+++ b/docs/assets/css/pages/home.scss
@@ -208,7 +208,6 @@ body.index-page {
margin-left: 15px;
}
}
-
@media all and (max-width: 450px) {
flex-direction: column;
min-width: auto;
@@ -281,7 +280,9 @@ body.index-page {
}
@media all and (max-width: 450px) {
- .hide-on-cxs { display: none; }
+ .hide-on-cxs {
+ display: none;
+ }
}
}
@@ -318,7 +319,7 @@ body.index-page {
@media all and (max-width: 991px) {
.row.flex {
flex-direction: column;
- & > .col-xs-12:first-child {
+ &>.col-xs-12:first-child {
margin-bottom: 50px;
max-width: 800px;
margin-left: auto;
diff --git a/go.mod b/go.mod
index 69c357018..8e65c7da4 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
module github.com/gruntwork-io/terratest
-go 1.17
+go 1.18
require (
cloud.google.com/go v0.83.0
diff --git a/go.sum b/go.sum
index 87098efd9..6015a9acd 100644
--- a/go.sum
+++ b/go.sum
@@ -110,7 +110,6 @@ github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRF
github.com/alexflint/go-filemutex v0.0.0-20171022225611-72bdc8eae2ae/go.mod h1:CgnQgUtFrFz9mxFNtED3jI5tLDjKlOM+oUF/sTk6ps0=
github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
github.com/apparentlymart/go-dump v0.0.0-20180507223929-23540a00eaa3/go.mod h1:oL81AME2rN47vu18xqj1S1jPIPuN7afo62yKTNn3XMM=
-github.com/apparentlymart/go-textseg v1.0.0 h1:rRmlIsPEEhUTIKQb7T++Nz/A5Q6C9IuX2wFoYVvnCs0=
github.com/apparentlymart/go-textseg v1.0.0/go.mod h1:z96Txxhf3xSFMPmb5X/1W05FF/Nj9VFpLOpjS5yuumk=
github.com/apparentlymart/go-textseg/v13 v13.0.0 h1:Y+KvPE1NYz0xl601PVImeQfFyEy6iT90AvPUL1NNfNw=
github.com/apparentlymart/go-textseg/v13 v13.0.0/go.mod h1:ZK2fH7c4NqDTLtiYLvIkEghdlcqw7yxLeM89kiTRPUo=
@@ -503,7 +502,6 @@ github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
-github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
github.com/hashicorp/hcl/v2 v2.9.1 h1:eOy4gREY0/ZQHNItlfuEZqtcQbXIxzojlP301hDpnac=
github.com/hashicorp/hcl/v2 v2.9.1/go.mod h1:FwWsfWEjyV/CMj8s/gqAuiviY72rJ1/oayI9WftqcKg=
@@ -1308,7 +1306,6 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gotest.tools v2.2.0+incompatible h1:VsBPFP1AI068pPrMxtb/S8Zkgf9xEmTLJjfM+P5UIEo=
gotest.tools v2.2.0+incompatible/go.mod h1:DsYFclhRJ6vuDpmuTbkuFWG+y2sxOXAzmJt81HFBacw=
gotest.tools/v3 v3.0.2/go.mod h1:3SzNCllyD9/Y+b5r9JIKQ474KzkZyqLqEfYqMsX94Bk=
gotest.tools/v3 v3.0.3 h1:4AuOwCGf4lLR9u3YOe2awrHygurzhO/HeQ6laiA6Sx0=
diff --git a/modules/aws/ecr.go b/modules/aws/ecr.go
index d002a12e3..cb2f9f8cc 100644
--- a/modules/aws/ecr.go
+++ b/modules/aws/ecr.go
@@ -6,6 +6,7 @@ import (
"github.com/aws/aws-sdk-go/aws"
"github.com/aws/aws-sdk-go/service/ecr"
"github.com/gruntwork-io/go-commons/errors"
+ "github.com/gruntwork-io/terratest/modules/logger"
"github.com/gruntwork-io/terratest/modules/testing"
"github.com/stretchr/testify/require"
)
@@ -97,3 +98,46 @@ func NewECRClientE(t testing.TestingT, region string) (*ecr.ECR, error) {
}
return ecr.New(sess), nil
}
+
+// GetECRRepoLifecyclePolicy gets the policies for the given ECR repository.
+// This will fail the test and stop execution if there is an error.
+func GetECRRepoLifecyclePolicy(t testing.TestingT, region string, repo *ecr.Repository) string {
+ policy, err := GetECRRepoLifecyclePolicyE(t, region, repo)
+ require.NoError(t, err)
+ return policy
+}
+
+// GetECRRepoLifecyclePolicyE gets the policies for the given ECR repository.
+func GetECRRepoLifecyclePolicyE(t testing.TestingT, region string, repo *ecr.Repository) (string, error) {
+ client := NewECRClient(t, region)
+ resp, err := client.GetLifecyclePolicy(&ecr.GetLifecyclePolicyInput{RepositoryName: repo.RepositoryName})
+ if err != nil {
+ return "", err
+ }
+ return *resp.LifecyclePolicyText, nil
+}
+
+// PutECRRepoLifecyclePolicy puts the given policy for the given ECR repository.
+// This will fail the test and stop execution if there is an error.
+func PutECRRepoLifecyclePolicy(t testing.TestingT, region string, repo *ecr.Repository, policy string) {
+ err := PutECRRepoLifecyclePolicyE(t, region, repo, policy)
+ require.NoError(t, err)
+}
+
+// PutEcrRepoLifecyclePolicy puts the given policy for the given ECR repository.
+func PutECRRepoLifecyclePolicyE(t testing.TestingT, region string, repo *ecr.Repository, policy string) error {
+ logger.Logf(t, "Applying policy for repository %s in %s", *repo.RepositoryName, region)
+
+ client, err := NewECRClientE(t, region)
+ if err != nil {
+ return err
+ }
+
+ input := &ecr.PutLifecyclePolicyInput{
+ RepositoryName: repo.RepositoryName,
+ LifecyclePolicyText: aws.String(policy),
+ }
+
+ _, err = client.PutLifecyclePolicy(input)
+ return err
+}
diff --git a/modules/aws/ecr_test.go b/modules/aws/ecr_test.go
index b21652192..30a4c342a 100644
--- a/modules/aws/ecr_test.go
+++ b/modules/aws/ecr_test.go
@@ -26,3 +26,52 @@ func TestEcrRepo(t *testing.T) {
require.NoError(t, err)
assert.Equal(t, ecrRepoName, aws.StringValue(repo2.RepositoryName))
}
+
+func TestGetEcrRepoLifecyclePolicyError(t *testing.T) {
+ t.Parallel()
+
+ region := GetRandomStableRegion(t, nil, nil)
+ ecrRepoName := fmt.Sprintf("terratest%s", strings.ToLower(random.UniqueId()))
+ repo1, err := CreateECRRepoE(t, region, ecrRepoName)
+ defer DeleteECRRepo(t, region, repo1)
+ require.NoError(t, err)
+
+ assert.Equal(t, ecrRepoName, aws.StringValue(repo1.RepositoryName))
+
+ _, err = GetECRRepoLifecyclePolicyE(t, region, repo1)
+ require.Error(t, err)
+}
+
+func TestCanSetECRRepoLifecyclePolicyWithSingleRule(t *testing.T) {
+ t.Parallel()
+
+ region := GetRandomStableRegion(t, nil, nil)
+ ecrRepoName := fmt.Sprintf("terratest%s", strings.ToLower(random.UniqueId()))
+ repo1, err := CreateECRRepoE(t, region, ecrRepoName)
+ defer DeleteECRRepo(t, region, repo1)
+ require.NoError(t, err)
+
+ lifecyclePolicy := `{
+ "rules": [
+ {
+ "rulePriority": 1,
+ "description": "Expire images older than 14 days",
+ "selection": {
+ "tagStatus": "untagged",
+ "countType": "sinceImagePushed",
+ "countUnit": "days",
+ "countNumber": 14
+ },
+ "action": {
+ "type": "expire"
+ }
+ }
+ ]
+ }`
+
+ err = PutECRRepoLifecyclePolicyE(t, region, repo1, lifecyclePolicy)
+ require.NoError(t, err)
+
+ policy := GetECRRepoLifecyclePolicy(t, region, repo1)
+ assert.JSONEq(t, lifecyclePolicy, policy)
+}
diff --git a/modules/aws/s3.go b/modules/aws/s3.go
index c8bf21992..56eddeca2 100644
--- a/modules/aws/s3.go
+++ b/modules/aws/s3.go
@@ -70,7 +70,7 @@ func GetS3BucketTags(t testing.TestingT, awsRegion string, bucket string) map[st
return tags
}
-// GetS3BucketTagsE fetches the given bucket's tags and returns them as a string map of strings.
+// GetS3BucketTagsE fetches the given bucket's tags and returns them as a string map of strings.
func GetS3BucketTagsE(t testing.TestingT, awsRegion string, bucket string) (map[string]string, error) {
s3Client, err := NewS3ClientE(t, awsRegion)
if err != nil {
diff --git a/modules/azure/mysql.go b/modules/azure/mysql.go
index b73e2a299..5f447f448 100644
--- a/modules/azure/mysql.go
+++ b/modules/azure/mysql.go
@@ -82,7 +82,7 @@ func GetMYSQLDBClientE(subscriptionID string) (*mysql.DatabasesClient, error) {
return &mysqlDBClient, nil
}
-//GetMYSQLDB is a helper function that gets the database.
+// GetMYSQLDB is a helper function that gets the database.
// This function would fail the test if there is an error.
func GetMYSQLDB(t testing.TestingT, resGroupName string, serverName string, dbName string, subscriptionID string) *mysql.Database {
database, err := GetMYSQLDBE(t, subscriptionID, resGroupName, serverName, dbName)
@@ -91,7 +91,7 @@ func GetMYSQLDB(t testing.TestingT, resGroupName string, serverName string, dbNa
return database
}
-//GetMYSQLDBE is a helper function that gets the database.
+// GetMYSQLDBE is a helper function that gets the database.
func GetMYSQLDBE(t testing.TestingT, subscriptionID string, resGroupName string, serverName string, dbName string) (*mysql.Database, error) {
// Create a mySQl db client
mysqldbClient, err := GetMYSQLDBClientE(subscriptionID)
@@ -109,7 +109,7 @@ func GetMYSQLDBE(t testing.TestingT, subscriptionID string, resGroupName string,
return &mysqlDb, nil
}
-//ListMySQLDB is a helper function that gets all databases per server.
+// ListMySQLDB is a helper function that gets all databases per server.
func ListMySQLDB(t testing.TestingT, resGroupName string, serverName string, subscriptionID string) []mysql.Database {
dblist, err := ListMySQLDBE(t, subscriptionID, resGroupName, serverName)
require.NoError(t, err)
@@ -117,7 +117,7 @@ func ListMySQLDB(t testing.TestingT, resGroupName string, serverName string, sub
return dblist
}
-//ListMySQLDBE is a helper function that gets all databases per server.
+// ListMySQLDBE is a helper function that gets all databases per server.
func ListMySQLDBE(t testing.TestingT, subscriptionID string, resGroupName string, serverName string) ([]mysql.Database, error) {
// Create a mySQl db client
mysqldbClient, err := GetMYSQLDBClientE(subscriptionID)
diff --git a/modules/azure/postgresql.go b/modules/azure/postgresql.go
index d9f3b47f9..dec69edb2 100644
--- a/modules/azure/postgresql.go
+++ b/modules/azure/postgresql.go
@@ -81,7 +81,7 @@ func GetPostgreSQLDBClientE(subscriptionID string) (*postgresql.DatabasesClient,
return &postgresqlDBClient, nil
}
-//GetPostgreSQLDB is a helper function that gets the database.
+// GetPostgreSQLDB is a helper function that gets the database.
// This function would fail the test if there is an error.
func GetPostgreSQLDB(t testing.TestingT, resGroupName string, serverName string, dbName string, subscriptionID string) *postgresql.Database {
database, err := GetPostgreSQLDBE(t, subscriptionID, resGroupName, serverName, dbName)
@@ -90,7 +90,7 @@ func GetPostgreSQLDB(t testing.TestingT, resGroupName string, serverName string,
return database
}
-//GetPostgreSQLDBE is a helper function that gets the database.
+// GetPostgreSQLDBE is a helper function that gets the database.
func GetPostgreSQLDBE(t testing.TestingT, subscriptionID string, resGroupName string, serverName string, dbName string) (*postgresql.Database, error) {
// Create a postgresql db client
postgresqldbClient, err := GetPostgreSQLDBClientE(subscriptionID)
@@ -108,7 +108,7 @@ func GetPostgreSQLDBE(t testing.TestingT, subscriptionID string, resGroupName st
return &postgresqlDb, nil
}
-//ListPostgreSQLDB is a helper function that gets all databases per server.
+// ListPostgreSQLDB is a helper function that gets all databases per server.
func ListPostgreSQLDB(t testing.TestingT, subscriptionID string, resGroupName string, serverName string) []postgresql.Database {
dblist, err := ListPostgreSQLDBE(t, subscriptionID, resGroupName, serverName)
require.NoError(t, err)
@@ -116,7 +116,7 @@ func ListPostgreSQLDB(t testing.TestingT, subscriptionID string, resGroupName st
return dblist
}
-//ListPostgreSQLDBE is a helper function that gets all databases per server.
+// ListPostgreSQLDBE is a helper function that gets all databases per server.
func ListPostgreSQLDBE(t testing.TestingT, subscriptionID string, resGroupName string, serverName string) ([]postgresql.Database, error) {
// Create a postgresql db client
postgresqldbClient, err := GetPostgreSQLDBClientE(subscriptionID)
diff --git a/modules/azure/sql.go b/modules/azure/sql.go
index b1e5992be..771c10803 100644
--- a/modules/azure/sql.go
+++ b/modules/azure/sql.go
@@ -83,7 +83,7 @@ func GetDatabaseClient(subscriptionID string) (*sql.DatabasesClient, error) {
return &sqlDBClient, nil
}
-//ListSQLServerDatabases is a helper function that gets a list of databases on a sql server
+// ListSQLServerDatabases is a helper function that gets a list of databases on a sql server
func ListSQLServerDatabases(t testing.TestingT, resGroupName string, serverName string, subscriptionID string) *[]sql.Database {
dbList, err := ListSQLServerDatabasesE(t, resGroupName, serverName, subscriptionID)
require.NoError(t, err)
@@ -91,7 +91,7 @@ func ListSQLServerDatabases(t testing.TestingT, resGroupName string, serverName
return dbList
}
-//ListSQLServerDatabasesE is a helper function that gets a list of databases on a sql server
+// ListSQLServerDatabasesE is a helper function that gets a list of databases on a sql server
func ListSQLServerDatabasesE(t testing.TestingT, resGroupName string, serverName string, subscriptionID string) (*[]sql.Database, error) {
// Create a SQl db client
sqlClient, err := CreateDatabaseClient(subscriptionID)
diff --git a/modules/docker/build.go b/modules/docker/build.go
index 94b60af12..3ffb1ebfd 100644
--- a/modules/docker/build.go
+++ b/modules/docker/build.go
@@ -50,6 +50,12 @@ type BuildOptions struct {
// solely focus on the most important ones.
OtherOptions []string
+ // Whether ot not to enable buildkit. You can find more information about buildkit here https://docs.docker.com/build/buildkit/#getting-started.
+ EnableBuildKit bool
+
+ // Additional environment variables to pass in when running docker build command.
+ Env map[string]string
+
// Set a logger that should be used. See the logger package for more info.
Logger *logger.Logger
}
@@ -64,10 +70,20 @@ func Build(t testing.TestingT, path string, options *BuildOptions) {
func BuildE(t testing.TestingT, path string, options *BuildOptions) error {
options.Logger.Logf(t, "Running 'docker build' in %s", path)
+ env := make(map[string]string)
+ if options.Env != nil {
+ env = options.Env
+ }
+
+ if options.EnableBuildKit {
+ env["DOCKER_BUILDKIT"] = "1"
+ }
+
cmd := shell.Command{
Command: "docker",
Args: formatDockerBuildArgs(path, options),
Logger: options.Logger,
+ Env: env,
}
if err := shell.RunCommandE(t, cmd); err != nil {
diff --git a/modules/docker/build_test.go b/modules/docker/build_test.go
index 62c281b38..c971ae7d8 100644
--- a/modules/docker/build_test.go
+++ b/modules/docker/build_test.go
@@ -28,6 +28,23 @@ func TestBuild(t *testing.T) {
require.Contains(t, out, text)
}
+func TestBuildWithBuildKit(t *testing.T) {
+ t.Parallel()
+
+ tag := "gruntwork-io/test-image-with-buildkit:v1"
+ testToken := "testToken"
+ options := &BuildOptions{
+ Tags: []string{tag},
+ EnableBuildKit: true,
+ OtherOptions: []string{"--secret", fmt.Sprintf("id=github-token,env=%s", "GITHUB_OAUTH_TOKEN")},
+ Env: map[string]string{"GITHUB_OAUTH_TOKEN": testToken},
+ }
+
+ Build(t, "../../test/fixtures/docker-with-buildkit", options)
+ out := Run(t, tag, &RunOptions{Remove: false})
+ require.Contains(t, out, testToken)
+}
+
func TestBuildMultiArch(t *testing.T) {
t.Parallel()
diff --git a/modules/docker/docker_compose.go b/modules/docker/docker_compose.go
index da015ea1a..330309d1c 100644
--- a/modules/docker/docker_compose.go
+++ b/modules/docker/docker_compose.go
@@ -1,6 +1,7 @@
package docker
import (
+ "regexp"
"strings"
"github.com/gruntwork-io/terratest/modules/logger"
@@ -14,6 +15,10 @@ import (
type Options struct {
WorkingDir string
EnvVars map[string]string
+
+ // Whether ot not to enable buildkit. You can find more information about buildkit here https://docs.docker.com/build/buildkit/#getting-started.
+ EnableBuildKit bool
+
// Set a logger that should be used. See the logger package for more info.
Logger *logger.Logger
ProjectName string
@@ -50,10 +55,20 @@ func runDockerComposeE(t testing.TestingT, stdout bool, options *Options, args .
dockerComposeVersionCmd := icmd.Command("docker", "compose", "version")
result := icmd.RunCmd(dockerComposeVersionCmd)
+
+ if options.EnableBuildKit {
+ if options.EnvVars == nil {
+ options.EnvVars = make(map[string]string)
+ }
+
+ options.EnvVars["DOCKER_BUILDKIT"] = "1"
+ options.EnvVars["COMPOSE_DOCKER_CLI_BUILD"] = "1"
+ }
+
if result.ExitCode == 0 {
cmd = shell.Command{
Command: "docker",
- Args: append([]string{"compose", "--project-name", projectName}, args...),
+ Args: append([]string{"compose", "--project-name", generateValidDockerComposeProjectName(projectName)}, args...),
WorkingDir: options.WorkingDir,
Env: options.EnvVars,
Logger: options.Logger,
@@ -63,7 +78,7 @@ func runDockerComposeE(t testing.TestingT, stdout bool, options *Options, args .
Command: "docker-compose",
// We append --project-name to ensure containers from multiple different tests using Docker Compose don't end
// up in the same project and end up conflicting with each other.
- Args: append([]string{"--project-name", projectName}, args...),
+ Args: append([]string{"--project-name", generateValidDockerComposeProjectName(projectName)}, args...),
WorkingDir: options.WorkingDir,
Env: options.EnvVars,
Logger: options.Logger,
@@ -73,5 +88,12 @@ func runDockerComposeE(t testing.TestingT, stdout bool, options *Options, args .
if stdout {
return shell.RunCommandAndGetStdOut(t, cmd), nil
}
+
return shell.RunCommandAndGetOutputE(t, cmd)
}
+
+// Note: docker-compose command doesn't like lower case or special characters, other than -.
+func generateValidDockerComposeProjectName(str string) string {
+ lower_str := strings.ToLower(str)
+ return regexp.MustCompile(`[^a-zA-Z0-9 ]+`).ReplaceAllString(lower_str, "-")
+}
diff --git a/modules/docker/docker_compose_test.go b/modules/docker/docker_compose_test.go
new file mode 100644
index 000000000..b9799a356
--- /dev/null
+++ b/modules/docker/docker_compose_test.go
@@ -0,0 +1,27 @@
+package docker
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/require"
+)
+
+func TestDockerComposeWithBuildKit(t *testing.T) {
+ t.Parallel()
+
+ testToken := "testToken"
+ dockerOptions := &Options{
+ // Directory where docker-compose.yml lives
+ WorkingDir: "../../test/fixtures/docker-compose-with-buildkit",
+
+ // Configure the port the web app will listen on and the text it will return using environment variables
+ EnvVars: map[string]string{
+ "GITHUB_OAUTH_TOKEN": testToken,
+ },
+ EnableBuildKit: true,
+ }
+ out := RunDockerCompose(t, dockerOptions, "build", "--no-cache")
+ out = RunDockerCompose(t, dockerOptions, "up")
+
+ require.Contains(t, out, testToken)
+}
diff --git a/modules/k8s/config_test.go b/modules/k8s/config_test.go
index 37b06706d..be0246fcf 100644
--- a/modules/k8s/config_test.go
+++ b/modules/k8s/config_test.go
@@ -114,6 +114,7 @@ preferences: {}
users:
- name: minikube
user:
+ as-user-extra: null
client-certificate: /home/terratest/.minikube/client.crt
client-key: /home/terratest/.minikube/client.key
`
@@ -264,6 +265,7 @@ preferences: {}
users:
- name: minikube
user:
+ as-user-extra: null
client-certificate: /home/terratest/.minikube/client.crt
client-key: /home/terratest/.minikube/client.key
`
diff --git a/modules/k8s/configmap.go b/modules/k8s/configmap.go
new file mode 100644
index 000000000..86d623645
--- /dev/null
+++ b/modules/k8s/configmap.go
@@ -0,0 +1,54 @@
+package k8s
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ corev1 "k8s.io/api/core/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+ "github.com/gruntwork-io/terratest/modules/logger"
+ "github.com/gruntwork-io/terratest/modules/retry"
+ "github.com/gruntwork-io/terratest/modules/testing"
+ "github.com/stretchr/testify/require"
+)
+
+// GetConfigMap returns a Kubernetes configmap resource in the provided namespace with the given name. The namespace used
+// is the one provided in the KubectlOptions. This will fail the test if there is an error.
+func GetConfigMap(t testing.TestingT, options *KubectlOptions, configMapName string) *corev1.ConfigMap {
+ configMap, err := GetConfigMapE(t, options, configMapName)
+ require.NoError(t, err)
+ return configMap
+}
+
+// GetConfigMapE returns a Kubernetes configmap resource in the provided namespace with the given name. The namespace used
+// is the one provided in the KubectlOptions.
+func GetConfigMapE(t testing.TestingT, options *KubectlOptions, configMapName string) (*corev1.ConfigMap, error) {
+ clientset, err := GetKubernetesClientFromOptionsE(t, options)
+ if err != nil {
+ return nil, err
+ }
+ return clientset.CoreV1().ConfigMaps(options.Namespace).Get(context.Background(), configMapName, metav1.GetOptions{})
+}
+
+// WaitUntilConfigMapAvailable waits until the configmap is present on the cluster in cases where it is not immediately
+// available (for example, when using ClusterIssuer to request a certificate).
+func WaitUntilConfigMapAvailable(t testing.TestingT, options *KubectlOptions, configMapName string, retries int, sleepBetweenRetries time.Duration) {
+ statusMsg := fmt.Sprintf("Wait for configmap %s to be provisioned.", configMapName)
+ message := retry.DoWithRetry(
+ t,
+ statusMsg,
+ retries,
+ sleepBetweenRetries,
+ func() (string, error) {
+ _, err := GetConfigMapE(t, options, configMapName)
+ if err != nil {
+ return "", err
+ }
+
+ return "configmap is now available", nil
+ },
+ )
+ logger.Logf(t, message)
+}
diff --git a/modules/k8s/configmap_test.go b/modules/k8s/configmap_test.go
new file mode 100644
index 000000000..798ba8f7f
--- /dev/null
+++ b/modules/k8s/configmap_test.go
@@ -0,0 +1,68 @@
+//go:build kubeall || kubernetes
+// +build kubeall kubernetes
+
+// NOTE: we have build tags to differentiate kubernetes tests from non-kubernetes tests. This is done because minikube
+// is heavy and can interfere with docker related tests in terratest. Specifically, many of the tests start to fail with
+// `connection refused` errors from `minikube`. To avoid overloading the system, we run the kubernetes tests and helm
+// tests separately from the others. This may not be necessary if you have a sufficiently powerful machine. We
+// recommend at least 4 cores and 16GB of RAM if you want to run all the tests together.
+
+package k8s
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/gruntwork-io/terratest/modules/random"
+)
+
+func TestGetConfigMapEReturnsErrorForNonExistantConfigMap(t *testing.T) {
+ t.Parallel()
+
+ options := NewKubectlOptions("", "", "default")
+ _, err := GetConfigMapE(t, options, "test-config-map")
+ require.Error(t, err)
+}
+
+func TestGetConfigMapEReturnsCorrectConfigMapInCorrectNamespace(t *testing.T) {
+ t.Parallel()
+
+ uniqueID := strings.ToLower(random.UniqueId())
+ options := NewKubectlOptions("", "", uniqueID)
+ configData := fmt.Sprintf(EXAMPLE_CONFIGMAP_YAML_TEMPLATE, uniqueID, uniqueID)
+ defer KubectlDeleteFromString(t, options, configData)
+ KubectlApplyFromString(t, options, configData)
+
+ configMap := GetConfigMap(t, options, "test-config-map")
+ require.Equal(t, configMap.Name, "test-config-map")
+ require.Equal(t, configMap.Namespace, uniqueID)
+}
+
+func TestWaitUntilConfigMapAvailableReturnsSuccessfully(t *testing.T) {
+ t.Parallel()
+
+ uniqueID := strings.ToLower(random.UniqueId())
+ options := NewKubectlOptions("", "", uniqueID)
+ configData := fmt.Sprintf(EXAMPLE_CONFIGMAP_YAML_TEMPLATE, uniqueID, uniqueID)
+ defer KubectlDeleteFromString(t, options, configData)
+
+ KubectlApplyFromString(t, options, configData)
+ WaitUntilConfigMapAvailable(t, options, "test-config-map", 10, 1*time.Second)
+}
+
+const EXAMPLE_CONFIGMAP_YAML_TEMPLATE = `---
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: %s
+---
+apiVersion: v1
+kind: ConfigMap
+metadata:
+ name: test-config-map
+ namespace: %s
+`
diff --git a/modules/k8s/networkpolicy.go b/modules/k8s/networkpolicy.go
new file mode 100644
index 000000000..124028e6e
--- /dev/null
+++ b/modules/k8s/networkpolicy.go
@@ -0,0 +1,53 @@
+package k8s
+
+import (
+ "context"
+ "fmt"
+ "time"
+
+ "github.com/gruntwork-io/terratest/modules/logger"
+ "github.com/gruntwork-io/terratest/modules/retry"
+ "github.com/gruntwork-io/terratest/modules/testing"
+ "github.com/stretchr/testify/require"
+ networkingv1 "k8s.io/api/networking/v1"
+ metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+// GetNetworkPolicy returns a Kubernetes networkpolicy resource in the provided namespace with the given name. The namespace used
+// is the one provided in the KubectlOptions. This will fail the test if there is an error.
+func GetNetworkPolicy(t testing.TestingT, options *KubectlOptions, networkPolicyName string) *networkingv1.NetworkPolicy {
+ networkPolicy, err := GetNetworkPolicyE(t, options, networkPolicyName)
+ require.NoError(t, err)
+ return networkPolicy
+}
+
+// GetNetworkPolicyE returns a Kubernetes networkpolicy resource in the provided namespace with the given name. The namespace used
+// is the one provided in the KubectlOptions.
+func GetNetworkPolicyE(t testing.TestingT, options *KubectlOptions, networkPolicyName string) (*networkingv1.NetworkPolicy, error) {
+ clientset, err := GetKubernetesClientFromOptionsE(t, options)
+ if err != nil {
+ return nil, err
+ }
+ return clientset.NetworkingV1().NetworkPolicies(options.Namespace).Get(context.Background(), networkPolicyName, metav1.GetOptions{})
+}
+
+// WaitUntilNetworkPolicyAvailable waits until the networkpolicy is present on the cluster in cases where it is not immediately
+// available (for example, when using ClusterIssuer to request a certificate).
+func WaitUntilNetworkPolicyAvailable(t testing.TestingT, options *KubectlOptions, networkPolicyName string, retries int, sleepBetweenRetries time.Duration) {
+ statusMsg := fmt.Sprintf("Wait for networkpolicy %s to be provisioned.", networkPolicyName)
+ message := retry.DoWithRetry(
+ t,
+ statusMsg,
+ retries,
+ sleepBetweenRetries,
+ func() (string, error) {
+ _, err := GetNetworkPolicyE(t, options, networkPolicyName)
+ if err != nil {
+ return "", err
+ }
+
+ return "networkpolicy is now available", nil
+ },
+ )
+ logger.Logf(t, message)
+}
diff --git a/modules/k8s/networkpolicy_test.go b/modules/k8s/networkpolicy_test.go
new file mode 100644
index 000000000..9e06933e5
--- /dev/null
+++ b/modules/k8s/networkpolicy_test.go
@@ -0,0 +1,72 @@
+//go:build kubeall || kubernetes
+// +build kubeall kubernetes
+
+// NOTE: we have build tags to differentiate kubernetes tests from non-kubernetes tests. This is done because minikube
+// is heavy and can interfere with docker related tests in terratest. Specifically, many of the tests start to fail with
+// `connection refused` errors from `minikube`. To avoid overloading the system, we run the kubernetes tests and helm
+// tests separately from the others. This may not be necessary if you have a sufficiently powerful machine. We
+// recommend at least 4 cores and 16GB of RAM if you want to run all the tests together.
+
+package k8s
+
+import (
+ "fmt"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/stretchr/testify/require"
+
+ "github.com/gruntwork-io/terratest/modules/random"
+)
+
+func TestGetNetworkPolicyEReturnsErrorForNonExistantNetworkPolicy(t *testing.T) {
+ t.Parallel()
+
+ options := NewKubectlOptions("", "", "default")
+ _, err := GetNetworkPolicyE(t, options, "test-network-policy")
+ require.Error(t, err)
+}
+
+func TestGetNetworkPolicyEReturnsCorrectNetworkPolicyInCorrectNamespace(t *testing.T) {
+ t.Parallel()
+
+ uniqueID := strings.ToLower(random.UniqueId())
+ options := NewKubectlOptions("", "", uniqueID)
+ configData := fmt.Sprintf(EXAMPLE_NETWORK_POLICY_YAML_TEMPLATE, uniqueID, uniqueID)
+ defer KubectlDeleteFromString(t, options, configData)
+ KubectlApplyFromString(t, options, configData)
+
+ networkPolicy := GetNetworkPolicy(t, options, "test-network-policy")
+ require.Equal(t, networkPolicy.Name, "test-network-policy")
+ require.Equal(t, networkPolicy.Namespace, uniqueID)
+}
+
+func TestWaitUntilNetworkPolicyAvailableReturnsSuccessfully(t *testing.T) {
+ t.Parallel()
+
+ uniqueID := strings.ToLower(random.UniqueId())
+ options := NewKubectlOptions("", "", uniqueID)
+ configData := fmt.Sprintf(EXAMPLE_NETWORK_POLICY_YAML_TEMPLATE, uniqueID, uniqueID)
+ defer KubectlDeleteFromString(t, options, configData)
+
+ KubectlApplyFromString(t, options, configData)
+ WaitUntilNetworkPolicyAvailable(t, options, "test-network-policy", 10, 1*time.Second)
+}
+
+const EXAMPLE_NETWORK_POLICY_YAML_TEMPLATE = `---
+apiVersion: v1
+kind: Namespace
+metadata:
+ name: %s
+---
+apiVersion: networking.k8s.io/v1
+kind: NetworkPolicy
+metadata:
+ name: test-network-policy
+ namespace: %s
+spec:
+ podSelector: {}
+ policyTypes:
+ - Ingress
+`
diff --git a/modules/k8s/service.go b/modules/k8s/service.go
index c67f86509..fb50f0612 100644
--- a/modules/k8s/service.go
+++ b/modules/k8s/service.go
@@ -109,12 +109,12 @@ func GetServiceEndpoint(t testing.TestingT, options *KubectlOptions, service *co
}
// GetServiceEndpointE will return the service access point using the following logic:
-// - For ClusterIP service type, return the URL that maps to ClusterIP and Service Port
-// - For NodePort service type, identify the public IP of the node (if it exists, otherwise return the bound hostname),
-// and the assigned node port for the provided service port, and return the URL that maps to node ip and node port.
-// - For LoadBalancer service type, return the publicly accessible hostname of the load balancer.
-// If the hostname is empty, it will return the public IP of the LoadBalancer.
-// - All other service types are not supported.
+// - For ClusterIP service type, return the URL that maps to ClusterIP and Service Port
+// - For NodePort service type, identify the public IP of the node (if it exists, otherwise return the bound hostname),
+// and the assigned node port for the provided service port, and return the URL that maps to node ip and node port.
+// - For LoadBalancer service type, return the publicly accessible hostname of the load balancer.
+// If the hostname is empty, it will return the public IP of the LoadBalancer.
+// - All other service types are not supported.
func GetServiceEndpointE(t testing.TestingT, options *KubectlOptions, service *corev1.Service, servicePort int) (string, error) {
switch service.Spec.Type {
case corev1.ServiceTypeClusterIP:
diff --git a/modules/logger/logger.go b/modules/logger/logger.go
index d290c6eee..5b544520e 100644
--- a/modules/logger/logger.go
+++ b/modules/logger/logger.go
@@ -108,16 +108,17 @@ func (_ terratestLogger) Logf(t testing.TestingT, format string, args ...interfa
// immediately, rather than buffering all log output and only displaying it at the very end of the test. This is useful
// because:
//
-// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
-// right away, rather than at the very end of the test run.
+// 1. It allows you to iterate faster locally, as you get feedback on whether your code changes are working as expected
+// right away, rather than at the very end of the test run.
//
-// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
-// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
-// output available.
+// 2. If you have a bug in your code that causes a test to never complete or if the test code crashes, t.Logf would
+// show you no log output whatsoever, making debugging very hard, where as this method will show you all the log
+// output available.
+//
+// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
+// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
+// this log method, you get log output continuously.
//
-// 3. If you have a test that takes a long time to complete, some CI systems will kill the test suite prematurely
-// because there is no log output with t.Logf (e.g., CircleCI kills tests after 10 minutes of no log output). With
-// this log method, you get log output continuously.
// Although t.Logf now supports streaming output since Go 1.14, this is kept for compatibility purposes.
func Logf(t testing.TestingT, format string, args ...interface{}) {
if tt, ok := t.(helper); ok {
diff --git a/modules/logger/parser/failed_test_marker.go b/modules/logger/parser/failed_test_marker.go
index e4821b710..f9f4dd305 100644
--- a/modules/logger/parser/failed_test_marker.go
+++ b/modules/logger/parser/failed_test_marker.go
@@ -4,8 +4,10 @@ package parser
// TestResultMarker tracks the indentation level of a test result line in go test output.
// Example:
// --- FAIL: TestSnafu
-// --- PASS: TestSnafu/Situation
-// --- FAIL: TestSnafu/Normal
+//
+// --- PASS: TestSnafu/Situation
+// --- FAIL: TestSnafu/Normal
+//
// The three markers for the above in order are:
// TestResultMarker{TestName: "TestSnafu", IndentLevel: 0}
// TestResultMarker{TestName: "TestSnafu/Situation", IndentLevel: 4}
diff --git a/modules/logger/parser/fixtures/basic_example_expected/report.xml b/modules/logger/parser/fixtures/basic_example_expected/report.xml
index e652dfde2..774275eea 100644
--- a/modules/logger/parser/fixtures/basic_example_expected/report.xml
+++ b/modules/logger/parser/fixtures/basic_example_expected/report.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/modules/logger/parser/fixtures/failing_example_expected/report.xml b/modules/logger/parser/fixtures/failing_example_expected/report.xml
index 1d2fcb316..9f5b19015 100644
--- a/modules/logger/parser/fixtures/failing_example_expected/report.xml
+++ b/modules/logger/parser/fixtures/failing_example_expected/report.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/modules/logger/parser/fixtures/new_go_failing_example_expected/report.xml b/modules/logger/parser/fixtures/new_go_failing_example_expected/report.xml
index d3338191d..203ffeb72 100644
--- a/modules/logger/parser/fixtures/new_go_failing_example_expected/report.xml
+++ b/modules/logger/parser/fixtures/new_go_failing_example_expected/report.xml
@@ -2,7 +2,7 @@
-
+
integration_test.go:57:
diff --git a/modules/logger/parser/fixtures/panic_example_expected/report.xml b/modules/logger/parser/fixtures/panic_example_expected/report.xml
index 776f2ed55..3daaec76b 100644
--- a/modules/logger/parser/fixtures/panic_example_expected/report.xml
+++ b/modules/logger/parser/fixtures/panic_example_expected/report.xml
@@ -2,7 +2,7 @@
-
+
diff --git a/modules/logger/parser/parser.go b/modules/logger/parser/parser.go
index 09692efec..fa78a10ad 100644
--- a/modules/logger/parser/parser.go
+++ b/modules/logger/parser/parser.go
@@ -47,8 +47,9 @@ var (
// getIndent takes a line and returns the indent string
// Example:
-// in: " --- FAIL: TestSnafu"
-// out: " "
+//
+// in: " --- FAIL: TestSnafu"
+// out: " "
func getIndent(data string) string {
re := regexp.MustCompile(`^\s+`)
indent := re.FindString(data)
@@ -57,8 +58,9 @@ func getIndent(data string) string {
// getTestNameFromResultLine takes a go testing result line and extracts out the test name
// Example:
-// in: --- FAIL: TestSnafu
-// out: TestSnafu
+//
+// in: --- FAIL: TestSnafu
+// out: TestSnafu
func getTestNameFromResultLine(text string) string {
m := regexResult.FindStringSubmatch(text)
return m[2]
@@ -71,8 +73,9 @@ func isResultLine(text string) bool {
// getTestNameFromStatusLine takes a go testing status line and extracts out the test name
// Example:
-// in: === RUN TestSnafu
-// out: TestSnafu
+//
+// in: === RUN TestSnafu
+// out: TestSnafu
func getTestNameFromStatusLine(text string) string {
m := regexStatus.FindStringSubmatch(text)
return m[2]
diff --git a/modules/opa/download_policy.go b/modules/opa/download_policy.go
index b786a6f2b..c06d8ff9a 100644
--- a/modules/opa/download_policy.go
+++ b/modules/opa/download_policy.go
@@ -22,7 +22,9 @@ var (
// can be passed to opa. The temporary directory that is used is cached based on the go-getter base path, and reused
// across calls.
// For example, if you call DownloadPolicyE with the go-getter URL multiple times:
-// git::https://github.com/gruntwork-io/terratest.git//policies/foo.rego?ref=master
+//
+// git::https://github.com/gruntwork-io/terratest.git//policies/foo.rego?ref=master
+//
// The first time the gruntwork-io/terratest repo will be downloaded to a new temp directory. All subsequent calls will
// reuse that first temporary dir where the repo was cloned. This is preserved even if a different subdir is requested
// later, e.g.: git::https://github.com/gruntwork-io/terratest.git//examples/bar.rego?ref=master.
diff --git a/modules/opa/eval.go b/modules/opa/eval.go
index e9e77689f..0d5de5beb 100644
--- a/modules/opa/eval.go
+++ b/modules/opa/eval.go
@@ -47,7 +47,9 @@ const (
)
// EvalE runs `opa eval` on the given JSON files using the configured policy file and result query. Translates to:
-// opa eval -i $JSONFile -d $RulePath $ResultQuery
+//
+// opa eval -i $JSONFile -d $RulePath $ResultQuery
+//
// This will asynchronously run OPA on each file concurrently using goroutines.
// This will fail the test if any one of the files failed.
func Eval(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resultQuery string) {
@@ -55,7 +57,9 @@ func Eval(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resu
}
// EvalE runs `opa eval` on the given JSON files using the configured policy file and result query. Translates to:
-// opa eval -i $JSONFile -d $RulePath $ResultQuery
+//
+// opa eval -i $JSONFile -d $RulePath $ResultQuery
+//
// This will asynchronously run OPA on each file concurrently using goroutines.
func EvalE(t testing.TestingT, options *EvalOptions, jsonFilePaths []string, resultQuery string) error {
downloadedPolicyPath, err := DownloadPolicyE(t, options.RulePath)
diff --git a/modules/packer/packer.go b/modules/packer/packer.go
index 58439b796..8f11b4c4a 100644
--- a/modules/packer/packer.go
+++ b/modules/packer/packer.go
@@ -162,7 +162,6 @@ func BuildAmiE(t testing.TestingT, options *Options) (string, error) {
//
// 1456332887,amazon-ebs,artifact,0,id,us-east-1:ami-b481b3de
// 1533742764,googlecompute,artifact,0,id,terratest-packer-example-2018-08-08t15-35-19z
-//
func extractArtifactID(packerLogOutput string) (string, error) {
re := regexp.MustCompile(`.+artifact,\d+?,id,(?:.+?:|)(.+)`)
matches := re.FindStringSubmatch(packerLogOutput)
diff --git a/modules/terraform/format_test.go b/modules/terraform/format_test.go
index acc3c1f06..3a2a325cb 100644
--- a/modules/terraform/format_test.go
+++ b/modules/terraform/format_test.go
@@ -127,16 +127,16 @@ func TestMapToHclString(t *testing.T) {
//
// We have a few unsatisfactory ways to solve this problem:
//
-// 1. Enforce iteration order. This is easy to do in other languages, where you have built-in sorted maps. In Go, no
-// such map exists, and if you create a custom one, you can't use the range keyword on it
-// (http://stackoverflow.com/a/35810932/483528). As a result, we'd have to modify our implementation code to take
-// iteration order into account which is a totally unnecessary feature that increases complexity.
-// 2. We could parse the output string and do an order-independent comparison. However, that adds a bunch of parsing
-// logic into the test code which is a totally unnecessary feature that increases complexity.
-// 3. We accept that Go is a shitty language and, if the test fails, we re-run it a bunch of times in the hope that, if
-// the bug is caused by key ordering, we will randomly get the proper order in a future run. The code being tested
-// here is tiny & fast, so doing a hundred retries is still sub millisecond, so while ugly, this provides a very
-// simple solution.
+// 1. Enforce iteration order. This is easy to do in other languages, where you have built-in sorted maps. In Go, no
+// such map exists, and if you create a custom one, you can't use the range keyword on it
+// (http://stackoverflow.com/a/35810932/483528). As a result, we'd have to modify our implementation code to take
+// iteration order into account which is a totally unnecessary feature that increases complexity.
+// 2. We could parse the output string and do an order-independent comparison. However, that adds a bunch of parsing
+// logic into the test code which is a totally unnecessary feature that increases complexity.
+// 3. We accept that Go is a shitty language and, if the test fails, we re-run it a bunch of times in the hope that, if
+// the bug is caused by key ordering, we will randomly get the proper order in a future run. The code being tested
+// here is tiny & fast, so doing a hundred retries is still sub millisecond, so while ugly, this provides a very
+// simple solution.
//
// Isn't it great that Go's designers built features into the language to prevent bugs that now force every Go
// developer to write thousands of lines of extra code like this, which is of course likely to contain bugs itself?
diff --git a/modules/test-structure/test_structure.go b/modules/test-structure/test_structure.go
index 93ebc4e9a..032716e6e 100644
--- a/modules/test-structure/test_structure.go
+++ b/modules/test-structure/test_structure.go
@@ -53,19 +53,19 @@ func SkipStageEnvVarSet() bool {
// relative to the repo root. If your tests reside in the "/test" relative to the root, then you will use this as
// follows:
//
-// // Root folder where terraform files should be (relative to the test folder)
-// rootFolder := ".."
+// // Root folder where terraform files should be (relative to the test folder)
+// rootFolder := ".."
//
-// // Relative path to terraform module being tested from the root folder
-// terraformFolderRelativeToRoot := "examples/terraform-aws-example"
+// // Relative path to terraform module being tested from the root folder
+// terraformFolderRelativeToRoot := "examples/terraform-aws-example"
//
-// // Copy the terraform folder to a temp folder
-// tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
+// // Copy the terraform folder to a temp folder
+// tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot)
//
-// // Make sure to use the temp test folder in the terraform options
-// terraformOptions := &terraform.Options{
-// TerraformDir: tempTestFolder,
-// }
+// // Make sure to use the temp test folder in the terraform options
+// terraformOptions := &terraform.Options{
+// TerraformDir: tempTestFolder,
+// }
//
// Note that if any of the SKIP_ environment variables is set, we assume this is a test in the local dev where
// there are no other concurrent tests running and we want to be able to cache test data between test stages, so in that
@@ -84,23 +84,23 @@ func CopyTerraformFolderToTemp(t testing.TestingT, rootFolder string, terraformM
// relative to the repo root. If your tests reside in the "/test" relative to the root, then you will use this as
// follows:
//
-// // Destination for the copy of the files. In this example we are using the Azure Dev Ops variable
-// // for the folder that is cleaned after each pipeline job.
-// destRootFolder := os.Getenv("AGENT_TEMPDIRECTORY")
+// // Destination for the copy of the files. In this example we are using the Azure Dev Ops variable
+// // for the folder that is cleaned after each pipeline job.
+// destRootFolder := os.Getenv("AGENT_TEMPDIRECTORY")
//
-// // Root folder where terraform files should be (relative to the test folder)
-// rootFolder := ".."
+// // Root folder where terraform files should be (relative to the test folder)
+// rootFolder := ".."
//
-// // Relative path to terraform module being tested from the root folder
-// terraformFolderRelativeToRoot := "examples/terraform-aws-example"
+// // Relative path to terraform module being tested from the root folder
+// terraformFolderRelativeToRoot := "examples/terraform-aws-example"
//
-// // Copy the terraform folder to a temp folder
-// tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot, destRootFolder)
+// // Copy the terraform folder to a temp folder
+// tempTestFolder := test_structure.CopyTerraformFolderToTemp(t, rootFolder, terraformFolderRelativeToRoot, destRootFolder)
//
-// // Make sure to use the temp test folder in the terraform options
-// terraformOptions := &terraform.Options{
-// TerraformDir: tempTestFolder,
-// }
+// // Make sure to use the temp test folder in the terraform options
+// terraformOptions := &terraform.Options{
+// TerraformDir: tempTestFolder,
+// }
//
// Note that if any of the SKIP_ environment variables is set, we assume this is a test in the local dev where
// there are no other concurrent tests running and we want to be able to cache test data between test stages, so in that
diff --git a/modules/version-checker/version_checker.go b/modules/version-checker/version_checker.go
index dba27a86b..a6ac4cca7 100644
--- a/modules/version-checker/version_checker.go
+++ b/modules/version-checker/version_checker.go
@@ -148,8 +148,8 @@ func extractVersionFromShellCommandOutput(output string) (string, error) {
// It returns Error for ill-formatted version string and VersionMismatchErr for
// minimum version check failure.
//
-// checkVersionConstraint(t, "1.2.31", ">= 1.2.0, < 2.0.0") - no error
-// checkVersionConstraint(t, "1.0.31", ">= 1.2.0, < 2.0.0") - error
+// checkVersionConstraint(t, "1.2.31", ">= 1.2.0, < 2.0.0") - no error
+// checkVersionConstraint(t, "1.0.31", ">= 1.2.0, < 2.0.0") - error
func checkVersionConstraint(actualVersionStr string, versionConstraintStr string) error {
actualVersion, err := version.NewVersion(actualVersionStr)
if err != nil {
diff --git a/test/fixtures/docker-compose-with-buildkit/Dockerfile b/test/fixtures/docker-compose-with-buildkit/Dockerfile
new file mode 100644
index 000000000..6f4648ad8
--- /dev/null
+++ b/test/fixtures/docker-compose-with-buildkit/Dockerfile
@@ -0,0 +1,5 @@
+# A "Hello, World" Docker image used in automated tests for the docker.Build command.
+FROM ubuntu:20.04 as with-secrets
+
+RUN --mount=type=secret,id=github-token echo "$(cat /run/secrets/github-token)" > text.txt
+COPY ./bash_script.sh /usr/local/bin/bash_script.sh
\ No newline at end of file
diff --git a/test/fixtures/docker-compose-with-buildkit/bash_script.sh b/test/fixtures/docker-compose-with-buildkit/bash_script.sh
new file mode 100755
index 000000000..38e4b6b3c
--- /dev/null
+++ b/test/fixtures/docker-compose-with-buildkit/bash_script.sh
@@ -0,0 +1,4 @@
+#!/bin/bash
+set -e
+
+cat text.txt
diff --git a/test/fixtures/docker-compose-with-buildkit/docker-compose.yml b/test/fixtures/docker-compose-with-buildkit/docker-compose.yml
new file mode 100644
index 000000000..6757d33fd
--- /dev/null
+++ b/test/fixtures/docker-compose-with-buildkit/docker-compose.yml
@@ -0,0 +1,11 @@
+services:
+ test-docker-image:
+ build:
+ context: .
+ secrets:
+ - github-token
+ entrypoint: bash_script.sh
+
+secrets:
+ github-token:
+ environment: GITHUB_OAUTH_TOKEN
\ No newline at end of file
diff --git a/test/fixtures/docker-with-buildkit/Dockerfile b/test/fixtures/docker-with-buildkit/Dockerfile
new file mode 100644
index 000000000..24fd46c16
--- /dev/null
+++ b/test/fixtures/docker-with-buildkit/Dockerfile
@@ -0,0 +1,5 @@
+# A "Hello, World" Docker image used in automated tests for the docker.Build command.
+FROM ubuntu:20.04 as with-secrets
+
+RUN --mount=type=secret,id=github-token echo "$(cat /run/secrets/github-token)" > text.txt
+CMD ["cat", "text.txt"]
\ No newline at end of file