diff --git a/tfplan2cai/test/cli_test.go b/tfplan2cai/test/cli_test.go index ee9f078e0..2d684b611 100644 --- a/tfplan2cai/test/cli_test.go +++ b/tfplan2cai/test/cli_test.go @@ -15,6 +15,7 @@ package test import ( + "encoding/json" "fmt" "log" "os" @@ -23,6 +24,7 @@ import ( "github.com/GoogleCloudPlatform/terraform-google-conversion/v2/caiasset" "github.com/google/go-cmp/cmp" + terraformJSON "github.com/hashicorp/terraform-json" ) // TestCLI tests the "convert" and "validate" subcommand against a generated .tfplan file. @@ -32,6 +34,19 @@ func TestCLI(t *testing.T) { return } + if os.Getenv("CREATE_TEST_PROJECT") != "" && os.Getenv("TEST_ORG_ID") != "" { + orgID := os.Getenv("TEST_ORG_ID") + dir, err := os.MkdirTemp(t.TempDir(), "terraform") + if err != nil { + t.Fatalf("os.MkdirTemp = %v", err) + } + // Do not use defer since it will execute before t.Parallel() + t.Cleanup(func() { + terraformDestroy(t, "terraform", dir, "") + }) + createTestProject(t, dir, orgID) + } + // Test cases for each type of resource is defined here. cases := []struct { name string @@ -176,3 +191,34 @@ func compareMergedIamBindingOutput(t *testing.T, expected []caiasset.Asset, actu t.Errorf("%v diff(-want, +got):\n%s", t.Name(), diff) } } + +func createTestProject(t *testing.T, dir string, orgID string) { + generateTestFiles(t, "./", dir, "create_test_project.tf") + // any terraform execute failure will trigger t.Fatal + terraformInit(t, "terraform", dir) + terraformPlan(t, "terraform", dir, "create_test_project.tfplan") + terraformApply(t, "terraform", dir, "") + // terraform show result contains format_version field which is required in unmarshal. + b := terraformShow(t, "terraform", dir, "") + var state terraformJSON.State + err := json.Unmarshal(b, &state) + if err != nil { + t.Fatal(err) + } + + var ok bool + for _, resource := range state.Values.RootModule.Resources { + if resource.Type == "google_project" { + data.FolderID, ok = resource.AttributeValues["folder_id"].(string) + if !ok { + t.Fatalf("Failed to get folder ID from value %v", resource.AttributeValues["folder_id"]) + } + data.Project["project"], ok = resource.AttributeValues["project_id"].(string) + if !ok { + t.Fatalf("Failed to get project ID from value %v", resource.AttributeValues["project_id"]) + } + } + } + data.Ancestry = fmt.Sprintf("organizations/%s/folders/%s", orgID, data.FolderID) + t.Logf("Successfully created folder_id=%v, project_id=%v", data.FolderID, data.Project["project"]) +} diff --git a/tfplan2cai/test/create_test_project.tf b/tfplan2cai/test/create_test_project.tf new file mode 100644 index 000000000..b90703875 --- /dev/null +++ b/tfplan2cai/test/create_test_project.tf @@ -0,0 +1,30 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google-beta" + version = "~> {{.Provider.version}}" + } + } +} + +provider "google" { + {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}} +} + +resource "random_string" "suffix" { + length = 10 + upper = false + special = false +} + +resource "google_folder" "test-folder" { + display_name = "test-folder-${random_string.suffix.result}" + parent = "organizations/{{.OrgID}}" +} + +resource "google_project" "test-project" { + folder_id = google_folder.test-folder.name + name = "My Project ${random_string.suffix.result}" + project_id = "test-project-${random_string.suffix.result}" +} + diff --git a/tfplan2cai/test/utils_test.go b/tfplan2cai/test/utils_test.go index 4cdfa6d88..dea1a9957 100644 --- a/tfplan2cai/test/utils_test.go +++ b/tfplan2cai/test/utils_test.go @@ -63,8 +63,28 @@ func terraformPlan(t *testing.T, executable, dir, tfplan string) { terraformExec(t, executable, dir, "plan", "-input=false", "-refresh=false", "-out", tfplan) } +func terraformApply(t *testing.T, executable, dir, statePath string) { + if statePath != "" { + terraformExec(t, executable, dir, "apply", "-auto-approve", "-state="+statePath) + return + } + terraformExec(t, executable, dir, "apply", "-auto-approve") +} + +func terraformDestroy(t *testing.T, executable, dir string, statePath string) { + if statePath != "" { + terraformExec(t, executable, dir, "destroy", "-state="+statePath, "-auto-approve") + return + } + terraformExec(t, executable, dir, "destroy", "-state="+statePath, "-auto-approve") +} + func terraformShow(t *testing.T, executable, dir, tfplan string) []byte { - return terraformExec(t, executable, dir, "show", "--json", tfplan) + if tfplan != "" { + return terraformExec(t, executable, dir, "show", "-json", tfplan) + } + // If no tfplan provided, default to show terraform.tfstate file. + return terraformExec(t, executable, dir, "show", "-json") } func terraformExec(t *testing.T, executable, dir string, args ...string) []byte {