Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions build/int.cloudbuild.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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'
Expand Down
3 changes: 2 additions & 1 deletion infra/examples/simple_example/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,6 @@
module "dynamic-python-webapp" {
source = "../.."

project_id = var.project_id
random_suffix = false
project_id = var.project_id
}
24 changes: 24 additions & 0 deletions infra/examples/suffix_example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Simple Example

This example illustrates how to use the `dynamic-python-webapp` module.

<!-- BEGINNING OF PRE-COMMIT-TERRAFORM DOCS HOOK -->
## 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 |

<!-- END OF PRE-COMMIT-TERRAFORM DOCS HOOK -->

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
21 changes: 21 additions & 0 deletions infra/examples/suffix_example/main.tf
Original file line number Diff line number Diff line change
@@ -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
}
26 changes: 26 additions & 0 deletions infra/examples/suffix_example/outputs.tf
Original file line number Diff line number Diff line change
@@ -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
}
20 changes: 20 additions & 0 deletions infra/examples/suffix_example/variables.tf
Original file line number Diff line number Diff line change
@@ -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
}
25 changes: 25 additions & 0 deletions infra/examples/suffix_example/versions.tf
Original file line number Diff line number Diff line change
@@ -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"
}
127 changes: 127 additions & 0 deletions infra/test/integration/suffix_example/suffix_example_test.go
Original file line number Diff line number Diff line change
@@ -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, "<title>Avocano</title>")
}
})
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
}