From 410ced297d37334d2832ab8bc4a6ddba2fe1b385 Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Thu, 27 Apr 2023 16:07:19 +1000 Subject: [PATCH 1/3] feat: test suffix and non-suffix variations --- infra/examples/simple_example/main.tf | 1 + infra/examples/suffix_example/README.md | 24 ++++ infra/examples/suffix_example/main.tf | 21 +++ infra/examples/suffix_example/outputs.tf | 26 ++++ infra/examples/suffix_example/variables.tf | 20 +++ infra/examples/suffix_example/versions.tf | 25 ++++ .../suffix_example/suffix_example_test.go | 127 ++++++++++++++++++ 7 files changed, 244 insertions(+) create mode 100644 infra/examples/suffix_example/README.md create mode 100644 infra/examples/suffix_example/main.tf create mode 100644 infra/examples/suffix_example/outputs.tf create mode 100644 infra/examples/suffix_example/variables.tf create mode 100644 infra/examples/suffix_example/versions.tf create mode 100644 infra/test/integration/suffix_example/suffix_example_test.go diff --git a/infra/examples/simple_example/main.tf b/infra/examples/simple_example/main.tf index f4d2207a..117ee39d 100644 --- a/infra/examples/simple_example/main.tf +++ b/infra/examples/simple_example/main.tf @@ -17,5 +17,6 @@ module "dynamic-python-webapp" { source = "../.." + random_suffix = false project_id = var.project_id } diff --git a/infra/examples/suffix_example/README.md b/infra/examples/suffix_example/README.md new file mode 100644 index 00000000..b0a1040a --- /dev/null +++ b/infra/examples/suffix_example/README.md @@ -0,0 +1,24 @@ +# Simple Example + +This example illustrates how to use the `dynamic-python-webapp` module. + + +## Inputs + +| Name | Description | Type | Default | Required | +|------|-------------|------|---------|:--------:| +| project\_id | The ID of the project in which to provision resources. | `string` | n/a | yes | + +## Outputs + +| Name | Description | +|------|-------------| +| usage | Connection details for the project | + + + +To provision this example, run the following from within this directory: +- `terraform init` to get the plugins +- `terraform plan` to see the infrastructure plan +- `terraform apply` to apply the infrastructure build +- `terraform destroy` to destroy the built infrastructure diff --git a/infra/examples/suffix_example/main.tf b/infra/examples/suffix_example/main.tf new file mode 100644 index 00000000..f4d2207a --- /dev/null +++ b/infra/examples/suffix_example/main.tf @@ -0,0 +1,21 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +module "dynamic-python-webapp" { + source = "../.." + + project_id = var.project_id +} diff --git a/infra/examples/suffix_example/outputs.tf b/infra/examples/suffix_example/outputs.tf new file mode 100644 index 00000000..5cfcac20 --- /dev/null +++ b/infra/examples/suffix_example/outputs.tf @@ -0,0 +1,26 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +output "usage" { + sensitive = true + description = "Connection details for the project" + value = module.dynamic-python-webapp.usage +} + +output "suffix" { + description = "random suffix" + value = random_id.suffix.hex +} \ No newline at end of file diff --git a/infra/examples/suffix_example/variables.tf b/infra/examples/suffix_example/variables.tf new file mode 100644 index 00000000..10a4e2da --- /dev/null +++ b/infra/examples/suffix_example/variables.tf @@ -0,0 +1,20 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +variable "project_id" { + description = "The ID of the project in which to provision resources." + type = string +} diff --git a/infra/examples/suffix_example/versions.tf b/infra/examples/suffix_example/versions.tf new file mode 100644 index 00000000..96fc6080 --- /dev/null +++ b/infra/examples/suffix_example/versions.tf @@ -0,0 +1,25 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> 4.0" + } + } + required_version = ">= 0.13" +} diff --git a/infra/test/integration/suffix_example/suffix_example_test.go b/infra/test/integration/suffix_example/suffix_example_test.go new file mode 100644 index 00000000..bf221f51 --- /dev/null +++ b/infra/test/integration/suffix_example/suffix_example_test.go @@ -0,0 +1,127 @@ +// Copyright 2023 Google LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package multiple_buckets + +import ( + "fmt" + "io" + "net/http" + "strings" + "testing" + "time" + + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/tft" + "github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/utils" + "github.com/stretchr/testify/assert" +) + +func TestSimpleExample(t *testing.T) { + example := tft.NewTFBlueprintTest(t) + + example.DefineVerify(func(assert *assert.Assertions) { + example.DefaultVerify(assert) + + projectID := example.GetTFSetupStringOutput("project_id") + region := "us-central1" + suffix := example.GetTFSetupStringOutput("suffix") + t.Logf("Using Project ID %q, suffix %q", projectID, suffix) + + { + // Check that the Cloud Storage API is enabled + services := gcloud.Run(t, "services list", gcloud.WithCommonArgs([]string{"--project", projectID, "--format", "json"})).Array() + match := utils.GetFirstMatchResult(t, services, "config.name", "storage.googleapis.com") + assert.Equal("ENABLED", match.Get("state").String(), "storage service should be enabled") + } + + { + // Check that the Cloud Run service is deployed, is serving, and accepts unauthenticated requests + cloudRunServices := gcloud.Run(t, "run services list", gcloud.WithCommonArgs([]string{"--project", projectID, "--format", "json"})).Array() + nbServices := len(cloudRunServices) + assert.Equal(1, nbServices, "we expected a single Cloud Run service to be deployed, found %d services", nbServices) + match := utils.GetFirstMatchResult(t, cloudRunServices, "kind", "Service") + + // Ensure service named with suffix + serviceName := match.Get("name").String() + assert.Truef(strings.HasSuffix(serviceURL, suffix), "unexpected service name %q", serviceName) + + // Ensure service is running + serviceURL := match.Get("status.url").String() + assert.Truef(strings.HasSuffix(serviceURL, ".run.app"), "unexpected service URL %q", serviceURL) + t.Log("Cloud Run service is running at", serviceURL) + + // The Cloud Run service is the app's API backend (it does not serve the Avocano homepage) + assertResponseContains(assert, serviceURL, "/api", "/admin") + + // The data is populated by two Cloud Run jobs, so wait for the final job to finish before continuing. + // A job execution is completed if it has a completed time. + isJobFinished := func() (bool, error) { + clientJobExecs := gcloud.Run(t, "beta run jobs executions list ", gcloud.WithCommonArgs([]string{"--filter", "metadata.name~client", "--project", projectID, "--region", region, "--format", "json"})).Array() + + if len(clientJobExecs) == 0 { + t.Log("Cloud Run job been executed. Retrying...") + return true, nil + } + + match := utils.GetFirstMatchResult(t, clientJobExecs, "kind", "Execution") + completionTime := match.Get("status.completionTime").String() + + if completionTime == "" { + // retry + t.Log("Cloud Run job execution hasn't completed. Retrying...") + return true, nil + } + + t.Log("Cloud Run job completed", completionTime) + assert.NotEqual(completionTime, "", "completedTime must have a value") + + succeededCount := match.Get("status.succeededCount").Int() + assert.Equal(succeededCount, int64(1), "succeededCount must not be 0") + + return false, nil + } + utils.Poll(t, isJobFinished, 10, time.Second*10) + + // The API must return a list that includes our flagship product + assertResponseContains(assert, serviceURL+"/api/products/", "Sparkly Avocado") + } + { + // Check that the Avocano front page is deployed to Firebase Hosting, and serving + firebaseURL := fmt.Sprintf("https://%s-%s.web.app", projectID, suffix) + assertResponseContains(assert, firebaseURL, "Avocano") + } + }) + example.Test() +} + +func assertResponseContains(assert *assert.Assertions, url string, text ...string) { + code, responseBody, err := httpGetRequest(url) + assert.Nil(err) + assert.GreaterOrEqual(code, 200) + assert.LessOrEqual(code, 299) + for _, fragment := range text { + assert.Containsf(responseBody, fragment, "couldn't find %q in response body", fragment) + } +} + +func httpGetRequest(url string) (statusCode int, body string, err error) { + res, err := http.Get(url) + if err != nil { + return 0, "", err + } + buffer, err := io.ReadAll(res.Body) + res.Body.Close() + return res.StatusCode, string(buffer), err +} From 63ac8b00e395587e68c5ba0ca988f3a7bb09194c Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Thu, 27 Apr 2023 16:23:02 +1000 Subject: [PATCH 2/3] terraform fmt --- infra/examples/simple_example/main.tf | 2 +- infra/examples/suffix_example/outputs.tf | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/infra/examples/simple_example/main.tf b/infra/examples/simple_example/main.tf index 117ee39d..31833d82 100644 --- a/infra/examples/simple_example/main.tf +++ b/infra/examples/simple_example/main.tf @@ -18,5 +18,5 @@ module "dynamic-python-webapp" { source = "../.." random_suffix = false - project_id = var.project_id + project_id = var.project_id } diff --git a/infra/examples/suffix_example/outputs.tf b/infra/examples/suffix_example/outputs.tf index 5cfcac20..c283f834 100644 --- a/infra/examples/suffix_example/outputs.tf +++ b/infra/examples/suffix_example/outputs.tf @@ -20,7 +20,7 @@ output "usage" { value = module.dynamic-python-webapp.usage } -output "suffix" { +output "suffix" { description = "random suffix" - value = random_id.suffix.hex -} \ No newline at end of file + value = random_id.suffix.hex +} From bc5e8615eb24f711f106bb8b4de4bee5d20e6472 Mon Sep 17 00:00:00 2001 From: Katie McLaughlin Date: Thu, 27 Apr 2023 16:31:02 +1000 Subject: [PATCH 3/3] add additional test to cloud build based on https://github.com/GoogleCloudPlatform/terraform-google-secure-cicd/blob/main/build/int.cloudbuild.yaml#L31 --- build/int.cloudbuild.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/build/int.cloudbuild.yaml b/build/int.cloudbuild.yaml index 1c61fd84..8ed7e16f 100644 --- a/build/int.cloudbuild.yaml +++ b/build/int.cloudbuild.yaml @@ -22,6 +22,12 @@ steps: - 'TF_VAR_org_id=$_ORG_ID' - 'TF_VAR_folder_id=$_FOLDER_ID' - 'TF_VAR_billing_account=$_BILLING_ACCOUNT' + +- id: init-all + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run all --stage init --verbose'] + +# simple - id: simple-example-init dir: infra name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' @@ -38,6 +44,26 @@ steps: dir: infra name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' args: ['/bin/bash', '-c', 'cft test run TestSimpleExample --stage teardown --verbose'] + +# suffix +- id: suffix-example-init + dir: infra + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestSuffixExample --stage init --verbose'] +- id: suffix-example-apply + dir: infra + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestSuffixExample --stage apply --verbose'] +- id: suffix-example-verify + dir: infra + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestSuffixExample --stage verify --verbose'] +- id: suffix-example-teardown + dir: infra + name: 'gcr.io/cloud-foundation-cicd/$_DOCKER_IMAGE_DEVELOPER_TOOLS:$_DOCKER_TAG_VERSION_DEVELOPER_TOOLS' + args: ['/bin/bash', '-c', 'cft test run TestSuffixExample --stage teardown --verbose'] + + tags: - 'ci' - 'integration'