diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml index a57d5b9..aacfabf 100644 --- a/.github/workflows/go.yml +++ b/.github/workflows/go.yml @@ -36,7 +36,7 @@ jobs: - name: Setup Terraform uses: hashicorp/setup-terraform@v2 with: - terraform_version: 1.3.7 + terraform_version: 1.4.6 terraform_wrapper: false - run: terraform version -json diff --git a/README.md b/README.md index 058739f..a064dcd 100644 --- a/README.md +++ b/README.md @@ -267,6 +267,15 @@ The report is customizable (text or JSON, per hour, month...), cf [Configuration

+### Existing terraform plan file + +In case you want to read an existing terraform file, you need to pass it as argument. It can either be a raw tfplan or a json plan. +This is useful when some variables or credentials are required to run `terraform plan`. In that case `carbonifer plan` won't try to run `terraform plan` for you, and won't expect to have any credentials or variable set (via env var...) + +```bash +carbonifer plan /path/to/my/project.tfplan +``` + ## Methodology This tool will: @@ -299,9 +308,12 @@ See the [Scope](doc/scope.md) document for more details. ## Usage -`carbonifer [path of terraform files]` +`carbonifer plan [target]` -The targeted terraform folder is provided as the only argument. By default, it uses the current folder. +- `target` can be + - a terraform project folder + - a terraform plan file (json or raw) + - default: the current folder ### Prerequisites diff --git a/cmd/plan.go b/cmd/plan.go index 438a040..97bb032 100644 --- a/cmd/plan.go +++ b/cmd/plan.go @@ -6,8 +6,7 @@ package cmd import ( "bufio" "os" - "path" - "strings" + "path/filepath" log "github.com/sirupsen/logrus" @@ -22,9 +21,21 @@ var test_planCmdHasRun = false // planCmd represents the plan command var planCmd = &cobra.Command{ - Use: "plan", - Short: "Estimate CO2 from your infrastructure code", - Args: cobra.MaximumNArgs(1), + Use: "plan", + Long: `Estimate CO2 from your infrastructure code. + +The 'plan' command optionally takes a single argument: + + directory : + - default: current directory + - directory: a terraform project directory + - file: a terraform plan file (raw or json) +Example usages: + carbonifer plan + carbonifer plan /path/to/terraform/project + carbonifer plan /path/to/terraform/plan.json + carbonifer plan /path/to/terraform/plan.tfplan`, + Args: cobra.MaximumNArgs(1), Run: func(cmd *cobra.Command, args []string) { test_planCmdHasRun = true log.Debug("Running command 'plan'") @@ -33,20 +44,23 @@ var planCmd = &cobra.Command{ if err != nil { log.Fatal(err) } + + input := workdir if len(args) != 0 { - terraformProject := args[0] - if strings.HasPrefix(terraformProject, "/") { - workdir = terraformProject - } else { - workdir = path.Join(workdir, terraformProject) + input = args[0] + if !filepath.IsAbs(input) { + input = filepath.Join(workdir, input) } } - viper.Set("workdir", workdir) - log.Debugf("Workdir : %v", workdir) + // Generate or Read Terraform plan + tfPlan, err := terraform.CarboniferPlan(input) + if err != nil { + log.Fatal(err) + } // Read resources from terraform plan - resources, err := terraform.GetResources() + resources, err := terraform.GetResources(tfPlan) if err != nil { log.Fatal(err) } diff --git a/doc/scope.md b/doc/scope.md index 838b304..37da48c 100644 --- a/doc/scope.md +++ b/doc/scope.md @@ -6,6 +6,7 @@ Currently, Carbonifer can read only Terraform files. It has been tested with the following versions: +- 1.4.6 - 1.3.7 - 1.3.6 diff --git a/internal/terraform/resources.go b/internal/terraform/resources.go new file mode 100644 index 0000000..e762413 --- /dev/null +++ b/internal/terraform/resources.go @@ -0,0 +1,107 @@ +package terraform + +import ( + "encoding/json" + "os" + "strings" + + "github.com/carboniferio/carbonifer/internal/resources" + "github.com/carboniferio/carbonifer/internal/terraform/aws" + "github.com/carboniferio/carbonifer/internal/terraform/gcp" + "github.com/carboniferio/carbonifer/internal/terraform/tfrefs" + tfjson "github.com/hashicorp/terraform-json" + + log "github.com/sirupsen/logrus" +) + +func GetResources(tfPlan *tfjson.Plan) (map[string]resources.Resource, error) { + + log.Debugf("Reading resources from Terraform plan: %d resources", len(tfPlan.PlannedValues.RootModule.Resources)) + resourcesMap := make(map[string]resources.Resource) + terraformRefs := tfrefs.References{ + ResourceConfigs: map[string]*tfjson.ConfigResource{}, + ResourceReferences: map[string]*tfjson.StateResource{}, + DataResources: map[string]resources.DataResource{}, + ProviderConfigs: map[string]string{}, + } + for _, priorRes := range tfPlan.PlannedValues.RootModule.Resources { + log.Debugf("Reading prior state resources %v", priorRes.Address) + if priorRes.Mode == "data" { + if strings.HasPrefix(priorRes.Type, "google") { + dataResource := gcp.GetDataResource(*priorRes) + terraformRefs.DataResources[dataResource.GetKey()] = dataResource + } + } + } + + // Find template first + for _, res := range tfPlan.PlannedValues.RootModule.Resources { + log.Debugf("Reading resource %v", res.Address) + if strings.HasPrefix(res.Type, "google") && (strings.HasSuffix(res.Type, "_template") || + strings.HasSuffix(res.Type, "_autoscaler")) { + if res.Mode == "managed" { + terraformRefs.ResourceReferences[res.Address] = res + } + } + } + + // Index configurations in order to find relationships + for _, resConfig := range tfPlan.Config.RootModule.Resources { + log.Debugf("Reading resource config %v", resConfig.Address) + if strings.HasPrefix(resConfig.Type, "google") { + if resConfig.Mode == "managed" { + terraformRefs.ResourceConfigs[resConfig.Address] = resConfig + } + } + } + + // Get default values + for provider, resConfig := range tfPlan.Config.ProviderConfigs { + if provider == "aws" { + log.Debugf("Reading provider config %v", resConfig.Name) + // TODO #58 Improve way we get default regions (env var, profile...) + var region interface{} + regionExpr := resConfig.Expressions["region"] + if regionExpr != nil { + region = regionExpr.ConstantValue + } else { + if os.Getenv("AWS_REGION") != "" { + region = os.Getenv("AWS_REGION") + } + } + if region != nil { + terraformRefs.ProviderConfigs["region"] = region.(string) + } + } + } + + // Get All resources + for _, res := range tfPlan.PlannedValues.RootModule.Resources { + log.Debugf("Reading resource %v", res.Address) + + if res.Mode == "managed" { + var resource resources.Resource + prefix := strings.Split(res.Type, "_")[0] + if prefix == "google" { + resource = gcp.GetResource(*res, &terraformRefs) + } else if prefix == "aws" { + resource = aws.GetResource(*res, &terraformRefs) + } else { + log.Warnf("Skipping resource %s. Provider not supported : %s", res.Type, prefix) + } + if resource != nil { + resourcesMap[resource.GetAddress()] = resource + if log.IsLevelEnabled(log.DebugLevel) { + computeJsonStr := "" + if resource.IsSupported() { + computeJson, _ := json.Marshal(resource) + computeJsonStr = string(computeJson) + } + log.Debugf(" Compute resource : %v", string(computeJsonStr)) + } + } + } + + } + return resourcesMap, nil +} diff --git a/internal/terraform/resources_test.go b/internal/terraform/resources_test.go new file mode 100644 index 0000000..a62c85a --- /dev/null +++ b/internal/terraform/resources_test.go @@ -0,0 +1,303 @@ +package terraform_test + +import ( + "log" + "path" + "testing" + + "github.com/carboniferio/carbonifer/internal/providers" + "github.com/carboniferio/carbonifer/internal/resources" + "github.com/carboniferio/carbonifer/internal/terraform" + "github.com/carboniferio/carbonifer/internal/testutils" + "github.com/shopspring/decimal" + "github.com/spf13/viper" + "github.com/stretchr/testify/assert" +) + +func TestGetResources(t *testing.T) { + + // reset + terraform.ResetTerraformExec() + + wd := path.Join(testutils.RootDir, "test/terraform/gcp_1") + viper.Set("workdir", wd) + + wantResources := map[string]resources.Resource{ + "google_compute_disk.first": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "first", + ResourceType: "google_compute_disk", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + Specs: &resources.ComputeResourceSpecs{ + GpuTypes: nil, + HddStorage: decimal.NewFromInt(1024), + SsdStorage: decimal.Zero, + MemoryMb: 0, + VCPUs: 0, + CPUType: "", + ReplicationFactor: 1, + }, + }, + "google_compute_instance.first": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "first", + ResourceType: "google_compute_instance", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + Specs: &resources.ComputeResourceSpecs{ + HddStorage: decimal.Zero, + SsdStorage: decimal.NewFromFloat(567).Add(decimal.NewFromFloat(375).Add(decimal.NewFromFloat(375))), + MemoryMb: 87040, + VCPUs: 12, + GpuTypes: []string{ + "testing-custom-data-file", // Default of a2-highgpu-1g" + "nvidia-tesla-k80", // Added by user in main.tf + "nvidia-tesla-k80", // Added by user in main.tf + }, + ReplicationFactor: 1, + }, + }, + "google_compute_instance.second": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "second", + ResourceType: "google_compute_instance", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + Specs: &resources.ComputeResourceSpecs{ + GpuTypes: nil, + HddStorage: decimal.NewFromFloat(10), + SsdStorage: decimal.Zero, + MemoryMb: 4098, + VCPUs: 2, + CPUType: "", + ReplicationFactor: 1, + }, + }, + "google_compute_network.vpc_network": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "vpc_network", + ResourceType: "google_compute_network", + Provider: providers.GCP, + Region: "", + Count: 1, + }, + }, + "google_compute_region_disk.regional-first": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "regional-first", + ResourceType: "google_compute_region_disk", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + Specs: &resources.ComputeResourceSpecs{ + GpuTypes: nil, + HddStorage: decimal.NewFromInt(1024), + SsdStorage: decimal.Zero, + MemoryMb: 0, + VCPUs: 0, + CPUType: "", + ReplicationFactor: 2, + }, + }, + "google_compute_subnetwork.first": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "first", + ResourceType: "google_compute_subnetwork", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + }, + "google_sql_database_instance.instance": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "instance", + ResourceType: "google_sql_database_instance", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + Specs: &resources.ComputeResourceSpecs{ + GpuTypes: nil, + HddStorage: decimal.Zero, + SsdStorage: decimal.NewFromFloat(10), + MemoryMb: 15360, + VCPUs: 4, + CPUType: "", + ReplicationFactor: 2, + }, + }, + } + tfPlan, _ := terraform.TerraformPlan() + resources, _ := terraform.GetResources(tfPlan) + assert.Equal(t, len(resources), len(wantResources)) + for i, resource := range resources { + wantResource := wantResources[i] + assert.Equal(t, wantResource, resource) + } +} + +func TestGetResources_DiskImage(t *testing.T) { + testutils.SkipWithCreds(t) + // reset + terraform.ResetTerraformExec() + + t.Setenv("GOOGLE_OAUTH_ACCESS_TOKEN", "") + + wd := path.Join(testutils.RootDir, "test/terraform/gcp_images") + viper.Set("workdir", wd) + + wantResources := map[string]resources.Resource{ + "google_compute_disk.diskImage": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "diskImage", + ResourceType: "google_compute_disk", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + Specs: &resources.ComputeResourceSpecs{ + GpuTypes: nil, + HddStorage: decimal.New(int64(50), 1), + SsdStorage: decimal.Zero, + MemoryMb: 0, + VCPUs: 0, + CPUType: "", + ReplicationFactor: 1, + }, + }, + } + + tfPlan, _ := terraform.TerraformPlan() + resourceList, err := terraform.GetResources(tfPlan) + if assert.NoError(t, err) { + assert.Equal(t, len(wantResources), len(resourceList)) + for i, resource := range resourceList { + wantResource := wantResources[i] + log.Println(resource.(resources.ComputeResource).Specs.HddStorage) + assert.EqualValues(t, wantResource, resource) + } + } + +} + +func TestGetResources_GroupInstance(t *testing.T) { + // reset + terraform.ResetTerraformExec() + + wd := path.Join(testutils.RootDir, "test/terraform/gcp_group") + viper.Set("workdir", wd) + + wantResources := map[string]resources.Resource{ + "google_compute_network.vpc_network": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "vpc_network", + ResourceType: "google_compute_network", + Provider: providers.GCP, + Region: "", + Count: 1, + }, + }, + "google_compute_subnetwork.first": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "first", + ResourceType: "google_compute_subnetwork", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + }, + "google_compute_instance_group_manager.my-group-manager": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "my-group-manager", + ResourceType: "google_compute_instance_group_manager", + Provider: providers.GCP, + Region: "europe-west9", + Count: 5, + }, + Specs: &resources.ComputeResourceSpecs{ + GpuTypes: nil, + HddStorage: decimal.NewFromFloat(20), + SsdStorage: decimal.Zero, + MemoryMb: 8192, + VCPUs: 2, + CPUType: "", + ReplicationFactor: 1, + }, + }, + } + + tfPlan, _ := terraform.TerraformPlan() + resources, err := terraform.GetResources(tfPlan) + if assert.NoError(t, err) { + for i, resource := range resources { + wantResource := wantResources[i] + assert.EqualValues(t, wantResource, resource) + } + } + +} + +func TestGetResources_InstanceFromTemplate(t *testing.T) { + // reset + terraform.ResetTerraformExec() + + wd := path.Join(testutils.RootDir, "test/terraform/gcp_cit") + viper.Set("workdir", wd) + + wantResources := map[string]resources.Resource{ + "google_compute_network.vpc_network": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "vpc_network", + ResourceType: "google_compute_network", + Provider: providers.GCP, + Region: "", + Count: 1, + }, + }, + "google_compute_subnetwork.first": resources.UnsupportedResource{ + Identification: &resources.ResourceIdentification{ + Name: "first", + ResourceType: "google_compute_subnetwork", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + }, + "google_compute_instance_from_template.ifromtpl": resources.ComputeResource{ + Identification: &resources.ResourceIdentification{ + Name: "ifromtpl", + ResourceType: "google_compute_instance_from_template", + Provider: providers.GCP, + Region: "europe-west9", + Count: 1, + }, + Specs: &resources.ComputeResourceSpecs{ + GpuTypes: nil, + HddStorage: decimal.NewFromFloat(20), + SsdStorage: decimal.Zero, + MemoryMb: 8192, + VCPUs: 2, + CPUType: "", + ReplicationFactor: 1, + }, + }, + } + + tfPlan, _ := terraform.TerraformPlan() + resources, err := terraform.GetResources(tfPlan) + if assert.NoError(t, err) { + for i, resource := range resources { + wantResource := wantResources[i] + assert.EqualValues(t, wantResource, resource) + } + } + +} diff --git a/internal/terraform/terraform.go b/internal/terraform/terraform.go index 2cc2a06..8870bcd 100644 --- a/internal/terraform/terraform.go +++ b/internal/terraform/terraform.go @@ -5,12 +5,9 @@ import ( "encoding/json" "os" "os/exec" + "path/filepath" "strings" - "github.com/carboniferio/carbonifer/internal/resources" - "github.com/carboniferio/carbonifer/internal/terraform/aws" - "github.com/carboniferio/carbonifer/internal/terraform/gcp" - "github.com/carboniferio/carbonifer/internal/terraform/tfrefs" "github.com/hashicorp/go-version" "github.com/hashicorp/hc-install/product" "github.com/hashicorp/hc-install/releases" @@ -52,6 +49,10 @@ func GetTerraformExec() (*tfexec.Terraform, error) { return terraformExec, nil } +func ResetTerraformExec() { + terraformExec = nil +} + func installTerraform() string { var execPath string terraformInstallDir := viper.GetString("terraform.path") @@ -89,24 +90,61 @@ func installTerraform() string { return execPath } -func TerraformPlan() (*tfjson.Plan, error) { +func TerraformInit() (*tfexec.Terraform, *context.Context, error) { tf, err := GetTerraformExec() if err != nil { - return nil, err + return nil, nil, err } - log.Debug("Running terraform plan in ", tf.WorkingDir()) + log.Debug("Running terraform init in ", tf.WorkingDir()) ctx := context.Background() // Terraform init err = tf.Init(ctx) + if err != nil { + return nil, &ctx, err + } + + return tf, &ctx, err +} + +func CarboniferPlan(input string) (*tfjson.Plan, error) { + fileInfo, err := os.Stat(input) + if err != nil { + return nil, err + } + + // If the path points to a file, run show + if !fileInfo.IsDir() { + parentDir := filepath.Dir(input) + fileName := filepath.Base(input) + viper.Set("workdir", parentDir) + tfPlan, err := TerraformShow(fileName) + return tfPlan, err + } else { + // If the path points to a directory, run plan + viper.Set("workdir", input) + tfPlan, err := TerraformPlan() + if err != nil { + if e, ok := err.(*ProviderAuthError); ok { + log.Warnf("Skipping Authentication error: %v", e) + } else { + return nil, err + } + } + return tfPlan, err + } +} + +func TerraformPlan() (*tfjson.Plan, error) { + tf, ctx, err := TerraformInit() if err != nil { return nil, err } // Terraform Validate - _, err = tf.Validate(ctx) + _, err = tf.Validate(*ctx) if err != nil { return nil, err } @@ -135,19 +173,13 @@ func TerraformPlan() (*tfjson.Plan, error) { log.Debugf("Running terraform exec %v", tf.ExecPath()) // Run Terraform Plan with an output file - out := tfexec.Out(tfPlanFile.Name()) - _, err = tf.Plan(ctx, out) + err = terraformPlanExec(tf, *ctx, tfPlanFile) if err != nil { - if strings.Contains(err.Error(), "invalid authentication credentials") || - strings.Contains(err.Error(), "No credentials loaded") || - strings.Contains(err.Error(), "no valid credential") { - return nil, &ProviderAuthError{ParentError: err} - } return nil, err } // Run Terraform Show reading file outputed in step above - tfplan, err := tf.ShowPlanFile(ctx, tfPlanFile.Name()) + tfplan, err := tf.ShowPlanFile(*ctx, tfPlanFile.Name()) if err != nil { log.Infof("error running Terraform Show: %s", err) return nil, err @@ -155,102 +187,52 @@ func TerraformPlan() (*tfjson.Plan, error) { return tfplan, nil } -func GetResources() (map[string]resources.Resource, error) { - log.Debug("Reading planned resources from Terraform plan") - tfPlan, err := TerraformPlan() +func terraformPlanExec(tf *tfexec.Terraform, ctx context.Context, tfPlanFile *os.File) error { + out := tfexec.Out(tfPlanFile.Name()) + _, err := tf.Plan(ctx, out) + var authError ProviderAuthError if err != nil { - if e, ok := err.(*ProviderAuthError); ok { - return nil, e + uwErr := err.Error() + if strings.Contains(uwErr, "invalid authentication credentials") || + strings.Contains(uwErr, "No credentials loaded") || + strings.Contains(uwErr, "no valid credential") { + authError = ProviderAuthError{ParentError: err} + return &authError } else { - log.Fatal(err) - } - } - log.Debugf("Reading resources from Terraform plan: %d resources", len(tfPlan.PlannedValues.RootModule.Resources)) - resourcesMap := make(map[string]resources.Resource) - terraformRefs := tfrefs.References{ - ResourceConfigs: map[string]*tfjson.ConfigResource{}, - ResourceReferences: map[string]*tfjson.StateResource{}, - DataResources: map[string]resources.DataResource{}, - ProviderConfigs: map[string]string{}, - } - for _, priorRes := range tfPlan.PlannedValues.RootModule.Resources { - log.Debugf("Reading prior state resources %v", priorRes.Address) - if priorRes.Mode == "data" { - if strings.HasPrefix(priorRes.Type, "google") { - dataResource := gcp.GetDataResource(*priorRes) - terraformRefs.DataResources[dataResource.GetKey()] = dataResource - } + log.Errorf("error running Terraform Plan: %s", err) + return err } } + return nil +} - // Find template first - for _, res := range tfPlan.PlannedValues.RootModule.Resources { - log.Debugf("Reading resource %v", res.Address) - if strings.HasPrefix(res.Type, "google") && (strings.HasSuffix(res.Type, "_template") || - strings.HasSuffix(res.Type, "_autoscaler")) { - if res.Mode == "managed" { - terraformRefs.ResourceReferences[res.Address] = res - } +func TerraformShow(fileName string) (*tfjson.Plan, error) { + if strings.HasSuffix(fileName, ".json") { + planFilePath := filepath.Join(viper.GetString("workdir"), fileName) + log.Debugf("Reading Terraform plan from %v", planFilePath) + jsonFile, err := os.Open(planFilePath) + if err != nil { + return nil, err } - } - - // Index configurations in order to find relationships - for _, resConfig := range tfPlan.Config.RootModule.Resources { - log.Debugf("Reading resource config %v", resConfig.Address) - if strings.HasPrefix(resConfig.Type, "google") { - if resConfig.Mode == "managed" { - terraformRefs.ResourceConfigs[resConfig.Address] = resConfig - } + defer jsonFile.Close() + byteValue, _ := os.ReadFile(planFilePath) + var tfplan tfjson.Plan + err = json.Unmarshal(byteValue, &tfplan) + if err != nil { + return nil, err } + return &tfplan, nil } - // Get default values - for provider, resConfig := range tfPlan.Config.ProviderConfigs { - if provider == "aws" { - log.Debugf("Reading provider config %v", resConfig.Name) - // TODO #58 Improve way we get default regions (env var, profile...) - var region interface{} - regionExpr := resConfig.Expressions["region"] - if regionExpr != nil { - region = regionExpr.ConstantValue - } else { - if os.Getenv("AWS_REGION") != "" { - region = os.Getenv("AWS_REGION") - } - } - if region != nil { - terraformRefs.ProviderConfigs["region"] = region.(string) - } - } + tf, ctx, err := TerraformInit() + if err != nil { + return nil, err } - // Get All resources - for _, res := range tfPlan.PlannedValues.RootModule.Resources { - log.Debugf("Reading resource %v", res.Address) - - if res.Mode == "managed" { - var resource resources.Resource - prefix := strings.Split(res.Type, "_")[0] - if prefix == "google" { - resource = gcp.GetResource(*res, &terraformRefs) - } else if prefix == "aws" { - resource = aws.GetResource(*res, &terraformRefs) - } else { - log.Warnf("Skipping resource %s. Provider not supported : %s", res.Type, prefix) - } - if resource != nil { - resourcesMap[resource.GetAddress()] = resource - if log.IsLevelEnabled(log.DebugLevel) { - computeJsonStr := "" - if resource.IsSupported() { - computeJson, _ := json.Marshal(resource) - computeJsonStr = string(computeJson) - } - log.Debugf(" Compute resource : %v", string(computeJsonStr)) - } - } - } - + // Run Terraform Show + tfstate, err := tf.ShowPlanFile(*ctx, fileName) + if err != nil { + return nil, err } - return resourcesMap, nil + return tfstate, nil } diff --git a/internal/terraform/terraform_test.go b/internal/terraform/terraform_test.go index 9442287..b66cdf8 100644 --- a/internal/terraform/terraform_test.go +++ b/internal/terraform/terraform_test.go @@ -5,11 +5,8 @@ import ( "path" "testing" - "github.com/carboniferio/carbonifer/internal/providers" - "github.com/carboniferio/carbonifer/internal/resources" "github.com/carboniferio/carbonifer/internal/testutils" _ "github.com/carboniferio/carbonifer/internal/testutils" - "github.com/shopspring/decimal" "github.com/sirupsen/logrus" log "github.com/sirupsen/logrus" "github.com/spf13/viper" @@ -18,7 +15,7 @@ import ( func TestGetTerraformExec(t *testing.T) { // reset - terraformExec = nil + ResetTerraformExec() viper.Set("workdir", ".") tfExec, err := GetTerraformExec() @@ -30,12 +27,12 @@ func TestGetTerraformExec(t *testing.T) { func TestGetTerraformExec_NotExistingExactVersion(t *testing.T) { // reset t.Setenv("PATH", "") - terraformExec = nil + ResetTerraformExec() wantedVersion := "1.2.0" viper.Set("workdir", ".") viper.Set("terraform.version", wantedVersion) - terraformExec = nil + ResetTerraformExec() tfExec, err := GetTerraformExec() assert.NoError(t, err) assert.NotNil(t, tfExec) @@ -50,7 +47,7 @@ func TestGetTerraformExec_NotExistingExactVersion(t *testing.T) { func TestGetTerraformExec_NotExistingNoVersion(t *testing.T) { // reset t.Setenv("PATH", "") - terraformExec = nil + ResetTerraformExec() viper.Set("terraform.version", "") viper.Set("workdir", ".") @@ -62,7 +59,7 @@ func TestGetTerraformExec_NotExistingNoVersion(t *testing.T) { func TestTerraformPlan_NoFile(t *testing.T) { // reset - terraformExec = nil + ResetTerraformExec() wd := path.Join(testutils.RootDir, "test/terraform/empty") logrus.Infof("workdir: %v", wd) @@ -75,7 +72,7 @@ func TestTerraformPlan_NoFile(t *testing.T) { func TestTerraformPlan_NoTfFile(t *testing.T) { // reset - terraformExec = nil + ResetTerraformExec() wd := path.Join(testutils.RootDir, "test/terraform/notTf") logrus.Infof("workdir: %v", wd) @@ -88,7 +85,7 @@ func TestTerraformPlan_NoTfFile(t *testing.T) { func TestTerraformPlan_BadTfFile(t *testing.T) { // reset - terraformExec = nil + ResetTerraformExec() wd := path.Join(testutils.RootDir, "test/terraform/badTf") logrus.Infof("workdir: %v", wd) @@ -99,303 +96,68 @@ func TestTerraformPlan_BadTfFile(t *testing.T) { assert.Contains(t, err.Error(), "configuration is invalid") } -func TestGetResources(t *testing.T) { +func TestTerraformPlan_MissingCreds(t *testing.T) { // reset - terraformExec = nil - - wd := path.Join(testutils.RootDir, "test/terraform/gcp_1") - viper.Set("workdir", wd) - - wantResources := map[string]resources.Resource{ - "google_compute_disk.first": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "first", - ResourceType: "google_compute_disk", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - Specs: &resources.ComputeResourceSpecs{ - GpuTypes: nil, - HddStorage: decimal.NewFromInt(1024), - SsdStorage: decimal.Zero, - MemoryMb: 0, - VCPUs: 0, - CPUType: "", - ReplicationFactor: 1, - }, - }, - "google_compute_instance.first": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "first", - ResourceType: "google_compute_instance", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - Specs: &resources.ComputeResourceSpecs{ - HddStorage: decimal.Zero, - SsdStorage: decimal.NewFromFloat(567).Add(decimal.NewFromFloat(375).Add(decimal.NewFromFloat(375))), - MemoryMb: 87040, - VCPUs: 12, - GpuTypes: []string{ - "testing-custom-data-file", // Default of a2-highgpu-1g" - "nvidia-tesla-k80", // Added by user in main.tf - "nvidia-tesla-k80", // Added by user in main.tf - }, - ReplicationFactor: 1, - }, - }, - "google_compute_instance.second": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "second", - ResourceType: "google_compute_instance", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - Specs: &resources.ComputeResourceSpecs{ - GpuTypes: nil, - HddStorage: decimal.NewFromFloat(10), - SsdStorage: decimal.Zero, - MemoryMb: 4098, - VCPUs: 2, - CPUType: "", - ReplicationFactor: 1, - }, - }, - "google_compute_network.vpc_network": resources.UnsupportedResource{ - Identification: &resources.ResourceIdentification{ - Name: "vpc_network", - ResourceType: "google_compute_network", - Provider: providers.GCP, - Region: "", - Count: 1, - }, - }, - "google_compute_region_disk.regional-first": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "regional-first", - ResourceType: "google_compute_region_disk", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - Specs: &resources.ComputeResourceSpecs{ - GpuTypes: nil, - HddStorage: decimal.NewFromInt(1024), - SsdStorage: decimal.Zero, - MemoryMb: 0, - VCPUs: 0, - CPUType: "", - ReplicationFactor: 2, - }, - }, - "google_compute_subnetwork.first": resources.UnsupportedResource{ - Identification: &resources.ResourceIdentification{ - Name: "first", - ResourceType: "google_compute_subnetwork", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - }, - "google_sql_database_instance.instance": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "instance", - ResourceType: "google_sql_database_instance", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - Specs: &resources.ComputeResourceSpecs{ - GpuTypes: nil, - HddStorage: decimal.Zero, - SsdStorage: decimal.NewFromFloat(10), - MemoryMb: 15360, - VCPUs: 4, - CPUType: "", - ReplicationFactor: 2, - }, - }, - } - - resources, _ := GetResources() - assert.Equal(t, len(resources), len(wantResources)) - for i, resource := range resources { - wantResource := wantResources[i] - assert.Equal(t, wantResource, resource) - } -} - -func TestGetResources_MissingCreds(t *testing.T) { - // reset - terraformExec = nil + ResetTerraformExec() wd := path.Join(testutils.RootDir, "test/terraform/gcp_images") viper.Set("workdir", wd) - _, err := GetResources() + _, err := TerraformPlan() assert.IsType(t, (*ProviderAuthError)(nil), err) } -func TestGetResources_DiskImage(t *testing.T) { - testutils.SkipWithCreds(t) +func TestTerraformShow_JSON(t *testing.T) { // reset - terraformExec = nil + ResetTerraformExec() - t.Setenv("GOOGLE_OAUTH_ACCESS_TOKEN", "") - - wd := path.Join(testutils.RootDir, "test/terraform/gcp_images") - viper.Set("workdir", wd) + tfPlan, err := CarboniferPlan("test/terraform/planJson/plan.json") + assert.NoError(t, err) + assert.Equal(t, tfPlan.TerraformVersion, "1.3.7") - wantResources := map[string]resources.Resource{ - "google_compute_disk.diskImage": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "diskImage", - ResourceType: "google_compute_disk", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - Specs: &resources.ComputeResourceSpecs{ - GpuTypes: nil, - HddStorage: decimal.New(int64(50), 1), - SsdStorage: decimal.Zero, - MemoryMb: 0, - VCPUs: 0, - CPUType: "", - ReplicationFactor: 1, - }, - }, - } +} - resourceList, err := GetResources() - if assert.NoError(t, err) { - assert.Equal(t, len(wantResources), len(resourceList)) - for i, resource := range resourceList { - wantResource := wantResources[i] - log.Println(resource.(resources.ComputeResource).Specs.HddStorage) - assert.EqualValues(t, wantResource, resource) - } - } +func TestTerraformShow_NotExistJSON(t *testing.T) { + // reset + ResetTerraformExec() + _, err := CarboniferPlan("test/terraform/planJson/plan2.json") + assert.Error(t, err) } -func TestGetResources_GroupInstance(t *testing.T) { - testutils.SkipWithCreds(t) +func TestTerraformShow_RawPlan(t *testing.T) { // reset - terraformExec = nil + ResetTerraformExec() - t.Setenv("GOOGLE_OAUTH_ACCESS_TOKEN", "") + tfPlan, err := CarboniferPlan("test/terraform/planRaw/plan.tfplan") + assert.NoError(t, err) + assert.Equal(t, tfPlan.TerraformVersion, "1.4.6") - wd := path.Join(testutils.RootDir, "test/terraform/gcp_group") - viper.Set("workdir", wd) +} - wantResources := map[string]resources.Resource{ - "google_compute_network.vpc_network": resources.UnsupportedResource{ - Identification: &resources.ResourceIdentification{ - Name: "vpc_network", - ResourceType: "google_compute_network", - Provider: providers.GCP, - Region: "", - Count: 1, - }, - }, - "google_compute_subnetwork.first": resources.UnsupportedResource{ - Identification: &resources.ResourceIdentification{ - Name: "first", - ResourceType: "google_compute_subnetwork", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - }, - "google_compute_instance_group_manager.my-group-manager": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "my-group-manager", - ResourceType: "google_compute_instance_group_manager", - Provider: providers.GCP, - Region: "europe-west9", - Count: 5, - }, - Specs: &resources.ComputeResourceSpecs{ - GpuTypes: nil, - HddStorage: decimal.NewFromFloat(20), - SsdStorage: decimal.Zero, - MemoryMb: 8192, - VCPUs: 2, - CPUType: "", - ReplicationFactor: 1, - }, - }, - } +func TestTerraformShow_WithUnsetVar(t *testing.T) { + // reset + ResetTerraformExec() - resources, err := GetResources() - if assert.NoError(t, err) { - for i, resource := range resources { - wantResource := wantResources[i] - assert.EqualValues(t, wantResource, resource) - } - } + _, err := CarboniferPlan("test/terraform/planRaw") + assert.Error(t, err) + assert.ErrorContains(t, err, "was required but not supplied") } -func TestGetResources_InstanceFromTemplate(t *testing.T) { - testutils.SkipWithCreds(t) +func TestTerraformShow_SetVarDifferentFromPlanFile(t *testing.T) { // reset - terraformExec = nil - - t.Setenv("GOOGLE_OAUTH_ACCESS_TOKEN", "") - - wd := path.Join(testutils.RootDir, "test/terraform/gcp_cit") - viper.Set("workdir", wd) + ResetTerraformExec() - wantResources := map[string]resources.Resource{ - "google_compute_network.vpc_network": resources.UnsupportedResource{ - Identification: &resources.ResourceIdentification{ - Name: "vpc_network", - ResourceType: "google_compute_network", - Provider: providers.GCP, - Region: "", - Count: 1, - }, - }, - "google_compute_subnetwork.first": resources.UnsupportedResource{ - Identification: &resources.ResourceIdentification{ - Name: "first", - ResourceType: "google_compute_subnetwork", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - }, - "google_compute_instance_from_template.ifromtpl": resources.ComputeResource{ - Identification: &resources.ResourceIdentification{ - Name: "ifromtpl", - ResourceType: "google_compute_instance_from_template", - Provider: providers.GCP, - Region: "europe-west9", - Count: 1, - }, - Specs: &resources.ComputeResourceSpecs{ - GpuTypes: nil, - HddStorage: decimal.NewFromFloat(20), - SsdStorage: decimal.Zero, - MemoryMb: 8192, - VCPUs: 2, - CPUType: "", - ReplicationFactor: 1, - }, - }, - } + t.Setenv("TF_VAR_machine_type", "f1-medium") - resources, err := GetResources() - if assert.NoError(t, err) { - for i, resource := range resources { - wantResource := wantResources[i] - assert.EqualValues(t, wantResource, resource) - } - } + wd := path.Join(testutils.RootDir, "test/terraform/planRaw") + plan, err := CarboniferPlan(wd) + assert.NoError(t, err) + assert.Equal(t, plan.Variables["machine_type"].Value, "f1-medium") + wd2 := path.Join(testutils.RootDir, "test/terraform/planRaw/plan.tfplan") + plan2, err2 := CarboniferPlan(wd2) + assert.NoError(t, err2) + assert.Equal(t, plan2.Variables["machine_type"].Value, "f1-micro") } diff --git a/test/terraform/planJson/plan.json b/test/terraform/planJson/plan.json new file mode 100644 index 0000000..a2409fb --- /dev/null +++ b/test/terraform/planJson/plan.json @@ -0,0 +1,2608 @@ +{ + "format_version": "1.1", + "terraform_version": "1.3.7", + "variables": { + "instance_count": { + "value": 2 + } + }, + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_compute_autoscaler.foobar", + "mode": "managed", + "type": "google_compute_autoscaler", + "name": "foobar", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "autoscaling_policy": [ + { + "cooldown_period": 60, + "cpu_utilization": [ + { + "predictive_method": "NONE", + "target": 0.5 + } + ], + "load_balancing_utilization": [], + "max_replicas": 10, + "metric": [], + "min_replicas": 1, + "mode": "ON", + "scale_in_control": [], + "scaling_schedules": [] + } + ], + "description": null, + "name": "my-autoscaler", + "timeouts": null + }, + "sensitive_values": { + "autoscaling_policy": [ + { + "cpu_utilization": [ + {} + ], + "load_balancing_utilization": [], + "metric": [], + "scale_in_control": [], + "scaling_schedules": [] + } + ] + } + }, + { + "address": "google_compute_disk.first", + "mode": "managed", + "type": "google_compute_disk", + "name": "first", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "disk_encryption_key": [], + "image": null, + "labels": null, + "name": "cbf-disk-first", + "snapshot": null, + "source_disk": null, + "source_image_encryption_key": [], + "source_snapshot_encryption_key": [], + "timeouts": null, + "type": "pd-standard", + "zone": "europe-west9-a" + }, + "sensitive_values": { + "disk_encryption_key": [], + "source_image_encryption_key": [], + "source_snapshot_encryption_key": [], + "users": [] + } + }, + { + "address": "google_compute_instance.default[0]", + "mode": "managed", + "type": "google_compute_instance", + "name": "default", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 6, + "values": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [ + { + "disk_encryption_key_raw": null, + "mode": "READ_WRITE" + } + ], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-11-bullseye-v20230206", + "size": 564 + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "guest_accelerator": [ + { + "count": 2, + "type": "nvidia-tesla-k80" + } + ], + "hostname": null, + "labels": null, + "machine_type": "n1-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "name": "cbf-test-vm", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "sensitive_values": { + "advanced_machine_features": [], + "attached_disk": [ + {} + ], + "boot_disk": [ + { + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [ + {} + ], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + }, + { + "address": "google_compute_instance.default[1]", + "mode": "managed", + "type": "google_compute_instance", + "name": "default", + "index": 1, + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 6, + "values": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [ + { + "disk_encryption_key_raw": null, + "mode": "READ_WRITE" + } + ], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-11-bullseye-v20230206", + "size": 564 + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "guest_accelerator": [ + { + "count": 2, + "type": "nvidia-tesla-k80" + } + ], + "hostname": null, + "labels": null, + "machine_type": "n1-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "name": "cbf-test-vm", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "sensitive_values": { + "advanced_machine_features": [], + "attached_disk": [ + {} + ], + "boot_disk": [ + { + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [ + {} + ], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + }, + { + "address": "google_compute_instance.foo[0]", + "mode": "managed", + "type": "google_compute_instance", + "name": "foo", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 6, + "values": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "debian-cloud/debian-11" + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "hostname": null, + "labels": null, + "machine_type": "n2-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "min_cpu_platform": "Intel Cascade Lake", + "name": "cbf-test-other", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "sensitive_values": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [ + { + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + }, + { + "address": "google_compute_instance.foo[1]", + "mode": "managed", + "type": "google_compute_instance", + "name": "foo", + "index": 1, + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 6, + "values": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "debian-cloud/debian-11" + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "hostname": null, + "labels": null, + "machine_type": "n2-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "min_cpu_platform": "Intel Cascade Lake", + "name": "cbf-test-other", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "sensitive_values": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [ + { + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + }, + { + "address": "google_compute_instance_from_template.ifromtpl", + "mode": "managed", + "type": "google_compute_instance_from_template", + "name": "ifromtpl", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "can_ip_forward": false, + "labels": { + "my_key": "my_value" + }, + "name": "instance-from-template", + "shielded_instance_config": [], + "timeouts": null, + "zone": "europe-west9-a" + }, + "sensitive_values": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [], + "confidential_instance_config": [], + "guest_accelerator": [], + "labels": {}, + "metadata": {}, + "network_interface": [], + "reservation_affinity": [], + "resource_policies": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [] + } + }, + { + "address": "google_compute_instance_template.my-instance-template", + "mode": "managed", + "type": "google_compute_instance_template", + "name": "my-instance-template", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 1, + "values": { + "advanced_machine_features": [], + "can_ip_forward": false, + "description": null, + "disk": [ + { + "auto_delete": true, + "boot": true, + "disk_encryption_key": [], + "disk_name": null, + "disk_size_gb": 20, + "labels": null, + "resource_policies": null, + "source": null, + "source_image_encryption_key": [], + "source_snapshot": null, + "source_snapshot_encryption_key": [] + } + ], + "guest_accelerator": [], + "instance_description": null, + "labels": null, + "machine_type": "n2-standard-2", + "metadata": null, + "metadata_startup_script": null, + "min_cpu_platform": null, + "name": "my-instance-template", + "network_interface": [ + { + "access_config": [], + "alias_ip_range": [], + "ipv6_access_config": [], + "network_ip": null, + "nic_type": null, + "queue_count": null + } + ], + "reservation_affinity": [], + "service_account": [], + "shielded_instance_config": [], + "tags": null, + "timeouts": null + }, + "sensitive_values": { + "advanced_machine_features": [], + "confidential_instance_config": [], + "disk": [ + { + "disk_encryption_key": [], + "source_image_encryption_key": [], + "source_snapshot_encryption_key": [] + } + ], + "guest_accelerator": [], + "network_interface": [ + { + "access_config": [], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "service_account": [], + "shielded_instance_config": [] + } + }, + { + "address": "google_compute_network.vpc_network", + "mode": "managed", + "type": "google_compute_network", + "name": "vpc_network", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "auto_create_subnetworks": false, + "delete_default_routes_on_create": false, + "description": null, + "enable_ula_internal_ipv6": null, + "mtu": 1460, + "name": "cbf-network", + "timeouts": null + }, + "sensitive_values": {} + }, + { + "address": "google_compute_region_disk.second", + "mode": "managed", + "type": "google_compute_region_disk", + "name": "second", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "disk_encryption_key": [], + "labels": null, + "name": "cbf-disk-second", + "replica_zones": [ + "europe-west9-a", + "europe-west6-b" + ], + "snapshot": null, + "source_disk": null, + "source_snapshot_encryption_key": [], + "timeouts": null, + "type": "pd-standard" + }, + "sensitive_values": { + "disk_encryption_key": [], + "replica_zones": [ + false, + false + ], + "source_snapshot_encryption_key": [], + "users": [] + } + }, + { + "address": "google_compute_region_instance_group_manager.my-group-manager", + "mode": "managed", + "type": "google_compute_region_instance_group_manager", + "name": "my-group-manager", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "auto_healing_policies": [], + "base_instance_name": "managed", + "description": null, + "distribution_policy_zones": [ + "europe-west9-a", + "europe-west9-b" + ], + "list_managed_instances_results": "PAGELESS", + "name": "my-group-manager", + "named_port": [], + "stateful_disk": [], + "target_pools": null, + "target_size": 3, + "timeouts": null, + "version": [ + { + "name": null, + "target_size": [] + } + ], + "wait_for_instances": false, + "wait_for_instances_status": "STABLE" + }, + "sensitive_values": { + "auto_healing_policies": [], + "distribution_policy_zones": [ + false, + false + ], + "named_port": [], + "stateful_disk": [], + "status": [], + "update_policy": [], + "version": [ + { + "target_size": [] + } + ] + } + }, + { + "address": "google_compute_subnetwork.default", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "default", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "description": null, + "ip_cidr_range": "10.0.1.0/24", + "ipv6_access_type": null, + "log_config": [], + "name": "cbf-subnet", + "region": "europe-west9", + "role": null, + "timeouts": null + }, + "sensitive_values": { + "log_config": [], + "secondary_ip_range": [] + } + }, + { + "address": "google_sql_database_instance.instance", + "mode": "managed", + "type": "google_sql_database_instance", + "name": "instance", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "clone": [], + "database_version": "POSTGRES_14", + "deletion_protection": true, + "name": "my-database-instance", + "region": "europe-west9", + "restore_backup_context": [], + "root_password": null, + "settings": [ + { + "activation_policy": "ALWAYS", + "active_directory_config": [], + "availability_type": "REGIONAL", + "collation": null, + "database_flags": [], + "deletion_protection_enabled": null, + "deny_maintenance_period": [], + "disk_autoresize": true, + "disk_autoresize_limit": 0, + "disk_type": "PD_SSD", + "insights_config": [], + "maintenance_window": [], + "password_validation_policy": [], + "pricing_plan": "PER_USE", + "sql_server_audit_config": [], + "tier": "db-g1-small", + "time_zone": null + } + ], + "timeouts": null + }, + "sensitive_values": { + "available_maintenance_versions": [], + "clone": [], + "ip_address": [], + "replica_configuration": [], + "restore_backup_context": [], + "server_ca_cert": [], + "settings": [ + { + "active_directory_config": [], + "backup_configuration": [], + "database_flags": [], + "deny_maintenance_period": [], + "insights_config": [], + "ip_configuration": [], + "location_preference": [], + "maintenance_window": [], + "password_validation_policy": [], + "sql_server_audit_config": [], + "user_labels": {} + } + ] + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_compute_autoscaler.foobar", + "mode": "managed", + "type": "google_compute_autoscaler", + "name": "foobar", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "autoscaling_policy": [ + { + "cooldown_period": 60, + "cpu_utilization": [ + { + "predictive_method": "NONE", + "target": 0.5 + } + ], + "load_balancing_utilization": [], + "max_replicas": 10, + "metric": [], + "min_replicas": 1, + "mode": "ON", + "scale_in_control": [], + "scaling_schedules": [] + } + ], + "description": null, + "name": "my-autoscaler", + "timeouts": null + }, + "after_unknown": { + "autoscaling_policy": [ + { + "cpu_utilization": [ + {} + ], + "load_balancing_utilization": [], + "metric": [], + "scale_in_control": [], + "scaling_schedules": [] + } + ], + "creation_timestamp": true, + "id": true, + "project": true, + "self_link": true, + "target": true, + "zone": true + }, + "before_sensitive": false, + "after_sensitive": { + "autoscaling_policy": [ + { + "cpu_utilization": [ + {} + ], + "load_balancing_utilization": [], + "metric": [], + "scale_in_control": [], + "scaling_schedules": [] + } + ] + } + } + }, + { + "address": "google_compute_disk.first", + "mode": "managed", + "type": "google_compute_disk", + "name": "first", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "disk_encryption_key": [], + "image": null, + "labels": null, + "name": "cbf-disk-first", + "snapshot": null, + "source_disk": null, + "source_image_encryption_key": [], + "source_snapshot_encryption_key": [], + "timeouts": null, + "type": "pd-standard", + "zone": "europe-west9-a" + }, + "after_unknown": { + "creation_timestamp": true, + "disk_encryption_key": [], + "id": true, + "label_fingerprint": true, + "last_attach_timestamp": true, + "last_detach_timestamp": true, + "physical_block_size_bytes": true, + "project": true, + "provisioned_iops": true, + "self_link": true, + "size": true, + "source_disk_id": true, + "source_image_encryption_key": [], + "source_image_id": true, + "source_snapshot_encryption_key": [], + "source_snapshot_id": true, + "users": true + }, + "before_sensitive": false, + "after_sensitive": { + "disk_encryption_key": [], + "source_image_encryption_key": [], + "source_snapshot_encryption_key": [], + "users": [] + } + } + }, + { + "address": "google_compute_instance.default[0]", + "mode": "managed", + "type": "google_compute_instance", + "name": "default", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [ + { + "disk_encryption_key_raw": null, + "mode": "READ_WRITE" + } + ], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-11-bullseye-v20230206", + "size": 564 + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "guest_accelerator": [ + { + "count": 2, + "type": "nvidia-tesla-k80" + } + ], + "hostname": null, + "labels": null, + "machine_type": "n1-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "name": "cbf-test-vm", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "after_unknown": { + "advanced_machine_features": [], + "attached_disk": [ + { + "device_name": true, + "disk_encryption_key_sha256": true, + "kms_key_self_link": true, + "source": true + } + ], + "boot_disk": [ + { + "device_name": true, + "disk_encryption_key_sha256": true, + "initialize_params": [ + { + "labels": true, + "type": true + } + ], + "kms_key_self_link": true, + "source": true + } + ], + "confidential_instance_config": true, + "cpu_platform": true, + "current_status": true, + "guest_accelerator": [ + {} + ], + "id": true, + "instance_id": true, + "label_fingerprint": true, + "metadata_fingerprint": true, + "min_cpu_platform": true, + "network_interface": [ + { + "access_config": [ + { + "nat_ip": true, + "network_tier": true + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "ipv6_access_type": true, + "name": true, + "network": true, + "network_ip": true, + "stack_type": true, + "subnetwork": true, + "subnetwork_project": true + } + ], + "project": true, + "reservation_affinity": true, + "scheduling": true, + "scratch_disk": [], + "self_link": true, + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ], + "tags_fingerprint": true + }, + "before_sensitive": false, + "after_sensitive": { + "advanced_machine_features": [], + "attached_disk": [ + { + "disk_encryption_key_raw": true + } + ], + "boot_disk": [ + { + "disk_encryption_key_raw": true, + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [ + {} + ], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + } + }, + { + "address": "google_compute_instance.default[1]", + "mode": "managed", + "type": "google_compute_instance", + "name": "default", + "index": 1, + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [ + { + "disk_encryption_key_raw": null, + "mode": "READ_WRITE" + } + ], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-11-bullseye-v20230206", + "size": 564 + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "guest_accelerator": [ + { + "count": 2, + "type": "nvidia-tesla-k80" + } + ], + "hostname": null, + "labels": null, + "machine_type": "n1-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "name": "cbf-test-vm", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "after_unknown": { + "advanced_machine_features": [], + "attached_disk": [ + { + "device_name": true, + "disk_encryption_key_sha256": true, + "kms_key_self_link": true, + "source": true + } + ], + "boot_disk": [ + { + "device_name": true, + "disk_encryption_key_sha256": true, + "initialize_params": [ + { + "labels": true, + "type": true + } + ], + "kms_key_self_link": true, + "source": true + } + ], + "confidential_instance_config": true, + "cpu_platform": true, + "current_status": true, + "guest_accelerator": [ + {} + ], + "id": true, + "instance_id": true, + "label_fingerprint": true, + "metadata_fingerprint": true, + "min_cpu_platform": true, + "network_interface": [ + { + "access_config": [ + { + "nat_ip": true, + "network_tier": true + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "ipv6_access_type": true, + "name": true, + "network": true, + "network_ip": true, + "stack_type": true, + "subnetwork": true, + "subnetwork_project": true + } + ], + "project": true, + "reservation_affinity": true, + "scheduling": true, + "scratch_disk": [], + "self_link": true, + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ], + "tags_fingerprint": true + }, + "before_sensitive": false, + "after_sensitive": { + "advanced_machine_features": [], + "attached_disk": [ + { + "disk_encryption_key_raw": true + } + ], + "boot_disk": [ + { + "disk_encryption_key_raw": true, + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [ + {} + ], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + } + }, + { + "address": "google_compute_instance.foo[0]", + "mode": "managed", + "type": "google_compute_instance", + "name": "foo", + "index": 0, + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "debian-cloud/debian-11" + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "hostname": null, + "labels": null, + "machine_type": "n2-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "min_cpu_platform": "Intel Cascade Lake", + "name": "cbf-test-other", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "after_unknown": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [ + { + "device_name": true, + "disk_encryption_key_sha256": true, + "initialize_params": [ + { + "labels": true, + "size": true, + "type": true + } + ], + "kms_key_self_link": true, + "source": true + } + ], + "confidential_instance_config": true, + "cpu_platform": true, + "current_status": true, + "guest_accelerator": true, + "id": true, + "instance_id": true, + "label_fingerprint": true, + "metadata_fingerprint": true, + "network_interface": [ + { + "access_config": [ + { + "nat_ip": true, + "network_tier": true + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "ipv6_access_type": true, + "name": true, + "network": true, + "network_ip": true, + "stack_type": true, + "subnetwork": true, + "subnetwork_project": true + } + ], + "project": true, + "reservation_affinity": true, + "scheduling": true, + "scratch_disk": [], + "self_link": true, + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ], + "tags_fingerprint": true + }, + "before_sensitive": false, + "after_sensitive": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [ + { + "disk_encryption_key_raw": true, + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + } + }, + { + "address": "google_compute_instance.foo[1]", + "mode": "managed", + "type": "google_compute_instance", + "name": "foo", + "index": 1, + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "advanced_machine_features": [], + "allow_stopping_for_update": null, + "attached_disk": [], + "boot_disk": [ + { + "auto_delete": true, + "disk_encryption_key_raw": null, + "initialize_params": [ + { + "image": "debian-cloud/debian-11" + } + ], + "mode": "READ_WRITE" + } + ], + "can_ip_forward": false, + "deletion_protection": false, + "description": null, + "desired_status": null, + "enable_display": null, + "hostname": null, + "labels": null, + "machine_type": "n2-standard-2", + "metadata": null, + "metadata_startup_script": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask", + "min_cpu_platform": "Intel Cascade Lake", + "name": "cbf-test-other", + "network_interface": [ + { + "access_config": [ + { + "public_ptr_domain_name": null + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "nic_type": null, + "queue_count": null + } + ], + "resource_policies": null, + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + "ssh" + ], + "timeouts": null, + "zone": "europe-west9-a" + }, + "after_unknown": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [ + { + "device_name": true, + "disk_encryption_key_sha256": true, + "initialize_params": [ + { + "labels": true, + "size": true, + "type": true + } + ], + "kms_key_self_link": true, + "source": true + } + ], + "confidential_instance_config": true, + "cpu_platform": true, + "current_status": true, + "guest_accelerator": true, + "id": true, + "instance_id": true, + "label_fingerprint": true, + "metadata_fingerprint": true, + "network_interface": [ + { + "access_config": [ + { + "nat_ip": true, + "network_tier": true + } + ], + "alias_ip_range": [], + "ipv6_access_config": [], + "ipv6_access_type": true, + "name": true, + "network": true, + "network_ip": true, + "stack_type": true, + "subnetwork": true, + "subnetwork_project": true + } + ], + "project": true, + "reservation_affinity": true, + "scheduling": true, + "scratch_disk": [], + "self_link": true, + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ], + "tags_fingerprint": true + }, + "before_sensitive": false, + "after_sensitive": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [ + { + "disk_encryption_key_raw": true, + "initialize_params": [ + { + "labels": {} + } + ] + } + ], + "confidential_instance_config": [], + "guest_accelerator": [], + "network_interface": [ + { + "access_config": [ + {} + ], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [ + false + ] + } + } + }, + { + "address": "google_compute_instance_from_template.ifromtpl", + "mode": "managed", + "type": "google_compute_instance_from_template", + "name": "ifromtpl", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "can_ip_forward": false, + "labels": { + "my_key": "my_value" + }, + "name": "instance-from-template", + "shielded_instance_config": [], + "timeouts": null, + "zone": "europe-west9-a" + }, + "after_unknown": { + "advanced_machine_features": true, + "allow_stopping_for_update": true, + "attached_disk": true, + "boot_disk": true, + "confidential_instance_config": true, + "cpu_platform": true, + "current_status": true, + "deletion_protection": true, + "description": true, + "desired_status": true, + "enable_display": true, + "guest_accelerator": true, + "hostname": true, + "id": true, + "instance_id": true, + "label_fingerprint": true, + "labels": {}, + "machine_type": true, + "metadata": true, + "metadata_fingerprint": true, + "metadata_startup_script": true, + "min_cpu_platform": true, + "network_interface": true, + "project": true, + "reservation_affinity": true, + "resource_policies": true, + "scheduling": true, + "scratch_disk": true, + "self_link": true, + "service_account": true, + "shielded_instance_config": [], + "source_instance_template": true, + "tags": true, + "tags_fingerprint": true + }, + "before_sensitive": false, + "after_sensitive": { + "advanced_machine_features": [], + "attached_disk": [], + "boot_disk": [], + "confidential_instance_config": [], + "guest_accelerator": [], + "labels": {}, + "metadata": {}, + "network_interface": [], + "reservation_affinity": [], + "resource_policies": [], + "scheduling": [], + "scratch_disk": [], + "service_account": [], + "shielded_instance_config": [], + "tags": [] + } + } + }, + { + "address": "google_compute_instance_template.my-instance-template", + "mode": "managed", + "type": "google_compute_instance_template", + "name": "my-instance-template", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "advanced_machine_features": [], + "can_ip_forward": false, + "description": null, + "disk": [ + { + "auto_delete": true, + "boot": true, + "disk_encryption_key": [], + "disk_name": null, + "disk_size_gb": 20, + "labels": null, + "resource_policies": null, + "source": null, + "source_image_encryption_key": [], + "source_snapshot": null, + "source_snapshot_encryption_key": [] + } + ], + "guest_accelerator": [], + "instance_description": null, + "labels": null, + "machine_type": "n2-standard-2", + "metadata": null, + "metadata_startup_script": null, + "min_cpu_platform": null, + "name": "my-instance-template", + "network_interface": [ + { + "access_config": [], + "alias_ip_range": [], + "ipv6_access_config": [], + "network_ip": null, + "nic_type": null, + "queue_count": null + } + ], + "reservation_affinity": [], + "service_account": [], + "shielded_instance_config": [], + "tags": null, + "timeouts": null + }, + "after_unknown": { + "advanced_machine_features": [], + "confidential_instance_config": true, + "disk": [ + { + "device_name": true, + "disk_encryption_key": [], + "disk_type": true, + "interface": true, + "mode": true, + "source_image": true, + "source_image_encryption_key": [], + "source_snapshot_encryption_key": [], + "type": true + } + ], + "guest_accelerator": [], + "id": true, + "metadata_fingerprint": true, + "name_prefix": true, + "network_interface": [ + { + "access_config": [], + "alias_ip_range": [], + "ipv6_access_config": [], + "ipv6_access_type": true, + "name": true, + "network": true, + "stack_type": true, + "subnetwork": true, + "subnetwork_project": true + } + ], + "project": true, + "region": true, + "reservation_affinity": [], + "scheduling": true, + "self_link": true, + "service_account": [], + "shielded_instance_config": [], + "tags_fingerprint": true + }, + "before_sensitive": false, + "after_sensitive": { + "advanced_machine_features": [], + "confidential_instance_config": [], + "disk": [ + { + "disk_encryption_key": [], + "source_image_encryption_key": [], + "source_snapshot_encryption_key": [] + } + ], + "guest_accelerator": [], + "network_interface": [ + { + "access_config": [], + "alias_ip_range": [], + "ipv6_access_config": [] + } + ], + "reservation_affinity": [], + "scheduling": [], + "service_account": [], + "shielded_instance_config": [] + } + } + }, + { + "address": "google_compute_network.vpc_network", + "mode": "managed", + "type": "google_compute_network", + "name": "vpc_network", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "auto_create_subnetworks": false, + "delete_default_routes_on_create": false, + "description": null, + "enable_ula_internal_ipv6": null, + "mtu": 1460, + "name": "cbf-network", + "timeouts": null + }, + "after_unknown": { + "gateway_ipv4": true, + "id": true, + "internal_ipv6_range": true, + "project": true, + "routing_mode": true, + "self_link": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + }, + { + "address": "google_compute_region_disk.second", + "mode": "managed", + "type": "google_compute_region_disk", + "name": "second", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "disk_encryption_key": [], + "labels": null, + "name": "cbf-disk-second", + "replica_zones": [ + "europe-west9-a", + "europe-west6-b" + ], + "snapshot": null, + "source_disk": null, + "source_snapshot_encryption_key": [], + "timeouts": null, + "type": "pd-standard" + }, + "after_unknown": { + "creation_timestamp": true, + "disk_encryption_key": [], + "id": true, + "label_fingerprint": true, + "last_attach_timestamp": true, + "last_detach_timestamp": true, + "physical_block_size_bytes": true, + "project": true, + "region": true, + "replica_zones": [ + false, + false + ], + "self_link": true, + "size": true, + "source_disk_id": true, + "source_snapshot_encryption_key": [], + "source_snapshot_id": true, + "users": true + }, + "before_sensitive": false, + "after_sensitive": { + "disk_encryption_key": [], + "replica_zones": [ + false, + false + ], + "source_snapshot_encryption_key": [], + "users": [] + } + } + }, + { + "address": "google_compute_region_instance_group_manager.my-group-manager", + "mode": "managed", + "type": "google_compute_region_instance_group_manager", + "name": "my-group-manager", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "auto_healing_policies": [], + "base_instance_name": "managed", + "description": null, + "distribution_policy_zones": [ + "europe-west9-a", + "europe-west9-b" + ], + "list_managed_instances_results": "PAGELESS", + "name": "my-group-manager", + "named_port": [], + "stateful_disk": [], + "target_pools": null, + "target_size": 3, + "timeouts": null, + "version": [ + { + "name": null, + "target_size": [] + } + ], + "wait_for_instances": false, + "wait_for_instances_status": "STABLE" + }, + "after_unknown": { + "auto_healing_policies": [], + "distribution_policy_target_shape": true, + "distribution_policy_zones": [ + false, + false + ], + "fingerprint": true, + "id": true, + "instance_group": true, + "named_port": [], + "project": true, + "region": true, + "self_link": true, + "stateful_disk": [], + "status": true, + "update_policy": true, + "version": [ + { + "instance_template": true, + "target_size": [] + } + ] + }, + "before_sensitive": false, + "after_sensitive": { + "auto_healing_policies": [], + "distribution_policy_zones": [ + false, + false + ], + "named_port": [], + "stateful_disk": [], + "status": [], + "update_policy": [], + "version": [ + { + "target_size": [] + } + ] + } + } + }, + { + "address": "google_compute_subnetwork.default", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "default", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "description": null, + "ip_cidr_range": "10.0.1.0/24", + "ipv6_access_type": null, + "log_config": [], + "name": "cbf-subnet", + "region": "europe-west9", + "role": null, + "timeouts": null + }, + "after_unknown": { + "creation_timestamp": true, + "external_ipv6_prefix": true, + "fingerprint": true, + "gateway_address": true, + "id": true, + "ipv6_cidr_range": true, + "log_config": [], + "network": true, + "private_ip_google_access": true, + "private_ipv6_google_access": true, + "project": true, + "purpose": true, + "secondary_ip_range": true, + "self_link": true, + "stack_type": true + }, + "before_sensitive": false, + "after_sensitive": { + "log_config": [], + "secondary_ip_range": [] + } + } + }, + { + "address": "google_sql_database_instance.instance", + "mode": "managed", + "type": "google_sql_database_instance", + "name": "instance", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "clone": [], + "database_version": "POSTGRES_14", + "deletion_protection": true, + "name": "my-database-instance", + "region": "europe-west9", + "restore_backup_context": [], + "root_password": null, + "settings": [ + { + "activation_policy": "ALWAYS", + "active_directory_config": [], + "availability_type": "REGIONAL", + "collation": null, + "database_flags": [], + "deletion_protection_enabled": null, + "deny_maintenance_period": [], + "disk_autoresize": true, + "disk_autoresize_limit": 0, + "disk_type": "PD_SSD", + "insights_config": [], + "maintenance_window": [], + "password_validation_policy": [], + "pricing_plan": "PER_USE", + "sql_server_audit_config": [], + "tier": "db-g1-small", + "time_zone": null + } + ], + "timeouts": null + }, + "after_unknown": { + "available_maintenance_versions": true, + "clone": [], + "connection_name": true, + "encryption_key_name": true, + "first_ip_address": true, + "id": true, + "instance_type": true, + "ip_address": true, + "maintenance_version": true, + "master_instance_name": true, + "private_ip_address": true, + "project": true, + "public_ip_address": true, + "replica_configuration": true, + "restore_backup_context": [], + "self_link": true, + "server_ca_cert": true, + "service_account_email_address": true, + "settings": [ + { + "active_directory_config": [], + "backup_configuration": true, + "connector_enforcement": true, + "database_flags": [], + "deny_maintenance_period": [], + "disk_size": true, + "insights_config": [], + "ip_configuration": true, + "location_preference": true, + "maintenance_window": [], + "password_validation_policy": [], + "sql_server_audit_config": [], + "user_labels": true, + "version": true + } + ] + }, + "before_sensitive": false, + "after_sensitive": { + "available_maintenance_versions": [], + "clone": [], + "ip_address": [], + "replica_configuration": [], + "restore_backup_context": [], + "root_password": true, + "server_ca_cert": [], + "settings": [ + { + "active_directory_config": [], + "backup_configuration": [], + "database_flags": [], + "deny_maintenance_period": [], + "insights_config": [], + "ip_configuration": [], + "location_preference": [], + "maintenance_window": [], + "password_validation_policy": [], + "sql_server_audit_config": [], + "user_labels": {} + } + ] + } + } + } + ], + "prior_state": { + "format_version": "1.0", + "terraform_version": "1.3.7", + "values": { + "root_module": { + "resources": [ + { + "address": "data.google_compute_image.debian", + "mode": "data", + "type": "google_compute_image", + "name": "debian", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "archive_size_bytes": 1675957440, + "creation_timestamp": "2023-02-06T09:16:17.455-08:00", + "description": "Debian, Debian GNU/Linux, 11 (bullseye), amd64 built on 20230206, supports Shielded VM features", + "disk_size_gb": 10, + "family": "debian-11", + "filter": null, + "id": "projects/debian-cloud/global/images/debian-11-bullseye-v20230206", + "image_encryption_key_sha256": "", + "image_id": "2681395124550535951", + "label_fingerprint": "42WmSpB8rSM=", + "labels": {}, + "licenses": [ + "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/licenses/debian-11-bullseye" + ], + "name": "debian-11-bullseye-v20230206", + "project": "debian-cloud", + "self_link": "https://www.googleapis.com/compute/v1/projects/debian-cloud/global/images/debian-11-bullseye-v20230206", + "source_disk": "", + "source_disk_encryption_key_sha256": "", + "source_disk_id": "", + "source_image_id": "", + "status": "READY" + }, + "sensitive_values": { + "labels": {}, + "licenses": [ + false + ] + } + } + ] + } + } + }, + "configuration": { + "provider_config": { + "google": { + "name": "google", + "full_name": "registry.terraform.io/hashicorp/google", + "expressions": { + "credentials": {}, + "region": { + "constant_value": "europe-west9" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "google_compute_autoscaler.foobar", + "mode": "managed", + "type": "google_compute_autoscaler", + "name": "foobar", + "provider_config_key": "google", + "expressions": { + "autoscaling_policy": [ + { + "cooldown_period": { + "constant_value": 60 + }, + "cpu_utilization": [ + { + "target": { + "constant_value": 0.5 + } + } + ], + "max_replicas": { + "constant_value": 10 + }, + "min_replicas": { + "constant_value": 1 + } + } + ], + "name": { + "constant_value": "my-autoscaler" + }, + "target": { + "references": [ + "google_compute_region_instance_group_manager.my-group-manager.id", + "google_compute_region_instance_group_manager.my-group-manager" + ] + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_disk.first", + "mode": "managed", + "type": "google_compute_disk", + "name": "first", + "provider_config_key": "google", + "expressions": { + "name": { + "constant_value": "cbf-disk-first" + }, + "zone": { + "constant_value": "europe-west9-a" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_instance.default", + "mode": "managed", + "type": "google_compute_instance", + "name": "default", + "provider_config_key": "google", + "expressions": { + "attached_disk": [ + { + "source": { + "references": [ + "google_compute_disk.first.self_link", + "google_compute_disk.first" + ] + } + } + ], + "boot_disk": [ + { + "initialize_params": [ + { + "image": { + "references": [ + "data.google_compute_image.debian.self_link", + "data.google_compute_image.debian" + ] + }, + "size": { + "constant_value": 564 + } + } + ] + } + ], + "guest_accelerator": { + "constant_value": [ + { + "count": 2, + "type": "nvidia-tesla-k80" + } + ] + }, + "machine_type": { + "constant_value": "n1-standard-2" + }, + "metadata_startup_script": { + "constant_value": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask" + }, + "name": { + "constant_value": "cbf-test-vm" + }, + "network_interface": [ + { + "access_config": [ + {} + ], + "subnetwork": { + "references": [ + "google_compute_subnetwork.default.id", + "google_compute_subnetwork.default" + ] + } + } + ], + "tags": { + "constant_value": [ + "ssh" + ] + }, + "zone": { + "constant_value": "europe-west9-a" + } + }, + "schema_version": 6, + "count_expression": { + "references": [ + "var.instance_count" + ] + } + }, + { + "address": "google_compute_instance.foo", + "mode": "managed", + "type": "google_compute_instance", + "name": "foo", + "provider_config_key": "google", + "expressions": { + "boot_disk": [ + { + "initialize_params": [ + { + "image": { + "constant_value": "debian-cloud/debian-11" + } + } + ] + } + ], + "machine_type": { + "constant_value": "n2-standard-2" + }, + "metadata_startup_script": { + "constant_value": "sudo apt-get update; sudo apt-get install -yq build-essential python3-pip rsync; pip install flask" + }, + "min_cpu_platform": { + "constant_value": "Intel Cascade Lake" + }, + "name": { + "constant_value": "cbf-test-other" + }, + "network_interface": [ + { + "access_config": [ + {} + ], + "subnetwork": { + "references": [ + "google_compute_subnetwork.default.id", + "google_compute_subnetwork.default" + ] + } + } + ], + "tags": { + "constant_value": [ + "ssh" + ] + }, + "zone": { + "constant_value": "europe-west9-a" + } + }, + "schema_version": 6, + "count_expression": { + "references": [ + "var.instance_count" + ] + } + }, + { + "address": "google_compute_instance_from_template.ifromtpl", + "mode": "managed", + "type": "google_compute_instance_from_template", + "name": "ifromtpl", + "provider_config_key": "google", + "expressions": { + "can_ip_forward": { + "constant_value": false + }, + "labels": { + "constant_value": { + "my_key": "my_value" + } + }, + "name": { + "constant_value": "instance-from-template" + }, + "source_instance_template": { + "references": [ + "google_compute_instance_template.my-instance-template.id", + "google_compute_instance_template.my-instance-template" + ] + }, + "zone": { + "constant_value": "europe-west9-a" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_instance_template.my-instance-template", + "mode": "managed", + "type": "google_compute_instance_template", + "name": "my-instance-template", + "provider_config_key": "google", + "expressions": { + "disk": [ + { + "boot": { + "constant_value": true + }, + "disk_size_gb": { + "constant_value": 20 + } + } + ], + "machine_type": { + "constant_value": "n2-standard-2" + }, + "name": { + "constant_value": "my-instance-template" + }, + "network_interface": [ + { + "subnetwork": { + "references": [ + "google_compute_subnetwork.default.id", + "google_compute_subnetwork.default" + ] + } + } + ] + }, + "schema_version": 1 + }, + { + "address": "google_compute_network.vpc_network", + "mode": "managed", + "type": "google_compute_network", + "name": "vpc_network", + "provider_config_key": "google", + "expressions": { + "auto_create_subnetworks": { + "constant_value": false + }, + "mtu": { + "constant_value": 1460 + }, + "name": { + "constant_value": "cbf-network" + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_region_disk.second", + "mode": "managed", + "type": "google_compute_region_disk", + "name": "second", + "provider_config_key": "google", + "expressions": { + "name": { + "constant_value": "cbf-disk-second" + }, + "replica_zones": { + "constant_value": [ + "europe-west9-a", + "europe-west6-b" + ] + } + }, + "schema_version": 0 + }, + { + "address": "google_compute_region_instance_group_manager.my-group-manager", + "mode": "managed", + "type": "google_compute_region_instance_group_manager", + "name": "my-group-manager", + "provider_config_key": "google", + "expressions": { + "base_instance_name": { + "constant_value": "managed" + }, + "distribution_policy_zones": { + "constant_value": [ + "europe-west9-a", + "europe-west9-b" + ] + }, + "name": { + "constant_value": "my-group-manager" + }, + "target_size": { + "constant_value": 3 + }, + "version": [ + { + "instance_template": { + "references": [ + "google_compute_instance_template.my-instance-template.id", + "google_compute_instance_template.my-instance-template" + ] + } + } + ] + }, + "schema_version": 0 + }, + { + "address": "google_compute_subnetwork.default", + "mode": "managed", + "type": "google_compute_subnetwork", + "name": "default", + "provider_config_key": "google", + "expressions": { + "ip_cidr_range": { + "constant_value": "10.0.1.0/24" + }, + "name": { + "constant_value": "cbf-subnet" + }, + "network": { + "references": [ + "google_compute_network.vpc_network.id", + "google_compute_network.vpc_network" + ] + }, + "region": { + "constant_value": "europe-west9" + } + }, + "schema_version": 0 + }, + { + "address": "google_sql_database_instance.instance", + "mode": "managed", + "type": "google_sql_database_instance", + "name": "instance", + "provider_config_key": "google", + "expressions": { + "database_version": { + "constant_value": "POSTGRES_14" + }, + "name": { + "constant_value": "my-database-instance" + }, + "region": { + "constant_value": "europe-west9" + }, + "settings": [ + { + "availability_type": { + "constant_value": "REGIONAL" + }, + "tier": { + "constant_value": "db-g1-small" + } + } + ] + }, + "schema_version": 0 + }, + { + "address": "data.google_compute_image.debian", + "mode": "data", + "type": "google_compute_image", + "name": "debian", + "provider_config_key": "google", + "expressions": { + "family": { + "constant_value": "debian-11" + }, + "project": { + "constant_value": "debian-cloud" + } + }, + "schema_version": 0 + } + ], + "variables": { + "instance_count": { + "default": 2 + } + } + } + }, + "relevant_attributes": [ + { + "resource": "data.google_compute_image.debian", + "attribute": [ + "self_link" + ] + }, + { + "resource": "google_compute_disk.first", + "attribute": [ + "self_link" + ] + }, + { + "resource": "google_compute_network.vpc_network", + "attribute": [ + "id" + ] + }, + { + "resource": "google_compute_subnetwork.default", + "attribute": [ + "id" + ] + }, + { + "resource": "google_compute_instance_template.my-instance-template", + "attribute": [ + "id" + ] + }, + { + "resource": "google_compute_region_instance_group_manager.my-group-manager", + "attribute": [ + "id" + ] + } + ] +} diff --git a/test/terraform/planRaw/main.tf b/test/terraform/planRaw/main.tf new file mode 100644 index 0000000..f2240d2 --- /dev/null +++ b/test/terraform/planRaw/main.tf @@ -0,0 +1,39 @@ +resource "google_compute_network" "vpc_network" { + name = "cbf-network" + auto_create_subnetworks = false + mtu = 1460 +} + +resource "google_compute_subnetwork" "first" { + name = "cbf-subnet" + ip_cidr_range = "10.0.1.0/24" + region = "europe-west9" + network = google_compute_network.vpc_network.id +} + +resource "google_compute_instance" "first" { + name = "cbf-test-vm" + machine_type = var.machine_type + zone = "europe-west9-a" + tags = ["ssh"] + + boot_disk { + initialize_params { + image = "debian-cloud/debian-11" + size = 567 + type = "pd-balanced" + } + } + + network_interface { + subnetwork = google_compute_subnetwork.first.id + + access_config { + # Include this section to give the VM an external IP address + } + } +} + +variable "machine_type" { + type = string +} \ No newline at end of file diff --git a/test/terraform/planRaw/plan.tfplan b/test/terraform/planRaw/plan.tfplan new file mode 100644 index 0000000..3df2e1f Binary files /dev/null and b/test/terraform/planRaw/plan.tfplan differ diff --git a/test/terraform/planRaw/provider.tf b/test/terraform/planRaw/provider.tf new file mode 100644 index 0000000..7fb6a3d --- /dev/null +++ b/test/terraform/planRaw/provider.tf @@ -0,0 +1,4 @@ +provider "google" { + region = "europe-west9" +} +