Skip to content

Added new resource "Router Nat Address" #11390

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
0484c61
- Added test to reproduce issue;
matheusaleixo-cit Jul 16, 2024
097e398
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Jul 22, 2024
2141061
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Aug 1, 2024
9c5b9c6
- Added RouterNatAddress resource;
matheusaleixo-cit Aug 6, 2024
e21f84f
- Renamed RouterNatAddress custom create;
matheusaleixo-cit Aug 7, 2024
09b6a95
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Aug 7, 2024
8e8fb2b
- Removed placeholder example and lint fix;
matheusaleixo-cit Aug 8, 2024
f834ca4
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Aug 12, 2024
78230c3
- Added new field initialNatIps for RouterNat and related encoder;
matheusaleixo-cit Aug 15, 2024
d97167b
- Lint fix;
matheusaleixo-cit Aug 15, 2024
8f9ce9f
- Added drainNatIps field to RouterNatAddress and related CustomDiff;
matheusaleixo-cit Aug 19, 2024
f8531d0
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Aug 22, 2024
7c2d2eb
- (WiP) Added new test withAddressRemoved for RouterNatAddress;
matheusaleixo-cit Aug 24, 2024
33d127f
- Fixed RouterNatAddress recreating a RouterNat deleted in the same a…
matheusaleixo-cit Aug 26, 2024
cc20fec
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Aug 26, 2024
699f57e
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Sep 3, 2024
55521b7
- Removed unnecessary use of random_id in addresses configuration in …
matheusaleixo-cit Sep 5, 2024
74fbc6e
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Sep 10, 2024
64152bd
- Replaced the use of diff_suppress_func in fields natIps and drainNa…
matheusaleixo-cit Sep 11, 2024
e2edd02
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Sep 11, 2024
d1dae5e
- Removed unused test config after merge;
matheusaleixo-cit Sep 11, 2024
8749778
- Added datasource checks for RouterNatAddress tests;
matheusaleixo-cit Sep 13, 2024
dc7b650
- lint fix;
matheusaleixo-cit Sep 13, 2024
7487aca
- Added dependency to data source in RouterNatAddress test config;
matheusaleixo-cit Sep 13, 2024
c27f550
Merge branch 'main' into fix-cannot-delete-compute-address-when-its-d…
matheusaleixo-cit Sep 19, 2024
41f1e5c
- Added test RouterNatAddress_withAutoAllocateAndAddressRemoved;
matheusaleixo-cit Sep 19, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions mmv1/products/compute/RouterNat.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,7 @@ custom_code: !ruby/object:Provider::Terraform::CustomCode
constants: 'templates/terraform/constants/router_nat.go.erb'
pre_create: 'templates/terraform/constants/router_nat_validate_action_active_range.go.erb'
pre_update: 'templates/terraform/constants/router_nat_validate_action_active_range.go.erb'
encoder: 'templates/terraform/encoders/router_nat_set_initial_nat_ips.go.erb'
custom_diff: [
'resourceComputeRouterNatDrainNatIpsCustomDiff',
]
Expand Down Expand Up @@ -153,6 +154,25 @@ properties:
values:
- :MANUAL_ONLY
- :AUTO_ONLY
- !ruby/object:Api::Type::Array
name: 'initialNatIps'
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.
immutable: true
ignore_read: true
conflicts:
- natIps
- drainNatIps
send_empty_value: true
is_set: true
set_hash_func: computeRouterNatIPsHash
item_type: !ruby/object:Api::Type::ResourceRef
name: 'address'
resource: 'Address'
imports: 'selfLink'
description: 'A reference to an address associated with this NAT'
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
- !ruby/object:Api::Type::Array
name: 'natIps'
description: |
Expand All @@ -170,6 +190,7 @@ properties:
imports: 'selfLink'
description: 'A reference to an address associated with this NAT'
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
default_from_api: true
- !ruby/object:Api::Type::Array
name: 'drainNatIps'
description: |
Expand All @@ -183,6 +204,7 @@ properties:
imports: 'selfLink'
description: 'A reference to an address associated with this NAT'
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
default_from_api: true
- !ruby/object:Api::Type::Enum
name: 'sourceSubnetworkIpRangesToNat'
required: true
Expand Down
140 changes: 140 additions & 0 deletions mmv1/products/compute/RouterNatAddress.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Copyright 2023 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

