func TestHelloWorldAppUnit(t *testing.T) { terraformOptions := &terraform.Options{ TerraformDir: "../examples/hello-world-app", } defer terraform.Destroy(t, terraformOptions) terraform.InitAndApply(t, terraformOptions) validate(t, terraformOptions) } 4. Run terraform destroy at the end of the test to undeploy everything
func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } The validate function
func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 1. Run terraform output to get the web service URL
func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 2. Make HTTP requests to the URL
func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 3. Check the response for an expected status and body
func validate(t *testing.T, opts *terraform.Options) { url := terraform.Output(t, opts, "url") http_helper.HttpGetWithRetry(t, url, // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3 * time.Second // Time between retries ) } 4. Retry the request up to 10 times, as deployment is asynchronous
Note: since we’re testing a web service, we use HTTP requests to validate it.
Examples of other ways to validate: Infrastructure Example Validate with… Example Web service Terratest http_helper package Dockerized web app HTTP requests Server Terratest ssh package EC2 instance SSH commands Cloud service Terratest aws or gcp packages SQS Cloud APIs Database MySQL SQL queries MySQL driver for Go
$ export AWS_ACCESS_KEY_ID=xxxx $ export AWS_SECRET_ACCESS_KEY=xxxxx To run the test, first authenticate to AWS
$ go test -v -timeout 15m -run TestHelloWorldAppUnit … --- PASS: TestHelloWorldAppUnit (31.57s) Then run go test . You now have a unit test you can run after every commit!
Un Unit test sts 1. 1. Un Unit testing basics 2. 2. Ex Exampl ple: Terra rrafo form rm unit t te tests ts 3. 3. Ex Exampl ple: Docker/ r/Kube bern rnete tes unit t te tests ts 4. 4. Cl Clean aning up af after tests
What about other tools, such as Docker + Kubernetes?
infrastructure-as-code-testing-talk └ examples └ hello-world-app └ docker-kubernetes └ Dockerfile └ deployment.yml └ modules └ test └ README.md docker-kubernetes : deploy a “Hello, World” web service to Kubernetes
FROM ubuntu:18.04 EXPOSE 8080 RUN DEBIAN_FRONTEND=noninteractive apt-get update && \ apt-get install -y busybox RUN echo 'Hello, World!' > index.html CMD ["busybox", "httpd", "-f", "-p", "8080"] Dockerfile : Dockerize a simple “Hello, World!” web service
apiVersion: apps/v1 kind: Deployment metadata: name: hello-world-app-deployment spec: selector: matchLabels: app: hello-world-app replicas: 1 spec: containers: - name: hello-world-app deployment.yml : define how to deploy a image: gruntwork-io/hello-world-app:v1 Docker container in Kubernetes ports: - containerPort: 8080
$ cd examples/docker-kubernetes $ docker build -t gruntwork-io/hello-world-app:v1 . Successfully tagged gruntwork-io/hello-world-app:v1 $ kubectl apply -f deployment.yml deployment.apps/hello-world-app-deployment created service/hello-world-app-service created $ curl localhost:8080 Hello, World! Build the Docker image, deploy to Kubernetes, and check URL
Let’s write a unit test for this code.
infrastructure-as-code-testing-talk └ examples └ modules └ test └ hello_world_app_test.go └ docker_kubernetes_test.go └ README.md Create docker_kubernetes_test.go
func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } The basic test structure
func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 1. Build the Docker image. You’ll see the buildDockerImage method shortly.
func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 2. Tell Terratest where your Kubernetes deployment is defined
func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 3. Configure kubectl options to authenticate to Kubernetes
func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 4. Run kubectl apply to deploy the web app to Kubernetes
func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 5. Check the app is working. You’ll see the validate method shortly.
func TestDockerKubernetes(t *testing.T) { buildDockerImage(t) path := "../examples/docker-kubernetes/deployment.yml" options := k8s.NewKubectlOptions("", "", "") defer k8s.KubectlDelete(t, options, path) k8s.KubectlApply(t, options, path) validate(t, options) } 6. At the end of the test, remove all Kubernetes resources you deployed
func buildDockerImage(t *testing.T) { options := &docker.BuildOptions{ Tags: []string{"gruntwork-io/hello-world-app:v1"}, } path := "../examples/docker-kubernetes" docker.Build(t, path, options) } The buildDockerImage method
func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } The validate method
func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } 1. Wait until the service is deployed
func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } 2. Make HTTP requests
func validate(t *testing.T, opts *k8s.KubectlOptions) { k8s.WaitUntilServiceAvailable(t, opts, "hello-world- app-service", 10, 1*time.Second) http_helper.HttpGetWithRetry(t, serviceUrl(t, opts), // URL to test 200, // Expected status code "Hello, World!", // Expected body 10, // Max retries 3*time.Second // Time between retries ) } 3. Use serviceUrl method to get URL
func serviceUrl(t *testing.T, opts *k8s.KubectlOptions) string { service := k8s.GetService(t, options, "hello-world-app-service") endpoint := k8s.GetServiceEndpoint(t, options, service, 8080) return fmt.Sprintf("http://%s", endpoint) } The serviceUrl method
$ kubectl config set-credentials … To run the test, first authenticate to a Kubernetes cluster.
Note: Kubernetes is now part of Docker Desktop. Test 100% locally!
Recommend
More recommend