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' diff --git a/infra/examples/simple_example/main.tf b/infra/examples/simple_example/main.tf index f4d2207a..31833d82 100644 --- a/infra/examples/simple_example/main.tf +++ b/infra/examples/simple_example/main.tf @@ -17,5 +17,6 @@ module "dynamic-python-webapp" { source = "../.." - project_id = var.project_id + 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..c283f834 --- /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 +} 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, "