diff --git a/mmv1/provider/terraform_validator.rb b/mmv1/provider/terraform_validator.rb index 0f6251277109..01c263c7641d 100644 --- a/mmv1/provider/terraform_validator.rb +++ b/mmv1/provider/terraform_validator.rb @@ -242,6 +242,8 @@ def copy_common_files(output_folder, generate_code, _generate_docs) 'third_party/validator/project_service.go'], ['converters/google/resources/monitoring_slo_helper.go', 'third_party/validator/monitoring_slo_helper.go'], + ['converters/google/resources/service_account.go', + 'third_party/validator/service_account.go'], ['converters/google/resources/image.go', 'third_party/terraform/utils/image.go'], ['converters/google/resources/import.go', diff --git a/mmv1/templates/validator/resource_converters.go.erb b/mmv1/templates/validator/resource_converters.go.erb index 59731b75dc9a..98d2ced4eaba 100644 --- a/mmv1/templates/validator/resource_converters.go.erb +++ b/mmv1/templates/validator/resource_converters.go.erb @@ -136,5 +136,6 @@ func ResourceConverters() map[string][]ResourceConverter { "google_organization_iam_custom_role": {resourceConverterOrganizationIAMCustomRole()}, "google_vpc_access_connector": {resourceConverterVPCAccessConnector()}, "google_logging_metric": {resourceConverterLoggingMetric()}, + "google_service_account": {resourceConverterServiceAccount()}, } } diff --git a/mmv1/third_party/validator/service_account.go b/mmv1/third_party/validator/service_account.go new file mode 100644 index 000000000000..5e7e92cd3f48 --- /dev/null +++ b/mmv1/third_party/validator/service_account.go @@ -0,0 +1,138 @@ +package google + +import ( + "fmt" + "reflect" +) + +const ServiceAccountAssetType string = "iam.googleapis.com/ServiceAccount" + +func resourceConverterServiceAccount() ResourceConverter { + return ResourceConverter{ + AssetType: ServiceAccountAssetType, + Convert: GetServiceAccountCaiObject, + } +} + +func GetServiceAccountCaiObject(d TerraformResourceData, config *Config) ([]Asset, error) { + name, err := assetName(d, config, "//iam.googleapis.com/projects/{{project}}/serviceAccounts/{{unique_id}}") + if err != nil { + return []Asset{}, err + } + if obj, err := GetServiceAccountApiObject(d, config); err == nil { + return []Asset{{ + Name: name, + Type: ServiceAccountAssetType, + Resource: &AssetResource{ + Version: "v1", + DiscoveryDocumentURI: "https://iam.googleapis.com/$discovery/rest", + DiscoveryName: "ServiceAccount", + Data: obj, + }, + }}, nil + } else { + return []Asset{}, err + } +} + +func GetServiceAccountApiObject(d TerraformResourceData, config *Config) (map[string]interface{}, error) { + obj := make(map[string]interface{}) + + project, err := getProject(d, config) + if err != nil { + return nil, err + } + + descriptionProp, err := expandServiceAccountDescription(d.Get("description"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("description"); !isEmptyValue(reflect.ValueOf(descriptionProp)) && (ok || !reflect.DeepEqual(v, descriptionProp)) { + obj["description"] = descriptionProp + } + + emailProp, err := expandServiceAccountDescription(d.Get("email"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("email"); !isEmptyValue(reflect.ValueOf(emailProp)) && (ok || !reflect.DeepEqual(v, emailProp)) { + obj["email"] = emailProp + } + + displayNameProp, err := expandServiceAccountDisplayName(d.Get("display_name"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("display_name"); !isEmptyValue(reflect.ValueOf(displayNameProp)) && (ok || !reflect.DeepEqual(v, displayNameProp)) { + obj["displayName"] = displayNameProp + } + + nameProp, err := expandServiceAccountName(d.Get("name"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("name"); !isEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + + disabledProp, err := expandServiceAccountDisabled(d.Get("disabled"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("disabled"); !isEmptyValue(reflect.ValueOf(disabledProp)) && (ok || !reflect.DeepEqual(v, disabledProp)) { + obj["disabled"] = disabledProp + } + + uniqueIdProp, err := expandServiceAccountUniqueId(d.Get("unique_id"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("unique_id"); !isEmptyValue(reflect.ValueOf(uniqueIdProp)) && (ok || !reflect.DeepEqual(v, uniqueIdProp)) { + obj["uniqueId"] = uniqueIdProp + } + + projectProp, err := expandServiceAccountProject(d.Get("project"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("project"); !isEmptyValue(reflect.ValueOf(projectProp)) && (ok || !reflect.DeepEqual(v, projectProp)) { + obj["projectId"] = projectProp + } + + accountIdProp, err := expandServiceAccountId(d.Get("account_id"), d, config) + if err != nil { + return nil, err + } else if v, ok := d.GetOkExists("account_id"); !isEmptyValue(reflect.ValueOf(accountIdProp)) && (ok || !reflect.DeepEqual(v, accountIdProp)) { + accountId := accountIdProp + if _, ok := obj["email"]; !ok { + // Generating email when the service account is being created (email not present) + obj["email"] = fmt.Sprintf("%s@%s.iam.gserviceaccount.com", accountId, project) + } + } + return obj, nil +} + +func expandServiceAccountId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandServiceAccountDescription(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandServiceAccountDisplayName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandServiceAccountName(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandServiceAccountEmail(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandServiceAccountDisabled(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandServiceAccountUniqueId(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} + +func expandServiceAccountProject(v interface{}, d TerraformResourceData, config *Config) (interface{}, error) { + return v, nil +} diff --git a/mmv1/third_party/validator/tests/data/example_compute_instance.json b/mmv1/third_party/validator/tests/data/example_compute_instance.json index 9d76795f2c22..0f07c68c5e0f 100644 --- a/mmv1/third_party/validator/tests/data/example_compute_instance.json +++ b/mmv1/third_party/validator/tests/data/example_compute_instance.json @@ -68,5 +68,20 @@ } } } + }, + { + "name": "//iam.googleapis.com/projects/{{.Provider.project}}/serviceAccounts/placeholder-unique-id", + "asset_type": "iam.googleapis.com/ServiceAccount", + "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}", + "resource": { + "version": "v1", + "discovery_document_uri": "https://iam.googleapis.com/$discovery/rest", + "discovery_name": "ServiceAccount", + "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}", + "data": { + "displayName": "Service Account", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com" + } + } } ] diff --git a/mmv1/third_party/validator/tests/data/example_container_cluster.json b/mmv1/third_party/validator/tests/data/example_container_cluster.json index de33241f1aba..68b75fd4f1b0 100644 --- a/mmv1/third_party/validator/tests/data/example_container_cluster.json +++ b/mmv1/third_party/validator/tests/data/example_container_cluster.json @@ -41,5 +41,20 @@ "name": "my-node-pool" } } + }, + { + "name": "//iam.googleapis.com/projects/{{.Provider.project}}/serviceAccounts/placeholder-unique-id", + "asset_type": "iam.googleapis.com/ServiceAccount", + "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}", + "resource": { + "version": "v1", + "discovery_document_uri": "https://iam.googleapis.com/$discovery/rest", + "discovery_name": "ServiceAccount", + "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}", + "data": { + "displayName": "Service Account", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com" + } + } } ] diff --git a/mmv1/third_party/validator/tests/data/example_service_account.json b/mmv1/third_party/validator/tests/data/example_service_account.json new file mode 100644 index 000000000000..91d56691a629 --- /dev/null +++ b/mmv1/third_party/validator/tests/data/example_service_account.json @@ -0,0 +1,18 @@ +[ + { + "name": "//iam.googleapis.com/projects/{{.Provider.project}}/serviceAccounts/placeholder-unique-id", + "asset_type": "iam.googleapis.com/ServiceAccount", + "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}", + "resource": { + "version": "v1", + "discovery_document_uri": "https://iam.googleapis.com/$discovery/rest", + "discovery_name": "ServiceAccount", + "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}", + "data": { + "displayName": "Service Account", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "description": "Service Account Description" + } + } + } +] \ No newline at end of file diff --git a/mmv1/third_party/validator/tests/data/example_service_account.tf b/mmv1/third_party/validator/tests/data/example_service_account.tf new file mode 100644 index 000000000000..e8009b7c91b0 --- /dev/null +++ b/mmv1/third_party/validator/tests/data/example_service_account.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> {{.Provider.version}}" + } + } +} + +provider "google" { + {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}} +} + +resource "google_service_account" "service_account" { + account_id = "service-account-id" + display_name = "Service Account" + description = "Service Account Description" + disabled = false +} \ No newline at end of file diff --git a/mmv1/third_party/validator/tests/data/example_service_account.tfplan.json b/mmv1/third_party/validator/tests/data/example_service_account.tfplan.json new file mode 100644 index 000000000000..943ab61985ef --- /dev/null +++ b/mmv1/third_party/validator/tests/data/example_service_account.tfplan.json @@ -0,0 +1,100 @@ +{ + "format_version": "1.1", + "terraform_version": "1.3.6", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_service_account.service_account", + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider_name": "registry.terraform.io/hashicorp/google", + "schema_version": 0, + "values": { + "account_id": "service-account-id", + "description": "Service Account Description", + "disabled": false, + "display_name": "Service Account", + "timeouts": null + }, + "sensitive_values": {} + } + ] + } + }, + "resource_changes": [ + { + "address": "google_service_account.service_account", + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider_name": "registry.terraform.io/hashicorp/google", + "change": { + "actions": [ + "create" + ], + "before": null, + "after": { + "account_id": "service-account-id", + "description": "Service Account Description", + "disabled": false, + "display_name": "Service Account", + "timeouts": null + }, + "after_unknown": { + "email": true, + "id": true, + "member": true, + "name": true, + "project": true, + "unique_id": true + }, + "before_sensitive": false, + "after_sensitive": {} + } + } + ], + "configuration": { + "provider_config": { + "google": { + "name": "google", + "full_name": "registry.terraform.io/hashicorp/google", + "expressions": { + "project": { + "constant_value": "{{.Provider.project}}" + }, + "region": { + "constant_value": "asia-southeast-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "google_service_account.service_account", + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider_config_key": "google", + "expressions": { + "account_id": { + "constant_value": "service-account-id" + }, + "description": { + "constant_value": "Service Account Description" + }, + "disabled": { + "constant_value": false + }, + "display_name": { + "constant_value": "Service Account" + } + }, + "schema_version": 0 + } + ] + } + } +} \ No newline at end of file diff --git a/mmv1/third_party/validator/tests/data/example_service_account_update.json b/mmv1/third_party/validator/tests/data/example_service_account_update.json new file mode 100644 index 000000000000..68dc5d42edd4 --- /dev/null +++ b/mmv1/third_party/validator/tests/data/example_service_account_update.json @@ -0,0 +1,21 @@ +[ + { + "name": "//iam.googleapis.com/projects/{{.Provider.project}}/serviceAccounts/108592438577779299646", + "asset_type": "iam.googleapis.com/ServiceAccount", + "ancestry_path": "{{.Ancestry}}/project/{{.Provider.project}}", + "resource": { + "version": "v1", + "discovery_document_uri": "https://iam.googleapis.com/$discovery/rest", + "discovery_name": "ServiceAccount", + "parent": "//cloudresourcemanager.googleapis.com/projects/{{.Provider.project}}", + "data": { + "displayName": "Service Account", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "description": "Service Account Description", + "name": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "projectId": "{{.Provider.project}}", + "uniqueId": "108592438577779299646" + } + } + } +] \ No newline at end of file diff --git a/mmv1/third_party/validator/tests/data/example_service_account_update.tf b/mmv1/third_party/validator/tests/data/example_service_account_update.tf new file mode 100644 index 000000000000..f2bcec25b88c --- /dev/null +++ b/mmv1/third_party/validator/tests/data/example_service_account_update.tf @@ -0,0 +1,35 @@ +/** + * Copyright 2023 Google LLC + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +terraform { + required_providers { + google = { + source = "hashicorp/google" + version = "~> {{.Provider.version}}" + } + } +} + +provider "google" { + {{if .Provider.credentials }}credentials = "{{.Provider.credentials}}"{{end}} +} + +resource "google_service_account" "service_account" { + account_id = "service-account-id" + display_name = "Service Account" + description = "Service Account Description" + disabled = false +} diff --git a/mmv1/third_party/validator/tests/data/example_service_account_update.tfplan.json b/mmv1/third_party/validator/tests/data/example_service_account_update.tfplan.json new file mode 100644 index 000000000000..42adfd13c2e6 --- /dev/null +++ b/mmv1/third_party/validator/tests/data/example_service_account_update.tfplan.json @@ -0,0 +1,140 @@ +{ + "format_version": "0.1", + "terraform_version": "0.12.31", + "planned_values": { + "root_module": { + "resources": [ + { + "address": "google_service_account.service_account", + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider_name": "google", + "schema_version": 0, + "values": { + "account_id": "service-account-id", + "description": "Service Account Description", + "disabled": false, + "display_name": "Service Account", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "id": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "name": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "project": "{{.Provider.project}}", + "timeouts": null, + "unique_id": "108592438577779299646" + } + } + ] + } + }, + "resource_changes": [ + { + "address": "google_service_account.service_account", + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider_name": "google", + "change": { + "actions": [ + "update" + ], + "before": { + "account_id": "service-account-id", + "description": "Service Account Description", + "disabled": false, + "display_name": "Service Account 1", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "id": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "name": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "project": "{{.Provider.project}}", + "timeouts": null, + "unique_id": "108592438577779299646" + }, + "after": { + "account_id": "service-account-id", + "description": "Service Account Description", + "disabled": false, + "display_name": "Service Account", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "id": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "name": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "project": "{{.Provider.project}}", + "timeouts": null, + "unique_id": "108592438577779299646" + }, + "after_unknown": {} + } + } + ], + "prior_state": { + "format_version": "0.1", + "terraform_version": "0.12.31", + "values": { + "root_module": { + "resources": [ + { + "address": "google_service_account.service_account", + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider_name": "google", + "schema_version": 0, + "values": { + "account_id": "service-account-id", + "description": "Service Account Description", + "disabled": false, + "display_name": "Service Account 1", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "id": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "name": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "project": "{{.Provider.project}}", + "timeouts": null, + "unique_id": "108592438577779299646" + } + } + ] + } + } + }, + "configuration": { + "provider_config": { + "google": { + "name": "google", + "expressions": { + "project": { + "constant_value": "{{.Provider.project}}" + }, + "region": { + "constant_value": "asia-southeast-1" + } + } + } + }, + "root_module": { + "resources": [ + { + "address": "google_service_account.service_account", + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider_config_key": "google", + "expressions": { + "account_id": { + "constant_value": "service-account-id" + }, + "description": { + "constant_value": "Service Account Description" + }, + "disabled": { + "constant_value": false + }, + "display_name": { + "constant_value": "Service Account" + } + }, + "schema_version": 0 + } + ] + } + } +} \ No newline at end of file diff --git a/mmv1/third_party/validator/tests/data/example_service_account_update.tfstate b/mmv1/third_party/validator/tests/data/example_service_account_update.tfstate new file mode 100644 index 000000000000..1b351b321f0f --- /dev/null +++ b/mmv1/third_party/validator/tests/data/example_service_account_update.tfstate @@ -0,0 +1,33 @@ +{ + "version": 4, + "terraform_version": "0.12.31", + "serial": 1, + "lineage": "753c5d7c-1d38-33ba-9fe2-740244c61234", + "outputs": {}, + "resources": [ + { + "mode": "managed", + "type": "google_service_account", + "name": "service_account", + "provider": "provider.google", + "instances": [ + { + "schema_version": 0, + "attributes": { + "account_id": "service-account-id", + "description": "Service Account Description", + "disabled": false, + "display_name": "Service Account 1", + "email": "service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "id": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "name": "projects/{{.Provider.project}}/serviceAccounts/service-account-id@{{.Provider.project}}.iam.gserviceaccount.com", + "project": "{{.Provider.project}}", + "timeouts": null, + "unique_id": "108592438577779299646" + }, + "private": "eyJlMmJmYjczMC1lY2FhLTExZTYtOGY4OC0zNDM2M2JjN2M0YzAiOnsiY3JlYXRlIjozMDAwMDAwMDAwMDB9fQ==" + } + ] + } + ] +}