diff --git a/mmv1/third_party/terraform/services/composer/resource_composer_environment.go.erb b/mmv1/third_party/terraform/services/composer/resource_composer_environment.go.erb index 0369ce49f4d0..e192b510fa65 100644 --- a/mmv1/third_party/terraform/services/composer/resource_composer_environment.go.erb +++ b/mmv1/third_party/terraform/services/composer/resource_composer_environment.go.erb @@ -170,7 +170,9 @@ func ResourceComposerEnvironment() *schema.Resource { tpgresource.DefaultProviderRegion, tpgresource.SetLabelsDiff, <% unless version == "ga" -%> - customdiff.ValidateChange("config.0.software_config.0.image_version", imageVersionChangeValidationFunc), + customdiff.ForceNewIf("config.0.node_config.0.network", forceNewCustomDiff("config.0.node_config.0.network")), + customdiff.ForceNewIf("config.0.node_config.0.subnetwork", forceNewCustomDiff("config.0.node_config.0.subnetwork")), + customdiff.ValidateChange("config.0.software_config.0.image_version", imageVersionChangeValidationFunc), versionValidationCustomizeDiffFunc, <% end -%> ), @@ -242,17 +244,37 @@ func ResourceComposerEnvironment() *schema.Resource { Type: schema.TypeString, Computed: true, Optional: true, +<% if version == "ga" -%> ForceNew: true, +<% else -%> + ForceNew: false, + ConflictsWith: []string{"config.0.node_config.0.composer_network_attachment"}, +<% end -%> DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, Description: `The Compute Engine machine type used for cluster instances, specified as a name or relative resource name. For example: "projects/{project}/zones/{zone}/machineTypes/{machineType}". Must belong to the enclosing environment's project and region/zone. The network must belong to the environment's project. If unspecified, the "default" network ID in the environment's project is used. If a Custom Subnet Network is provided, subnetwork must also be provided.`, }, "subnetwork": { Type: schema.TypeString, Optional: true, +<% if version == "ga" -%> ForceNew: true, +<% else -%> + ForceNew: false, + Computed: true, + ConflictsWith: []string{"config.0.node_config.0.composer_network_attachment"}, +<% end -%> DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, - Description: `The Compute Engine subnetwork to be used for machine communications, , specified as a self-link, relative resource name (e.g. "projects/{project}/regions/{region}/subnetworks/{subnetwork}"), or by name. If subnetwork is provided, network must also be provided and the subnetwork must belong to the enclosing environment's project and region.`, + Description: `The Compute Engine subnetwork to be used for machine communications, specified as a self-link, relative resource name (e.g. "projects/{project}/regions/{region}/subnetworks/{subnetwork}"), or by name. If subnetwork is provided, network must also be provided and the subnetwork must belong to the enclosing environment's project and region.`, + }, +<% unless version == "ga" -%> + "composer_network_attachment": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: false, + Description: `PSC (Private Service Connect) Network entry point. Customers can pre-create the Network Attachment and point Cloud Composer environment to use. It is possible to share network attachment among many environments, provided enough IP addresses are available.`, }, +<% end -%> "disk_size_gb": { Type: schema.TypeInt, Computed: true, @@ -1195,6 +1217,68 @@ func resourceComposerEnvironmentUpdate(d *schema.ResourceData, meta interface{}) return err } +<% unless version == "ga" -%> + noChangeErrorMessage := "Update request does not result in any change to the environment's configuration" + if d.HasChange("config.0.node_config.0.network") || d.HasChange("config.0.node_config.0.subnetwork"){ + // step 1: update with empty network and subnetwork + patchObjEmpty := &composer.Environment{ + Config: &composer.EnvironmentConfig{ + NodeConfig: &composer.NodeConfig{}, + }, + } + err = resourceComposerEnvironmentPatchField("config.nodeConfig.network,config.nodeConfig.subnetwork", userAgent, patchObjEmpty, d, tfConfig) + if err != nil && !strings.Contains(err.Error(), noChangeErrorMessage){ + return err + } + + // step 2: update with new network and subnetwork, if new values are not empty + if (config.NodeConfig.Network != "" && config.NodeConfig.Subnetwork != ""){ + patchObj := &composer.Environment{ + Config: &composer.EnvironmentConfig{ + NodeConfig: &composer.NodeConfig{}, + }, + } + if config != nil && config.NodeConfig != nil { + patchObj.Config.NodeConfig.Network = config.NodeConfig.Network + patchObj.Config.NodeConfig.Subnetwork = config.NodeConfig.Subnetwork + } + err = resourceComposerEnvironmentPatchField("config.nodeConfig.network,config.nodeConfig.subnetwork", userAgent, patchObj, d, tfConfig) + if err != nil { + return err + } + } + } + + if d.HasChange("config.0.node_config.0.composer_network_attachment") { + // step 1: update with empty composer_network_attachment + patchObjEmpty := &composer.Environment{ + Config: &composer.EnvironmentConfig{ + NodeConfig: &composer.NodeConfig{}, + }, + } + err = resourceComposerEnvironmentPatchField("config.nodeConfig.composerNetworkAttachment", userAgent, patchObjEmpty, d, tfConfig) + if err != nil && !strings.Contains(err.Error(), noChangeErrorMessage){ + return err + } + + // step 2: update with new composer_network_attachment + if (config.NodeConfig.ComposerNetworkAttachment != ""){ + patchObj := &composer.Environment{ + Config: &composer.EnvironmentConfig{ + NodeConfig: &composer.NodeConfig{}, + }, + } + if config != nil && config.NodeConfig != nil { + patchObj.Config.NodeConfig.ComposerNetworkAttachment = config.NodeConfig.ComposerNetworkAttachment + } + err = resourceComposerEnvironmentPatchField("config.nodeConfig.composerNetworkAttachment", userAgent, patchObj, d, tfConfig) + if err != nil { + return err + } + } + } +<% end -%> + <% unless version == "ga" -%> if d.HasChange("config.0.software_config.0.image_version") { patchObj := &composer.Environment{ @@ -1853,6 +1937,9 @@ func flattenComposerEnvironmentConfigNodeConfig(nodeCfg *composer.NodeConfig) in transformed["machine_type"] = nodeCfg.MachineType transformed["network"] = nodeCfg.Network transformed["subnetwork"] = nodeCfg.Subnetwork +<% unless version == "ga" -%> + transformed["composer_network_attachment"] = nodeCfg.ComposerNetworkAttachment +<% end -%> transformed["disk_size_gb"] = nodeCfg.DiskSizeGb transformed["service_account"] = nodeCfg.ServiceAccount transformed["oauth_scopes"] = flattenComposerEnvironmentConfigNodeConfigOauthScopes(nodeCfg.OauthScopes) @@ -2470,6 +2557,13 @@ func expandComposerEnvironmentConfigNodeConfig(v interface{}, d *schema.Resource } transformed.Subnetwork = transformedSubnetwork } + +<% unless version == "ga" -%> + if v, ok := original["composer_network_attachment"]; ok { + transformed.ComposerNetworkAttachment = v.(string) + } +<% end -%> + transformedIPAllocationPolicy, err := expandComposerEnvironmentIPAllocationPolicy(original["ip_allocation_policy"], d, config) if err != nil { return nil, err @@ -2951,6 +3045,17 @@ func isComposer3(imageVersion string) bool { return strings.Contains(imageVersion, "composer-3") } +func forceNewCustomDiff(key string) customdiff.ResourceConditionFunc { + return func(ctx context.Context, d *schema.ResourceDiff, meta interface{}) bool { + old, new := d.GetChange(key) + imageVersion := d.Get("config.0.software_config.0.image_version").(string) + if isComposer3(imageVersion) || tpgresource.CompareSelfLinkRelativePaths("", old.(string), new.(string), nil) { + return false + } + return true + } +} + func imageVersionChangeValidationFunc(ctx context.Context, old, new, meta any) error { if old.(string) != "" && !isComposer3(old.(string)) && isComposer3(new.(string)) { return fmt.Errorf("upgrade to composer 3 is not yet supported") diff --git a/mmv1/third_party/terraform/services/composer/resource_composer_environment_test.go.erb b/mmv1/third_party/terraform/services/composer/resource_composer_environment_test.go.erb index 1ddb9257f35e..bde25f487b1f 100644 --- a/mmv1/third_party/terraform/services/composer/resource_composer_environment_test.go.erb +++ b/mmv1/third_party/terraform/services/composer/resource_composer_environment_test.go.erb @@ -21,6 +21,7 @@ import ( const testComposerEnvironmentPrefix = "tf-test-composer-env" const testComposerNetworkPrefix = "tf-test-composer-net" const testComposerBucketPrefix = "tf-test-composer-bucket" +const testComposerNetworkAttachmentPrefix = "tf-test-composer-nta" func allComposerServiceAgents() []string { return []string{ @@ -1186,13 +1187,13 @@ func TestAccComposerEnvironmentComposer3_update(t *testing.T) { }) } -func TestAccComposerEnvironmentComposer3_upgrade_expectError(t *testing.T) { +func TestAccComposerEnvironmentComposer3_withNetworkSubnetworkAndAttachment_expectError(t *testing.T) { t.Parallel() envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t)) network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, acctest.RandInt(t)) subnetwork := network + "-1" - errorRegExp, _ := regexp.Compile(".*upgrade to composer 3 is not yet supported.*") + networkAttachment := fmt.Sprintf("%s-%d", testComposerNetworkAttachmentPrefix, acctest.RandInt(t)) acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -1200,58 +1201,128 @@ func TestAccComposerEnvironmentComposer3_upgrade_expectError(t *testing.T) { CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccComposerEnvironmentComposer2_empty(envName, network, subnetwork), + Config: testAccComposerEnvironmentComposer3_withNetworkSubnetworkAndAttachment_expectError(envName, networkAttachment, network, subnetwork), + ExpectError: regexp.MustCompile("Conflicting configuration arguments"), }, + // This is a terrible clean-up step in order to get destroy to succeed, + // due to dangling firewall rules left by the Composer Environment blocking network deletion. + // TODO: Remove this check if firewall rules bug gets fixed by Composer. { - Config: testAccComposerEnvironmentComposer3_empty(envName, network, subnetwork), - ExpectError: errorRegExp, + PlanOnly: true, + ExpectNonEmptyPlan: true, + Config: testAccComposerEnvironmentComposer3_basic(envName, network, subnetwork), + Check: testAccCheckClearComposerEnvironmentFirewalls(t, network), + }, + }, + }) +} + +func TestAccComposerEnvironmentComposer3_withNetworkAttachment(t *testing.T) { + t.Parallel() + + envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t)) + network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, acctest.RandInt(t)) + subnetwork := network + "-1" + networkAttachment := fmt.Sprintf("%s-%d", testComposerNetworkAttachmentPrefix, acctest.RandInt(t)) + fullFormNetworkAttachmentName := fmt.Sprintf("projects/%s/regions/%s/networkAttachments/%s", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), networkAttachment) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComposerEnvironmentComposer3_withNetworkAttachment(envName, networkAttachment, network, subnetwork), + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateVerify: true, }, // This is a terrible clean-up step in order to get destroy to succeed, // due to dangling firewall rules left by the Composer Environment blocking network deletion. // TODO: Remove this check if firewall rules bug gets fixed by Composer. { PlanOnly: true, - ExpectNonEmptyPlan: false, - Config: testAccComposerEnvironmentComposer2_empty(envName, network, subnetwork), + Config: testAccComposerEnvironmentComposer3_withNetworkAttachment(envName, fullFormNetworkAttachmentName, network, subnetwork), Check: testAccCheckClearComposerEnvironmentFirewalls(t, network), + ExpectNonEmptyPlan: true, }, }, }) } -func TestAccComposerEnvironmentComposer2_usesUnsupportedField_expectError(t *testing.T) { +func TestAccComposerEnvironmentComposer3_updateWithNetworkAttachment(t *testing.T) { t.Parallel() envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t)) - errorRegExp, _ := regexp.Compile(".*error in configuration, .* should only be used in Composer 3.*") + network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, acctest.RandInt(t)) + subnetwork := network + "-1" + networkAttachment := fmt.Sprintf("%s-%d", testComposerNetworkAttachmentPrefix, acctest.RandInt(t)) + fullFormNetworkAttachmentName := fmt.Sprintf("projects/%s/regions/%s/networkAttachments/%s", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), networkAttachment) acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, + PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccComposerEnvironmentComposer2_usesUnsupportedField(envName), - ExpectError: errorRegExp, + Config: testAccComposerEnvironmentComposer3_withNetworkAndSubnetwork(envName, networkAttachment, network, subnetwork), + }, + { + Config: testAccComposerEnvironmentComposer3_withNetworkAttachment(envName, networkAttachment, network, subnetwork), + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateVerify: true, + }, + // This is a terrible clean-up step in order to get destroy to succeed, + // due to dangling firewall rules left by the Composer Environment blocking network deletion. + // TODO: Remove this check if firewall rules bug gets fixed by Composer. + { + PlanOnly: true, + Config: testAccComposerEnvironmentComposer3_withNetworkAttachment(envName, fullFormNetworkAttachmentName, network, subnetwork), + Check: testAccCheckClearComposerEnvironmentFirewalls(t, network), + ExpectNonEmptyPlan: true, }, }, }) } -func TestAccComposerEnvironmentComposer3_usesUnsupportedField_expectError(t *testing.T) { +func TestAccComposerEnvironmentComposer3_updateWithNetworkAndSubnetwork(t *testing.T) { t.Parallel() envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t)) - errorRegExp, _ := regexp.Compile(".*error in configuration, .* should not be used in Composer 3.*") + network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, acctest.RandInt(t)) + subnetwork := network + "-1" + networkAttachment := fmt.Sprintf("%s-%d", testComposerNetworkAttachmentPrefix, acctest.RandInt(t)) + fullFormNetworkAttachmentName := fmt.Sprintf("projects/%s/regions/%s/networkAttachments/%s", envvar.GetTestProjectFromEnv(), envvar.GetTestRegionFromEnv(), networkAttachment) acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, + PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), Steps: []resource.TestStep{ { - Config: testAccComposerEnvironmentComposer3_usesUnsupportedField(envName), - ExpectError: errorRegExp, + Config: testAccComposerEnvironmentComposer3_withNetworkAttachment(envName, networkAttachment, network, subnetwork), + }, + { + Config: testAccComposerEnvironmentComposer3_withNetworkAndSubnetwork(envName, networkAttachment, network, subnetwork), + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateVerify: true, + }, + // This is a terrible clean-up step in order to get destroy to succeed, + // due to dangling firewall rules left by the Composer Environment blocking network deletion. + // TODO: Remove this check if firewall rules bug gets fixed by Composer. + { + PlanOnly: true, + Config: testAccComposerEnvironmentComposer3_withNetworkAttachment(envName, fullFormNetworkAttachmentName, network, subnetwork), + Check: testAccCheckClearComposerEnvironmentFirewalls(t, network), + ExpectNonEmptyPlan: true, }, }, }) @@ -1266,7 +1337,7 @@ func TestAccComposerEnvironmentComposer3_updateToEmpty(t *testing.T) { subnetwork := network + "-1" acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, + PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), Steps: []resource.TestStep{ @@ -1303,7 +1374,7 @@ func TestAccComposerEnvironmentComposer3_updateFromEmpty(t *testing.T) { subnetwork := network + "-1" acctest.VcrTest(t, resource.TestCase{ - PreCheck: func() { acctest.AccTestPreCheck(t) }, + PreCheck: func() { acctest.AccTestPreCheck(t) }, ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), Steps: []resource.TestStep{ @@ -1330,6 +1401,77 @@ func TestAccComposerEnvironmentComposer3_updateFromEmpty(t *testing.T) { }, }) } + +func TestAccComposerEnvironmentComposer3_upgrade_expectError(t *testing.T) { + t.Parallel() + + envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t)) + network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, acctest.RandInt(t)) + subnetwork := network + "-1" + errorRegExp, _ := regexp.Compile(".*upgrade to composer 3 is not yet supported.*") + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComposerEnvironmentComposer2_empty(envName, network, subnetwork), + }, + { + Config: testAccComposerEnvironmentComposer3_empty(envName, network, subnetwork), + ExpectError: errorRegExp, + }, + // This is a terrible clean-up step in order to get destroy to succeed, + // due to dangling firewall rules left by the Composer Environment blocking network deletion. + // TODO: Remove this check if firewall rules bug gets fixed by Composer. + { + PlanOnly: true, + ExpectNonEmptyPlan: false, + Config: testAccComposerEnvironmentComposer2_empty(envName, network, subnetwork), + Check: testAccCheckClearComposerEnvironmentFirewalls(t, network), + }, + }, + }) +} + +func TestAccComposerEnvironmentComposer2_usesUnsupportedField_expectError(t *testing.T) { + t.Parallel() + + envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t)) + errorRegExp, _ := regexp.Compile(".*error in configuration, .* should only be used in Composer 3.*") + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComposerEnvironmentComposer2_usesUnsupportedField(envName), + ExpectError: errorRegExp, + }, + }, + }) +} + +func TestAccComposerEnvironmentComposer3_usesUnsupportedField_expectError(t *testing.T) { + t.Parallel() + + envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, acctest.RandInt(t)) + errorRegExp, _ := regexp.Compile(".*error in configuration, .* should not be used in Composer 3.*") + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComposerEnvironmentComposer3_usesUnsupportedField(envName), + ExpectError: errorRegExp, + }, + }, + }) +} <% end -%> func testAccComposerEnvironment_customBucket(bucketName, envName, network, subnetwork string) string { @@ -2943,6 +3085,10 @@ resource "google_composer_environment" "test" { software_config { image_version = "composer-3-airflow-2" } + node_config { + network = google_compute_network.test.id + subnetwork = google_compute_subnetwork.test.id + } } } @@ -3000,11 +3146,13 @@ resource "google_composer_environment" "test" { name = "%s" region = "us-central1" config { - software_config { - image_version = "composer-3-airflow-2" - } node_config { composer_internal_ipv4_cidr_block = "100.64.128.0/20" + network = google_compute_network.test.id + subnetwork = google_compute_subnetwork.test.id + } + software_config { + image_version = "composer-3-airflow-2" } workloads_config { dag_processor { @@ -3041,6 +3189,8 @@ resource "google_composer_environment" "test" { region = "us-central1" config { node_config { + network = google_compute_network.test_1.id + subnetwork = google_compute_subnetwork.test_1.id composer_internal_ipv4_cidr_block = "100.64.128.0/20" } software_config { @@ -3072,7 +3222,168 @@ resource "google_compute_subnetwork" "test" { region = "us-central1" network = google_compute_network.test.self_link } -`, name, network, subnetwork) + +resource "google_compute_network" "test_1" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test_1" { + name = "%s" + ip_cidr_range = "10.3.0.0/16" + region = "us-central1" + network = google_compute_network.test_1.self_link +} +`, name, network, subnetwork, network + "-update", subnetwork + "update") +} + +func testAccComposerEnvironmentComposer3_withNetworkAttachment(name, networkAttachment, network, subnetwork string) string { + return fmt.Sprintf(` +resource "google_composer_environment" "test" { + name = "%s" + region = "us-central1" + config { + node_config { + composer_network_attachment = google_compute_network_attachment.test.id + } + software_config { + image_version = "composer-3-airflow-2" + } + } +} + +resource "google_compute_network_attachment" "test" { + name = "%s" + region = "us-central1" + subnetworks = [ google_compute_subnetwork.test-att.id ] + connection_preference = "ACCEPT_MANUAL" + // Composer 3 is modifying producer_accept_lists outside terraform, ignoring this change for now + lifecycle { + ignore_changes = [producer_accept_lists] + } +} + +resource "google_compute_network" "test-att" { + name = "%s-att" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test-att" { + name = "%s-att" + ip_cidr_range = "10.3.0.0/16" + region = "us-central1" + network = google_compute_network.test-att.self_link +} + +// use a separate network to avoid conflicts with other tests running in parallel +// that use the default network/subnet +resource "google_compute_network" "test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = google_compute_network.test.self_link +} +`, name, networkAttachment, network, subnetwork, network, subnetwork) +} + +func testAccComposerEnvironmentComposer3_withNetworkAndSubnetwork(name, networkAttachment, network, subnetwork string) string { + return fmt.Sprintf(` +resource "google_composer_environment" "test" { + name = "%s" + region = "us-central1" + config { + node_config { + network = google_compute_network.test.id + subnetwork = google_compute_subnetwork.test.id + } + software_config { + image_version = "composer-3-airflow-2" + } + } +} + +resource "google_compute_network_attachment" "test" { + name = "%s" + region = "us-central1" + subnetworks = [ google_compute_subnetwork.test-att.id ] + connection_preference = "ACCEPT_MANUAL" + // Composer 3 is modifying producer_accept_lists outside terraform, ignoring this change for now + lifecycle { + ignore_changes = [producer_accept_lists] + } +} + +resource "google_compute_network" "test-att" { + name = "%s-att" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test-att" { + name = "%s-att" + ip_cidr_range = "10.3.0.0/16" + region = "us-central1" + network = google_compute_network.test-att.self_link +} + +// use a separate network to avoid conflicts with other tests running in parallel +// that use the default network/subnet +resource "google_compute_network" "test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = google_compute_network.test.self_link +} +`, name, networkAttachment, network, subnetwork, network, subnetwork) +} + +func testAccComposerEnvironmentComposer3_withNetworkSubnetworkAndAttachment_expectError(name, networkAttachment, network, subnetwork string) string { + return fmt.Sprintf(` +resource "google_composer_environment" "test" { + name = "%s" + region = "us-central1" + config { + node_config { + network = google_compute_network.test.id + subnetwork = google_compute_subnetwork.test.id + composer_network_attachment = google_compute_network_attachment.test.id + } + software_config { + image_version = "composer-3-airflow-2" + } + } +} + +resource "google_compute_network_attachment" "test" { + name = "%s" + region = "us-central1" + subnetworks = [ google_compute_subnetwork.test.id ] + connection_preference = "ACCEPT_MANUAL" +} + +// use a separate network to avoid conflicts with other tests running in parallel +// that use the default network/subnet +resource "google_compute_network" "test" { + name = "%s" + auto_create_subnetworks = false +} + +resource "google_compute_subnetwork" "test" { + name = "%s" + ip_cidr_range = "10.2.0.0/16" + region = "us-central1" + network = google_compute_network.test.self_link +} +`, name, networkAttachment, network, subnetwork) } <% end -%>