Skip to content

Commit baaf30e

Browse files
authored
fix: Check beta gcloud components (#1433)
1 parent a4ea938 commit baaf30e

File tree

12 files changed

+167
-18
lines changed

12 files changed

+167
-18
lines changed

.github/workflows/go-lint.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ name: go-lint
1616
on:
1717
pull_request:
1818
branches:
19-
- master
19+
- main
2020
paths:
2121
- ".github/workflows/go-lint.yaml"
2222
- "helpers/foundation-deployer/**"

.github/workflows/go-test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ name: go-test
1717
on:
1818
pull_request:
1919
branches:
20-
- 'master'
20+
- 'main'
2121
paths:
2222
- 'helpers/foundation-deployer/**'
2323
- '.github/workflows/go-test.yaml'

helpers/foundation-deployer/gcp/gcp.go

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,16 @@ func NewGCP() GCP {
4747
}
4848
}
4949

50+
// IsComponentInstalled checks if a given gcloud component is installed
51+
func (g GCP) IsComponentInstalled(t testing.TB, componentID string) bool {
52+
filter := fmt.Sprintf("\"id='%s'\"",componentID)
53+
components := g.Runf(t, "components list --filter %s", filter).Array()
54+
if len(components) == 0 {
55+
return false
56+
}
57+
return components[0].Get("state.name").String() != "Not Installed"
58+
}
59+
5060
// GetBuilds gets all Cloud Build builds form a project and region that satisfy the given filter.
5161
func (g GCP) GetBuilds(t testing.TB, projectID, region, filter string) map[string]string {
5262
var result = map[string]string{}
@@ -116,12 +126,12 @@ func (g GCP) WaitBuildSuccess(t testing.TB, project, region, repo, commitSha, fa
116126
return err
117127
}
118128
if status != StatusSuccess {
119-
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)
129+
return fmt.Errorf("%s\nSee:\nhttps://console.cloud.google.com/cloud-build/builds;region=%s/%s?project=%s\nfor details", failureMsg, region, build, project)
120130
}
121131
} else {
122132
status := g.GetLastBuildStatus(t, project, region, filter)
123133
if status != StatusSuccess {
124-
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)
134+
return fmt.Errorf("%s\nSee:\nhttps://console.cloud.google.com/cloud-build/builds;region=%s/%s?project=%s\nfor details", failureMsg, region, build, project)
125135
}
126136
}
127137
return nil

helpers/foundation-deployer/gcp/gcp_test.go

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,40 @@ import (
2525
"github.com/tidwall/gjson"
2626
)
2727

