From e3520ddf16386f11bf495c1747429e81908b4594 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 19:23:19 +0000 Subject: [PATCH 1/6] feat: Add LogSink converter Adds a new converter for the `google_logging_folder_sink` Terraform resource, converting it to the `logging.googleapis.com/LogSink` CAI asset type. This change includes: - The converter implementation in `mmv1/third_party/tgc/services/logging/logging_folder_sink.go`. - Registration of the new converter in the necessary files. - A test case with corresponding `.tf` and `.json` files to validate the converter's functionality. --- mmv1/provider/terraform_tgc.go | 1 + .../tgc/resource_converters.go.tmpl | 1 + .../services/logging/logging_folder_sink.go | 226 ++++++++++++++++++ .../example_google_logging_folder_sink.json | 50 ++++ .../example_google_logging_folder_sink.tf | 36 +++ 5 files changed, 314 insertions(+) create mode 100644 mmv1/third_party/tgc/services/logging/logging_folder_sink.go create mode 100644 mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json create mode 100644 mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.tf diff --git a/mmv1/provider/terraform_tgc.go b/mmv1/provider/terraform_tgc.go index 9489d077015d..d3c84002eaa5 100644 --- a/mmv1/provider/terraform_tgc.go +++ b/mmv1/provider/terraform_tgc.go @@ -435,6 +435,7 @@ func (tgc TerraformGoogleConversion) CopyCommonFiles(outputFolder string, genera "converters/google/resources/services/logging/logging_billing_account_bucket_config.go": "third_party/tgc/services/logging/logging_billing_account_bucket_config.go", "converters/google/resources/services/appengine/appengine_standard_version.go": "third_party/tgc/services/appengine/appengine_standard_version.go", "converters/google/resources/services/logging/logging_project_sink.go": "third_party/tgc/services/logging/logging_project_sink.go", + "converters/google/resources/services/logging/logging_folder_sink.go": "third_party/tgc/services/logging/logging_folder_sink.go", } tgc.CopyFileList(outputFolder, resourceConverters) } diff --git a/mmv1/third_party/tgc/resource_converters.go.tmpl b/mmv1/third_party/tgc/resource_converters.go.tmpl index bfaafa3512a4..b626d3a0c9f8 100644 --- a/mmv1/third_party/tgc/resource_converters.go.tmpl +++ b/mmv1/third_party/tgc/resource_converters.go.tmpl @@ -139,6 +139,7 @@ func ResourceConverters() map[string][]cai.ResourceConverter { "google_logging_project_bucket_config": {logging.ResourceConverterLogProjectBucket()}, "google_logging_billing_account_bucket_config": {logging.ResourceConverterLogBillingAccountBucket()}, "google_logging_project_sink": {logging.ResourceConverterLogProjectSink()}, + "google_logging_folder_sink": {logging.ResourceConverterLogFolderSink()}, "google_cloud_tasks_queue": {cloudtasks.ResourceConverterCloudTasksQueue()}, "google_pubsub_topic": {pubsub.ResourceConverterPubsubTopic()}, "google_kms_crypto_key": {kms.ResourceConverterKMSCryptoKey()}, diff --git a/mmv1/third_party/tgc/services/logging/logging_folder_sink.go b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go new file mode 100644 index 000000000000..3510f9c53548 --- /dev/null +++ b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go @@ -0,0 +1,226 @@ +package logging + +import ( + "fmt" + "reflect" + + "github.com/GoogleCloudPlatform/terraform-google-conversion/v7/tfplan2cai/converters/google/resources/cai" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +const LogSinkAssetType string = "logging.googleapis.com/LogSink" + +func ResourceConverterLogFolderSink() cai.ResourceConverter { + return cai.ResourceConverter{ + AssetType: LogSinkAssetType, + Convert: GetLogFolderSinkCaiObject, + } +} + +func GetLogFolderSinkCaiObject(d tpgresource.TerraformResourceData, config *transport_tpg.Config) ([]cai.Asset, error) { + name, err := cai.AssetName(d, config, "//logging.googleapis.com/folders/{{folder}}/sinks/{{name}}") + if err != nil { + return []cai.Asset{}, err + } + if obj, err := GetLogFolderSinkApiObject(d, config); err == nil { + return []cai.Asset{{ + Name: name, + Type: LogSinkAssetType, + Resource: &cai.AssetResource{ + Version: "v2", + DiscoveryDocumentURI: "https://logging.googleapis.com/$discovery/rest?version=v2", + DiscoveryName: "LogSink", + Data: obj, + }, + }}, nil + } else { + return []cai.Asset{}, err + } +} + +func GetLogFolderSinkApiObject(d tpgresource.TerraformResourceData, config *transport_tpg.Config) (map[string]interface{}, error) { + obj := make(map[string]interface{}) + nameProp, err := expandLogFolderSinkName(d.Get("name"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + + destinationProp, err := expandLogFolderSinkDestination(d.Get("destination"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("destination"); !tpgresource.IsEmptyValue(reflect.ValueOf(destinationProp)) && (ok || !reflect.DeepEqual(v, destinationProp)) { + obj["destination"] = destinationProp + } + + filterProp, err := expandLogFolderSinkFilter(d.Get("filter"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("filter"); !tpgresource.IsEmptyValue(reflect.ValueOf(filterProp)) && (ok || !reflect.DeepEqual(v, filterProp)) { + obj["filter"] = filterProp + } + + descriptionProp, err := expandLogFolderSinkDescription(d.Get("description"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("description"); !tpgresource.IsEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + + disabledProp, err := expandLogFolderSinkDisabled(d.Get("disabled"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("disabled"); !tpgresource.IsEmptyValue(reflect.ValueOf(disabledProp)) && (ok || !reflect.DeepEqual(v, disabledProp)) { + obj["disabled"] = disabledProp + } + + exclusionsProp, err := expandLogFolderSinkExclusions(d.Get("exclusions"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("exclusions"); !tpgresource.IsEmptyValue(reflect.ValueOf(exclusionsProp)) && (ok || !reflect.DeepEqual(v, exclusionsProp)) { + obj["exclusions"] = exclusionsProp + } + + includeChildrenProp, err := expandLogFolderSinkIncludeChildren(d.Get("include_children"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("include_children"); !tpgresource.IsEmptyValue(reflect.ValueOf(includeChildrenProp)) && (ok || !reflect.DeepEqual(v, includeChildrenProp)) { + obj["includeChildren"] = includeChildrenProp + } + + bigqueryOptionsProp, err := expandLogFolderSinkBigqueryOptions(d.Get("bigquery_options"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("bigquery_options"); !tpgresource.IsEmptyValue(reflect.ValueOf(bigqueryOptionsProp)) && (ok || !reflect.DeepEqual(v, bigqueryOptionsProp)) { + obj["bigqueryOptions"] = bigqueryOptionsProp + } + + folder, ok := d.GetOk("folder") + if !ok { + if val, ok := d.GetOk("folder_id"); ok { + folder = val + } else { + return nil, fmt.Errorf("required field 'folder' is not set") + } + } + if folder.(string) == "" { + if val, ok := d.GetOk("after_unknown.folder"); ok { + folder = val + } + } + obj["parent"] = fmt.Sprintf("//cloudresourcemanager.googleapis.com/folders/%s", folder.(string)) + + return obj, nil +} + +func expandLogFolderSinkName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkDestination(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkFilter(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkDisabled(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkExclusions(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l, ok := v.([]interface{}) + if !ok { + return nil, nil + } + + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + continue + } + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedName, err := expandLogFolderSinkExclusionsName(original["name"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedName); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["name"] = transformedName + } + + transformedDescription, err := expandLogFolderSinkExclusionsDescription(original["description"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDescription); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["description"] = transformedDescription + } + + transformedFilter, err := expandLogFolderSinkExclusionsFilter(original["filter"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedFilter); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["filter"] = transformedFilter + } + + transformedDisabled, err := expandLogFolderSinkExclusionsDisabled(original["disabled"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedDisabled); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["disabled"] = transformedDisabled + } + + req = append(req, transformed) + } + return req, nil +} + +func expandLogFolderSinkExclusionsName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkExclusionsDescription(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkExclusionsFilter(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkExclusionsDisabled(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkIncludeChildren(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func expandLogFolderSinkBigqueryOptions(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + l := v.([]interface{}) + if len(l) == 0 || l[0] == nil { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + transformed := make(map[string]interface{}) + + transformedUsePartitionedTables, err := expandLogFolderSinkBigqueryOptionsUsePartitionedTables(original["use_partitioned_tables"], d, config) + if err != nil { + return nil, err + } else if val := reflect.ValueOf(transformedUsePartitionedTables); val.IsValid() && !tpgresource.IsEmptyValue(val) { + transformed["usePartitionedTables"] = transformedUsePartitionedTables + } + + return transformed, nil +} + +func expandLogFolderSinkBigqueryOptionsUsePartitionedTables(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} diff --git a/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json b/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json new file mode 100644 index 000000000000..a8db8a7d639a --- /dev/null +++ b/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json @@ -0,0 +1,50 @@ +[{ + "name": "//logging.googleapis.com/folders/{{.FolderID}}/sinks/tf-test-sink", + "asset_type": "logging.googleapis.com/LogSink", + "ancestry_path": "{{.Ancestry}}/folder/{{.FolderID}}", + "resource": { + "version": "v2", + "discovery_document_uri": "https://logging.googleapis.com/$discovery/rest", + "discovery_name": "LogSink", + "parent": "//cloudresourcemanager.googleapis.com/folders/{{.FolderID}}", + "data": { + "name": "tf-test-sink", + "destination": "storage.googleapis.com/tf-test-bucket-{{.Project.Number}}", + "filter": "severity >= ERROR", + "includeChildren": true, + "exclusions": [{ + "name": "exclude-gce-activity", + "description": "Exclude GCE activity logs.", + "filter": "logName:\"logs/compute.googleapis.com%2Factivity_log\"" + }], + "bigqueryOptions": { + "usePartitionedTables": true + } + } + } +}, +{ + "name": "//storage.googleapis.com/tf-test-bucket-{{.Project.Number}}", + "asset_type": "storage.googleapis.com/Bucket", + "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}", + "resource": { + "version": "v2", + "discovery_document_uri": "https://www.googleapis.com/discovery/v1/apis/storage/v1/rest", + "discovery_name": "Bucket", + "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}", + "data": { + "iamConfiguration": { + "uniformBucketLevelAccess": { + "enabled": false + } + }, + "lifecycle": { + "rule": [] + }, + "location": "US", + "name": "tf-test-bucket-{{.Project.Number}}", + "project": "{{.Provider.project}}", + "storageClass": "STANDARD" + } + } +}] diff --git a/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.tf b/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.tf new file mode 100644 index 000000000000..8af38988f948 --- /dev/null +++ b/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.tf @@ -0,0 +1,36 @@ +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = ">= 4.54.0" + } + } +} + +provider "google" { + project = "{{.Provider.project}}" +} + +resource "google_storage_bucket" "test_bucket" { + name = "tf-test-bucket-{{.Project.Number}}" + location = "US" + project = "{{.Provider.project}}" +} + +resource "google_logging_folder_sink" "test_sink" { + name = "tf-test-sink" + folder = "folders/{{.FolderID}}" + destination = "storage.googleapis.com/${google_storage_bucket.test_bucket.name}" + filter = "severity >= ERROR" + include_children = true + + exclusions { + name = "exclude-gce-activity" + description = "Exclude GCE activity logs." + filter = "logName:\"logs/compute.googleapis.com%2Factivity_log\"" + } + + bigquery_options { + use_partitioned_tables = true + } +} From 311280a6c8bf67cd87c84977094fa254b83ea170 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:08:47 +0000 Subject: [PATCH 2/6] feat: Add LogSink converter Adds a new converter for the `google_logging_folder_sink` Terraform resource, converting it to the `logging.googleapis.com/LogSink` CAI asset type. This change includes: - The converter implementation in `mmv1/third_party/tgc/services/logging/logging_folder_sink.go`. - Registration of the new converter in the necessary files. - A test case with corresponding `.tf` and `.json` files to validate the converter's functionality. From a4955646cba675b2337ad766fc74b8989e8a73b1 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Thu, 30 Oct 2025 20:30:31 +0000 Subject: [PATCH 3/6] feat: Add LogSink converter Adds a new converter for the `google_logging_folder_sink` Terraform resource, converting it to the `logging.googleapis.com/LogSink` CAI asset type. This change includes: - The converter implementation in `mmv1/third_party/tgc/services/logging/logging_folder_sink.go`. - Registration of the new converter in the necessary files. - A test case with corresponding `.tf` and `.json` files to validate the converter's functionality. --- .../tgc/services/logging/logging_folder_sink.go | 17 +++++------------ 1 file changed, 5 insertions(+), 12 deletions(-) diff --git a/mmv1/third_party/tgc/services/logging/logging_folder_sink.go b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go index 3510f9c53548..3c342450af4b 100644 --- a/mmv1/third_party/tgc/services/logging/logging_folder_sink.go +++ b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go @@ -3,6 +3,7 @@ package logging import ( "fmt" "reflect" + "strings" "github.com/GoogleCloudPlatform/terraform-google-conversion/v7/tfplan2cai/converters/google/resources/cai" "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" @@ -29,7 +30,7 @@ func GetLogFolderSinkCaiObject(d tpgresource.TerraformResourceData, config *tran Type: LogSinkAssetType, Resource: &cai.AssetResource{ Version: "v2", - DiscoveryDocumentURI: "https://logging.googleapis.com/$discovery/rest?version=v2", + DiscoveryDocumentURI: "https://logging.googleapis.com/$discovery/rest", DiscoveryName: "LogSink", Data: obj, }, @@ -99,18 +100,10 @@ func GetLogFolderSinkApiObject(d tpgresource.TerraformResourceData, config *tran folder, ok := d.GetOk("folder") if !ok { - if val, ok := d.GetOk("folder_id"); ok { - folder = val - } else { - return nil, fmt.Errorf("required field 'folder' is not set") - } - } - if folder.(string) == "" { - if val, ok := d.GetOk("after_unknown.folder"); ok { - folder = val - } + return nil, fmt.Errorf("required field 'folder' is not set") } - obj["parent"] = fmt.Sprintf("//cloudresourcemanager.googleapis.com/folders/%s", folder.(string)) + folderId := strings.TrimPrefix(folder.(string), "folders/") + obj["parent"] = fmt.Sprintf("//cloudresourcemanager.googleapis.com/folders/%s", folderId) return obj, nil } From 1c914347c62b2a548358a87d4ed470190f7ca700 Mon Sep 17 00:00:00 2001 From: hemantadil Date: Sat, 1 Nov 2025 03:23:06 +0530 Subject: [PATCH 4/6] update ancestry_path Co-authored-by: Zhenhua Li --- .../tgc/tests/data/example_google_logging_folder_sink.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json b/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json index a8db8a7d639a..37a402ab3070 100644 --- a/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json +++ b/mmv1/third_party/tgc/tests/data/example_google_logging_folder_sink.json @@ -1,7 +1,7 @@ [{ "name": "//logging.googleapis.com/folders/{{.FolderID}}/sinks/tf-test-sink", "asset_type": "logging.googleapis.com/LogSink", - "ancestry_path": "{{.Ancestry}}/folder/{{.FolderID}}", + "ancestry_path": "{{.Ancestry}}", "resource": { "version": "v2", "discovery_document_uri": "https://logging.googleapis.com/$discovery/rest", From 4e826f5866f66dd14d603624cc2a8bd8a404a8a4 Mon Sep 17 00:00:00 2001 From: hemantadil Date: Sat, 1 Nov 2025 03:47:55 +0530 Subject: [PATCH 5/6] Remove the parent field from data field Remove the parent field from data field --- .../tgc/services/logging/logging_folder_sink.go | 7 ------- 1 file changed, 7 deletions(-) diff --git a/mmv1/third_party/tgc/services/logging/logging_folder_sink.go b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go index 3c342450af4b..ffd670f81250 100644 --- a/mmv1/third_party/tgc/services/logging/logging_folder_sink.go +++ b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go @@ -98,13 +98,6 @@ func GetLogFolderSinkApiObject(d tpgresource.TerraformResourceData, config *tran obj["bigqueryOptions"] = bigqueryOptionsProp } - folder, ok := d.GetOk("folder") - if !ok { - return nil, fmt.Errorf("required field 'folder' is not set") - } - folderId := strings.TrimPrefix(folder.(string), "folders/") - obj["parent"] = fmt.Sprintf("//cloudresourcemanager.googleapis.com/folders/%s", folderId) - return obj, nil } From 970d1856fc5695561a32f17de864e1cea551f9a8 Mon Sep 17 00:00:00 2001 From: hemantadil Date: Sat, 1 Nov 2025 03:55:50 +0530 Subject: [PATCH 6/6] Clean up imports in logging_folder_sink.go Removed unused imports from logging_folder_sink.go --- mmv1/third_party/tgc/services/logging/logging_folder_sink.go | 2 -- 1 file changed, 2 deletions(-) diff --git a/mmv1/third_party/tgc/services/logging/logging_folder_sink.go b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go index ffd670f81250..ff4a71890ceb 100644 --- a/mmv1/third_party/tgc/services/logging/logging_folder_sink.go +++ b/mmv1/third_party/tgc/services/logging/logging_folder_sink.go @@ -1,9 +1,7 @@ package logging import ( - "fmt" "reflect" - "strings" "github.com/GoogleCloudPlatform/terraform-google-conversion/v7/tfplan2cai/converters/google/resources/cai" "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"