diff --git a/src/terraform/structure/terraform_block.go b/src/terraform/structure/terraform_block.go index 40b120fb..4b69e653 100644 --- a/src/terraform/structure/terraform_block.go +++ b/src/terraform/structure/terraform_block.go @@ -15,6 +15,12 @@ type TerraformBlock struct { var ProviderToTagAttribute = map[string]string{"aws": "tags", "azurerm": "tags", "google": "labels", "oci": "freeform_tags", "alicloud": "tags"} +// ResourceTypeToTagAttribute overrides ProviderToTagAttribute for specific resource types +// whose tag attribute differs from their provider's default. +var ResourceTypeToTagAttribute = map[string]string{ + "google_container_cluster": "resource_labels", +} + const ResourceBlockType = "resource" const ModuleBlockType = "module" const DataBlockType = "data" diff --git a/src/terraform/structure/terraform_block_test.go b/src/terraform/structure/terraform_block_test.go index f719c49f..72b7ec23 100644 --- a/src/terraform/structure/terraform_block_test.go +++ b/src/terraform/structure/terraform_block_test.go @@ -214,4 +214,23 @@ func TestTerraformBlock(t *testing.T) { assert.True(t, gcpBlock.IsGCPBlock()) }) + + t.Run("is_gcp_block_google_container_cluster", func(t *testing.T) { + gkeBlock := &TerraformBlock{ + HclSyntaxBlock: &hclsyntax.Block{Labels: []string{"google_container_cluster", "primary"}}, + Block: structure.Block{TagsAttributeName: "resource_labels"}, + } + + assert.True(t, gkeBlock.IsGCPBlock(), "google_container_cluster should be identified as a GCP block") + }) + + t.Run("resource_type_to_tag_attribute_override", func(t *testing.T) { + // Verify that google_container_cluster maps to resource_labels, not labels + attr, ok := ResourceTypeToTagAttribute["google_container_cluster"] + assert.True(t, ok, "google_container_cluster should have an entry in ResourceTypeToTagAttribute") + assert.Equal(t, "resource_labels", attr) + + // Verify that the provider-level default for google is still labels + assert.Equal(t, "labels", ProviderToTagAttribute["google"]) + }) } diff --git a/src/terraform/structure/terraform_parser.go b/src/terraform/structure/terraform_parser.go index 135c46e2..b9ed0ad4 100644 --- a/src/terraform/structure/terraform_parser.go +++ b/src/terraform/structure/terraform_parser.go @@ -648,6 +648,11 @@ func getProviderFromResourceType(resourceType string) string { } func getTagAttributeByResourceType(resourceType string) (string, error) { + // Check for resource-type-specific override first + if attr, ok := ResourceTypeToTagAttribute[resourceType]; ok { + return attr, nil + } + // Fall back to provider-level default prefix := ProviderToTagAttribute[getProviderFromResourceType(resourceType)] if prefix == "" { return "", fmt.Errorf("failed to find tags attribute name for resource type %s", resourceType) diff --git a/src/terraform/structure/terraform_parser_test.go b/src/terraform/structure/terraform_parser_test.go index 4c1fcf75..3d5bcd6f 100644 --- a/src/terraform/structure/terraform_parser_test.go +++ b/src/terraform/structure/terraform_parser_test.go @@ -169,6 +169,46 @@ func TestTerraformParser_ParseFile(t *testing.T) { assert.NotNil(t, err) }) + t.Run("parse gcp gke file with resource_labels", func(t *testing.T) { + p := &TerraformParser{} + p.Init("../../../tests/terraform/resources/gke", nil) + defer p.Close() + filePath := "../../../tests/terraform/resources/gke/main.tf" + parsedBlocks, err := p.ParseFile(filePath) + if err != nil { + t.Errorf("failed to read hcl file because %s", err) + } + assert.Equal(t, 2, len(parsedBlocks)) + + for _, block := range parsedBlocks { + hclBlock := block.GetRawBlock().(*hclwrite.Block) + assert.Equal(t, ResourceBlockType, hclBlock.Type()) + assert.Equal(t, "google_container_cluster", hclBlock.Labels()[0]) + assert.True(t, block.IsBlockTaggable(), fmt.Sprintf("expected block %s to be taggable", hclBlock.Labels())) + + tfBlock := block.(*TerraformBlock) + assert.Equal(t, "resource_labels", tfBlock.TagsAttributeName, + "google_container_cluster should use resource_labels, not labels") + assert.True(t, tfBlock.IsGCPBlock(), "google_container_cluster should be identified as a GCP block") + + resourceName := hclBlock.Labels()[1] + if resourceName == "primary" { + // The tagged resource should have existing tags + existingTags := block.GetExistingTags() + assert.Equal(t, 2, len(existingTags)) + tagMap := make(map[string]string) + for _, tag := range existingTags { + tagMap[tag.GetKey()] = tag.GetValue() + } + assert.Equal(t, "test", tagMap["env"]) + assert.Equal(t, "devops", tagMap["team"]) + } else if resourceName == "untagged" { + // The untagged resource should have no existing tags + assert.Equal(t, 0, len(block.GetExistingTags())) + } + } + }) + t.Run("Do not crash if getting malformed file", func(t *testing.T) { p := &TerraformParser{} p.Init("../../../tests/terraform/malformed_file_in_dir", nil) diff --git a/src/terraform/structure/tf_taggable.go b/src/terraform/structure/tf_taggable.go index bb2a8f15..9a149d65 100644 --- a/src/terraform/structure/tf_taggable.go +++ b/src/terraform/structure/tf_taggable.go @@ -630,6 +630,7 @@ var TfTaggableResourceTypes = []string{ "google_cloud_identity_group", "google_cloudfunctions_function", "google_composer_environment", + "google_container_cluster", "google_compute_disk", "google_compute_image", "google_compute_instance", diff --git a/tests/terraform/resources/gke/main.tf b/tests/terraform/resources/gke/main.tf new file mode 100644 index 00000000..0af59613 --- /dev/null +++ b/tests/terraform/resources/gke/main.tf @@ -0,0 +1,14 @@ +resource "google_container_cluster" "primary" { + name = "my-gke-cluster" + location = "us-central1" + + resource_labels = { + env = "test" + team = "devops" + } +} + +resource "google_container_cluster" "untagged" { + name = "my-gke-cluster-untagged" + location = "us-central1" +}