28+
func TestIsComponentInstalledFound(t *gotest.T) {
29+
betaComponents, err := os.ReadFile(filepath.Join(".", "testdata", "beta_components_installed.json"))
30+
assert.NoError(t, err)
31+
gcp := GCP{
32+
Runf: func(t testing.TB, cmd string, args ...interface{}) gjson.Result {
33+
return gjson.Result{
34+
Type: gjson.JSON,
35+
Raw: string(betaComponents[:]),
36+
}
37+
},
38+
sleepTime: 1,
39+
}
40+
componentID := "beta"
41+
result := gcp.IsComponentInstalled(t, componentID)
42+
assert.True(t, result, "component '%s' should be installed", componentID)
43+
}
44+
45+
func TestIsComponentInstalledNotFound(t *gotest.T) {
46+
betaComponents, err := os.ReadFile(filepath.Join(".", "testdata", "beta_components_not_installed.json"))
47+
assert.NoError(t, err)
48+
gcp := GCP{
49+
Runf: func(t testing.TB, cmd string, args ...interface{}) gjson.Result {
50+
return gjson.Result{
51+
Type: gjson.JSON,
52+
Raw: string(betaComponents[:]),
53+
}
54+
},
55+
sleepTime: 1,
56+
}
57+
componentID := "beta"
58+
result := gcp.IsComponentInstalled(t, componentID)
59+
assert.False(t, result, "component '%s' should not be installed", componentID)
60+
}
61+
2862
func TestGetLastBuildStatus(t *gotest.T) {
2963
current, err := os.ReadFile(filepath.Join(".", "testdata", "success_build.json"))
3064
assert.NoError(t, err)
@@ -101,7 +135,7 @@ func TestWaitBuildSuccess(t *gotest.T) {
101135
sleepTime: 1,
102136
}
103137

104-
err = gcp.WaitBuildSuccess(t, "prj-b-cicd-0123", "us-central1", "repo","", "failed_test_for_WaitBuildSuccess", 40)
138+
err = gcp.WaitBuildSuccess(t, "prj-b-cicd-0123", "us-central1", "repo", "", "failed_test_for_WaitBuildSuccess", 40)
105139
assert.Error(t, err, "should have failed")
106140
assert.Contains(t, err.Error(), "failed_test_for_WaitBuildSuccess", "should have failed with custom info")
107141
assert.Equal(t, callCount, 3, "Runf must be called three times")
@@ -133,7 +167,7 @@ func TestWaitBuildTimeout(t *gotest.T) {
133167
sleepTime: 1,
134168
}
135169

136-
err = gcp.WaitBuildSuccess(t, "prj-b-cicd-0123", "us-central1", "repo","", "failed_test_for_WaitBuildSuccess", 1)
170+
err = gcp.WaitBuildSuccess(t, "prj-b-cicd-0123", "us-central1", "repo", "", "failed_test_for_WaitBuildSuccess", 1)
137171
assert.Error(t, err, "should have failed")
138172
assert.Contains(t, err.Error(), "timeout waiting for build '736f4689-2497-4382-afd0-b5f0f50eea5b' execution", "should have failed with timeout error")
139173
assert.Equal(t, callCount, 3, "Runf must be called three times")
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"current_version_string": "2025.05.30",
4+
"gdu_only": false,
5+
"id": "beta",
6+
"is_configuration": false,
7+
"is_hidden": false,
8+
"latest_version_string": "2025.08.29",
9+
"name": "gcloud Beta Commands",
10+
"platform": {
11+
"architecture": {
12+
"file_name": "x86_64",
13+
"id": "x86_64",
14+
"name": "x86_64"
15+
},
16+
"operating_system": {
17+
"clean_version": "6.6.87",
18+
"file_name": "linux",
19+
"id": "LINUX",
20+
"name": "Linux",
21+
"version": "6.6.87.2-microsoft-standard-WSL2"
22+
}
23+
},
24+
"platform_required": false,
25+
"size": 797,
26+
"state": {
27+
"name": "Update Available"
28+
}
29+
}
30+
]
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
[
2+
{
3+
"current_version_string": "2025.05.30",
4+
"gdu_only": false,
5+
"id": "beta",
6+
"is_configuration": false,
7+
"is_hidden": false,
8+
"latest_version_string": "2025.08.29",
9+
"name": "gcloud Beta Commands",
10+
"platform": {
11+
"architecture": {
12+
"file_name": "x86_64",
13+
"id": "x86_64",
14+
"name": "x86_64"
15+
},
16+
"operating_system": {
17+
"clean_version": "6.6.87",
18+
"file_name": "linux",
19+
"id": "LINUX",
20+
"name": "Linux",
21+
"version": "6.6.87.2-microsoft-standard-WSL2"
22+
}
23+
},
24+
"platform_required": false,
25+
"size": 797,
26+
"state": {
27+
"name": "Not Installed"
28+
}
29+
}
30+
]

helpers/foundation-deployer/main.go

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434