--- !ruby/object:Api::Resource
name: 'RouterNatAddress'
base_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
self_link: projects/{{project}}/regions/{{region}}/routers/{{router}}
create_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
update_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
delete_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
create_verb: :PATCH
update_verb: :PATCH
delete_verb: :PATCH
identity:
- routerNat
collection_url_key: nats
nested_query: !ruby/object:Api::Resource::NestedQuery
modify_by_patch: true
keys:
- nats
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.

~> **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.
references: !ruby/object:Api::Resource::ReferenceLinks
guides:
'Google Cloud Router': 'https://cloud.google.com/router/docs/'
api: 'https://cloud.google.com/compute/docs/reference/rest/v1/routers'
async: !ruby/object:Api::OpAsync
operation: !ruby/object:Api::OpAsync::Operation
kind: 'compute#operation'
path: 'routerNat'
base_url: 'projects/{{project}}/regions/{{regions}}/operations/{{op_id}}'
wait_ms: 1000
result: !ruby/object:Api::OpAsync::Result
path: 'targetLink'
status: !ruby/object:Api::OpAsync::Status
path: 'status'
complete: 'DONE'
allowed:
- 'PENDING'
- 'RUNNING'
- 'DONE'
error: !ruby/object:Api::OpAsync::Error
path: 'error/errors'
message: 'message'
exclude_tgc: true
id_format: 'projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}'
mutex: router/{{region}}/{{router}}
examples:
- !ruby/object:Provider::Terraform::Examples
name: 'router_nat_address_count'
primary_resource_id: 'nat_address'
skip_test: true
vars:
router_name: 'my-router'
nat_name: 'my-router-nat'
network_name: 'my-network'
subnet_name: 'my-subnetwork'
address_name: 'nat-manual-ip'
# ToDo: We use a custom code for CREATE since the generated code is erroneously not replacing the generated encoder with the custom one provided
custom_code: !ruby/object:Provider::Terraform::CustomCode
constants: 'templates/terraform/constants/router_nat_address.go.erb'
custom_create: templates/terraform/custom_create/router_nat_address_nested_query_create_encoder.go.erb
pre_delete: templates/terraform/pre_delete/compute_router_nat_address_delete_nat_ips_only.go.erb
encoder: 'templates/terraform/encoders/router_nat_address_patch_on_create.go.erb'
update_encoder: 'templates/terraform/encoders/router_nat_address_update_skip_encoder.go.erb'
custom_diff: [
'resourceComputeRouterNatAddressDrainNatIpsCustomDiff',
]
parameters:
- !ruby/object:Api::Type::ResourceRef
name: 'router'
resource: 'Router'
imports: 'name'
description: |
The name of the Cloud Router in which the referenced NAT service is configured.
required: true
immutable: true
url_param_only: true
- !ruby/object:Api::Type::ResourceRef
name: 'routerNat'
resource: 'RouterNat'
imports: 'name'
api_name: 'name'
description: |
The name of the Nat service in which this address will be configured.
required: true
immutable: true
- !ruby/object:Api::Type::ResourceRef
name: region
resource: Region
imports: name
description: Region where the NAT service reside.
immutable: true
required: false
url_param_only: true
default_from_api: true
custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb'
properties:
- !ruby/object:Api::Type::Array
name: 'natIps'
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.
send_empty_value: true
required: true
is_set: true
set_hash_func: computeRouterNatIPsHash
item_type: !ruby/object:Api::Type::ResourceRef
name: 'address'
resource: 'Address'
imports: 'selfLink'
description: 'A reference to an address to be associated with this NAT'
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
- !ruby/object:Api::Type::Array
name: 'drainNatIps'
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.
send_empty_value: true
is_set: true
item_type: !ruby/object:Api::Type::ResourceRef
name: 'address'
resource: 'Address'
imports: 'selfLink'
description: 'A reference to an address associated with this NAT'
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
2 changes: 1 addition & 1 deletion mmv1/templates/terraform/constants/router_nat.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -174,4 +174,4 @@ func computeRouterNatRulesHash(v interface{}) int {
routerNatRulesHash += sourceNatActiveRangeHash + sourceNatDrainRangeHash
<% end -%>
return routerNatRulesHash
}
}
93 changes: 93 additions & 0 deletions mmv1/templates/terraform/constants/router_nat_address.go.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
<%- # the license inside this block applies to this file
# Copyright 2019 Google Inc.
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
-%>
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
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// 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)
Loading