helpers/foundation-deployer/gcp/gcp.go (118 lines of code) (raw):
// 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 gcp
import (
"fmt"
"strings"
"time"
"github.com/GoogleCloudPlatform/cloud-foundation-toolkit/infra/blueprint-test/pkg/gcloud"
"github.com/mitchellh/go-testing-interface"
"github.com/tidwall/gjson"
"github.com/terraform-google-modules/terraform-example-foundation/test/integration/testutils"
)
const (
StatusQueued = "QUEUED"
StatusWorking = "WORKING"
StatusSuccess = "SUCCESS"
StatusFailure = "FAILURE"
StatusCancelled = "CANCELLED"
)
type GCP struct {
Runf func(t testing.TB, cmd string, args ...interface{}) gjson.Result
sleepTime time.Duration
}
// NewGCP creates a new wrapper for Google Cloud Platform CLI.
func NewGCP() GCP {
return GCP{
Runf: gcloud.Runf,
sleepTime: 20,
}
}
// GetBuilds gets all Cloud Build builds form a project and region that satisfy the given filter.
func (g GCP) GetBuilds(t testing.TB, projectID, region, filter string) map[string]string {
var result = map[string]string{}
builds := g.Runf(t, "builds list --project %s --region %s --filter %s", projectID, region, filter).Array()
if len(builds) > 0 {
for _, b := range builds {
result[b.Get("id").String()] = b.Get("status").String()
}
}
return result
}
// GetLastBuildStatus gets the status of the last build form a project and region that satisfy the given filter.
func (g GCP) GetLastBuildStatus(t testing.TB, projectID, region, filter string) string {
return g.Runf(t, "builds list --project %s --region %s --limit 1 --sort-by ~createTime --filter %s", projectID, region, filter).Array()[0].Get("status").String()
}
// GetBuildStatus gets the status of the given build
func (g GCP) GetBuildStatus(t testing.TB, projectID, region, buildID string) string {
return g.Runf(t, "builds describe %s --project %s --region %s", buildID, projectID, region).Get("status").String()
}
// GetRunningBuildID gets the current build running for the given project, region, and filter
func (g GCP) GetRunningBuildID(t testing.TB, projectID, region, filter string) string {
time.Sleep(g.sleepTime * time.Second)
builds := g.GetBuilds(t, projectID, region, filter)
for id, status := range builds {
if status == StatusQueued || status == StatusWorking {
return id
}
}
return ""
}
// GetFinalBuildState gets the terminal status of the given build. It will wait if build is not finished.
func (g GCP) GetFinalBuildState(t testing.TB, projectID, region, buildID string, maxRetry int) (string, error) {
var status string
count := 0
fmt.Printf("waiting for build %s execution.\n", buildID)
status = g.GetBuildStatus(t, projectID, region, buildID)
fmt.Printf("build status is %s\n", status)
for status != StatusSuccess && status != StatusFailure && status != StatusCancelled {
fmt.Printf("build status is %s\n", status)
if count >= maxRetry {
return "", fmt.Errorf("timeout waiting for build '%s' execution", buildID)
}
count = count + 1
time.Sleep(g.sleepTime * time.Second)
status = g.GetBuildStatus(t, projectID, region, buildID)
}
fmt.Printf("final build status is %s\n", status)
return status, nil
}
// WaitBuildSuccess waits for the current build in a repo to finish.
func (g GCP) WaitBuildSuccess(t testing.TB, project, region, repo, commitSha, failureMsg string, maxRetry int) error {
var filter string
if commitSha == "" {
filter = fmt.Sprintf("source.repoSource.repoName:%s", repo)
} else {
filter = fmt.Sprintf("source.repoSource.commitSha:%s", commitSha)
}
build := g.GetRunningBuildID(t, project, region, filter)
if build != "" {
status, err := g.GetFinalBuildState(t, project, region, build, maxRetry)
if err != nil {
return err
}
if status != StatusSuccess {
return fmt.Errorf("%s\nSee:\nhttps://console.cloud.google.com/cloud-build/builds;region=%s/%s?project=%s\nfor details.\n", failureMsg, region, build, project)
}
} else {
status := g.GetLastBuildStatus(t, project, region, filter)
if status != StatusSuccess {
return fmt.Errorf("%s\nSee:\nhttps://console.cloud.google.com/cloud-build/builds;region=%s/%s?project=%s\nfor details.\n", failureMsg, region, build, project)
}
}
return nil
}
// HasSccNotification checks if a Security Command Center notification exists
func (g GCP) HasSccNotification(t testing.TB, orgID, sccName string) bool {
filter := fmt.Sprintf("name=organizations/%s/notificationConfigs/%s", orgID, sccName)
scc := g.Runf(t, "scc notifications list organizations/%s --filter %s --quiet", orgID, filter).Array()
if len(scc) == 0 {
return false
}
return testutils.GetLastSplitElement(scc[0].Get("name").String(), "/") == sccName
}
// HasTagKey checks if a Tag Key exists
func (g GCP) HasTagKey(t testing.TB, orgID, tag string) bool {
filter := fmt.Sprintf("shortName=%s", tag)
tags := g.Runf(t, "resource-manager tags keys list --parent organizations/%s --filter %s ", orgID, filter).Array()
if len(tags) == 0 {
return false
}
return tags[0].Get("shortName").String() == tag
}
// EnableApis enables the apis in the given project
func (g GCP) EnableApis(t testing.TB, project string, apis []string) {
g.Runf(t, "services enable %s --project %s", strings.Join(apis, " "), project)
}
// IsApiEnabled checks if the api is enabled in the given project
func (g GCP) IsApiEnabled(t testing.TB, project, api string) bool {
filter := fmt.Sprintf("config.name=%s", api)
return len(g.Runf(t, "services list --enabled --project %s --filter %s", project, filter).Array()) > 0
}