3535
var (
3636
validatorApis = []string{
37-
"securitycenter.googleapis.com",
3837
"accesscontextmanager.googleapis.com",
3938
}
4039
)
@@ -94,6 +93,14 @@ func main() {
9493
// init infra
9594
gotest.Init()
9695
t := &testing.RuntimeT{}
96+
97+
// validate gcloud components
98+
err = stages.ValidateComponents(t)
99+
if err != nil {
100+
fmt.Printf("# Failed validating gcloud components. Error: %s\n", err.Error())
101+
os.Exit(1)
102+
}
103+
97104
conf := stages.CommonConf{
98105
FoundationPath: globalTFVars.FoundationCodePath,
99106
CheckoutPath: globalTFVars.CodeCheckoutPath,
@@ -108,6 +115,9 @@ func main() {
108115
conf.ValidatorProject = *globalTFVars.ValidatorProjectId
109116
var apis []string
110117
gcpConf := gcp.NewGCP()
118+
if globalTFVars.EnableSccResourcesInTerraform != nil && *globalTFVars.EnableSccResourcesInTerraform {
119+
validatorApis = append(validatorApis, "securitycenter.googleapis.com")
120+
}
111121
for _, a := range validatorApis {
112122
if !gcpConf.IsApiEnabled(t, *globalTFVars.ValidatorProjectId, a) {
113123
apis = append(apis, a)

helpers/foundation-deployer/stages/data.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -320,11 +320,11 @@ func ReadGlobalTFVars(file string) (GlobalTFVars, error) {
320320
}
321321
_, err := os.Stat(file)
322322
if os.IsNotExist(err) {
323-
return globalTfvars, fmt.Errorf("tfvars file '%s' does not exits\n", file)
323+
return globalTfvars, fmt.Errorf("tfvars file '%s' does not exits", file)
324324
}
325325
err = utils.ReadTfvars(file, &globalTfvars)
326326
if err != nil {
327-
return globalTfvars, fmt.Errorf("Failed to load tfvars file %s. Error: %s\n", file, err.Error())
327+
return globalTfvars, fmt.Errorf("failed to load tfvars file %s. Error: %s", file, err.Error())
328328
}
329329
return globalTfvars, nil
330330
}

helpers/foundation-deployer/stages/validate.go

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,11 +33,30 @@ const (
3333
func ValidateDirectories(g GlobalTFVars) error {
3434
_, err := os.Stat(g.FoundationCodePath)
3535
if os.IsNotExist(err) {
36-
return fmt.Errorf("Stopping execution, FoundationCodePath directory '%s' does not exits\n", g.FoundationCodePath)
36+
return fmt.Errorf("stopping execution, FoundationCodePath directory '%s' does not exits", g.FoundationCodePath)
3737
}
3838
_, err = os.Stat(g.CodeCheckoutPath)
3939
if os.IsNotExist(err) {
40-
return fmt.Errorf("Stopping execution, CodeCheckoutPath directory '%s' does not exits\n", g.CodeCheckoutPath)
40+
return fmt.Errorf("stopping execution, CodeCheckoutPath directory '%s' does not exits", g.CodeCheckoutPath)
41+
}
42+
return nil
43+
}
44+
45+
// ValidateComponents checks if gcloud Beta Components and Terraform Tools are installed
46+
func ValidateComponents(t testing.TB) error {
47+
gcpConf := gcp.NewGCP()
48+
components := []string{
49+
"beta",
50+
"terraform-tools",
51+
}
52+
missing := []string{}
53+
for _, c := range components {
54+
if !gcpConf.IsComponentInstalled(t, c) {
55+
missing = append(missing, fmt.Sprintf("'%s' not installed", c))
56+
}
57+
}
58+
if len(missing) > 0 {
59+
return fmt.Errorf("missing Google Cloud SDK component:%v", missing)
4160
}
4261
return nil
4362
}

helpers/foundation-deployer/stages/vet.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,22 +51,35 @@ func TerraformVet(t testing.TB, terraformDir, policyPath, project string) error
5151
return err
5252
}
5353
jsonFile, err := utils.WriteTmpFileWithExtension(jsonPlan, "json")
54-
defer os.Remove(jsonFile)
55-
defer os.Remove(options.PlanFilePath)
54+
55+
defer func() {
56+
err := os.Remove(jsonFile)
57+
if err != nil {
58+
fmt.Fprintf(os.Stderr, "Error removing file: %s\n", err)
59+
}
60+
}()
61+
62+
defer func() {
63+
err := os.Remove(options.PlanFilePath)
64+
if err != nil {
65+
fmt.Fprintf(os.Stderr, "Error removing file: %s\n", err)
66+
}
67+
}()
68+
5669
if err != nil {
5770
return err
5871
}
5972
command := fmt.Sprintf("beta terraform vet %s --policy-library=%s --project=%s --quiet", jsonFile, policyPath, project)
6073
result, err := gcloud.RunCmdE(t, command)
61-
if err != nil && !(strings.Contains(err.Error(), "Validating resources") && strings.Contains(err.Error(), "done")) {
74+
if err != nil && (!strings.Contains(err.Error(), "Validating resources") || !strings.Contains(err.Error(), "done")) {
6275
return err
6376
}
6477
if !gjson.Valid(result) {
65-
return fmt.Errorf("Error parsing output, invalid json: %s", result)
78+
return fmt.Errorf("error parsing output, invalid json: %s", result)
6679
}
6780

6881
if len(gjson.Parse(result).Array()) > 0 {
69-
return fmt.Errorf("Policy violations found: %s", result)
82+
return fmt.Errorf("policy violations found: %s", result)
7083
}
7184
fmt.Println("")
7285
fmt.Println("# The configuration passed tf vet.")

0 commit comments

Comments
 (0)