diff --git a/.changelog/11390.txt b/.changelog/11390.txt new file mode 100644 index 0000000000..c99eacce9c --- /dev/null +++ b/.changelog/11390.txt @@ -0,0 +1,6 @@ +```release-note:new-resource +`google_compute_router_nat_address` +``` +```release-note:enhancement +compute: added 'initial_nat_ip' field to 'google_compute_router_nat' resource, to enable use of the fine-grained resource `google_compute_router_nat_address` +``` \ No newline at end of file diff --git a/google-beta/provider/provider_mmv1_resources.go b/google-beta/provider/provider_mmv1_resources.go index a141c64ad1..9e9cf75a32 100644 --- a/google-beta/provider/provider_mmv1_resources.go +++ b/google-beta/provider/provider_mmv1_resources.go @@ -485,9 +485,9 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{ } // Resources -// Generated resources: 528 +// Generated resources: 529 // Generated IAM resources: 291 -// Total generated resources: 819 +// Total generated resources: 820 var generatedResources = map[string]*schema.Resource{ "google_folder_access_approval_settings": accessapproval.ResourceAccessApprovalFolderSettings(), "google_organization_access_approval_settings": accessapproval.ResourceAccessApprovalOrganizationSettings(), @@ -763,6 +763,7 @@ var generatedResources = map[string]*schema.Resource{ "google_compute_route": compute.ResourceComputeRoute(), "google_compute_router": compute.ResourceComputeRouter(), "google_compute_router_nat": compute.ResourceComputeRouterNat(), + "google_compute_router_nat_address": compute.ResourceComputeRouterNatAddress(), "google_compute_router_route_policy": compute.ResourceComputeRouterRoutePolicy(), "google_compute_security_policy_rule": compute.ResourceComputeSecurityPolicyRule(), "google_compute_service_attachment": compute.ResourceComputeServiceAttachment(), diff --git a/google-beta/services/compute/resource_compute_router_nat.go b/google-beta/services/compute/resource_compute_router_nat.go index d01fa5917b..e7bc07ce88 100644 --- a/google-beta/services/compute/resource_compute_router_nat.go +++ b/google-beta/services/compute/resource_compute_router_nat.go @@ -255,6 +255,7 @@ project-level default tier is used. Possible values: ["PREMIUM", "STANDARD"]`, }, "drain_nat_ips": { Type: schema.TypeSet, + Computed: true, Optional: true, Description: `A list of URLs of the IP resources to be drained. These IPs must be valid static external IPs that have been assigned to the NAT.`, @@ -303,6 +304,19 @@ Supported values include: Description: `Timeout (in seconds) for ICMP connections. Defaults to 30s if not set.`, Default: 30, }, + "initial_nat_ips": { + Type: schema.TypeSet, + Optional: true, + ForceNew: true, + Description: `Self-links of NAT IPs to be used as initial value for creation alongside a RouterNatAddress resource. +Conflicts with natIps and drainNatIps. Only valid if natIpAllocateOption is set to MANUAL_ONLY.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + }, + Set: computeRouterNatIPsHash, + ConflictsWith: []string{"nat_ips", "drain_nat_ips"}, + }, "log_config": { Type: schema.TypeList, Optional: true, @@ -346,6 +360,7 @@ Platform, or 'MANUAL_ONLY' for only user-allocated NAT IP addresses. Possible va }, "nat_ips": { Type: schema.TypeSet, + Computed: true, Optional: true, Description: `Self-links of NAT IPs. Only valid if natIpAllocateOption is set to MANUAL_ONLY. @@ -583,6 +598,12 @@ func resourceComputeRouterNatCreate(d *schema.ResourceData, meta interface{}) er } else if v, ok := d.GetOkExists("nat_ip_allocate_option"); !tpgresource.IsEmptyValue(reflect.ValueOf(natIpAllocateOptionProp)) && (ok || !reflect.DeepEqual(v, natIpAllocateOptionProp)) { obj["natIpAllocateOption"] = natIpAllocateOptionProp } + initialNatIpsProp, err := expandNestedComputeRouterNatInitialNatIps(d.Get("initial_nat_ips"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("initial_nat_ips"); ok || !reflect.DeepEqual(v, initialNatIpsProp) { + obj["initialNatIps"] = initialNatIpsProp + } natIpsProp, err := expandNestedComputeRouterNatNatIps(d.Get("nat_ips"), d, config) if err != nil { return err @@ -692,6 +713,11 @@ func resourceComputeRouterNatCreate(d *schema.ResourceData, meta interface{}) er obj["autoNetworkTier"] = autoNetworkTierProp } + obj, err = resourceComputeRouterNatEncoder(d, meta, obj) + if err != nil { + return err + } + lockName, err := tpgresource.ReplaceVars(d, config, "router/{{region}}/{{router}}") if err != nil { return err @@ -1023,6 +1049,11 @@ func resourceComputeRouterNatUpdate(d *schema.ResourceData, meta interface{}) er obj["autoNetworkTier"] = autoNetworkTierProp } + obj, err = resourceComputeRouterNatEncoder(d, meta, obj) + if err != nil { + return err + } + lockName, err := tpgresource.ReplaceVars(d, config, "router/{{region}}/{{router}}") if err != nil { return err @@ -1506,6 +1537,23 @@ func expandNestedComputeRouterNatNatIpAllocateOption(v interface{}, d tpgresourc return v, nil } +func expandNestedComputeRouterNatInitialNatIps(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + return nil, fmt.Errorf("Invalid value for initial_nat_ips: nil") + } + f, err := tpgresource.ParseRegionalFieldValue("addresses", raw.(string), "project", "region", "zone", d, config, true) + if err != nil { + return nil, fmt.Errorf("Invalid value for initial_nat_ips: %s", err) + } + req = append(req, f.RelativeLink()) + } + return req, nil +} + func expandNestedComputeRouterNatNatIps(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { v = v.(*schema.Set).List() l := v.([]interface{}) @@ -1845,6 +1893,24 @@ func expandNestedComputeRouterNatAutoNetworkTier(v interface{}, d tpgresource.Te return v, nil } +func resourceComputeRouterNatEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + // initial_nat_ips uses the same api_name as nat_ips + if tpgresource.IsEmptyValue(reflect.ValueOf(obj["initialNatIps"])) { + return obj, nil + } + + newObj := make(map[string]interface{}) + for key, value := range obj { + newObj[key] = value + } + + newObj["natIps"] = obj["initialNatIps"] + delete(newObj, "initialNatIps") + + log.Printf("[DEBUG] Replacing initialNatIps value \n oldObj: %+v \n newObj: %+v", obj, newObj) + return newObj, nil +} + func flattenNestedComputeRouterNat(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { var v interface{} var ok bool diff --git a/google-beta/services/compute/resource_compute_router_nat_address.go b/google-beta/services/compute/resource_compute_router_nat_address.go new file mode 100644 index 0000000000..f0765e98f1 --- /dev/null +++ b/google-beta/services/compute/resource_compute_router_nat_address.go @@ -0,0 +1,814 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 + +// ---------------------------------------------------------------------------- +// +// *** AUTO GENERATED CODE *** Type: MMv1 *** +// +// ---------------------------------------------------------------------------- +// +// This file is automatically generated by Magic Modules and manual +// changes will be clobbered when the file is regenerated. +// +// Please read more about how to change this file in +// .github/CONTRIBUTING.md. +// +// ---------------------------------------------------------------------------- + +package compute + +import ( + "context" + "fmt" + "log" + "net/http" + "reflect" + "time" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/customdiff" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" + + "github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource" + transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport" +) + +func addressResourceNameSetFromSelfLinkSet(v interface{}) *schema.Set { + if v == nil { + return schema.NewSet(schema.HashString, nil) + } + vSet := v.(*schema.Set) + ls := make([]interface{}, 0, vSet.Len()) + for _, v := range vSet.List() { + if v == nil { + continue + } + ls = append(ls, tpgresource.GetResourceNameFromSelfLink(v.(string))) + } + return schema.NewSet(schema.HashString, ls) +} + +// drain_nat_ips MUST be set from (just set) previous values of nat_ips +// so this customizeDiff func makes sure drainNatIps values: +// - aren't set at creation time +// - are in old value of nat_ips but not in new values +func resourceComputeRouterNatAddressDrainNatIpsCustomDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error { + o, n := diff.GetChange("drain_nat_ips") + oSet := addressResourceNameSetFromSelfLinkSet(o) + nSet := addressResourceNameSetFromSelfLinkSet(n) + addDrainIps := nSet.Difference(oSet) + + // We don't care if there are no new drainNatIps + if addDrainIps.Len() == 0 { + return nil + } + + // Resource hasn't been created yet - return error + if diff.Id() == "" { + return fmt.Errorf("New RouterNat cannot have drain_nat_ips, got values %+v", addDrainIps.List()) + } + // + o, n = diff.GetChange("nat_ips") + oNatSet := addressResourceNameSetFromSelfLinkSet(o) + nNatSet := addressResourceNameSetFromSelfLinkSet(n) + + // Resource is being updated - make sure new drainNatIps were in natIps prior d and no longer are in natIps. + for _, v := range addDrainIps.List() { + if !oNatSet.Contains(v) { + return fmt.Errorf("drain_nat_ip %q was not previously set in nat_ips %+v", v.(string), oNatSet.List()) + } + if nNatSet.Contains(v) { + return fmt.Errorf("drain_nat_ip %q cannot be drained if still set in nat_ips %+v", v.(string), nNatSet.List()) + } + } + return nil +} + +func resourceComputeRouterNatAddressDeleteOnlyNatIps(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + items, err := resourceComputeRouterNatAddressListForPatch(d, meta) + if err != nil { + return nil, err + } + + idx, item, err := resourceComputeRouterNatAddressFindNestedObjectInList(d, meta, items) + if err != nil { + return nil, err + } + + // Return error if item to update does not exist. + if item == nil { + return nil, fmt.Errorf("Unable to update RouterNatAddress %q - not found in list", d.Id()) + } + + if item["natIps"] != nil { + croppedNatIps := item["natIps"].([]interface{})[:1] + item["natIps"] = croppedNatIps + } + + items[idx] = item + // Return list with new item added + res := map[string]interface{}{ + "nats": items, + } + return res, nil +} + +func ResourceComputeRouterNatAddress() *schema.Resource { + return &schema.Resource{ + Create: resourceComputeRouterNatAddressCreate, + Read: resourceComputeRouterNatAddressRead, + Update: resourceComputeRouterNatAddressUpdate, + Delete: resourceComputeRouterNatAddressDelete, + + Importer: &schema.ResourceImporter{ + State: resourceComputeRouterNatAddressImport, + }, + + Timeouts: &schema.ResourceTimeout{ + Create: schema.DefaultTimeout(20 * time.Minute), + Update: schema.DefaultTimeout(20 * time.Minute), + Delete: schema.DefaultTimeout(20 * time.Minute), + }, + + CustomizeDiff: customdiff.All( + resourceComputeRouterNatAddressDrainNatIpsCustomDiff, + tpgresource.DefaultProviderProject, + ), + + Schema: map[string]*schema.Schema{ + "nat_ips": { + Type: schema.TypeSet, + Required: true, + Description: `Self-links of NAT IPs to be used in a Nat service. Only valid if the referenced RouterNat +natIpAllocateOption is set to MANUAL_ONLY.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + }, + Set: computeRouterNatIPsHash, + }, + "router": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + Description: `The name of the Cloud Router in which the referenced NAT service is configured.`, + }, + "router_nat": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + Description: `The name of the Nat service in which this address will be configured.`, + }, + "drain_nat_ips": { + Type: schema.TypeSet, + Optional: true, + Description: `A list of URLs of the IP resources to be drained. These IPs must be +valid static external IPs that have been assigned to the NAT.`, + Elem: &schema.Schema{ + Type: schema.TypeString, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + }, + // Default schema.HashSchema is used. + }, + "region": { + Type: schema.TypeString, + Computed: true, + Optional: true, + ForceNew: true, + DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName, + Description: `Region where the NAT service reside.`, + }, + "project": { + Type: schema.TypeString, + Optional: true, + Computed: true, + ForceNew: true, + }, + }, + UseJSONNumber: true, + } +} + +func resourceComputeRouterNatAddressCreate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + // A custom_create function similar to the generated code when using a nested_query, but replaces the encoder with a custom one instead of just injecting it; + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + obj := make(map[string]interface{}) + natIpsProp, err := expandNestedComputeRouterNatAddressNatIps(d.Get("nat_ips"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("nat_ips"); ok || !reflect.DeepEqual(v, natIpsProp) { + obj["natIps"] = natIpsProp + } + drainNatIpsProp, err := expandNestedComputeRouterNatAddressDrainNatIps(d.Get("drain_nat_ips"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("drain_nat_ips"); ok || !reflect.DeepEqual(v, drainNatIpsProp) { + obj["drainNatIps"] = drainNatIpsProp + } + nameProp, err := expandNestedComputeRouterNatAddressRouterNat(d.Get("router_nat"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("router_nat"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) { + obj["name"] = nameProp + } + + log.Printf("[DEBUG] Creating new RouterNatAddress: %#v", obj) + + obj, err = resourceComputeRouterNatAddressEncoder(d, meta, obj) + if err != nil { + return err + } + + lockName, err := tpgresource.ReplaceVars(d, config, "router/{{region}}/{{router}}") + if err != nil { + return err + } + transport_tpg.MutexStore.Lock(lockName) + defer transport_tpg.MutexStore.Unlock(lockName) + + url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/routers/{{router}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RouterNatAddress: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutCreate), + Headers: headers, + }) + if err != nil { + return fmt.Errorf("Error creating RouterNatAddress: %s", err) + } + + // Store the ID now + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}") + if err != nil { + return fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + err = ComputeOperationWaitTime( + config, res, project, "Creating RouterNatAddress", userAgent, + d.Timeout(schema.TimeoutCreate)) + + if err != nil { + // The resource didn't actually create + d.SetId("") + return fmt.Errorf("Error waiting to create RouterNatAddress: %s", err) + } + + log.Printf("[DEBUG] Finished creating RouterNatAddress %q: %#v", d.Id(), res) + + return resourceComputeRouterNatAddressRead(d, meta) +} + +func resourceComputeRouterNatAddressRead(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/routers/{{router}}") + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RouterNatAddress: %s", err) + } + billingProject = project + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Headers: headers, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ComputeRouterNatAddress %q", d.Id())) + } + + res, err = flattenNestedComputeRouterNatAddress(d, meta, res) + if err != nil { + return err + } + + if res == nil { + // Object isn't there any more - remove it from the state. + log.Printf("[DEBUG] Removing ComputeRouterNatAddress because it couldn't be matched.") + d.SetId("") + return nil + } + + if err := d.Set("project", project); err != nil { + return fmt.Errorf("Error reading RouterNatAddress: %s", err) + } + + if err := d.Set("nat_ips", flattenNestedComputeRouterNatAddressNatIps(res["natIps"], d, config)); err != nil { + return fmt.Errorf("Error reading RouterNatAddress: %s", err) + } + if err := d.Set("drain_nat_ips", flattenNestedComputeRouterNatAddressDrainNatIps(res["drainNatIps"], d, config)); err != nil { + return fmt.Errorf("Error reading RouterNatAddress: %s", err) + } + if err := d.Set("router_nat", flattenNestedComputeRouterNatAddressRouterNat(res["name"], d, config)); err != nil { + return fmt.Errorf("Error reading RouterNatAddress: %s", err) + } + + return nil +} + +func resourceComputeRouterNatAddressUpdate(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RouterNatAddress: %s", err) + } + billingProject = project + + obj := make(map[string]interface{}) + natIpsProp, err := expandNestedComputeRouterNatAddressNatIps(d.Get("nat_ips"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("nat_ips"); ok || !reflect.DeepEqual(v, natIpsProp) { + obj["natIps"] = natIpsProp + } + drainNatIpsProp, err := expandNestedComputeRouterNatAddressDrainNatIps(d.Get("drain_nat_ips"), d, config) + if err != nil { + return err + } else if v, ok := d.GetOkExists("drain_nat_ips"); ok || !reflect.DeepEqual(v, drainNatIpsProp) { + obj["drainNatIps"] = drainNatIpsProp + } + + obj, err = resourceComputeRouterNatAddressUpdateEncoder(d, meta, obj) + if err != nil { + return err + } + + lockName, err := tpgresource.ReplaceVars(d, config, "router/{{region}}/{{router}}") + if err != nil { + return err + } + transport_tpg.MutexStore.Lock(lockName) + defer transport_tpg.MutexStore.Unlock(lockName) + + url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/routers/{{router}}") + if err != nil { + return err + } + + log.Printf("[DEBUG] Updating RouterNatAddress %q: %#v", d.Id(), obj) + headers := make(http.Header) + + obj, err = resourceComputeRouterNatAddressPatchUpdateEncoder(d, meta, obj) + if err != nil { + return err + } + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutUpdate), + Headers: headers, + }) + + if err != nil { + return fmt.Errorf("Error updating RouterNatAddress %q: %s", d.Id(), err) + } else { + log.Printf("[DEBUG] Finished updating RouterNatAddress %q: %#v", d.Id(), res) + } + + err = ComputeOperationWaitTime( + config, res, project, "Updating RouterNatAddress", userAgent, + d.Timeout(schema.TimeoutUpdate)) + + if err != nil { + return err + } + + return resourceComputeRouterNatAddressRead(d, meta) +} + +func resourceComputeRouterNatAddressDelete(d *schema.ResourceData, meta interface{}) error { + config := meta.(*transport_tpg.Config) + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return err + } + + billingProject := "" + + project, err := tpgresource.GetProject(d, config) + if err != nil { + return fmt.Errorf("Error fetching project for RouterNatAddress: %s", err) + } + billingProject = project + + lockName, err := tpgresource.ReplaceVars(d, config, "router/{{region}}/{{router}}") + if err != nil { + return err + } + transport_tpg.MutexStore.Lock(lockName) + defer transport_tpg.MutexStore.Unlock(lockName) + + url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/routers/{{router}}") + if err != nil { + return err + } + + var obj map[string]interface{} + + obj, err = resourceComputeRouterNatAddressPatchDeleteEncoder(d, meta, obj) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "RouterNatAddress") + } + + // err == nil indicates that the billing_project value was found + if bp, err := tpgresource.GetBillingProject(d, config); err == nil { + billingProject = bp + } + + headers := make(http.Header) + // Since RouterNatAddress reopresents only the natIps field, we must make sure we only remove this value and not the entire nat + obj, err = resourceComputeRouterNatAddressDeleteOnlyNatIps(d, meta, obj) + if err != nil { + return err + } + + log.Printf("[DEBUG] Deleting RouterNatAddress %q", d.Id()) + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "PATCH", + Project: billingProject, + RawURL: url, + UserAgent: userAgent, + Body: obj, + Timeout: d.Timeout(schema.TimeoutDelete), + Headers: headers, + }) + if err != nil { + return transport_tpg.HandleNotFoundError(err, d, "RouterNatAddress") + } + + err = ComputeOperationWaitTime( + config, res, project, "Deleting RouterNatAddress", userAgent, + d.Timeout(schema.TimeoutDelete)) + + if err != nil { + return err + } + + log.Printf("[DEBUG] Finished deleting RouterNatAddress %q: %#v", d.Id(), res) + return nil +} + +func resourceComputeRouterNatAddressImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { + config := meta.(*transport_tpg.Config) + if err := tpgresource.ParseImportId([]string{ + "^projects/(?P[^/]+)/regions/(?P[^/]+)/routers/(?P[^/]+)/(?P[^/]+)$", + "^(?P[^/]+)/(?P[^/]+)/(?P[^/]+)/(?P[^/]+)$", + "^(?P[^/]+)/(?P[^/]+)/(?P[^/]+)$", + "^(?P[^/]+)/(?P[^/]+)$", + }, d, config); err != nil { + return nil, err + } + + // Replace import id for the resource id + id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}") + if err != nil { + return nil, fmt.Errorf("Error constructing id: %s", err) + } + d.SetId(id) + + return []*schema.ResourceData{d}, nil +} + +func flattenNestedComputeRouterNatAddressNatIps(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.ConvertAndMapStringArr(v.([]interface{}), tpgresource.ConvertSelfLinkToV1) +} + +func flattenNestedComputeRouterNatAddressDrainNatIps(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.ConvertAndMapStringArr(v.([]interface{}), tpgresource.ConvertSelfLinkToV1) +} + +func flattenNestedComputeRouterNatAddressRouterNat(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} { + if v == nil { + return v + } + return tpgresource.ConvertSelfLinkToV1(v.(string)) +} + +func expandNestedComputeRouterNatAddressNatIps(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + return nil, fmt.Errorf("Invalid value for nat_ips: nil") + } + f, err := tpgresource.ParseRegionalFieldValue("addresses", raw.(string), "project", "region", "zone", d, config, true) + if err != nil { + return nil, fmt.Errorf("Invalid value for nat_ips: %s", err) + } + req = append(req, f.RelativeLink()) + } + return req, nil +} + +func expandNestedComputeRouterNatAddressDrainNatIps(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + v = v.(*schema.Set).List() + l := v.([]interface{}) + req := make([]interface{}, 0, len(l)) + for _, raw := range l { + if raw == nil { + return nil, fmt.Errorf("Invalid value for drain_nat_ips: nil") + } + f, err := tpgresource.ParseRegionalFieldValue("addresses", raw.(string), "project", "region", "zone", d, config, true) + if err != nil { + return nil, fmt.Errorf("Invalid value for drain_nat_ips: %s", err) + } + req = append(req, f.RelativeLink()) + } + return req, nil +} + +func expandNestedComputeRouterNatAddressRouterNat(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) { + return v, nil +} + +func resourceComputeRouterNatAddressEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + currItems, err := resourceComputeRouterNatAddressListForPatch(d, meta) + if err != nil { + return nil, err + } + + idx, found, err := resourceComputeRouterNatAddressFindNestedObjectInList(d, meta, currItems) + if err != nil { + return nil, err + } + + // Merge new with existing item if item was already created (with the router nat resource). + if found != nil { + // Merge new object into old. + for k, v := range obj { + found[k] = v + } + currItems[idx] = found + + // Return list with new item added + resPatch := map[string]interface{}{ + "nats": currItems, + } + + return resPatch, nil + } + + // Prevent creating a RouterNatAddress if no RouterNat has been found + log.Printf("[WARNING] No RouterNat resource %+v found, preventing RouterNatAddress creation", obj) + res := map[string]interface{}{ + "nats": nil, + } + + return res, nil +} + +func resourceComputeRouterNatAddressUpdateEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + // Since we only want to change the handling of the CREATE function, this encoder just returns the unchanged obj value + return obj, nil +} + +func flattenNestedComputeRouterNatAddress(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) { + var v interface{} + var ok bool + + v, ok = res["nats"] + if !ok || v == nil { + return nil, nil + } + + switch v.(type) { + case []interface{}: + break + case map[string]interface{}: + // Construct list out of single nested resource + v = []interface{}{v} + default: + return nil, fmt.Errorf("expected list or map for value nats. Actual value: %v", v) + } + + _, item, err := resourceComputeRouterNatAddressFindNestedObjectInList(d, meta, v.([]interface{})) + if err != nil { + return nil, err + } + return item, nil +} + +func resourceComputeRouterNatAddressFindNestedObjectInList(d *schema.ResourceData, meta interface{}, items []interface{}) (index int, item map[string]interface{}, err error) { + expectedRouterNat, err := expandNestedComputeRouterNatAddressRouterNat(d.Get("router_nat"), d, meta.(*transport_tpg.Config)) + if err != nil { + return -1, nil, err + } + expectedFlattenedRouterNat := flattenNestedComputeRouterNatAddressRouterNat(expectedRouterNat, d, meta.(*transport_tpg.Config)) + + // Search list for this resource. + for idx, itemRaw := range items { + if itemRaw == nil { + continue + } + item := itemRaw.(map[string]interface{}) + + itemRouterNat := flattenNestedComputeRouterNatAddressRouterNat(item["name"], d, meta.(*transport_tpg.Config)) + // IsEmptyValue check so that if one is nil and the other is "", that's considered a match + if !(tpgresource.IsEmptyValue(reflect.ValueOf(itemRouterNat)) && tpgresource.IsEmptyValue(reflect.ValueOf(expectedFlattenedRouterNat))) && !reflect.DeepEqual(itemRouterNat, expectedFlattenedRouterNat) { + log.Printf("[DEBUG] Skipping item with name= %#v, looking for %#v)", itemRouterNat, expectedFlattenedRouterNat) + continue + } + log.Printf("[DEBUG] Found item for resource %q: %#v)", d.Id(), item) + return idx, item, nil + } + return -1, nil, nil +} + +// PatchCreateEncoder handles creating request data to PATCH parent resource +// with list including new object. +func resourceComputeRouterNatAddressPatchCreateEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + currItems, err := resourceComputeRouterNatAddressListForPatch(d, meta) + if err != nil { + return nil, err + } + + _, found, err := resourceComputeRouterNatAddressFindNestedObjectInList(d, meta, currItems) + if err != nil { + return nil, err + } + + // Return error if item already created. + if found != nil { + return nil, fmt.Errorf("Unable to create RouterNatAddress, existing object already found: %+v", found) + } + + // Return list with the resource to create appended + res := map[string]interface{}{ + "nats": append(currItems, obj), + } + + return res, nil +} + +// PatchUpdateEncoder handles creating request data to PATCH parent resource +// with list including updated object. +func resourceComputeRouterNatAddressPatchUpdateEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + items, err := resourceComputeRouterNatAddressListForPatch(d, meta) + if err != nil { + return nil, err + } + + idx, item, err := resourceComputeRouterNatAddressFindNestedObjectInList(d, meta, items) + if err != nil { + return nil, err + } + + // Return error if item to update does not exist. + if item == nil { + return nil, fmt.Errorf("Unable to update RouterNatAddress %q - not found in list", d.Id()) + } + + // Merge new object into old. + for k, v := range obj { + item[k] = v + } + items[idx] = item + + // Return list with new item added + res := map[string]interface{}{ + "nats": items, + } + + return res, nil +} + +// PatchDeleteEncoder handles creating request data to PATCH parent resource +// with list excluding object to delete. +func resourceComputeRouterNatAddressPatchDeleteEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) { + currItems, err := resourceComputeRouterNatAddressListForPatch(d, meta) + if err != nil { + return nil, err + } + + idx, item, err := resourceComputeRouterNatAddressFindNestedObjectInList(d, meta, currItems) + if err != nil { + return nil, err + } + if item == nil { + // Spoof 404 error for proper handling by Delete (i.e. no-op) + return nil, tpgresource.Fake404("nested", "ComputeRouterNatAddress") + } + + updatedItems := append(currItems[:idx], currItems[idx+1:]...) + res := map[string]interface{}{ + "nats": updatedItems, + } + + return res, nil +} + +// ListForPatch handles making API request to get parent resource and +// extracting list of objects. +func resourceComputeRouterNatAddressListForPatch(d *schema.ResourceData, meta interface{}) ([]interface{}, error) { + config := meta.(*transport_tpg.Config) + url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/routers/{{router}}") + if err != nil { + return nil, err + } + project, err := tpgresource.GetProject(d, config) + if err != nil { + return nil, err + } + + userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent) + if err != nil { + return nil, err + } + + res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{ + Config: config, + Method: "GET", + Project: project, + RawURL: url, + UserAgent: userAgent, + }) + if err != nil { + return nil, err + } + + var v interface{} + var ok bool + + v, ok = res["nats"] + if ok && v != nil { + ls, lsOk := v.([]interface{}) + if !lsOk { + return nil, fmt.Errorf(`expected list for nested field "nats"`) + } + return ls, nil + } + return nil, nil +} diff --git a/google-beta/services/compute/resource_compute_router_nat_address_test.go b/google-beta/services/compute/resource_compute_router_nat_address_test.go new file mode 100644 index 0000000000..5858ac7b22 --- /dev/null +++ b/google-beta/services/compute/resource_compute_router_nat_address_test.go @@ -0,0 +1,609 @@ +// Copyright (c) HashiCorp, Inc. +// SPDX-License-Identifier: MPL-2.0 +package compute_test + +import ( + "fmt" + "regexp" + "testing" + + "github.com/hashicorp/terraform-plugin-testing/helper/resource" + "github.com/hashicorp/terraform-plugin-testing/terraform" + "github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest" +) + +func testAccCheckComputeRouterNatAddressDestroyProducer(t *testing.T) func(s *terraform.State) error { + return func(s *terraform.State) error { + config := acctest.GoogleProviderConfig(t) + + routersService := config.NewComputeClient(config.UserAgent).Routers + + for _, rs := range s.RootModule().Resources { + if rs.Type != "google_compute_router" { + continue + } + + project, err := acctest.GetTestProject(rs.Primary, config) + if err != nil { + return err + } + + region, err := acctest.GetTestRegion(rs.Primary, config) + if err != nil { + return err + } + + routerName := rs.Primary.Attributes["router"] + + _, err = routersService.Get(project, region, routerName).Do() + + if err == nil { + return fmt.Errorf("Error, Router %s in region %s still exists", routerName, region) + } + } + + return nil + } +} + +func TestAccComputeRouterNatAddress_withAddressCountDecrease(t *testing.T) { + t.Parallel() + + testId := acctest.RandString(t, 10) + routerName := fmt.Sprintf("tf-test-router-nat-%s", testId) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeRouterNatAddressDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeRouterNatAddress_withAddressCount(routerName, "2"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "initial_nat_ips.#", "1"), + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "nat_ips.#", "1"), + resource.TestCheckResourceAttr("google_compute_router_nat_address.foobar", "nat_ips.#", "2"), + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "2"), + ), + }, + { + ResourceName: "google_compute_router_nat_address.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatAddress_withAddressCount(routerName, "3"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "initial_nat_ips.#", "1"), + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "nat_ips.#", "2"), + resource.TestCheckResourceAttr("google_compute_router_nat_address.foobar", "nat_ips.#", "3"), + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "3"), + ), + }, + { + ResourceName: "google_compute_router_nat_address.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatAddress_withAddressCount(routerName, "2"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "initial_nat_ips.#", "1"), + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "nat_ips.#", "3"), + resource.TestCheckResourceAttr("google_compute_router_nat_address.foobar", "nat_ips.#", "2"), + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "2"), + ), + }, + { + ResourceName: "google_compute_router_nat_address.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeRouterNatAddress_withAddressRemoved(t *testing.T) { + t.Parallel() + + testId := acctest.RandString(t, 10) + routerName := fmt.Sprintf("tf-test-router-nat-%s", testId) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "random": {}, + }, + CheckDestroy: testAccCheckComputeRouterNatAddressDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeRouterNatAddressWithNatIps(routerName), + }, + { + ResourceName: "google_compute_router_nat_address.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatAddressWithAddressRemoved(routerName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "1"), + acctest.CheckDataSourceStateMatchesResourceStateWithIgnores( + "data.google_compute_router_nat.foo", + "google_compute_router_nat.foobar", + map[string]struct{}{ + "initial_nat_ips": {}, + "initial_nat_ips.#": {}, + "initial_nat_ips.0": {}, + }, + ), + ), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeRouterNatAddress_withAutoAllocateAndAddressRemoved(t *testing.T) { + t.Parallel() + + testId := acctest.RandString(t, 10) + routerName := fmt.Sprintf("tf-test-router-nat-%s", testId) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + ExternalProviders: map[string]resource.ExternalProvider{ + "random": {}, + }, + CheckDestroy: testAccCheckComputeRouterNatAddressDestroyProducer(t), + Steps: []resource.TestStep{ + { + Config: testAccComputeRouterNatAddressWithNatIps(routerName), + }, + { + ResourceName: "google_compute_router_nat_address.foobar", + ImportState: true, + ImportStateVerify: true, + }, + { + Config: testAccComputeRouterNatAddressWithAutoAllocateAndAddressRemoved(routerName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "0"), + acctest.CheckDataSourceStateMatchesResourceStateWithIgnores( + "data.google_compute_router_nat.foo", + "google_compute_router_nat.foobar", + map[string]struct{}{ + "initial_nat_ips": {}, + "initial_nat_ips.#": {}, + "initial_nat_ips.0": {}, + }, + ), + ), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func TestAccComputeRouterNatAddress_withNatIpsAndDrainNatIps(t *testing.T) { + t.Parallel() + + testId := acctest.RandString(t, 10) + routerName := fmt.Sprintf("tf-test-router-nat-%s", testId) + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + CheckDestroy: testAccCheckComputeRouterNatDestroyProducer(t), + Steps: []resource.TestStep{ + // (ERROR): Creation with drain nat IPs should fail + { + Config: testAccComputeRouterNatAddressWithOneDrainOneRemovedNatIps(routerName), + ExpectError: regexp.MustCompile("New RouterNat cannot have drain_nat_ips"), + }, + // Create NAT with three nat IPs + { + Config: testAccComputeRouterNatAddressWithNatIps(routerName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "initial_nat_ips.#", "1"), + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "nat_ips.#", "1"), + resource.TestCheckResourceAttr("google_compute_router_nat_address.foobar", "nat_ips.#", "3"), + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "3"), + ), + }, + { + ResourceName: "google_compute_router_nat_address.foobar", + ImportState: true, + ImportStateVerify: true, + }, + // (ERROR) - Should not allow draining IPs still in natIps + { + Config: testAccComputeRouterNatAddressWithInvalidDrainNatIpsStillInNatIps(routerName), + ExpectError: regexp.MustCompile("cannot be drained if still set in nat_ips"), + }, + // natIps #1, #2, #3--> natIp #2, drainNatIp #3 + { + Config: testAccComputeRouterNatAddressWithOneDrainOneRemovedNatIps(routerName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "initial_nat_ips.#", "1"), + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "nat_ips.#", "3"), + resource.TestCheckResourceAttr("google_compute_router_nat_address.foobar", "nat_ips.#", "1"), + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "1"), + ), + }, + { + ResourceName: "google_compute_router_nat_address.foobar", + ImportState: true, + ImportStateVerify: true, + }, + // (ERROR): Should not be able to drain previously removed natIps (#1) + { + Config: testAccComputeRouterNatAddressWithInvalidDrainMissingNatIp(routerName), + ExpectError: regexp.MustCompile("was not previously set in nat_ips"), + }, + { + Config: testAccComputeRouterNatAddressWithAddressRemoved(routerName), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckNoResourceAttr("google_compute_router_nat.foobar", "initial_nat_ips"), + resource.TestCheckResourceAttr("google_compute_router_nat.foobar", "nat_ips.#", "1"), + resource.TestCheckResourceAttr("data.google_compute_router_nat.foo", "nat_ips.#", "1"), + acctest.CheckDataSourceStateMatchesResourceStateWithIgnores( + "data.google_compute_router_nat.foo", + "google_compute_router_nat.foobar", + map[string]struct{}{ + "initial_nat_ips": {}, + "initial_nat_ips.#": {}, + "initial_nat_ips.0": {}, + }, + ), + ), + }, + { + ResourceName: "google_compute_router_nat.foobar", + ImportState: true, + ImportStateVerify: true, + }, + }, + }) +} + +func testAccComputeRouterNatAddress_withAddressCount(routerName, routerCount string) string { + return fmt.Sprintf(` +resource "google_compute_network" "foobar" { + name = "%s-net" +} + +resource "google_compute_subnetwork" "foobar" { + name = "%s-subnet" + network = google_compute_network.foobar.self_link + ip_cidr_range = "10.0.0.0/16" + region = "us-east1" +} + +resource "google_compute_router" "foobar" { + name = "%s" + region = google_compute_subnetwork.foobar.region + network = google_compute_network.foobar.self_link +} + +resource "google_compute_address" "foobar" { + count = %s + name = "%s-address-${count.index}" + region = google_compute_subnetwork.foobar.region + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_router_nat_address" "foobar" { + nat_ips = google_compute_address.foobar.*.self_link + router = google_compute_router.foobar.name + router_nat = google_compute_router_nat.foobar.name + region = google_compute_router_nat.foobar.region +} + +resource "google_compute_router_nat" "foobar" { + name = "%s-nat" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + + initial_nat_ips = [google_compute_address.foobar[0].self_link] + + subnetwork { + name = google_compute_subnetwork.foobar.self_link + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + min_ports_per_vm = 1024 + + log_config { + enable = true + filter = "ERRORS_ONLY" + } +} + +data "google_compute_router_nat" "foo" { + name = google_compute_router_nat.foobar.name + router = google_compute_router_nat.foobar.router + region = google_compute_router.foobar.region + + depends_on = [google_compute_router_nat_address.foobar] +} +`, routerName, routerName, routerName, routerCount, routerName, routerName) +} + +func testAccComputeRouterNatAddressBaseResourcesWithNatIps(routerName string) string { + return fmt.Sprintf(` +resource "google_compute_network" "foobar" { + name = "%s-net" + auto_create_subnetworks = "false" +} + +resource "google_compute_subnetwork" "foobar" { + name = "%s-subnet" + network = google_compute_network.foobar.self_link + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" +} + +resource "google_compute_address" "addr1" { + name = "%s-addr1" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_address" "addr2" { + name = "%s-addr2" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_address" "addr3" { + name = "%s-addr3" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_address" "addr4" { + name = "%s-addr4" + region = google_compute_subnetwork.foobar.region +} + +resource "google_compute_router" "foobar" { + name = "%s" + region = google_compute_subnetwork.foobar.region + network = google_compute_network.foobar.self_link +} +`, routerName, routerName, routerName, routerName, routerName, routerName, routerName) +} + +func testAccComputeRouterNatAddressWithNatIps(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat_address" "foobar" { + nat_ips = [ + google_compute_address.addr1.self_link, + google_compute_address.addr2.self_link, + google_compute_address.addr3.self_link, + ] + router = google_compute_router.foobar.name + router_nat = google_compute_router_nat.foobar.name + region = google_compute_router_nat.foobar.region +} + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + initial_nat_ips = [google_compute_address.addr4.self_link] + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.self_link + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } +} + +data "google_compute_router_nat" "foo" { + name = google_compute_router_nat.foobar.name + router = google_compute_router_nat.foobar.router + region = google_compute_router.foobar.region + + depends_on = [google_compute_router_nat_address.foobar] +} +`, testAccComputeRouterNatAddressBaseResourcesWithNatIps(routerName), routerName) +} + +func testAccComputeRouterNatAddressWithAddressRemoved(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "MANUAL_ONLY" + nat_ips = [google_compute_address.addr4.self_link] + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.self_link + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } +} + +data "google_compute_router_nat" "foo" { + name = google_compute_router_nat.foobar.name + router = google_compute_router_nat.foobar.router + region = google_compute_router.foobar.region +} +`, testAccComputeRouterNatAddressBaseResourcesWithNatIps(routerName), routerName) +} + +func testAccComputeRouterNatAddressWithAutoAllocateAndAddressRemoved(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + nat_ip_allocate_option = "AUTO_ONLY" + nat_ips = [] + + source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" +} + +data "google_compute_router_nat" "foo" { + name = google_compute_router_nat.foobar.name + router = google_compute_router_nat.foobar.router + region = google_compute_router.foobar.region +} +`, testAccComputeRouterNatAddressBaseResourcesWithNatIps(routerName), routerName) +} + +func testAccComputeRouterNatAddressWithOneDrainOneRemovedNatIps(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat_address" "foobar" { + nat_ips = [ + google_compute_address.addr2.self_link, + ] + + drain_nat_ips = [ + google_compute_address.addr3.self_link, + ] + router = google_compute_router.foobar.name + router_nat = google_compute_router_nat.foobar.name + region = google_compute_router_nat.foobar.region +} + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.self_link + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + nat_ip_allocate_option = "MANUAL_ONLY" + initial_nat_ips = [google_compute_address.addr4.self_link] +} + +data "google_compute_router_nat" "foo" { + name = google_compute_router_nat.foobar.name + router = google_compute_router_nat.foobar.router + region = google_compute_router.foobar.region + + depends_on = [google_compute_router_nat_address.foobar] +} +`, testAccComputeRouterNatAddressBaseResourcesWithNatIps(routerName), routerName) +} + +func testAccComputeRouterNatAddressWithInvalidDrainNatIpsStillInNatIps(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat_address" "foobar" { + nat_ips = [ + google_compute_address.addr1.self_link, + google_compute_address.addr2.self_link, + google_compute_address.addr3.self_link, + ] + + drain_nat_ips = [ + google_compute_address.addr3.self_link, + ] + router = google_compute_router.foobar.name + router_nat = google_compute_router_nat.foobar.name + region = google_compute_router_nat.foobar.region +} + + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.self_link + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + nat_ip_allocate_option = "MANUAL_ONLY" + initial_nat_ips = [google_compute_address.addr4.self_link] +} + +data "google_compute_router_nat" "foo" { + name = google_compute_router_nat.foobar.name + router = google_compute_router_nat.foobar.router + region = google_compute_router.foobar.region + + depends_on = [google_compute_router_nat_address.foobar] +} +`, testAccComputeRouterNatAddressBaseResourcesWithNatIps(routerName), routerName) +} + +func testAccComputeRouterNatAddressWithInvalidDrainMissingNatIp(routerName string) string { + return fmt.Sprintf(` +%s + +resource "google_compute_router_nat_address" "foobar" { + nat_ips = [ + google_compute_address.addr2.self_link, + ] + + drain_nat_ips = [ + google_compute_address.addr1.self_link, + google_compute_address.addr3.self_link, + ] + router = google_compute_router.foobar.name + router_nat = google_compute_router_nat.foobar.name + region = google_compute_router_nat.foobar.region +} + +resource "google_compute_router_nat" "foobar" { + name = "%s" + router = google_compute_router.foobar.name + region = google_compute_router.foobar.region + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.foobar.self_link + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } + + nat_ip_allocate_option = "MANUAL_ONLY" + initial_nat_ips = [google_compute_address.addr4.self_link] +} + +data "google_compute_router_nat" "foo" { + name = google_compute_router_nat.foobar.name + router = google_compute_router_nat.foobar.router + region = google_compute_router.foobar.region + + depends_on = [google_compute_router_nat_address.foobar] +} +`, testAccComputeRouterNatAddressBaseResourcesWithNatIps(routerName), routerName) +} diff --git a/google-beta/services/compute/resource_compute_router_nat_test.go b/google-beta/services/compute/resource_compute_router_nat_test.go index ecd35033e3..f41365d18a 100644 --- a/google-beta/services/compute/resource_compute_router_nat_test.go +++ b/google-beta/services/compute/resource_compute_router_nat_test.go @@ -1030,6 +1030,7 @@ resource "google_compute_router_nat" "foobar" { router = google_compute_router.foobar.name region = google_compute_router.foobar.region nat_ip_allocate_option = "AUTO_ONLY" + nat_ips = [] source_subnetwork_ip_ranges_to_nat = "ALL_SUBNETWORKS_ALL_IP_RANGES" log_config { diff --git a/website/docs/r/compute_router_nat.html.markdown b/website/docs/r/compute_router_nat.html.markdown index 693872d793..50b8e70c68 100644 --- a/website/docs/r/compute_router_nat.html.markdown +++ b/website/docs/r/compute_router_nat.html.markdown @@ -296,6 +296,11 @@ The following arguments are supported: Platform, or `MANUAL_ONLY` for only user-allocated NAT IP addresses. Possible values are: `MANUAL_ONLY`, `AUTO_ONLY`. +* `initial_nat_ips` - + (Optional) + Self-links of NAT IPs to be used as initial value for creation alongside a RouterNatAddress resource. + Conflicts with natIps and drainNatIps. Only valid if natIpAllocateOption is set to MANUAL_ONLY. + * `nat_ips` - (Optional) Self-links of NAT IPs. Only valid if natIpAllocateOption diff --git a/website/docs/r/compute_router_nat_address.html.markdown b/website/docs/r/compute_router_nat_address.html.markdown new file mode 100644 index 0000000000..2387e89528 --- /dev/null +++ b/website/docs/r/compute_router_nat_address.html.markdown @@ -0,0 +1,171 @@ +--- +# ---------------------------------------------------------------------------- +# +# *** AUTO GENERATED CODE *** Type: MMv1 *** +# +# ---------------------------------------------------------------------------- +# +# This file is automatically generated by Magic Modules and manual +# changes will be clobbered when the file is regenerated. +# +# Please read more about how to change this file in +# .github/CONTRIBUTING.md. +# +# ---------------------------------------------------------------------------- +subcategory: "Compute Engine" +description: |- + A resource used to set the list of IP addresses to be used in a NAT service and manage the draining of destroyed IPs. +--- + +# google_compute_router_nat_address + +A resource used to set the list of IP addresses to be used in a NAT service and manage the draining of destroyed IPs. + +~> **Note:** This resource is to be used alongside a `google_compute_router_nat` resource, +the router nat resource must have no defined `nat_ips` or `drain_nat_ips` parameters, +instead using the `initial_nat_ips` parameter to set at least one IP for the creation of the resource. + + +To get more information about RouterNatAddress, see: + +* [API documentation](https://cloud.google.com/compute/docs/reference/rest/v1/routers) +* How-to Guides + * [Google Cloud Router](https://cloud.google.com/router/docs/) + +## Example Usage - Router Nat Address Count + + +```hcl +resource "google_compute_network" "net" { + name = "my-network" +} + +resource "google_compute_subnetwork" "subnet" { + name = "my-subnetwork" + network = google_compute_network.net.id + ip_cidr_range = "10.0.0.0/16" + region = "us-central1" +} + +resource "google_compute_router" "router" { + name = "my-router" + region = google_compute_subnetwork.subnet.region + network = google_compute_network.net.id +} + +resource "google_compute_address" "address" { + count = 3 + name = "nat-manual-ip-${count.index}" + region = google_compute_subnetwork.subnet.region + + lifecycle { + create_before_destroy = true + } +} + +resource "google_compute_router_nat_address" "nat_address" { + nat_ips = google_compute_address.address.*.self_link + router = google_compute_router.router.name + router_nat = google_compute_router_nat.router_nat.name + region = google_compute_router_nat.router_nat.region +} + +resource "google_compute_router_nat" "router_nat" { + name = "my-router-nat" + router = google_compute_router.router.name + region = google_compute_router.router.region + + nat_ip_allocate_option = "MANUAL_ONLY" + initial_nat_ips = [google_compute_address.address[0].self_link] + + source_subnetwork_ip_ranges_to_nat = "LIST_OF_SUBNETWORKS" + subnetwork { + name = google_compute_subnetwork.subnet.id + source_ip_ranges_to_nat = ["ALL_IP_RANGES"] + } +} +``` + +## Argument Reference + +The following arguments are supported: + + +* `nat_ips` - + (Required) + Self-links of NAT IPs to be used in a Nat service. Only valid if the referenced RouterNat + natIpAllocateOption is set to MANUAL_ONLY. + +* `router` - + (Required) + The name of the Cloud Router in which the referenced NAT service is configured. + +* `router_nat` - + (Required) + The name of the Nat service in which this address will be configured. + + +- - - + + +* `drain_nat_ips` - + (Optional) + A list of URLs of the IP resources to be drained. These IPs must be + valid static external IPs that have been assigned to the NAT. + +* `region` - + (Optional) + Region where the NAT service reside. + +* `project` - (Optional) The ID of the project in which the resource belongs. + If it is not provided, the provider project is used. + + +## Attributes Reference + +In addition to the arguments listed above, the following computed attributes are exported: + +* `id` - an identifier for the resource with format `projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}` + + +## Timeouts + +This resource provides the following +[Timeouts](https://developer.hashicorp.com/terraform/plugin/sdkv2/resources/retries-and-customizable-timeouts) configuration options: + +- `create` - Default is 20 minutes. +- `update` - Default is 20 minutes. +- `delete` - Default is 20 minutes. + +## Import + + +RouterNatAddress can be imported using any of these accepted formats: + +* `projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}` +* `{{project}}/{{region}}/{{router}}/{{router_nat}}` +* `{{region}}/{{router}}/{{router_nat}}` +* `{{router}}/{{router_nat}}` + + +In Terraform v1.5.0 and later, use an [`import` block](https://developer.hashicorp.com/terraform/language/import) to import RouterNatAddress using one of the formats above. For example: + +```tf +import { + id = "projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}" + to = google_compute_router_nat_address.default +} +``` + +When using the [`terraform import` command](https://developer.hashicorp.com/terraform/cli/commands/import), RouterNatAddress can be imported using one of the formats above. For example: + +``` +$ terraform import google_compute_router_nat_address.default projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}} +$ terraform import google_compute_router_nat_address.default {{project}}/{{region}}/{{router}}/{{router_nat}} +$ terraform import google_compute_router_nat_address.default {{region}}/{{router}}/{{router_nat}} +$ terraform import google_compute_router_nat_address.default {{router}}/{{router_nat}} +``` + +## User Project Overrides + +This resource supports [User Project Overrides](https://registry.terraform.io/providers/hashicorp/google/latest/docs/guides/provider_reference#user_project_override).