From 179f05fb69bdd96efbd5764ca0c87f22135875b0 Mon Sep 17 00:00:00 2001 From: megan07 Date: Thu, 7 Nov 2019 16:12:00 -0600 Subject: [PATCH] Update hand-written resources with `AtLeastOneOf` and `ExactlyOneOf` attributes (#2608) --- products/appengine/api.yaml | 4 + products/bigtable/api.yaml | 1 + products/cloudbuild/api.yaml | 5 + products/cloudrun/api.yaml | 5 +- products/cloudscheduler/api.yaml | 2 + products/compute/terraform.yaml | 1 + products/dns/api.yaml | 2 + products/monitoring/api.yaml | 1 + .../resource_app_engine_application.go | 2 +- .../resources/resource_bigquery_table.go.erb | 13 +- .../resources/resource_cloudiot_registry.go | 2 +- .../resource_composer_environment.go.erb | 19 +- .../resources/resource_compute_instance.go | 212 +++++++++++--- ...urce_compute_instance_group_manager.go.erb | 57 +++- .../resource_compute_instance_template.go | 176 +++++++++-- ...mpute_region_instance_group_manager.go.erb | 53 +++- .../resources/resource_compute_router_peer.go | 2 +- .../resource_container_cluster.go.erb | 86 +++--- .../resource_dataproc_cluster.go.erb | 277 ++++++++++++------ .../resources/resource_dataproc_job.go | 161 +++++----- .../resource_google_organization_policy.go | 65 ++-- .../resource_sql_database_instance.go | 207 ++++++++----- .../resources/resource_storage_bucket.go | 49 +++- .../resource_storage_transfer_job.go | 70 +++-- .../tests/resource_dataproc_job_test.go | 54 ++-- .../tests/resource_storage_bucket_test.go | 44 +++ third_party/terraform/utils/utils.go.erb | 14 + .../guides/version_3_upgrade.html.markdown | 226 ++++++++++++-- 28 files changed, 1336 insertions(+), 474 deletions(-) diff --git a/products/appengine/api.yaml b/products/appengine/api.yaml index d33bfd8087bc..6f3842221f61 100644 --- a/products/appengine/api.yaml +++ b/products/appengine/api.yaml @@ -94,6 +94,7 @@ objects: Example: 12345. - !ruby/object:Api::Type::Enum name: 'sslManagementType' + required: true description: | SSL management type for this domain. If `AUTOMATIC`, a managed certificate is automatically provisioned. If `MANUAL`, `certificateId` must be manually specified in order to configure SSL for this domain. @@ -407,6 +408,7 @@ objects: - !ruby/object:Api::Type::String name: 'sourceUrl' description: 'Source URL' + required: true - !ruby/object:Api::Type::Integer name: 'filesCount' description: 'files count' @@ -428,6 +430,7 @@ objects: SHA1 checksum of the file - !ruby/object:Api::Type::String name: 'sourceUrl' + required: true description: | Source URL - !ruby/object:Api::Type::NestedObject @@ -438,6 +441,7 @@ objects: properties: - !ruby/object:Api::Type::String name: 'shell' + required: true description: | The format should be a shell command that can be fed to bash -c. - !ruby/object:Api::Type::String diff --git a/products/bigtable/api.yaml b/products/bigtable/api.yaml index aea5c2162cf7..b5523a155a57 100644 --- a/products/bigtable/api.yaml +++ b/products/bigtable/api.yaml @@ -81,6 +81,7 @@ objects: properties: - !ruby/object:Api::Type::String name: 'clusterId' + required: true description: | The cluster to which read/write requests should be routed. - !ruby/object:Api::Type::Boolean diff --git a/products/cloudbuild/api.yaml b/products/cloudbuild/api.yaml index 229d60969fa2..2c1826ba3591 100644 --- a/products/cloudbuild/api.yaml +++ b/products/cloudbuild/api.yaml @@ -170,6 +170,7 @@ objects: properties: - !ruby/object:Api::Type::String name: 'branch' + required: true description: | Regex of branches to match. - !ruby/object:Api::Type::Enum @@ -212,12 +213,14 @@ objects: If any of the images fail to be pushed, the build status is marked FAILURE. - !ruby/object:Api::Type::Array name: 'steps' + required: true description: | The operations to be performed on the workspace. item_type: !ruby/object:Api::Type::NestedObject properties: - !ruby/object:Api::Type::String name: 'name' + required: true description: | The name of the container image that will run this particular build step. @@ -314,6 +317,7 @@ objects: properties: - !ruby/object:Api::Type::String name: 'name' + required: true description: | Name of the volume to mount. @@ -321,6 +325,7 @@ objects: Docker volumes. Each named volume must be used by at least two build steps. - !ruby/object:Api::Type::String name: 'path' + required: true description: | Path at which to mount the volume. diff --git a/products/cloudrun/api.yaml b/products/cloudrun/api.yaml index aca9ded3edbe..4cbbb647cb35 100644 --- a/products/cloudrun/api.yaml +++ b/products/cloudrun/api.yaml @@ -272,6 +272,7 @@ objects: properties: - !ruby/object:Api::Type::NestedObject name: template + required: true description: |- template holds the latest specification for the Revision to be stamped out. The template references the container image, and may also @@ -341,7 +342,7 @@ objects: - !ruby/object:Api::Type::NestedObject name: configMapRef description: |- - The ConfigMap to select from + The ConfigMap to select from. properties: - !ruby/object:Api::Type::Boolean name: optional @@ -360,7 +361,7 @@ objects: - !ruby/object:Api::Type::NestedObject name: secretRef description: |- - The Secret to select from + The Secret to select from. properties: - !ruby/object:Api::Type::NestedObject name: localObjectReference diff --git a/products/cloudscheduler/api.yaml b/products/cloudscheduler/api.yaml index 7fb0fdb19cde..22d3785c8a52 100644 --- a/products/cloudscheduler/api.yaml +++ b/products/cloudscheduler/api.yaml @@ -267,6 +267,7 @@ objects: properties: - !ruby/object:Api::Type::String name: serviceAccountEmail + required: true description: | Service account email to be used for generating OAuth token. The service account must be within the same project as the job. @@ -284,6 +285,7 @@ objects: properties: - !ruby/object:Api::Type::String name: serviceAccountEmail + required: true description: | Service account email to be used for generating OAuth token. The service account must be within the same project as the job. diff --git a/products/compute/terraform.yaml b/products/compute/terraform.yaml index 075cfb6fcbff..f20833c2136d 100644 --- a/products/compute/terraform.yaml +++ b/products/compute/terraform.yaml @@ -1441,6 +1441,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides # https://github.com/GoogleCloudPlatform/magic-modules/issues/1019 ignore_read: true sensitive: true + required: true custom_flatten: templates/terraform/custom_flatten/compute_snapshot_snapshot_encryption_raw_key.go.erb snapshotEncryptionKey.kmsKeyName: !ruby/object:Overrides::Terraform::PropertyOverride # This is a beta field that showed up in GA. Removed from both. diff --git a/products/dns/api.yaml b/products/dns/api.yaml index 76213104a213..f536d7562481 100644 --- a/products/dns/api.yaml +++ b/products/dns/api.yaml @@ -170,6 +170,7 @@ objects: - !ruby/object:Api::Type::Array name: 'networks' description: 'The list of VPC networks that can see this zone.' + required: true item_type: !ruby/object:Api::Type::NestedObject properties: # TODO(drebes): Make 'networkUrl' a ResourceRef once cross-module references @@ -180,6 +181,7 @@ objects: The fully qualified URL of the VPC network to bind to. This should be formatted like `https://www.googleapis.com/compute/v1/projects/{project}/global/networks/{network}` + required: true - !ruby/object:Api::Type::NestedObject name: 'forwardingConfig' description: | diff --git a/products/monitoring/api.yaml b/products/monitoring/api.yaml index 3b899abfc4c7..1d92513a4fb9 100644 --- a/products/monitoring/api.yaml +++ b/products/monitoring/api.yaml @@ -933,6 +933,7 @@ objects: - !ruby/object:Api::Type::String name: content description: String or regex content to match (max 1024 bytes) + required: true - !ruby/object:Api::Type::Array name: selectedRegions description: The list of regions from which the check will be run. Some regions diff --git a/third_party/terraform/resources/resource_app_engine_application.go b/third_party/terraform/resources/resource_app_engine_application.go index e691feb2ecdd..c2748aa05b62 100644 --- a/third_party/terraform/resources/resource_app_engine_application.go +++ b/third_party/terraform/resources/resource_app_engine_application.go @@ -117,7 +117,7 @@ func appEngineApplicationFeatureSettingsResource() *schema.Resource { Schema: map[string]*schema.Schema{ "split_health_checks": { Type: schema.TypeBool, - Optional: true, + Required: true, }, }, } diff --git a/third_party/terraform/resources/resource_bigquery_table.go.erb b/third_party/terraform/resources/resource_bigquery_table.go.erb index 34f0314201d3..4ee13c90664a 100644 --- a/third_party/terraform/resources/resource_bigquery_table.go.erb +++ b/third_party/terraform/resources/resource_bigquery_table.go.erb @@ -164,17 +164,16 @@ func resourceBigQueryTable() *schema.Resource { // Range: [Optional] Range of a sheet to query from. Only used when non-empty. // Typical format: !: "range": { -<% if version.nil? || version == 'ga' -%> - Removed: "This field is in beta. Use it in the the google-beta provider instead. See https://terraform.io/docs/providers/google/guides/provider_versions.html for more details.", -<% end -%> - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{"external_data_configuration.0.google_sheets_options.0.range"}, }, // SkipLeadingRows: [Optional] The number of rows at the top // of the sheet that BigQuery will skip when reading the data. "skip_leading_rows": { - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, + AtLeastOneOf: []string{"external_data_configuration.0.google_sheets_options.0.skip_leading_rows"}, }, }, }, diff --git a/third_party/terraform/resources/resource_cloudiot_registry.go b/third_party/terraform/resources/resource_cloudiot_registry.go index 60ca3aa38d87..f392f02681cb 100644 --- a/third_party/terraform/resources/resource_cloudiot_registry.go +++ b/third_party/terraform/resources/resource_cloudiot_registry.go @@ -132,7 +132,7 @@ func resourceCloudIoTRegistry() *schema.Resource { Schema: map[string]*schema.Schema{ "public_key_certificate": { Type: schema.TypeMap, - Optional: true, + Required: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "format": { diff --git a/third_party/terraform/resources/resource_composer_environment.go.erb b/third_party/terraform/resources/resource_composer_environment.go.erb index 9083e47d2688..0a9e754cbf3b 100644 --- a/third_party/terraform/resources/resource_composer_environment.go.erb +++ b/third_party/terraform/resources/resource_composer_environment.go.erb @@ -36,6 +36,14 @@ var composerEnvironmentReservedEnvVar = map[string]struct{}{ "SQL_USER": {}, } +var composerSoftwareConfigKeys = []string{ + "config.0.software_config.0.airflow_config_overrides", + "config.0.software_config.0.pypi_packages", + "config.0.software_config.0.env_variables", + "config.0.software_config.0.image_version", + "config.0.software_config.0.python_version", +} + func resourceComposerEnvironment() *schema.Resource { return &schema.Resource{ Create: resourceComposerEnvironmentCreate, @@ -162,8 +170,7 @@ func resourceComposerEnvironment() *schema.Resource { Schema: map[string]*schema.Schema{ "use_ip_aliases": { Type: schema.TypeBool, - Optional: true, - Default: true, + Required: true, ForceNew: true, }, "cluster_secondary_range_name": { @@ -208,17 +215,20 @@ func resourceComposerEnvironment() *schema.Resource { "airflow_config_overrides": { Type: schema.TypeMap, Optional: true, + AtLeastOneOf: composerSoftwareConfigKeys, Elem: &schema.Schema{Type: schema.TypeString}, }, "pypi_packages": { Type: schema.TypeMap, Optional: true, + AtLeastOneOf: composerSoftwareConfigKeys, Elem: &schema.Schema{Type: schema.TypeString}, ValidateFunc: validateComposerEnvironmentPypiPackages, }, "env_variables": { Type: schema.TypeMap, Optional: true, + AtLeastOneOf: composerSoftwareConfigKeys, Elem: &schema.Schema{Type: schema.TypeString}, ValidateFunc: validateComposerEnvironmentEnvVariables, }, @@ -226,12 +236,14 @@ func resourceComposerEnvironment() *schema.Resource { Type: schema.TypeString, Computed: true, Optional: true, + AtLeastOneOf: composerSoftwareConfigKeys, ValidateFunc: validateRegexp(composerEnvironmentVersionRegexp), DiffSuppressFunc: composerImageVersionDiffSuppress, }, "python_version": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: composerSoftwareConfigKeys, Computed: true, ForceNew: true, }, @@ -248,9 +260,8 @@ func resourceComposerEnvironment() *schema.Resource { Schema: map[string]*schema.Schema{ "enable_private_endpoint": { Type: schema.TypeBool, - Optional: true, + Required: true, ForceNew: true, - Default: true, }, "master_ipv4_cidr_block": { Type: schema.TypeString, diff --git a/third_party/terraform/resources/resource_compute_instance.go b/third_party/terraform/resources/resource_compute_instance.go index 8cac9281fde6..7fd43442bc14 100644 --- a/third_party/terraform/resources/resource_compute_instance.go +++ b/third_party/terraform/resources/resource_compute_instance.go @@ -6,8 +6,8 @@ import ( "errors" "fmt" "log" + "sort" "strings" - "time" "github.com/hashicorp/errwrap" @@ -19,6 +19,112 @@ import ( "google.golang.org/api/compute/v1" ) +var ( + bootDiskKeys = []string{ + "boot_disk.0.auto_delete", + "boot_disk.0.device_name", + "boot_disk.0.disk_encryption_key_raw", + "boot_disk.0.kms_key_self_link", + "boot_disk.0.initialize_params", + "boot_disk.0.mode", + "boot_disk.0.source", + } + + initializeParamsKeys = []string{ + "boot_disk.0.initialize_params.0.size", + "boot_disk.0.initialize_params.0.type", + "boot_disk.0.initialize_params.0.image", + "boot_disk.0.initialize_params.0.labels", + } + + accessConfigKeys = []string{ + "network_interface.%d.access_config.%d.nat_ip", + "network_interface.%d.access_config.%d.network_tier", + "network_interface.%d.access_config.%d.public_ptr_domain_name", + } + + schedulingKeys = []string{ + "scheduling.0.on_host_maintenance", + "scheduling.0.automatic_restart", + "scheduling.0.preemptible", + "scheduling.0.node_affinities", + } + + shieldedInstanceConfigKeys = []string{ + "shielded_instance_config.0.enable_secure_boot", + "shielded_instance_config.0.enable_vtpm", + "shielded_instance_config.0.enable_integrity_monitoring", + } +) + +func resourceComputeInstanceAtLeastOneNetworkDiff(diff *schema.ResourceDiff, v interface{}) error { + atLeastOneOfList := []string{"network_interface.%d.network", "network_interface.%d.subnetwork"} + errorList := make([]string, 0) + + networkInterfaces := diff.Get("network_interface").([]interface{}) + if len(networkInterfaces) == 0 { + return nil + } + + for i := range networkInterfaces { + found := false + for _, atLeastOneOfKey := range atLeastOneOfList { + if val := diff.Get(fmt.Sprintf(atLeastOneOfKey, i)); val != "" { + found = true + } + } + + if found == false { + sort.Strings(atLeastOneOfList) + keyList := formatStringsInList(atLeastOneOfList, i) + errorList = append(errorList, fmt.Sprintf("network_interface: one of `%s` must be specified", strings.Join(keyList, ","))) + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + +func resourceComputeInstanceAtLeastOneAccessConfigAttrDiff(diff *schema.ResourceDiff, v interface{}) error { + errorList := make([]string, 0) + + networkInterfaces := diff.Get("network_interface").([]interface{}) + if len(networkInterfaces) == 0 { + return nil + } + + for i := range networkInterfaces { + accessConfigs := diff.Get(fmt.Sprintf("network_interface.%d.access_config", i)).([]interface{}) + if len(accessConfigs) == 0 { + continue + } + + for j := range accessConfigs { + found := false + for _, atLeastOneOfKey := range accessConfigKeys { + if val := diff.Get(fmt.Sprintf(atLeastOneOfKey, i, j)); val != "" { + found = true + } + } + + if found == false { + sort.Strings(accessConfigKeys) + keyList := formatStringsInList(accessConfigKeys, i, j) + errorList = append(errorList, fmt.Sprintf("network_interface.%d.access_config: one of `%s` must be specified", i, strings.Join(keyList, ","))) + } + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + func resourceComputeInstance() *schema.Resource { return &schema.Resource{ Create: resourceComputeInstanceCreate, @@ -50,24 +156,28 @@ func resourceComputeInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "auto_delete": { - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: bootDiskKeys, + Default: true, + ForceNew: true, }, "device_name": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: bootDiskKeys, + Computed: true, + ForceNew: true, }, "disk_encryption_key_raw": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Sensitive: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: bootDiskKeys, + ForceNew: true, + ConflictsWith: []string{"boot_disk.0.kms_key_self_link"}, + Sensitive: true, }, "disk_encryption_key_sha256": { @@ -78,6 +188,7 @@ func resourceComputeInstance() *schema.Resource { "kms_key_self_link": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: bootDiskKeys, ForceNew: true, ConflictsWith: []string{"boot_disk.0.disk_encryption_key_raw"}, DiffSuppressFunc: compareSelfLinkRelativePaths, @@ -85,16 +196,18 @@ func resourceComputeInstance() *schema.Resource { }, "initialize_params": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ForceNew: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: bootDiskKeys, + Computed: true, + ForceNew: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "size": { Type: schema.TypeInt, Optional: true, + AtLeastOneOf: initializeParamsKeys, Computed: true, ForceNew: true, ValidateFunc: validation.IntAtLeast(1), @@ -103,6 +216,7 @@ func resourceComputeInstance() *schema.Resource { "type": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: initializeParamsKeys, Computed: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd"}, false), @@ -111,16 +225,18 @@ func resourceComputeInstance() *schema.Resource { "image": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: initializeParamsKeys, Computed: true, ForceNew: true, DiffSuppressFunc: diskImageDiffSuppress, }, "labels": { - Type: schema.TypeMap, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeMap, + Optional: true, + AtLeastOneOf: initializeParamsKeys, + Computed: true, + ForceNew: true, }, }, }, @@ -129,6 +245,7 @@ func resourceComputeInstance() *schema.Resource { "mode": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: bootDiskKeys, ForceNew: true, Default: "READ_WRITE", ValidateFunc: validation.StringInSlice([]string{"READ_WRITE", "READ_ONLY"}, false), @@ -137,6 +254,7 @@ func resourceComputeInstance() *schema.Resource { "source": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: bootDiskKeys, Computed: true, ForceNew: true, ConflictsWith: []string{"boot_disk.initialize_params"}, @@ -380,27 +498,31 @@ func resourceComputeInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "on_host_maintenance": { - Type: schema.TypeString, - Optional: true, - Computed: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: schedulingKeys, }, "automatic_restart": { - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: schedulingKeys, + Default: true, }, "preemptible": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + Default: false, + AtLeastOneOf: schedulingKeys, + ForceNew: true, }, "node_affinities": { Type: schema.TypeSet, Optional: true, + AtLeastOneOf: schedulingKeys, ForceNew: true, Elem: instanceSchedulingNodeAffinitiesElemSchema(), DiffSuppressFunc: emptyOrDefaultStringSuppress(""), @@ -417,8 +539,7 @@ func resourceComputeInstance() *schema.Resource { Schema: map[string]*schema.Schema{ "interface": { Type: schema.TypeString, - Optional: true, - Default: "SCSI", + Required: true, ValidateFunc: validation.StringInSlice([]string{"SCSI", "NVME"}, false), }, }, @@ -463,21 +584,24 @@ func resourceComputeInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "enable_secure_boot": { - Type: schema.TypeBool, - Optional: true, - Default: false, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: shieldedInstanceConfigKeys, + Default: false, }, "enable_vtpm": { - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: shieldedInstanceConfigKeys, + Default: true, }, "enable_integrity_monitoring": { - Type: schema.TypeBool, - Optional: true, - Default: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: shieldedInstanceConfigKeys, + Default: true, }, }, }, @@ -540,6 +664,8 @@ func resourceComputeInstance() *schema.Resource { }, suppressEmptyGuestAcceleratorDiff, ), + resourceComputeInstanceAtLeastOneNetworkDiff, + resourceComputeInstanceAtLeastOneAccessConfigAttrDiff, ), } } diff --git a/third_party/terraform/resources/resource_compute_instance_group_manager.go.erb b/third_party/terraform/resources/resource_compute_instance_group_manager.go.erb index 04ab9e517df2..888943647a9a 100644 --- a/third_party/terraform/resources/resource_compute_instance_group_manager.go.erb +++ b/third_party/terraform/resources/resource_compute_instance_group_manager.go.erb @@ -5,6 +5,7 @@ import ( "fmt" "log" "regexp" + "sort" "strings" "time" @@ -16,6 +17,54 @@ import ( "google.golang.org/api/compute/v1" ) +func resourceComputeInstanceGroupManagerExactlyOneTargetSizeDiff(diff *schema.ResourceDiff, v interface{}) error { + exactlyOneOfList := []string{"version.0.target_size.0.fixed", "version.0.target_size.0.percent"} + errorList := make([]string, 0) + + versionBlocks := diff.Get("version").([]interface{}) + if len(versionBlocks) == 0 { + return nil + } + + for i := range versionBlocks { + targetBlocks := diff.Get(fmt.Sprintf("version.%d.target_size", i)).([]interface{}) + if len(targetBlocks) == 0 { + continue + } + + for j := range targetBlocks { + specified := make([]string, 0) + for _, exactlyOneOfKey := range exactlyOneOfList { + if val := diff.Get(fmt.Sprintf(exactlyOneOfKey, i, j)); val != 0 { + specified = append(specified, exactlyOneOfKey) + } + } + + if len(specified) == 1 { + continue + } + + sort.Strings(exactlyOneOfList) + keyList := formatStringsInList(exactlyOneOfList, i, j) + specified = formatStringsInList(specified, i, j) + + if len(specified) == 0 { + errorList = append(errorList, fmt.Sprintf("version.%d.target_size: one of `%s` must be specified", i, strings.Join(keyList, ","))) + } + + if len(specified) > 1 { + errorList = append(errorList, fmt.Sprintf("version.%d.target_size: only one of `%s` can be specified, but `%s` were specified", i, strings.Join(keyList, ","), strings.Join(specified, ","))) + } + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + func resourceComputeInstanceGroupManager() *schema.Resource { return &schema.Resource{ Create: resourceComputeInstanceGroupManagerCreate, @@ -25,6 +74,9 @@ func resourceComputeInstanceGroupManager() *schema.Resource { Importer: &schema.ResourceImporter{ State: resourceInstanceGroupManagerStateImporter, }, + CustomizeDiff: customdiff.All( + resourceComputeInstanceGroupManagerExactlyOneTargetSizeDiff, + ), Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(5 * time.Minute), Update: schema.DefaultTimeout(5 * time.Minute), @@ -81,10 +133,9 @@ func resourceComputeInstanceGroupManager() *schema.Resource { Type: schema.TypeInt, Optional: true, }, - "percent": &schema.Schema{ - Type: schema.TypeInt, - Optional: true, + Type: schema.TypeInt, + Optional: true, ValidateFunc: validation.IntBetween(0, 100), }, }, diff --git a/third_party/terraform/resources/resource_compute_instance_template.go b/third_party/terraform/resources/resource_compute_instance_template.go index 967138308a13..5886305730db 100644 --- a/third_party/terraform/resources/resource_compute_instance_template.go +++ b/third_party/terraform/resources/resource_compute_instance_template.go @@ -3,6 +3,7 @@ package google import ( "fmt" "reflect" + "sort" "strings" "github.com/hashicorp/errwrap" @@ -14,6 +15,21 @@ import ( computeBeta "google.golang.org/api/compute/v0.beta" ) +var ( + schedulingInstTemplateKeys = []string{ + "scheduling.0.on_host_maintenance", + "scheduling.0.automatic_restart", + "scheduling.0.preemptible", + "scheduling.0.node_affinities", + } + + shieldedInstanceTemplateConfigKeys = []string{ + "shielded_instance_config.0.enable_secure_boot", + "shielded_instance_config.0.enable_vtpm", + "shielded_instance_config.0.enable_integrity_monitoring", + } +) + func resourceComputeInstanceTemplate() *schema.Resource { return &schema.Resource{ Create: resourceComputeInstanceTemplateCreate, @@ -26,6 +42,9 @@ func resourceComputeInstanceTemplate() *schema.Resource { CustomizeDiff: customdiff.All( resourceComputeInstanceTemplateSourceImageCustomizeDiff, resourceComputeInstanceTemplateScratchDiskCustomizeDiff, + resourceComputeInstanceTemplateAtLeastOneDiskSourceDiff, + resourceComputeInstanceTemplateAtLeastOneNetworkDiff, + resourceComputeInstanceTemplateAtLeastOneAccessConfigAttrDiff, ), MigrateState: resourceComputeInstanceTemplateMigrateState, @@ -157,7 +176,7 @@ func resourceComputeInstanceTemplate() *schema.Resource { Schema: map[string]*schema.Schema{ "kms_key_self_link": { Type: schema.TypeString, - Optional: true, + Required: true, ForceNew: true, DiffSuppressFunc: compareSelfLinkRelativePaths, }, @@ -320,29 +339,33 @@ func resourceComputeInstanceTemplate() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "preemptible": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: schedulingInstTemplateKeys, + Default: false, + ForceNew: true, }, "automatic_restart": { - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: schedulingInstTemplateKeys, + Default: true, + ForceNew: true, }, "on_host_maintenance": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: schedulingInstTemplateKeys, + ForceNew: true, }, "node_affinities": { Type: schema.TypeSet, Optional: true, + AtLeastOneOf: schedulingInstTemplateKeys, ForceNew: true, Elem: instanceSchedulingNodeAffinitiesElemSchema(), DiffSuppressFunc: emptyOrDefaultStringSuppress(""), @@ -398,24 +421,27 @@ func resourceComputeInstanceTemplate() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "enable_secure_boot": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: shieldedInstanceTemplateConfigKeys, + Default: false, + ForceNew: true, }, "enable_vtpm": { - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: shieldedInstanceTemplateConfigKeys, + Default: true, + ForceNew: true, }, "enable_integrity_monitoring": { - Type: schema.TypeBool, - Optional: true, - Default: true, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: shieldedInstanceTemplateConfigKeys, + Default: true, + ForceNew: true, }, }, }, @@ -528,6 +554,106 @@ func resourceComputeInstanceTemplateSourceImageCustomizeDiff(diff *schema.Resour return nil } +func resourceComputeInstanceTemplateAtLeastOneDiskSourceDiff(diff *schema.ResourceDiff, v interface{}) error { + atLeastOneOfList := []string{"disk.%d.source_image", "disk.%d.source"} + errorList := make([]string, 0) + + disks := diff.Get("disk").([]interface{}) + if len(disks) == 0 { + return nil + } + + for i := range disks { + found := false + for _, atLeastOneOfKey := range atLeastOneOfList { + if val := diff.Get(fmt.Sprintf(atLeastOneOfKey, i)); val != "" { + found = true + } + } + + if found == false { + sort.Strings(atLeastOneOfList) + keyList := formatStringsInList(atLeastOneOfList, i) + errorList = append(errorList, fmt.Sprintf("disk: one of `%s` must be specified", strings.Join(keyList, ","))) + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + +func resourceComputeInstanceTemplateAtLeastOneNetworkDiff(diff *schema.ResourceDiff, v interface{}) error { + atLeastOneOfList := []string{"network_interface.%d.network", "network_interface.%d.subnetwork"} + errorList := make([]string, 0) + + networkInterfaces := diff.Get("network_interface").([]interface{}) + if len(networkInterfaces) == 0 { + return nil + } + + for i := range networkInterfaces { + found := false + for _, atLeastOneOfKey := range atLeastOneOfList { + if val := diff.Get(fmt.Sprintf(atLeastOneOfKey, i)); val != "" { + found = true + } + } + + if found == false { + sort.Strings(atLeastOneOfList) + keyList := formatStringsInList(atLeastOneOfList, i) + errorList = append(errorList, fmt.Sprintf("network_interface: one of `%s` must be specified", strings.Join(keyList, ","))) + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + +func resourceComputeInstanceTemplateAtLeastOneAccessConfigAttrDiff(diff *schema.ResourceDiff, v interface{}) error { + atLeastOneOfList := []string{"network_interface.%d.access_config.%d.nat_ip", "network_interface.%d.access_config.%d.network_tier"} + errorList := make([]string, 0) + + networkInterfaces := diff.Get("network_interface").([]interface{}) + if len(networkInterfaces) == 0 { + return nil + } + + for i := range networkInterfaces { + accessConfigs := diff.Get(fmt.Sprintf("network_interface.%d.access_config", i)).([]interface{}) + if len(accessConfigs) == 0 { + continue + } + + for j := range accessConfigs { + found := false + for _, atLeastOneOfKey := range atLeastOneOfList { + if val := diff.Get(fmt.Sprintf(atLeastOneOfKey, i, j)); val != "" { + found = true + } + } + + if found == false { + sort.Strings(atLeastOneOfList) + keyList := formatStringsInList(atLeastOneOfList, i, j) + errorList = append(errorList, fmt.Sprintf("network_interface.%d.access_config: one of `%s` must be specified", i, strings.Join(keyList, ","))) + } + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + func resourceComputeInstanceTemplateScratchDiskCustomizeDiff(diff *schema.ResourceDiff, meta interface{}) error { // separate func to allow unit testing return resourceComputeInstanceTemplateScratchDiskCustomizeDiffFunc(diff) diff --git a/third_party/terraform/resources/resource_compute_region_instance_group_manager.go.erb b/third_party/terraform/resources/resource_compute_region_instance_group_manager.go.erb index 24d619920d44..0f5df8bf1698 100644 --- a/third_party/terraform/resources/resource_compute_region_instance_group_manager.go.erb +++ b/third_party/terraform/resources/resource_compute_region_instance_group_manager.go.erb @@ -5,6 +5,7 @@ import ( "fmt" "log" "regexp" + "sort" "strings" "time" @@ -16,6 +17,54 @@ import ( computeBeta "google.golang.org/api/compute/v0.beta" ) +func resourceComputeRegionInstanceGroupManagerExactlyOneTargetSizeDiff(diff *schema.ResourceDiff, v interface{}) error { + exactlyOneOfList := []string{"version.%d.target_size.%d.fixed", "version.%d.target_size.%d.percent"} + errorList := make([]string, 0) + + versionBlocks := diff.Get("version").([]interface{}) + if len(versionBlocks) == 0 { + return nil + } + + for i := range versionBlocks { + targetBlocks := diff.Get(fmt.Sprintf("version.%d.target_size", i)).([]interface{}) + if len(targetBlocks) == 0 { + continue + } + + for j := range targetBlocks { + specified := make([]string, 0) + for _, exactlyOneOfKey := range exactlyOneOfList { + if val := diff.Get(fmt.Sprintf(exactlyOneOfKey, i, j)); val != 0 { + specified = append(specified, exactlyOneOfKey) + } + } + + if len(specified) == 1 { + continue + } + + sort.Strings(exactlyOneOfList) + keyList := formatStringsInList(exactlyOneOfList, i, j) + specified = formatStringsInList(specified, i, j) + + if len(specified) == 0 { + errorList = append(errorList, fmt.Sprintf("version.%d.target_size: one of `%s` must be specified", i, strings.Join(keyList, ","))) + } + + if len(specified) > 1 { + errorList = append(errorList, fmt.Sprintf("version.%d.target_size: only one of `%s` can be specified, but `%s` were specified", i, strings.Join(keyList, ","), strings.Join(specified, ","))) + } + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + func resourceComputeRegionInstanceGroupManager() *schema.Resource { return &schema.Resource{ Create: resourceComputeRegionInstanceGroupManagerCreate, @@ -25,6 +74,9 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Importer: &schema.ResourceImporter{ State: resourceRegionInstanceGroupManagerStateImporter, }, + CustomizeDiff: customdiff.All( + resourceComputeRegionInstanceGroupManagerExactlyOneTargetSizeDiff, + ), Timeouts: &schema.ResourceTimeout{ Create: schema.DefaultTimeout(5 * time.Minute), Update: schema.DefaultTimeout(5 * time.Minute), @@ -81,7 +133,6 @@ func resourceComputeRegionInstanceGroupManager() *schema.Resource { Type: schema.TypeInt, Optional: true, }, - "percent": &schema.Schema{ Type: schema.TypeInt, Optional: true, diff --git a/third_party/terraform/resources/resource_compute_router_peer.go b/third_party/terraform/resources/resource_compute_router_peer.go index 88ec144407c0..0201ca16d50c 100644 --- a/third_party/terraform/resources/resource_compute_router_peer.go +++ b/third_party/terraform/resources/resource_compute_router_peer.go @@ -85,7 +85,7 @@ func resourceComputeRouterPeer() *schema.Resource { }, "range": { Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, diff --git a/third_party/terraform/resources/resource_container_cluster.go.erb b/third_party/terraform/resources/resource_container_cluster.go.erb index 82966308e3ad..524c4b04c919 100644 --- a/third_party/terraform/resources/resource_container_cluster.go.erb +++ b/third_party/terraform/resources/resource_container_cluster.go.erb @@ -24,7 +24,7 @@ var ( Schema: map[string]*schema.Schema{ "cidr_blocks": { Type: schema.TypeSet, - Optional: true, + Required: true, Elem: cidrBlockConfig, }, }, @@ -46,6 +46,16 @@ var ( ipAllocationSubnetFields = []string{"ip_allocation_policy.0.create_subnetwork", "ip_allocation_policy.0.subnetwork_name"} ipAllocationCidrBlockFields = []string{"ip_allocation_policy.0.cluster_ipv4_cidr_block", "ip_allocation_policy.0.services_ipv4_cidr_block", "ip_allocation_policy.0.node_ipv4_cidr_block"} ipAllocationRangeFields = []string{"ip_allocation_policy.0.cluster_secondary_range_name", "ip_allocation_policy.0.services_secondary_range_name"} + + addonsConfigKeys = []string{ + "addons_config.0.http_load_balancing", + "addons_config.0.horizontal_pod_autoscaling", + "addons_config.0.network_policy_config", + <% unless version == 'ga' -%> + "addons_config.0.istio_config", + "addons_config.0.cloudrun_config", + <% end -%> + } ) <% unless version == 'ga' -%> @@ -167,29 +177,31 @@ func resourceContainerCluster() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "http_load_balancing": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + AtLeastOneOf: addonsConfigKeys, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disabled": { Type: schema.TypeBool, - Optional: true, + Required: true, }, }, }, }, "horizontal_pod_autoscaling": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + AtLeastOneOf: addonsConfigKeys, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disabled": { Type: schema.TypeBool, - Optional: true, + Required: true, }, }, }, @@ -209,31 +221,32 @@ func resourceContainerCluster() *schema.Resource { }, }, "network_policy_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + AtLeastOneOf: addonsConfigKeys, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disabled": { Type: schema.TypeBool, - Optional: true, + Required: true, }, }, }, }, <% unless version == 'ga' -%> "istio_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + AtLeastOneOf: addonsConfigKeys, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disabled": { Type: schema.TypeBool, - Default: false, - Optional: true, + Required: true, }, "auth": { Type: schema.TypeString, @@ -246,17 +259,17 @@ func resourceContainerCluster() *schema.Resource { }, }, "cloudrun_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - ForceNew: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + AtLeastOneOf: addonsConfigKeys, + ForceNew: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "disabled": { Type: schema.TypeBool, - Default: false, - Optional: true, + Required: true, }, }, }, @@ -466,12 +479,14 @@ func resourceContainerCluster() *schema.Resource { "password": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: []string{"master_auth.0.password", "master_auth.0.username", "master_auth.0.client_certificate_config"}, Sensitive: true, }, "username": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: []string{"master_auth.0.password", "master_auth.0.username", "master_auth.0.client_certificate_config"}, }, // Ideally, this would be Optional (and not Computed). @@ -483,6 +498,7 @@ func resourceContainerCluster() *schema.Resource { MaxItems: 1, Optional: true, Computed: true, + AtLeastOneOf: []string{"master_auth.0.password", "master_auth.0.username", "master_auth.0.client_certificate_config"}, ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -550,8 +566,7 @@ func resourceContainerCluster() *schema.Resource { Schema: map[string]*schema.Schema{ "enabled": { Type: schema.TypeBool, - Optional: true, - Default: false, + Required: true, }, "provider": { Type: schema.TypeString, @@ -665,7 +680,7 @@ func resourceContainerCluster() *schema.Resource { ConflictsWith: ipAllocationRangeFields, }, - "subnetwork_name": { + "subnetwork_name": { Type: schema.TypeString, Deprecated: "This field is being removed in 3.0.0. Define an explicit google_compute_subnetwork and use subnetwork instead.", Computed: true, @@ -735,7 +750,7 @@ func resourceContainerCluster() *schema.Resource { Schema: map[string]*schema.Schema{ "enable_private_endpoint": { Type: schema.TypeBool, - Optional: true, + Required: true, ForceNew: true, DiffSuppressFunc: containerClusterPrivateClusterConfigSuppress, }, @@ -787,8 +802,7 @@ func resourceContainerCluster() *schema.Resource { Schema: map[string]*schema.Schema{ "channel": { Type: schema.TypeString, - Default: "UNSPECIFIED", - Optional: true, + Required: true, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"UNSPECIFIED", "RAPID", "REGULAR", "STABLE"}, false), DiffSuppressFunc: emptyOrDefaultStringSuppress("UNSPECIFIED"), @@ -805,7 +819,7 @@ func resourceContainerCluster() *schema.Resource { Schema: map[string]*schema.Schema{ "enabled": { Type: schema.TypeBool, - Optional: true, + Required: true, }, }, }, diff --git a/third_party/terraform/resources/resource_dataproc_cluster.go.erb b/third_party/terraform/resources/resource_dataproc_cluster.go.erb index 6e33a85342a5..b97cd75df9d7 100644 --- a/third_party/terraform/resources/resource_dataproc_cluster.go.erb +++ b/third_party/terraform/resources/resource_dataproc_cluster.go.erb @@ -16,7 +16,43 @@ import ( "google.golang.org/api/dataproc/v1beta2" ) -var resolveDataprocImageVersion = regexp.MustCompile(`(?P[^\s.-]+)\.(?P[^\s.-]+)(?:\.(?P[^\s.-]+))?(?:\-(?P[^\s.-]+))?`) +var ( + resolveDataprocImageVersion = regexp.MustCompile(`(?P[^\s.-]+)\.(?P[^\s.-]+)(?:\.(?P[^\s.-]+))?(?:\-(?P[^\s.-]+))?`) + + gceClusterConfigKeys = []string{ + "cluster_config.0.gce_cluster_config.0.zone", + "cluster_config.0.gce_cluster_config.0.network", + "cluster_config.0.gce_cluster_config.0.subnetwork", + "cluster_config.0.gce_cluster_config.0.tags", + "cluster_config.0.gce_cluster_config.0.service_account", + "cluster_config.0.gce_cluster_config.0.service_account_scopes", + "cluster_config.0.gce_cluster_config.0.internal_ip_only", + "cluster_config.0.gce_cluster_config.0.metadata", + } + + preemptibleWorkerDiskConfigKeys = []string{ + "cluster_config.0.preemptible_worker_config.0.disk_config.0.num_local_ssds", + "cluster_config.0.preemptible_worker_config.0.disk_config.0.boot_disk_size_gb", + "cluster_config.0.preemptible_worker_config.0.disk_config.0.boot_disk_type", + } + + clusterSoftwareConfigKeys = []string{ + "cluster_config.0.software_config.0.image_version", + "cluster_config.0.software_config.0.override_properties", + "cluster_config.0.software_config.0.optional_components", + } + + clusterConfigKeys = []string{ + "cluster_config.0.staging_bucket", + "cluster_config.0.gce_cluster_config", + "cluster_config.0.master_config", + "cluster_config.0.worker_config", + "cluster_config.0.preemptible_worker_config", + "cluster_config.0.software_config", + "cluster_config.0.initialization_action", + "cluster_config.0.encryption_config", + } +) func resourceDataprocCluster() *schema.Resource { return &schema.Resource{ @@ -92,9 +128,10 @@ func resourceDataprocCluster() *schema.Resource { Schema: map[string]*schema.Schema{ "staging_bucket": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: clusterConfigKeys, + ForceNew: true, }, // If the user does not specify a staging bucket, GCP will allocate one automatically. // The staging_bucket field provides a way for the user to supply their own @@ -107,24 +144,27 @@ func resourceDataprocCluster() *schema.Resource { }, "gce_cluster_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: clusterConfigKeys, + Computed: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "zone": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: gceClusterConfigKeys, + ForceNew: true, }, "network": { Type: schema.TypeString, Optional: true, Computed: true, + AtLeastOneOf: gceClusterConfigKeys, ForceNew: true, ConflictsWith: []string{"cluster_config.0.gce_cluster_config.0.subnetwork"}, DiffSuppressFunc: compareSelfLinkOrResourceName, @@ -133,29 +173,33 @@ func resourceDataprocCluster() *schema.Resource { "subnetwork": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: gceClusterConfigKeys, ForceNew: true, ConflictsWith: []string{"cluster_config.0.gce_cluster_config.0.network"}, DiffSuppressFunc: compareSelfLinkOrResourceName, }, "tags": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeSet, + Optional: true, + AtLeastOneOf: gceClusterConfigKeys, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, "service_account": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: gceClusterConfigKeys, + ForceNew: true, }, "service_account_scopes": { - Type: schema.TypeSet, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeSet, + Optional: true, + Computed: true, + AtLeastOneOf: gceClusterConfigKeys, + ForceNew: true, Elem: &schema.Schema{ Type: schema.TypeString, StateFunc: func(v interface{}) string { @@ -166,36 +210,43 @@ func resourceDataprocCluster() *schema.Resource { }, "internal_ip_only": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, - Default: false, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: gceClusterConfigKeys, + ForceNew: true, + Default: false, }, "metadata": { - Type: schema.TypeMap, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ForceNew: true, + Type: schema.TypeMap, + Optional: true, + AtLeastOneOf: gceClusterConfigKeys, + Elem: &schema.Schema{Type: schema.TypeString}, + ForceNew: true, }, }, }, }, - "master_config": instanceConfigSchema(), - "worker_config": instanceConfigSchema(), + "master_config": instanceConfigSchema("master_config"), + "worker_config": instanceConfigSchema("worker_config"), // preemptible_worker_config has a slightly different config "preemptible_worker_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: clusterConfigKeys, + Computed: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "num_instances": { Type: schema.TypeInt, Optional: true, Computed: true, + AtLeastOneOf: []string{ + "cluster_config.0.preemptible_worker_config.0.num_instances", + "cluster_config.0.preemptible_worker_config.0.disk_config", + }, }, // API does not honour this if set ... @@ -208,21 +259,27 @@ func resourceDataprocCluster() *schema.Resource { Type: schema.TypeList, Optional: true, Computed: true, + AtLeastOneOf: []string{ + "cluster_config.0.preemptible_worker_config.0.num_instances", + "cluster_config.0.preemptible_worker_config.0.disk_config", + }, MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "num_local_ssds": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + Computed: true, + AtLeastOneOf: preemptibleWorkerDiskConfigKeys, + ForceNew: true, }, "boot_disk_size_gb": { Type: schema.TypeInt, Optional: true, Computed: true, + AtLeastOneOf: preemptibleWorkerDiskConfigKeys, ForceNew: true, ValidateFunc: validation.IntAtLeast(10), }, @@ -230,6 +287,7 @@ func resourceDataprocCluster() *schema.Resource { "boot_disk_type": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: preemptibleWorkerDiskConfigKeys, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd", ""}, false), Default: "pd-standard", @@ -248,10 +306,11 @@ func resourceDataprocCluster() *schema.Resource { }, "software_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: clusterConfigKeys, + Computed: true, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ @@ -259,15 +318,17 @@ func resourceDataprocCluster() *schema.Resource { Type: schema.TypeString, Optional: true, Computed: true, + AtLeastOneOf: clusterSoftwareConfigKeys, ForceNew: true, DiffSuppressFunc: dataprocImageVersionDiffSuppress, }, "override_properties": { - Type: schema.TypeMap, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, + Type: schema.TypeMap, + Optional: true, + AtLeastOneOf: clusterSoftwareConfigKeys, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, }, "properties": { @@ -285,8 +346,9 @@ func resourceDataprocCluster() *schema.Resource { // is overridden, this will be empty. "optional_components": { - Type: schema.TypeSet, - Optional: true, + Type: schema.TypeSet, + Optional: true, + AtLeastOneOf: clusterSoftwareConfigKeys, Elem: &schema.Schema{ Type: schema.TypeString, ValidateFunc: validation.StringInSlice([]string{"COMPONENT_UNSPECIFIED", "ANACONDA", "DRUID", "HIVE_WEBHCAT", @@ -298,9 +360,10 @@ func resourceDataprocCluster() *schema.Resource { }, "initialization_action": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: clusterConfigKeys, + ForceNew: true, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "script": { @@ -319,9 +382,10 @@ func resourceDataprocCluster() *schema.Resource { }, }, "encryption_config": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: clusterConfigKeys, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "kms_key_name": { @@ -340,7 +404,7 @@ func resourceDataprocCluster() *schema.Resource { Schema: map[string]*schema.Schema{ "policy_uri": { Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -353,61 +417,88 @@ func resourceDataprocCluster() *schema.Resource { } } -func instanceConfigSchema() *schema.Schema { +func instanceConfigSchema(parent string) *schema.Schema { + var instanceConfigKeys = []string{ + "cluster_config.0."+parent+".0.num_instances", + "cluster_config.0."+parent+".0.image_uri", + "cluster_config.0."+parent+".0.machine_type", +<% unless version == 'ga' -%> + "cluster_config.0."+parent+".0.min_cpu_platform", +<% end -%> + "cluster_config.0."+parent+".0.disk_config", + "cluster_config.0."+parent+".0.accelerators", + } + return &schema.Schema{ - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + AtLeastOneOf: clusterConfigKeys, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "num_instances": { - Type: schema.TypeInt, - Optional: true, - Computed: true, + Type: schema.TypeInt, + Optional: true, + Computed: true, + AtLeastOneOf: instanceConfigKeys, }, "image_uri": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: instanceConfigKeys, + ForceNew: true, }, "machine_type": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: instanceConfigKeys, + ForceNew: true, }, <% unless version == 'ga' -%> "min_cpu_platform": { - Type: schema.TypeString, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: instanceConfigKeys, + ForceNew: true, }, <% end -%> "disk_config": { - Type: schema.TypeList, - Optional: true, - Computed: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + Computed: true, + AtLeastOneOf: instanceConfigKeys, + MaxItems: 1, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "num_local_ssds": { - Type: schema.TypeInt, - Optional: true, - Computed: true, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + Computed: true, + AtLeastOneOf: []string{ + "cluster_config.0."+parent+".0.disk_config.0.num_local_ssds", + "cluster_config.0."+parent+".0.disk_config.0.boot_disk_size_gb", + "cluster_config.0."+parent+".0.disk_config.0.boot_disk_type", + }, + ForceNew: true, }, "boot_disk_size_gb": { Type: schema.TypeInt, Optional: true, Computed: true, + AtLeastOneOf: []string{ + "cluster_config.0."+parent+".0.disk_config.0.num_local_ssds", + "cluster_config.0."+parent+".0.disk_config.0.boot_disk_size_gb", + "cluster_config.0."+parent+".0.disk_config.0.boot_disk_type", + }, ForceNew: true, ValidateFunc: validation.IntAtLeast(10), }, @@ -415,6 +506,11 @@ func instanceConfigSchema() *schema.Schema { "boot_disk_type": { Type: schema.TypeString, Optional: true, + AtLeastOneOf: []string{ + "cluster_config.0."+parent+".0.disk_config.0.num_local_ssds", + "cluster_config.0."+parent+".0.disk_config.0.boot_disk_size_gb", + "cluster_config.0."+parent+".0.disk_config.0.boot_disk_type", + }, ForceNew: true, ValidateFunc: validation.StringInSlice([]string{"pd-standard", "pd-ssd", ""}, false), Default: "pd-standard", @@ -425,10 +521,11 @@ func instanceConfigSchema() *schema.Schema { // Note: preemptible workers don't support accelerators "accelerators": { - Type: schema.TypeSet, - Optional: true, - ForceNew: true, - Elem: acceleratorsSchema(), + Type: schema.TypeSet, + Optional: true, + AtLeastOneOf: instanceConfigKeys, + ForceNew: true, + Elem: acceleratorsSchema(), }, "instance_names": { diff --git a/third_party/terraform/resources/resource_dataproc_job.go b/third_party/terraform/resources/resource_dataproc_job.go index d7e2ccdf23e4..e4ced4e41b63 100644 --- a/third_party/terraform/resources/resource_dataproc_job.go +++ b/third_party/terraform/resources/resource_dataproc_job.go @@ -149,7 +149,7 @@ func resourceDataprocJob() *schema.Resource { "max_failures_per_hour": { Type: schema.TypeInt, Description: "Maximum number of times per hour a driver may be restarted as a result of driver terminating with non-zero code before job is reported failed.", - Optional: true, + Required: true, ForceNew: true, ValidateFunc: validation.IntAtMost(10), }, @@ -182,7 +182,6 @@ func resourceDataprocJobCreate(d *schema.ResourceData, meta interface{}) error { return err } - jobConfCount := 0 clusterName := d.Get("placement.0.cluster_name").(string) region := d.Get("region").(string) @@ -205,45 +204,35 @@ func resourceDataprocJobCreate(d *schema.ResourceData, meta interface{}) error { } if v, ok := d.GetOk("pyspark_config"); ok { - jobConfCount++ config := extractFirstMapConfig(v.([]interface{})) submitReq.Job.PysparkJob = expandPySparkJob(config) } if v, ok := d.GetOk("spark_config"); ok { - jobConfCount++ config := extractFirstMapConfig(v.([]interface{})) submitReq.Job.SparkJob = expandSparkJob(config) } if v, ok := d.GetOk("hadoop_config"); ok { - jobConfCount++ config := extractFirstMapConfig(v.([]interface{})) submitReq.Job.HadoopJob = expandHadoopJob(config) } if v, ok := d.GetOk("hive_config"); ok { - jobConfCount++ config := extractFirstMapConfig(v.([]interface{})) submitReq.Job.HiveJob = expandHiveJob(config) } if v, ok := d.GetOk("pig_config"); ok { - jobConfCount++ config := extractFirstMapConfig(v.([]interface{})) submitReq.Job.PigJob = expandPigJob(config) } if v, ok := d.GetOk("sparksql_config"); ok { - jobConfCount++ config := extractFirstMapConfig(v.([]interface{})) submitReq.Job.SparkSqlJob = expandSparkSqlJob(config) } - if jobConfCount != 1 { - return fmt.Errorf("You must define and configure exactly one xxx_config block") - } - // Submit the job job, err := config.clientDataproc.Projects.Regions.Jobs.Submit( project, region, submitReq).Do() @@ -373,7 +362,7 @@ var loggingConfig = &schema.Schema{ "driver_log_levels": { Type: schema.TypeMap, Description: "Optional. The per-package log levels for the driver. This may include 'root' package name to configure rootLogger. Examples: 'com.google = FATAL', 'root = INFO', 'org.apache = DEBUG'.", - Optional: true, + Required: true, ForceNew: true, Elem: &schema.Schema{Type: schema.TypeString}, }, @@ -382,11 +371,11 @@ var loggingConfig = &schema.Schema{ } var pySparkSchema = &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, - ConflictsWith: []string{"spark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + ExactlyOneOf: []string{"pyspark_config", "spark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "main_python_file_uri": { @@ -499,26 +488,26 @@ func expandPySparkJob(config map[string]interface{}) *dataproc.PySparkJob { // ---- Spark Job ---- var sparkSchema = &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, - ConflictsWith: []string{"pyspark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + ExactlyOneOf: []string{"pyspark_config", "spark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ // main driver: can be only one of the class | jar_file "main_class": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"spark_config.0.main_jar_file_uri"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"spark_config.0.main_class", "spark_config.0.main_jar_file_uri"}, }, "main_jar_file_uri": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"spark_config.0.main_class"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"spark_config.0.main_jar_file_uri", "spark_config.0.main_class"}, }, "args": { @@ -612,26 +601,26 @@ func expandSparkJob(config map[string]interface{}) *dataproc.SparkJob { // ---- Hadoop Job ---- var hadoopSchema = &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, - ConflictsWith: []string{"spark_config", "pyspark_config", "hive_config", "pig_config", "sparksql_config"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + ExactlyOneOf: []string{"spark_config", "pyspark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ // main driver: can be only one of the main_class | main_jar_file_uri "main_class": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"hadoop_config.0.main_jar_file_uri"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"hadoop_config.0.main_jar_file_uri", "hadoop_config.0.main_class"}, }, "main_jar_file_uri": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"hadoop_config.0.main_class"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"hadoop_config.0.main_jar_file_uri", "hadoop_config.0.main_class"}, }, "args": { @@ -725,27 +714,27 @@ func expandHadoopJob(config map[string]interface{}) *dataproc.HadoopJob { // ---- Hive Job ---- var hiveSchema = &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, - ConflictsWith: []string{"spark_config", "pyspark_config", "hadoop_config", "pig_config", "sparksql_config"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + ExactlyOneOf: []string{"spark_config", "pyspark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ // main query: can be only one of query_list | query_file_uri "query_list": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ConflictsWith: []string{"hive_config.0.query_file_uri"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ExactlyOneOf: []string{"hive_config.0.query_file_uri", "hive_config.0.query_list"}, }, "query_file_uri": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"hive_config.0.query_list"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"hive_config.0.query_file_uri", "hive_config.0.query_list"}, }, "continue_on_failure": { @@ -824,27 +813,27 @@ func expandHiveJob(config map[string]interface{}) *dataproc.HiveJob { // ---- Pig Job ---- var pigSchema = &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, - ConflictsWith: []string{"spark_config", "pyspark_config", "hadoop_config", "hive_config", "sparksql_config"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + ExactlyOneOf: []string{"spark_config", "pyspark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ // main query: can be only one of query_list | query_file_uri "query_list": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ConflictsWith: []string{"pig_config.0.query_file_uri"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ExactlyOneOf: []string{"pig_config.0.query_file_uri", "pig_config.0.query_list"}, }, "query_file_uri": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"pig_config.0.query_list"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"pig_config.0.query_file_uri", "pig_config.0.query_list"}, }, "continue_on_failure": { @@ -926,27 +915,27 @@ func expandPigJob(config map[string]interface{}) *dataproc.PigJob { // ---- Spark SQL Job ---- var sparkSqlSchema = &schema.Schema{ - Type: schema.TypeList, - Optional: true, - ForceNew: true, - MaxItems: 1, - ConflictsWith: []string{"spark_config", "pyspark_config", "hadoop_config", "hive_config", "pig_config"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + MaxItems: 1, + ExactlyOneOf: []string{"spark_config", "pyspark_config", "hadoop_config", "hive_config", "pig_config", "sparksql_config"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ // main query: can be only one of query_list | query_file_uri "query_list": { - Type: schema.TypeList, - Optional: true, - ForceNew: true, - Elem: &schema.Schema{Type: schema.TypeString}, - ConflictsWith: []string{"pig_config.0.query_file_uri"}, + Type: schema.TypeList, + Optional: true, + ForceNew: true, + Elem: &schema.Schema{Type: schema.TypeString}, + ExactlyOneOf: []string{"sparksql_config.0.query_file_uri", "sparksql_config.0.query_list"}, }, "query_file_uri": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - ConflictsWith: []string{"pig_config.0.query_list"}, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + ExactlyOneOf: []string{"sparksql_config.0.query_file_uri", "sparksql_config.0.query_list"}, }, "script_variables": { diff --git a/third_party/terraform/resources/resource_google_organization_policy.go b/third_party/terraform/resources/resource_google_organization_policy.go index b322d9e9425d..9e7535fad561 100644 --- a/third_party/terraform/resources/resource_google_organization_policy.go +++ b/third_party/terraform/resources/resource_google_organization_policy.go @@ -17,10 +17,10 @@ var schemaOrganizationPolicy = map[string]*schema.Schema{ DiffSuppressFunc: compareSelfLinkOrResourceName, }, "boolean_policy": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ConflictsWith: []string{"list_policy", "restore_policy"}, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"list_policy", "boolean_policy", "restore_policy"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "enforced": { @@ -31,10 +31,10 @@ var schemaOrganizationPolicy = map[string]*schema.Schema{ }, }, "list_policy": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ConflictsWith: []string{"boolean_policy", "restore_policy"}, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"list_policy", "boolean_policy", "restore_policy"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "allow": { @@ -45,37 +45,40 @@ var schemaOrganizationPolicy = map[string]*schema.Schema{ Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "all": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ConflictsWith: []string{"list_policy.0.allow.0.values"}, + Type: schema.TypeBool, + Optional: true, + Default: false, + ExactlyOneOf: []string{"list_policy.0.allow.0.all", "list_policy.0.allow.0.values"}, }, "values": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + Type: schema.TypeSet, + Optional: true, + ExactlyOneOf: []string{"list_policy.0.allow.0.all", "list_policy.0.allow.0.values"}, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, }, }, }, }, "deny": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ConflictsWith: []string{"list_policy.0.allow"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "all": { - Type: schema.TypeBool, - Optional: true, - Default: false, - ConflictsWith: []string{"list_policy.0.deny.0.values"}, + Type: schema.TypeBool, + Optional: true, + Default: false, + ExactlyOneOf: []string{"list_policy.0.deny.0.all", "list_policy.0.deny.0.values"}, }, "values": { - Type: schema.TypeSet, - Optional: true, - Elem: &schema.Schema{Type: schema.TypeString}, - Set: schema.HashString, + Type: schema.TypeSet, + Optional: true, + ExactlyOneOf: []string{"list_policy.0.deny.0.all", "list_policy.0.deny.0.values"}, + Elem: &schema.Schema{Type: schema.TypeString}, + Set: schema.HashString, }, }, }, @@ -106,10 +109,10 @@ var schemaOrganizationPolicy = map[string]*schema.Schema{ Computed: true, }, "restore_policy": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - ConflictsWith: []string{"boolean_policy", "list_policy"}, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + ExactlyOneOf: []string{"restore_policy", "boolean_policy", "list_policy"}, Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "default": { diff --git a/third_party/terraform/resources/resource_sql_database_instance.go b/third_party/terraform/resources/resource_sql_database_instance.go index 16966722af39..27217f7fae22 100644 --- a/third_party/terraform/resources/resource_sql_database_instance.go +++ b/third_party/terraform/resources/resource_sql_database_instance.go @@ -30,11 +30,55 @@ var sqlDatabaseAuthorizedNetWorkSchemaElem *schema.Resource = &schema.Resource{ }, "value": { Type: schema.TypeString, - Optional: true, + Required: true, }, }, } +var ( + backupConfigurationKeys = []string{ + "settings.0.backup_configuration.0.binary_log_enabled", + "settings.0.backup_configuration.0.enabled", + "settings.0.backup_configuration.0.start_time", + "settings.0.backup_configuration.0.location", + } + + ipConfigurationKeys = []string{ + "settings.0.ip_configuration.0.authorized_networks", + "settings.0.ip_configuration.0.ipv4_enabled", + "settings.0.ip_configuration.0.require_ssl", + "settings.0.ip_configuration.0.private_network", + } + + maintenanceWindowKeys = []string{ + "settings.0.maintenance_window.0.day", + "settings.0.maintenance_window.0.hour", + "settings.0.maintenance_window.0.update_track", + } + + serverCertsKeys = []string{ + "server_ca_cert.0.cert", + "server_ca_cert.0.common_name", + "server_ca_cert.0.create_time", + "server_ca_cert.0.expiration_time", + "server_ca_cert.0.sha1_fingerprint", + } + + replicaConfigurationKeys = []string{ + "replica_configuration.0.ca_certificate", + "replica_configuration.0.client_certificate", + "replica_configuration.0.client_key", + "replica_configuration.0.connect_retry_interval", + "replica_configuration.0.dump_file_path", + "replica_configuration.0.failover_target", + "replica_configuration.0.master_heartbeat_period", + "replica_configuration.0.password", + "replica_configuration.0.ssl_cipher", + "replica_configuration.0.username", + "replica_configuration.0.verify_server_certificate", + } +) + func resourceSqlDatabaseInstance() *schema.Resource { return &schema.Resource{ Create: resourceSqlDatabaseInstanceCreate, @@ -105,22 +149,26 @@ func resourceSqlDatabaseInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "binary_log_enabled": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: backupConfigurationKeys, }, "enabled": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: backupConfigurationKeys, }, "start_time": { Type: schema.TypeString, Optional: true, // start_time is randomly assigned if not set - Computed: true, + Computed: true, + AtLeastOneOf: backupConfigurationKeys, }, "location": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: backupConfigurationKeys, }, }, }, @@ -137,11 +185,11 @@ func resourceSqlDatabaseInstance() *schema.Resource { Schema: map[string]*schema.Schema{ "value": { Type: schema.TypeString, - Optional: true, + Required: true, }, "name": { Type: schema.TypeString, - Optional: true, + Required: true, }, }, }, @@ -172,26 +220,30 @@ func resourceSqlDatabaseInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "authorized_networks": { - Type: schema.TypeSet, - Optional: true, - Set: schema.HashResource(sqlDatabaseAuthorizedNetWorkSchemaElem), - Elem: sqlDatabaseAuthorizedNetWorkSchemaElem, + Type: schema.TypeSet, + Optional: true, + Set: schema.HashResource(sqlDatabaseAuthorizedNetWorkSchemaElem), + Elem: sqlDatabaseAuthorizedNetWorkSchemaElem, + AtLeastOneOf: ipConfigurationKeys, }, "ipv4_enabled": { Type: schema.TypeBool, Optional: true, // Defaults differ between first and second gen instances - Computed: true, + Computed: true, + AtLeastOneOf: ipConfigurationKeys, }, "require_ssl": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: ipConfigurationKeys, }, "private_network": { Type: schema.TypeString, Optional: true, ValidateFunc: orEmpty(validateRegexp(privateNetworkLinkRegex)), DiffSuppressFunc: compareSelfLinkRelativePaths, + AtLeastOneOf: ipConfigurationKeys, }, }, }, @@ -204,12 +256,14 @@ func resourceSqlDatabaseInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "follow_gae_application": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{"settings.0.location_preference.0.follow_gae_application", "settings.0.location_preference.0.zone"}, }, "zone": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{"settings.0.location_preference.0.follow_gae_application", "settings.0.location_preference.0.zone"}, }, }, }, @@ -224,15 +278,18 @@ func resourceSqlDatabaseInstance() *schema.Resource { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(1, 7), + AtLeastOneOf: maintenanceWindowKeys, }, "hour": { Type: schema.TypeInt, Optional: true, ValidateFunc: validation.IntBetween(0, 23), + AtLeastOneOf: maintenanceWindowKeys, }, "update_track": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: maintenanceWindowKeys, }, }, }, @@ -334,60 +391,71 @@ func resourceSqlDatabaseInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "ca_certificate": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "client_certificate": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "client_key": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "connect_retry_interval": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "dump_file_path": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "failover_target": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "master_heartbeat_period": { - Type: schema.TypeInt, - Optional: true, - ForceNew: true, + Type: schema.TypeInt, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "password": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, - Sensitive: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + Sensitive: true, + AtLeastOneOf: replicaConfigurationKeys, }, "ssl_cipher": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "username": { - Type: schema.TypeString, - Optional: true, - ForceNew: true, + Type: schema.TypeString, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, "verify_server_certificate": { - Type: schema.TypeBool, - Optional: true, - ForceNew: true, + Type: schema.TypeBool, + Optional: true, + ForceNew: true, + AtLeastOneOf: replicaConfigurationKeys, }, }, }, @@ -399,24 +467,29 @@ func resourceSqlDatabaseInstance() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "cert": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + AtLeastOneOf: serverCertsKeys, }, "common_name": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + AtLeastOneOf: serverCertsKeys, }, "create_time": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + AtLeastOneOf: serverCertsKeys, }, "expiration_time": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + AtLeastOneOf: serverCertsKeys, }, "sha1_fingerprint": { - Type: schema.TypeString, - Computed: true, + Type: schema.TypeString, + Computed: true, + AtLeastOneOf: serverCertsKeys, }, }, }, diff --git a/third_party/terraform/resources/resource_storage_bucket.go b/third_party/terraform/resources/resource_storage_bucket.go index 2ef34f2248d0..34ffa9ff98a4 100644 --- a/third_party/terraform/resources/resource_storage_bucket.go +++ b/third_party/terraform/resources/resource_storage_bucket.go @@ -7,6 +7,7 @@ import ( "log" "math" "runtime" + "sort" "strconv" "strings" "time" @@ -22,6 +23,37 @@ import ( "google.golang.org/api/storage/v1" ) +func resourceStorageBucketAtLeastOneCorsAttrDiff(diff *schema.ResourceDiff, v interface{}) error { + atLeastOneOfList := []string{"cors.%d.origin", "cors.%d.method", "cors.%d.response_header", "cors.%d.max_age_seconds"} + errorList := make([]string, 0) + + corsBlocks := diff.Get("cors").([]interface{}) + if len(corsBlocks) == 0 { + return nil + } + + for i := range corsBlocks { + found := false + for _, atLeastOneOfKey := range atLeastOneOfList { + if val := diff.Get(fmt.Sprintf(atLeastOneOfKey, i)); val != "" { + found = true + } + } + + if found == false { + sort.Strings(atLeastOneOfList) + keyList := formatStringsInList(atLeastOneOfList, i) + errorList = append(errorList, fmt.Sprintf("cors: one of `%s` must be specified", strings.Join(keyList, ","))) + } + } + + if len(errorList) > 0 { + return fmt.Errorf(strings.Join(errorList, "\n\t* ")) + } + + return nil +} + func resourceStorageBucket() *schema.Resource { return &schema.Resource{ Create: resourceStorageBucketCreate, @@ -32,7 +64,9 @@ func resourceStorageBucket() *schema.Resource { State: resourceStorageBucketStateImporter, }, CustomizeDiff: customdiff.All( - customdiff.ForceNewIfChange("retention_policy.0.is_locked", isPolicyLocked)), + customdiff.ForceNewIfChange("retention_policy.0.is_locked", isPolicyLocked), + resourceStorageBucketAtLeastOneCorsAttrDiff, + ), Schema: map[string]*schema.Schema{ "name": { @@ -182,8 +216,7 @@ func resourceStorageBucket() *schema.Resource { Schema: map[string]*schema.Schema{ "enabled": { Type: schema.TypeBool, - Optional: true, - Default: false, + Required: true, }, }, }, @@ -196,12 +229,14 @@ func resourceStorageBucket() *schema.Resource { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "main_page_suffix": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{"website.0.not_found_page", "website.0.main_page_suffix"}, }, "not_found_page": { - Type: schema.TypeString, - Optional: true, + Type: schema.TypeString, + Optional: true, + AtLeastOneOf: []string{"website.0.main_page_suffix", "website.0.not_found_page"}, }, }, }, diff --git a/third_party/terraform/resources/resource_storage_transfer_job.go b/third_party/terraform/resources/resource_storage_transfer_job.go index cc8c18cea94d..494e330b77da 100644 --- a/third_party/terraform/resources/resource_storage_transfer_job.go +++ b/third_party/terraform/resources/resource_storage_transfer_job.go @@ -11,6 +11,27 @@ import ( "time" ) +var ( + objectConditionsKeys = []string{ + "transfer_spec.0.object_conditions.0.min_time_elapsed_since_last_modification", + "transfer_spec.0.object_conditions.0.max_time_elapsed_since_last_modification", + "transfer_spec.0.object_conditions.0.include_prefixes", + "transfer_spec.0.object_conditions.0.exclude_prefixes", + } + + transferOptionsKeys = []string{ + "transfer_spec.0.transfer_options.0.overwrite_objects_already_existing_in_sink", + "transfer_spec.0.transfer_options.0.delete_objects_unique_in_sink", + "transfer_spec.0.transfer_options.0.delete_objects_from_source_after_transfer", + } + + transferSpecDataSourceKeys = []string{ + "transfer_spec.0.gcs_data_source", + "transfer_spec.0.aws_s3_data_source", + "transfer_spec.0.http_data_source", + } +) + func resourceStorageTransferJob() *schema.Resource { return &schema.Resource{ Create: resourceStorageTransferJobCreate, @@ -52,25 +73,25 @@ func resourceStorageTransferJob() *schema.Resource { Elem: gcsDataSchema(), }, "gcs_data_source": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: gcsDataSchema(), - ConflictsWith: []string{"transfer_spec.aws_s3_data_source", "transfer_spec.http_data_source"}, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: gcsDataSchema(), + ExactlyOneOf: transferSpecDataSourceKeys, }, "aws_s3_data_source": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: awsS3DataSchema(), - ConflictsWith: []string{"transfer_spec.gcs_data_source", "transfer_spec.http_data_source"}, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: awsS3DataSchema(), + ExactlyOneOf: transferSpecDataSourceKeys, }, "http_data_source": { - Type: schema.TypeList, - Optional: true, - MaxItems: 1, - Elem: httpDataSchema(), - ConflictsWith: []string{"transfer_spec.aws_s3_data_source", "transfer_spec.gcs_data_source"}, + Type: schema.TypeList, + Optional: true, + MaxItems: 1, + Elem: httpDataSchema(), + ExactlyOneOf: transferSpecDataSourceKeys, }, }, }, @@ -139,23 +160,27 @@ func objectConditionsSchema() *schema.Schema { Type: schema.TypeString, ValidateFunc: validateDuration(), Optional: true, + AtLeastOneOf: objectConditionsKeys, }, "max_time_elapsed_since_last_modification": { Type: schema.TypeString, ValidateFunc: validateDuration(), Optional: true, + AtLeastOneOf: objectConditionsKeys, }, "include_prefixes": { - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: objectConditionsKeys, Elem: &schema.Schema{ MaxItems: 1000, Type: schema.TypeString, }, }, "exclude_prefixes": { - Type: schema.TypeList, - Optional: true, + Type: schema.TypeList, + Optional: true, + AtLeastOneOf: objectConditionsKeys, Elem: &schema.Schema{ MaxItems: 1000, Type: schema.TypeString, @@ -174,17 +199,20 @@ func transferOptionsSchema() *schema.Schema { Elem: &schema.Resource{ Schema: map[string]*schema.Schema{ "overwrite_objects_already_existing_in_sink": { - Type: schema.TypeBool, - Optional: true, + Type: schema.TypeBool, + Optional: true, + AtLeastOneOf: transferOptionsKeys, }, "delete_objects_unique_in_sink": { Type: schema.TypeBool, Optional: true, + AtLeastOneOf: transferOptionsKeys, ConflictsWith: []string{"transfer_spec.transfer_options.delete_objects_from_source_after_transfer"}, }, "delete_objects_from_source_after_transfer": { Type: schema.TypeBool, Optional: true, + AtLeastOneOf: transferOptionsKeys, ConflictsWith: []string{"transfer_spec.transfer_options.delete_objects_unique_in_sink"}, }, }, diff --git a/third_party/terraform/tests/resource_dataproc_job_test.go b/third_party/terraform/tests/resource_dataproc_job_test.go index 4155c477a25e..b9f8bb04dffe 100644 --- a/third_party/terraform/tests/resource_dataproc_job_test.go +++ b/third_party/terraform/tests/resource_dataproc_job_test.go @@ -7,7 +7,7 @@ import ( "strings" "testing" - "regexp" + // "regexp" "github.com/hashicorp/errwrap" "github.com/hashicorp/terraform-plugin-sdk/helper/acctest" @@ -22,21 +22,22 @@ type jobTestField struct { gcp_attr interface{} } -func TestAccDataprocJob_failForMissingJobConfig(t *testing.T) { - t.Parallel() - - resource.Test(t, resource.TestCase{ - PreCheck: func() { testAccPreCheck(t) }, - Providers: testAccProviders, - CheckDestroy: testAccCheckDataprocJobDestroy, - Steps: []resource.TestStep{ - { - Config: testAccDataprocJob_missingJobConf(), - ExpectError: regexp.MustCompile("You must define and configure exactly one xxx_config block"), - }, - }, - }) -} +// TODO (mbang): Test `ExactlyOneOf` here +// func TestAccDataprocJob_failForMissingJobConfig(t *testing.T) { +// t.Parallel() + +// resource.Test(t, resource.TestCase{ +// PreCheck: func() { testAccPreCheck(t) }, +// Providers: testAccProviders, +// CheckDestroy: testAccCheckDataprocJobDestroy, +// Steps: []resource.TestStep{ +// { +// Config: testAccDataprocJob_missingJobConf(), +// ExpectError: regexp.MustCompile("You must define and configure exactly one xxx_config block"), +// }, +// }, +// }) +// } func TestAccDataprocJob_updatable(t *testing.T) { t.Parallel() @@ -472,16 +473,17 @@ func testAccCheckDataprocJobAttrMatch(n, jobType string, job *dataproc.Job) reso } } -func testAccDataprocJob_missingJobConf() string { - return ` -resource "google_dataproc_job" "missing_config" { - placement { - cluster_name = "na" - } - - force_delete = true -}` -} +// TODO (mbang): Test `ExactlyOneOf` here +// func testAccDataprocJob_missingJobConf() string { +// return ` +// resource "google_dataproc_job" "missing_config" { +// placement { +// cluster_name = "na" +// } + +// force_delete = true +// }` +// } var singleNodeClusterConfig = ` resource "google_dataproc_cluster" "basic" { diff --git a/third_party/terraform/tests/resource_storage_bucket_test.go b/third_party/terraform/tests/resource_storage_bucket_test.go index 45ca19539cee..56663df37769 100644 --- a/third_party/terraform/tests/resource_storage_bucket_test.go +++ b/third_party/terraform/tests/resource_storage_bucket_test.go @@ -4,6 +4,8 @@ import ( "bytes" "fmt" "log" + "regexp" + "strings" "testing" "time" @@ -828,11 +830,27 @@ func TestAccStorageBucket_website(t *testing.T) { bucketSuffix := acctest.RandomWithPrefix("tf-website-test") + websiteKeys := []string{"website.0.main_page_suffix", "website.0.not_found_page"} + errMsg := fmt.Sprintf("one of `%s` must be specified", strings.Join(websiteKeys, ",")) + fullErr := fmt.Sprintf("config is invalid: 2 problems:\n\n- \"%s\": %s\n- \"%s\": %s", websiteKeys[0], errMsg, websiteKeys[1], errMsg) + resource.Test(t, resource.TestCase{ PreCheck: func() { testAccPreCheck(t) }, Providers: testAccProviders, CheckDestroy: testAccStorageBucketDestroy, Steps: []resource.TestStep{ + { + Config: testAccStorageBucket_websiteNoAttributes(bucketSuffix), + ExpectError: regexp.MustCompile(fullErr), + }, + { + Config: testAccStorageBucket_websiteOneAttribute(bucketSuffix), + }, + { + ResourceName: "google_storage_bucket.website", + ImportState: true, + ImportStateVerify: true, + }, { Config: testAccStorageBucket_website(bucketSuffix), }, @@ -1420,3 +1438,29 @@ resource "google_storage_bucket" "bucket" { } `, bucketName) } + +func testAccStorageBucket_websiteNoAttributes(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "website" { + name = "%s.gcp.tfacc.hashicorptest.com" + location = "US" + storage_class = "MULTI_REGIONAL" + + website {} + } +`, bucketName) +} + +func testAccStorageBucket_websiteOneAttribute(bucketName string) string { + return fmt.Sprintf(` +resource "google_storage_bucket" "website" { + name = "%s.gcp.tfacc.hashicorptest.com" + location = "US" + storage_class = "MULTI_REGIONAL" + + website { + main_page_suffix = "index.html" + } + } +`, bucketName) +} diff --git a/third_party/terraform/utils/utils.go.erb b/third_party/terraform/utils/utils.go.erb index 9b540cf3a6e2..714ee92920eb 100644 --- a/third_party/terraform/utils/utils.go.erb +++ b/third_party/terraform/utils/utils.go.erb @@ -576,3 +576,17 @@ func calcAddRemove(from []string, to []string) (add, remove []string) { } return add, remove } + +// Format all the strings in the list with the list of values +// The strings in listOfStrings must have the same formats. +// The values in values must be the correct type and order +// for the strings' formats. +func formatStringsInList(listOfStrings []string, values ...interface{}) []string { + result := make([]string, 0) + + for _, s := range listOfStrings { + result = append(result, fmt.Sprintf(s, values...)) + } + + return result +} diff --git a/third_party/terraform/website/docs/guides/version_3_upgrade.html.markdown b/third_party/terraform/website/docs/guides/version_3_upgrade.html.markdown index fd3714018422..5592cb9b2026 100644 --- a/third_party/terraform/website/docs/guides/version_3_upgrade.html.markdown +++ b/third_party/terraform/website/docs/guides/version_3_upgrade.html.markdown @@ -53,19 +53,30 @@ so Terraform knows to manage them. - [Provider Version Configuration](#provider-version-configuration) - [Data Source: `google_container_engine_versions`](#data-source-google_container_engine_versions) -- [Resource: `google_container_cluster`](#resource-google_container_cluster) +- [Resource: `google_app_engine_application`](#resource-google_app_engine_application) - [Resource: `google_cloudfunctions_function`](#resource-google_cloudfunctions_function) - [Resource: `google_cloudiot_registry`](#resource-google_cloudiot_registry) +- [Resource: `google_composer_environment`](#resource-google_composer_environment) - [Resource: `google_compute_forwarding_rule`](#resource-google_compute_forwarding_rule) +- [Resource: `google_compute_instance`](#resource-google_compute_instance) +- [Resource: `google_compute_instance_template`](#resource-google_compute_instance_template) - [Resource: `google_compute_network`](#resource-google_compute_network) - [Resource: `google_compute_network_peering`](#resource-google_compute_network_peering) - [Resource: `google_compute_region_instance_group_manager`](#resource-google_compute_region_instance_group_manager) +- [Resource: `google_compute_router_peer`](#resource-google_compute_router_peer) +- [Resource: `google_compute_snapshot`](#resource-google_compute_snapshot) - [Resource: `google_container_cluster`](#resource-google_container_cluster) - [Resource: `google_container_node_pool`](#resource-google_container_node_pool) +- [Resource: `google_dataproc_cluster`](#resource-google_dataproc_cluster) +- [Resource: `google_dataproc_job`](#resource-google_dataproc_job) +- [Resource: `google_dns_managed_zone`](#resource-google_dns_managed_zone) - [Resource: `google_monitoring_alert_policy`](#resource-google_monitoring_alert_policy) - [Resource: `google_monitoring_uptime_check_config`](#resource-google_monitoring_uptime_check_config) +- [Resource: `google_organization_policy`](#resource-google_organization_policy) - [Resource: `google_project_services`](#resource-google_project_services) +- [Resource: `google_sql_database_instance`](#resource-google_sql_database_instance) - [Resource: `google_storage_bucket`](#resource-google_storage_bucket) +- [Resource: `google_storage_transfer_job`](#resource-google_storage_transfer_job) @@ -217,29 +228,12 @@ GKE Stackdriver Monitoring (the GKE-specific Stackdriver experience) is now enabled at cluster creation by default, similar to the default in GKE `1.14` through other tools. -Terraform will now detect changes out of band when the field(s) are not defined -in config, attempting to return them to their new defaults, and will be clear -about what values will be set when creating a cluster. +## Resource: `google_app_engine_application` -`terraform plan` will report changes upon upgrading if the field was previously -unset. Applying this change will enable the new Stackdriver service without -recreating clusters. Users who wish to use another value should record their -intended value in config; the old default values can be added to a -`google_container_cluster` resource config block to preserve them. - -#### Old Defaults +### `split_health_checks` is now required on block `google_app_engine_application.feature_settings` -```hcl -logging_service = "logging.googleapis.com" -monitoring_service = "monitoring.googleapis.com" -``` - -#### New Defaults - -```hcl -logging_service = "logging.googleapis.com/kubernetes" -monitoring_service = "monitoring.googleapis.com/kubernetes" -``` +In an attempt to avoid allowing empty blocks in config files, `split_health_checks` is now +required on the `google_app_engine_application.feature_settings` block. ### `taint` field is now authoritative when set @@ -272,12 +266,43 @@ documentation. `event_notification_config` has been removed in favor of `event_notification_configs` (plural). Please switch to using the plural field. +### `public_key_certificate` is now required on block `google_cloudiot_registry.credentials` + +In an attempt to avoid allowing empty blocks in config files, `public_key_certificate` is now +required on the `google_cloudiot_registry.credentials` block. + +## Resource: `google_composer_environment` + +### `use_ip_aliases` is now required on block `google_composer_environment.ip_allocation_policy` + +Previously the default value of `use_ip_aliases` was `true`. In an attempt to avoid allowing empty blocks +in config files, `use_ip_aliases` is now required on the `google_composer_environment.ip_allocation_policy` block. + +### `enable_private_endpoint` is now required on block `google_composer_environment.private_environment_config` + +Previously the default value of `enable_private_endpoint` was `true`. In an attempt to avoid allowing empty blocks +in config files, `enable_private_endpoint` is now required on the `google_composer_environment.private_environment_config` block. + ## Resource: `google_compute_forwarding_rule` ### `ip_version` is now removed `ip_version` is not used for regional forwarding rules. +## Resource: `google_compute_instance` + +### `interface` is now required on block `google_compute_instance.scratch_disk` + +Previously the default value of `interface` was `SCSI`. In an attempt to avoid allowing empty blocks +in config files, `interface` is now required on the `google_compute_instance.scratch_disk` block. + +## Resource: `google_compute_instance_template` + +### `kms_key_self_link` is now required on block `google_compute_instance_template.disk_encryption_key` + +In an attempt to avoid allowing empty blocks in config files, `kms_key_self_link` is now +required on the `google_compute_instance_template.disk_encryption_key` block. + ## Resource: `google_compute_network` ### `ipv4_range` is now removed @@ -299,14 +324,91 @@ user-configurable. With `rolling_update_policy` removed, `update_strategy` has no effect anymore. Before updating, remove it from your config. +## Resource: `google_compute_router_peer` + +### `range` is now required on block `google_compute_router_peer.advertised_ip_ranges` + +In an attempt to avoid allowing empty blocks in config files, `range` is now +required on the `google_compute_router_peer.advertised_ip_ranges` block. + +## Resource: `google_compute_snapshot` + +### `raw_key` is now required on block `google_compute_snapshot.source_disk_encryption_key` + +In an attempt to avoid allowing empty blocks in config files, `raw_key` is now +required on the `google_compute_snapshot.source_disk_encryption_key` block. + ## Resource: `google_container_cluster` + ### `addons_config.kubernetes_dashboard` is now removed The `kubernetes_dashboard` addon is deprecated for clusters on GKE and will soon be removed. It is recommended to use alternative GCP Console dashboards. +### `cidr_blocks` is now required on block `google_container_cluster.master_authorized_networks_config` + +In an attempt to avoid allowing empty blocks in config files, `cidr_blocks` is now +required on the `google_container_cluster.master_authorized_networks_config` block. + +### The `disabled` field is now required on the `addons_config` blocks for +`http_load_balancing`, `horizontal_pod_autoscaling`, `istio_config`, +`cloudrun_config` and `network_policy_config`. + +In an attempt to avoid allowing empty blocks in config files, `disabled` is now +required on the different `google_container_cluster.addons_config` blocks. + +### `enabled` is now required on block `google_container_cluster.vertical_pod_autoscaling` + +In an attempt to avoid allowing empty blocks in config files, `enabled` is now +required on the `google_container_cluster.vertical_pod_autoscaling` block. + +### `enabled` is now required on block `google_container_cluster.network_policy` + +Previously the default value of `enabled` was `false`. In an attempt to avoid allowing empty blocks +in config files, `enabled` is now required on the `google_container_cluster.network_policy` block. + +### `enable_private_endpoint` is now required on block `google_container_cluster.private_cluster_config` + +In an attempt to avoid allowing empty blocks in config files, `enable_private_endpoint` is now +required on the `google_container_cluster.private_cluster_config` block. + +### `logging_service` and `monitoring_service` defaults changed + +GKE Stackdriver Monitoring (the GKE-specific Stackdriver experience) is now +enabled at cluster creation by default, similar to the default in GKE `1.14` +through other tools. + +Terraform will now detect changes out of band when the field(s) are not defined +in config, attempting to return them to their new defaults, and will be clear +about what values will be set when creating a cluster. + +`terraform plan` will report changes upon upgrading if the field was previously +unset. Applying this change will enable the new Stackdriver service without +recreating clusters. Users who wish to use another value should record their +intended value in config; the old default values can be added to a +`google_container_cluster` resource config block to preserve them. + +#### Old Defaults + +```hcl +logging_service = "logging.googleapis.com" +monitoring_service = "monitoring.googleapis.com" +``` + +#### New Defaults + +```hcl +logging_service = "logging.googleapis.com/kubernetes" +monitoring_service = "monitoring.googleapis.com/kubernetes" +``` + +### `use_ip_aliases` is now required on block `google_container_cluster.ip_allocation_policy` + +Previously the default value of `use_ip_aliases` was `true`. In an attempt to avoid allowing empty blocks +in config files, `use_ip_aliases` is now required on the `google_container_cluster.ip_allocation_policy` block. + ### `zone`, `region` and `additional_zones` are now removed `zone` and `region` have been removed in favor of `location` and @@ -318,6 +420,40 @@ dashboards. `zone` and `region` have been removed in favor of `location` +## Resource: `google_dataproc_cluster` + +### `policy_uri` is now required on `google_dataproc_cluster.autoscaling_config` block. + +In an attempt to avoid allowing empty blocks in config files, `policy_uri` is now +required on the `google_dataproc_cluster.autoscaling_config` block. + +## Resource: `google_dataproc_job` + +### `driver_log_levels` is now required on `logging_config` blocks for +`google_dataproc_job.pyspark_config`, `google_dataproc_job.hadoop_config`, +`google_dataproc_job.spark_config`, `google_dataproc_job.pig_config`, and +`google_dataproc_job.sparksql_config`. + +In an attempt to avoid allowing empty blocks in config files, `driver_log_levels` is now +required on the different `google_dataproc_job` config blocks. + +### `max_failures_per_hour` is now required on block `google_dataproc_job.scheduling` + +In an attempt to avoid allowing empty blocks in config files, `max_failures_per_hour` is now +required on the `google_dataproc_job.scheduling` block. + +## Resource: `google_dns_managed_zone` + +### `networks` is now required on block `google_dns_managed_zone.private_visibility_config` + +In an attempt to avoid allowing empty blocks in config files, `networks` is now +required on the `google_dns_managed_zone.private_visibility_config` block. + +### `network_url` is now required on block `google_dns_managed_zone.private_visibility_config.networks` + +In an attempt to avoid allowing empty blocks in config files, `network_url` is now +required on the `google_dns_managed_zone.private_visibility_config.networks` block. + ## Resource: `google_monitoring_alert_policy` ### `labels` is now removed @@ -326,10 +462,22 @@ dashboards. ## Resource: `google_monitoring_uptime_check_config` +### `content` is now required on block `google_monitoring_uptime_check_config.content_matchers` + +In an attempt to avoid allowing empty blocks in config files, `content` is now +required on the `google_monitoring_uptime_check_config.content_matchers` block. + ### `is_internal` and `internal_checker` are now removed `is_internal` and `internal_checker` never worked, and are now removed. +## Resource: `google_organization_policy` + +### `inherit_from_parent` is now required on block `google_organization_policy.list_policy` + +In an attempt to avoid allowing empty blocks in config files, `inherit_from_parent` is now +required on the `google_organization_policy.list_policy` block. + ## Resource: `google_project_services` ### `google_project_services` has been removed from the provider @@ -403,8 +551,42 @@ resource "google_project_service" "project_cloudresourcemanager" { } ``` +## Resource: `google_sql_database_instance` + +### `dump_file_path`, `username` and `password` are now required on block `google_sql_database_instance.replica_configuration` + +In an attempt to avoid allowing empty blocks in config files, `dump_file_path`, `username` and `password` are now +required on the `google_sql_database_instance.replica_configuration` block. + +### `name` and `value` are now required on block `google_sql_database_instance.settings.database_flags` + +In an attempt to avoid allowing empty blocks in config files, `name` and `value` are now +required on the `google_sql_database_instance.settings.database_flags` block. + +### `value` is now required on block `google_sql_database_instance.settings.ip_configuration.authorized_networks` + +In an attempt to avoid allowing empty blocks in config files, `value` is now +required on the `google_sql_database_instance.settings.ip_configuration.authorized_networks` block. + +### `zone` is now required on block `google_sql_database_instance.settings.location_preference` + +In an attempt to avoid allowing empty blocks in config files, `zone` is now +required on the `google_sql_database_instance.settings.location_preference` block. + ## Resource: `google_storage_bucket` +### `enabled` is now required on block `google_storage_bucket.versioning` + +Previously the default value of `enabled` was `false`. In an attempt to avoid allowing empty blocks +in config files, `enabled` is now required on the `google_storage_bucket.versioning` block. + ### `is_live` is now removed Please use `with_state` instead, as `is_live` is now removed. + +## Resource: `google_storage_transfer_job` + +### `overwrite_objects_already_existing_in_sink` is now required on block `google_storage_transfer_job.transfer_options` + +In an attempt to avoid allowing empty blocks in config files, `overwrite_objects_already_existing_in_sink` is now +required on the `google_storage_transfer_job.transfer_options` block. \ No newline at end of file