diff --git a/cli/Makefile b/cli/Makefile index f6d3f7aa40d..27666a246f3 100644 --- a/cli/Makefile +++ b/cli/Makefile @@ -1,7 +1,7 @@ SHELL := /bin/bash # Changing this value will trigger a new release -VERSION=v1.5.6 +VERSION=v1.5.7 BINARY=bin/cft GITHUB_REPO=github.com/GoogleCloudPlatform/cloud-foundation-toolkit PLATFORMS := linux windows darwin diff --git a/cli/bpmetadata/tfconfig.go b/cli/bpmetadata/tfconfig.go index e589bdc8558..caccbfe2fbd 100644 --- a/cli/bpmetadata/tfconfig.go +++ b/cli/bpmetadata/tfconfig.go @@ -441,9 +441,32 @@ func parseBlueprintRoles(rolesFile *hcl.File) ([]*BlueprintRoles, error) { break } + sortBlueprintRoles(r) return r, nil } +// Sort blueprint roles. +func sortBlueprintRoles(r []*BlueprintRoles) { + sort.SliceStable(r, func(i, j int) bool { + // 1. Sort by Level + if r[i].Level != r[j].Level { + return r[i].Level < r[j].Level + } + + // 2. Sort by the len of roles + if len(r[i].Roles) != len(r[j].Roles) { + return len(r[i].Roles) < len(r[j].Roles) + } + + // 3. Sort by the first role (if available) + if len(r[i].Roles) > 0 && len(r[j].Roles) > 0 { + return r[i].Roles[0] < r[j].Roles[0] + } + + return false + }) +} + // parseBlueprintServices gets the gcp api services required for the blueprint // to be provisioned func parseBlueprintServices(servicesFile *hcl.File) ([]string, error) { diff --git a/cli/bpmetadata/tfconfig_test.go b/cli/bpmetadata/tfconfig_test.go index b65a44dbef7..3d6c2a6fdff 100644 --- a/cli/bpmetadata/tfconfig_test.go +++ b/cli/bpmetadata/tfconfig_test.go @@ -258,6 +258,32 @@ func TestTFRoles(t *testing.T) { }, }, }, + { + name: "simple list of roles in order for multiple level", + configName: "iam-multi-level.tf", + wantRoles: []*BlueprintRoles{ + { + Level: "Project", + Roles: []string{ + "roles/owner", + "roles/storage.admin", + }, + }, + { + Level: "Project", + Roles: []string{ + "roles/cloudsql.admin", + "roles/compute.networkAdmin", + "roles/iam.serviceAccountAdmin", + "roles/resourcemanager.projectIamAdmin", + "roles/storage.admin", + "roles/workflows.admin", + "roles/cloudscheduler.admin", + "roles/iam.serviceAccountUser", + }, + }, + }, + }, } for _, tt := range tests { @@ -271,6 +297,117 @@ func TestTFRoles(t *testing.T) { } } +func TestSortBlueprintRoles(t *testing.T) { + tests := []struct { + name string + in []*BlueprintRoles + want []*BlueprintRoles + }{ + { + name: "sort by level", + in: []*BlueprintRoles{ + { + Level: "Project", + Roles: []string{ + "roles/cloudsql.admin", + }, + }, + { + Level: "Folder", + Roles: []string{ + "roles/storage.admin", + }, + }, + }, + want: []*BlueprintRoles{ + { + Level: "Folder", + Roles: []string{ + "roles/storage.admin", + }, + }, + { + Level: "Project", + Roles: []string{ + "roles/cloudsql.admin", + }, + }, + }, + }, + { + name: "sort by length of roles", + in: []*BlueprintRoles{ + { + Level: "Project", + Roles: []string{ + "roles/storage.admin", + }, + }, + { + Level: "Project", + Roles: []string{ + "roles/cloudsql.admin", + "roles/owner", + }, + }, + }, + want: []*BlueprintRoles{ + { + Level: "Project", + Roles: []string{ + "roles/storage.admin", + }, + }, + { + Level: "Project", + Roles: []string{ + "roles/cloudsql.admin", + "roles/owner", + }, + }, + }, + }, + { + name: "sort by first role", + in: []*BlueprintRoles{ + { + Level: "Project", + Roles: []string{ + "roles/storage.admin", + }, + }, + { + Level: "Project", + Roles: []string{ + "roles/cloudsql.admin", + }, + }, + }, + want: []*BlueprintRoles{ + { + Level: "Project", + Roles: []string{ + "roles/cloudsql.admin", + }, + }, + { + Level: "Project", + Roles: []string{ + "roles/storage.admin", + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + sortBlueprintRoles(tt.in) + assert.Equal(t, tt.in, tt.want) + }) + } +} + func TestTFProviderVersions(t *testing.T) { tests := []struct { name string diff --git a/cli/testdata/bpmetadata/tf/iam-multi-level.tf b/cli/testdata/bpmetadata/tf/iam-multi-level.tf new file mode 100644 index 00000000000..4a5941b0265 --- /dev/null +++ b/cli/testdata/bpmetadata/tf/iam-multi-level.tf @@ -0,0 +1,16 @@ +locals { + int_required_roles = [ + "roles/cloudsql.admin", + "roles/compute.networkAdmin", + "roles/iam.serviceAccountAdmin", + "roles/resourcemanager.projectIamAdmin", + "roles/storage.admin", + "roles/workflows.admin", + "roles/cloudscheduler.admin", + "roles/iam.serviceAccountUser" + ] + int_required_project_roles = [ + "roles/owner", + "roles/storage.admin" + ] +}