diff --git a/third_party/terraform/resources/resource_composer_environment.go.erb b/third_party/terraform/resources/resource_composer_environment.go.erb index 53c572c94e96..7e9ba5967d29 100644 --- a/third_party/terraform/resources/resource_composer_environment.go.erb +++ b/third_party/terraform/resources/resource_composer_environment.go.erb @@ -50,8 +50,25 @@ var ( "config.0.node_config", "config.0.software_config", "config.0.private_environment_config", +<% unless version == "ga" -%> + "config.0.web_server_network_access_control", +<% end -%> } +<% unless version == "ga" -%> + allowedIpRangesConfig = &schema.Resource{ + Schema: map[string]*schema.Schema{ + "value": { + Type: schema.TypeString, + Required: true, + }, + "description": { + Type: schema.TypeString, + Optional: true, + }, + }, + } +<% end -%> ) func resourceComposerEnvironment() *schema.Resource { @@ -279,6 +296,8 @@ func resourceComposerEnvironment() *schema.Resource { AtLeastOneOf: []string{ "config.0.private_environment_config.0.enable_private_endpoint", "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", }, ForceNew: true, }, @@ -288,13 +307,57 @@ func resourceComposerEnvironment() *schema.Resource { AtLeastOneOf: []string{ "config.0.private_environment_config.0.enable_private_endpoint", "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", }, ForceNew: true, Default: "172.16.0.0/28", }, + "web_server_ipv4_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: []string{ + "config.0.private_environment_config.0.enable_private_endpoint", + "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", + }, + ForceNew: true, + }, + "cloud_sql_ipv4_cidr_block": { + Type: schema.TypeString, + Optional: true, + Computed: true, + AtLeastOneOf: []string{ + "config.0.private_environment_config.0.enable_private_endpoint", + "config.0.private_environment_config.0.master_ipv4_cidr_block", + "config.0.private_environment_config.0.cloud_sql_ipv4_cidr_block", + "config.0.private_environment_config.0.web_server_ipv4_cidr_block", + }, + ForceNew: true, + }, }, }, }, +<% unless version == "ga" -%> + "web_server_network_access_control": { + Type: schema.TypeList, + Optional: true, + Computed: true, + MaxItems: 1, + Elem: &schema.Resource{ + Schema: map[string]*schema.Schema{ + "allowed_ip_range": { + Type: schema.TypeSet, + Computed: true, + Optional: true, + Elem: allowedIpRangesConfig, + }, + }, + }, + }, +<% end -%> "airflow_uri": { Type: schema.TypeString, Computed: true, @@ -514,6 +577,18 @@ func resourceComposerEnvironmentUpdate(d *schema.ResourceData, meta interface{}) } d.SetPartial("config") } + + if d.HasChange("config.0.web_server_network_access_control") { + patchObj := &composer.Environment{Config: &composer.EnvironmentConfig{}} + if config != nil { + patchObj.Config.WebServerNetworkAccessControl = config.WebServerNetworkAccessControl + } + err = resourceComposerEnvironmentPatchField("config.webServerNetworkAccessControl", patchObj, d, tfConfig) + if err != nil { + return err + } + d.SetPartial("config") + } } if d.HasChange("labels") { @@ -630,10 +705,36 @@ func flattenComposerEnvironmentConfig(envCfg *composer.EnvironmentConfig) interf transformed["node_config"] = flattenComposerEnvironmentConfigNodeConfig(envCfg.NodeConfig) transformed["software_config"] = flattenComposerEnvironmentConfigSoftwareConfig(envCfg.SoftwareConfig) transformed["private_environment_config"] = flattenComposerEnvironmentConfigPrivateEnvironmentConfig(envCfg.PrivateEnvironmentConfig) +<% unless version == "ga" -%> + transformed["web_server_network_access_control"] = flattenComposerEnvironmentConfigWebServerNetworkAccessControl(envCfg.WebServerNetworkAccessControl) +<% end -%> return []interface{}{transformed} } +<% unless version == "ga" -%> +func flattenComposerEnvironmentConfigWebServerNetworkAccessControl(accessControl *composer.WebServerNetworkAccessControl) interface{} { + if accessControl == nil || accessControl.AllowedIpRanges == nil { + return nil + } + + transformed := make([]interface{}, 0, len(accessControl.AllowedIpRanges)) + for _, ipRange := range accessControl.AllowedIpRanges { + data := map[string]interface{}{ + "value": ipRange.Value, + "description": ipRange.Description, + } + transformed = append(transformed, data) + } + + webServerNetworkAccessControl := make(map[string]interface{}) + + webServerNetworkAccessControl["allowed_ip_range"] = schema.NewSet(schema.HashResource(allowedIpRangesConfig), transformed) + + return []interface{}{webServerNetworkAccessControl} +} + +<% end -%> func flattenComposerEnvironmentConfigPrivateEnvironmentConfig(envCfg *composer.PrivateEnvironmentConfig) interface{} { if envCfg == nil { return nil @@ -642,6 +743,8 @@ func flattenComposerEnvironmentConfigPrivateEnvironmentConfig(envCfg *composer.P transformed := make(map[string]interface{}) transformed["enable_private_endpoint"] = envCfg.PrivateClusterConfig.EnablePrivateEndpoint transformed["master_ipv4_cidr_block"] = envCfg.PrivateClusterConfig.MasterIpv4CidrBlock + transformed["cloud_sql_ipv4_cidr_block"] = envCfg.CloudSqlIpv4CidrBlock + transformed["web_server_ipv4_cidr_block"] = envCfg.WebServerIpv4CidrBlock return []interface{}{transformed} } @@ -738,6 +841,14 @@ func expandComposerEnvironmentConfig(v interface{}, d *schema.ResourceData, conf } transformed.PrivateEnvironmentConfig = transformedPrivateEnvironmentConfig +<% unless version == "ga" -%> + transformedWebServerNetworkAccessControl, err := expandComposerEnvironmentConfigWebServerNetworkAccessControl(original["web_server_network_access_control"], d, config) + if err != nil { + return nil, err + } + transformed.WebServerNetworkAccessControl = transformedWebServerNetworkAccessControl + +<% end -%> return transformed, nil } @@ -748,6 +859,37 @@ func expandComposerEnvironmentConfigNodeCount(v interface{}, d *schema.ResourceD return int64(v.(int)), nil } +<% unless version == "ga" -%> +func expandComposerEnvironmentConfigWebServerNetworkAccessControl(v interface{}, d *schema.ResourceData, config *Config) (*composer.WebServerNetworkAccessControl, error) { + l := v.([]interface{}) + if len(l) == 0 { + return nil, nil + } + raw := l[0] + original := raw.(map[string]interface{}) + + allowedIpRangesRaw := original["allowed_ip_range"].(*schema.Set).List() + if len(allowedIpRangesRaw) == 0 { + return nil, nil + } + + transformed := &composer.WebServerNetworkAccessControl{} + allowedIpRanges := make([]*composer.AllowedIpRange, 0, len(original)) + + for _, originalIpRange := range allowedIpRangesRaw { + originalRangeRaw := originalIpRange.(map[string]interface{}) + transformedRange := &composer.AllowedIpRange{Value: originalRangeRaw["value"].(string)} + if v, ok := originalRangeRaw["description"]; ok { + transformedRange.Description = v.(string) + } + allowedIpRanges = append(allowedIpRanges, transformedRange) + } + + transformed.AllowedIpRanges = allowedIpRanges + return transformed, nil +} + +<% end -%> func expandComposerEnvironmentConfigPrivateEnvironmentConfig(v interface{}, d *schema.ResourceData, config *Config) (*composer.PrivateEnvironmentConfig, error) { l := v.([]interface{}) if len(l) == 0 { @@ -769,6 +911,14 @@ func expandComposerEnvironmentConfigPrivateEnvironmentConfig(v interface{}, d *s subBlock.MasterIpv4CidrBlock = v.(string) } + if v, ok := original["cloud_sql_ipv4_cidr_block"]; ok { + transformed.CloudSqlIpv4CidrBlock = v.(string) + } + + if v, ok := original["web_server_ipv4_cidr_block"]; ok { + transformed.WebServerIpv4CidrBlock = v.(string) + } + transformed.PrivateClusterConfig = subBlock return transformed, nil diff --git a/third_party/terraform/tests/resource_composer_environment_test.go.erb b/third_party/terraform/tests/resource_composer_environment_test.go.erb index 908afc3eb139..f8b5a1e249b3 100644 --- a/third_party/terraform/tests/resource_composer_environment_test.go.erb +++ b/third_party/terraform/tests/resource_composer_environment_test.go.erb @@ -173,6 +173,56 @@ func TestAccComposerEnvironment_private(t *testing.T) { }) } +<% unless version == "ga" -%> +// Checks environment creation with minimum required information. +func TestAccComposerEnvironment_privateWithWebServerControl(t *testing.T) { + t.Parallel() + + envName := fmt.Sprintf("%s-%d", testComposerEnvironmentPrefix, randInt(t)) + network := fmt.Sprintf("%s-%d", testComposerNetworkPrefix, randInt(t)) + subnetwork := network + "-1" + + vcrTest(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testAccProviders, + CheckDestroy: testAccComposerEnvironmentDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComposerEnvironment_privateWithWebServerControl(envName, network, subnetwork), + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComposerEnvironment_privateWithWebServerControlUpdated(envName, network, subnetwork), + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateVerify: true, + }, + { + ResourceName: "google_composer_environment.test", + ImportState: true, + ImportStateId: fmt.Sprintf("projects/%s/locations/%s/environments/%s", getTestProjectFromEnv(), "us-central1", envName), + 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(emilyye): Remove this check if firewall rules bug gets fixed by Composer. + { + PlanOnly: true, + ExpectNonEmptyPlan: false, + Config: testAccComposerEnvironment_privateWithWebServerControlUpdated(envName, network, subnetwork), + Check: testAccCheckClearComposerEnvironmentFirewalls(t, network), + }, + }, + }) +} + +<% end -%> // Checks behavior of node config, including dependencies on Compute resources. func TestAccComposerEnvironment_withNodeConfig(t *testing.T) { t.Parallel() @@ -373,6 +423,112 @@ resource "google_compute_subnetwork" "test" { `, name, network, subnetwork) } +<% unless version == "ga" -%> +func testAccComposerEnvironment_privateWithWebServerControl(name, 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.self_link + subnetwork = google_compute_subnetwork.test.self_link + zone = "us-central1-a" + ip_allocation_policy { + use_ip_aliases = true + cluster_ipv4_cidr_block = "10.56.0.0/14" + services_ipv4_cidr_block = "10.122.0.0/20" + } + } + private_environment_config { + enable_private_endpoint = false + web_server_ipv4_cidr_block = "172.30.240.0/24" + cloud_sql_ipv4_cidr_block = "10.32.0.0/12" + master_ipv4_cidr_block = "172.17.50.0/28" + } + web_server_network_access_control { + allowed_ip_range { + value = "192.168.0.1" + description = "my range1" + } + allowed_ip_range { + value = "0.0.0.0/0" + } + } + } +} + +// 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 + private_ip_google_access = true +} +`, name, network, subnetwork) +} + +func testAccComposerEnvironment_privateWithWebServerControlUpdated(name, 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.self_link + subnetwork = google_compute_subnetwork.test.self_link + zone = "us-central1-a" + ip_allocation_policy { + use_ip_aliases = true + cluster_ipv4_cidr_block = "10.56.0.0/14" + services_ipv4_cidr_block = "10.122.0.0/20" + } + } + private_environment_config { + enable_private_endpoint = false + web_server_ipv4_cidr_block = "172.30.240.0/24" + cloud_sql_ipv4_cidr_block = "10.32.0.0/12" + master_ipv4_cidr_block = "172.17.50.0/28" + } + web_server_network_access_control { + allowed_ip_range { + value = "192.168.0.1" + description = "my range1" + } + allowed_ip_range { + value = "0.0.0.0/0" + } + } + } +} + +// 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 + private_ip_google_access = true +} +`, name, network, subnetwork) +} + +<% end -%> func testAccComposerEnvironment_update(name, network, subnetwork string) string { return fmt.Sprintf(` data "google_composer_image_versions" "all" { diff --git a/third_party/terraform/website/docs/r/composer_environment.html.markdown b/third_party/terraform/website/docs/r/composer_environment.html.markdown index 5f5534e37be3..5da2166539f3 100644 --- a/third_party/terraform/website/docs/r/composer_environment.html.markdown +++ b/third_party/terraform/website/docs/r/composer_environment.html.markdown @@ -169,6 +169,10 @@ The `config` block supports: (Optional) The configuration used for the Private IP Cloud Composer environment. Structure is documented below. +* `web_server_network_access_control` - + (Optional, [Beta](https://terraform.io/docs/providers/google/guides/provider_versions.html)) + The network-level access control policy for the Airflow web server. If unspecified, no network-level access restrictions will be applied. + The `node_config` block supports: @@ -289,7 +293,7 @@ The `software_config` block supports: The major version of Python used to run the Apache Airflow scheduler, worker, and webserver processes. Can be set to '2' or '3'. If not specified, the default is '2'. Cannot be updated. -The `private_environment_config` block supports: +See [documentation](https://cloud.google.com/composer/docs/how-to/managing/configuring-private-ip) for seting up private environments. The `private_environment_config` block supports: * `enable_private_endpoint` - If true, access to the public endpoint of the GKE cluster is denied. @@ -302,6 +306,32 @@ The `private_environment_config` block supports: in use within the cluster's network. If left blank, the default value of '172.16.0.0/28' is used. +* `cloud_sql_ipv4_cidr_block` - + (Optional) + The CIDR block from which IP range in tenant project will be reserved for Cloud SQL. Needs to be disjoint from `web_server_ipv4_cidr_block` + +* `web_server_ipv4_cidr_block` - + (Optional) + The CIDR block from which IP range for web server will be reserved. Needs to be disjoint from `master_ipv4_cidr_block` and `cloud_sql_ipv4_cidr_block`. + +The `web_server_network_access_control` supports: + +* `allowed_ip_range` - + A collection of allowed IP ranges with descriptions. Structure is documented below. + +The `allowed_ip_range` supports: + +* `value` - + (Required) + IP address or range, defined using CIDR notation, of requests that this rule applies to. + Examples: `192.168.1.1` or `192.168.0.0/16` or `2001:db8::/32` or `2001:0db8:0000:0042:0000:8a2e:0370:7334`. + IP range prefixes should be properly truncated. For example, + `1.2.3.4/24` should be truncated to `1.2.3.0/24`. Similarly, for IPv6, `2001:db8::1/32` should be truncated to `2001:db8::/32`. + +* `description` - + (Optional) + A description of this ip range. + The `ip_allocation_policy` block supports: * `use_ip_aliases` -