Skip to content

Commit 0c852de

Browse files
matheusaleixo-citabd-goog
authored andcommitted
Added new resource "Router Nat Address" (GoogleCloudPlatform#11390)
1 parent 4e9ca3c commit 0c852de

12 files changed

+1061
-1
lines changed

mmv1/products/compute/RouterNat.yaml

+22
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,7 @@ custom_code: !ruby/object:Provider::Terraform::CustomCode
110110
constants: 'templates/terraform/constants/router_nat.go.erb'
111111
pre_create: 'templates/terraform/constants/router_nat_validate_action_active_range.go.erb'
112112
pre_update: 'templates/terraform/constants/router_nat_validate_action_active_range.go.erb'
113+
encoder: 'templates/terraform/encoders/router_nat_set_initial_nat_ips.go.erb'
113114
custom_diff: [
114115
'resourceComputeRouterNatDrainNatIpsCustomDiff',
115116
]
@@ -153,6 +154,25 @@ properties:
153154
values:
154155
- :MANUAL_ONLY
155156
- :AUTO_ONLY
157+
- !ruby/object:Api::Type::Array
158+
name: 'initialNatIps'
159+
description: |
160+
Self-links of NAT IPs to be used as initial value for creation alongside a RouterNatAddress resource.
161+
Conflicts with natIps and drainNatIps. Only valid if natIpAllocateOption is set to MANUAL_ONLY.
162+
immutable: true
163+
ignore_read: true
164+
conflicts:
165+
- natIps
166+
- drainNatIps
167+
send_empty_value: true
168+
is_set: true
169+
set_hash_func: computeRouterNatIPsHash
170+
item_type: !ruby/object:Api::Type::ResourceRef
171+
name: 'address'
172+
resource: 'Address'
173+
imports: 'selfLink'
174+
description: 'A reference to an address associated with this NAT'
175+
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
156176
- !ruby/object:Api::Type::Array
157177
name: 'natIps'
158178
description: |
@@ -170,6 +190,7 @@ properties:
170190
imports: 'selfLink'
171191
description: 'A reference to an address associated with this NAT'
172192
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
193+
default_from_api: true
173194
- !ruby/object:Api::Type::Array
174195
name: 'drainNatIps'
175196
description: |
@@ -183,6 +204,7 @@ properties:
183204
imports: 'selfLink'
184205
description: 'A reference to an address associated with this NAT'
185206
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
207+
default_from_api: true
186208
- !ruby/object:Api::Type::Enum
187209
name: 'sourceSubnetworkIpRangesToNat'
188210
required: true
+140
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Copyright 2023 Google Inc.
2+
# Licensed under the Apache License, Version 2.0 (the "License");
3+
# you may not use this file except in compliance with the License.
4+
# You may obtain a copy of the License at
5+
#
6+
# http://www.apache.org/licenses/LICENSE-2.0
7+
#
8+
# Unless required by applicable law or agreed to in writing, software
9+
# distributed under the License is distributed on an "AS IS" BASIS,
10+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
# See the License for the specific language governing permissions and
12+
# limitations under the License.
13+
14+
--- !ruby/object:Api::Resource
15+
name: 'RouterNatAddress'
16+
base_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
17+
self_link: projects/{{project}}/regions/{{region}}/routers/{{router}}
18+
create_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
19+
update_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
20+
delete_url: projects/{{project}}/regions/{{region}}/routers/{{router}}
21+
create_verb: :PATCH
22+
update_verb: :PATCH
23+
delete_verb: :PATCH
24+
identity:
25+
- routerNat
26+
collection_url_key: nats
27+
nested_query: !ruby/object:Api::Resource::NestedQuery
28+
modify_by_patch: true
29+
keys:
30+
- nats
31+
description: |
32+
A resource used to set the list of IP addresses to be used in a NAT service and manage the draining of destroyed IPs.
33+
34+
~> **Note:** This resource is to be used alongside a `google_compute_router_nat` resource,
35+
the router nat resource must have no defined `nat_ips` or `drain_nat_ips` parameters,
36+
instead using the `initial_nat_ips` parameter to set at least one IP for the creation of the resource.
37+
references: !ruby/object:Api::Resource::ReferenceLinks
38+
guides:
39+
'Google Cloud Router': 'https://cloud.google.com/router/docs/'
40+
api: 'https://cloud.google.com/compute/docs/reference/rest/v1/routers'
41+
async: !ruby/object:Api::OpAsync
42+
operation: !ruby/object:Api::OpAsync::Operation
43+
kind: 'compute#operation'
44+
path: 'routerNat'
45+
base_url: 'projects/{{project}}/regions/{{regions}}/operations/{{op_id}}'
46+
wait_ms: 1000
47+
result: !ruby/object:Api::OpAsync::Result
48+
path: 'targetLink'
49+
status: !ruby/object:Api::OpAsync::Status
50+
path: 'status'
51+
complete: 'DONE'
52+
allowed:
53+
- 'PENDING'
54+
- 'RUNNING'
55+
- 'DONE'
56+
error: !ruby/object:Api::OpAsync::Error
57+
path: 'error/errors'
58+
message: 'message'
59+
exclude_tgc: true
60+
id_format: 'projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}'
61+
mutex: router/{{region}}/{{router}}
62+
examples:
63+
- !ruby/object:Provider::Terraform::Examples
64+
name: 'router_nat_address_count'
65+
primary_resource_id: 'nat_address'
66+
skip_test: true
67+
vars:
68+
router_name: 'my-router'
69+
nat_name: 'my-router-nat'
70+
network_name: 'my-network'
71+
subnet_name: 'my-subnetwork'
72+
address_name: 'nat-manual-ip'
73+
# ToDo: We use a custom code for CREATE since the generated code is erroneously not replacing the generated encoder with the custom one provided
74+
custom_code: !ruby/object:Provider::Terraform::CustomCode
75+
constants: 'templates/terraform/constants/router_nat_address.go.erb'
76+
custom_create: templates/terraform/custom_create/router_nat_address_nested_query_create_encoder.go.erb
77+
pre_delete: templates/terraform/pre_delete/compute_router_nat_address_delete_nat_ips_only.go.erb
78+
encoder: 'templates/terraform/encoders/router_nat_address_patch_on_create.go.erb'
79+
update_encoder: 'templates/terraform/encoders/router_nat_address_update_skip_encoder.go.erb'
80+
custom_diff: [
81+
'resourceComputeRouterNatAddressDrainNatIpsCustomDiff',
82+
]
83+
parameters:
84+
- !ruby/object:Api::Type::ResourceRef
85+
name: 'router'
86+
resource: 'Router'
87+
imports: 'name'
88+
description: |
89+
The name of the Cloud Router in which the referenced NAT service is configured.
90+
required: true
91+
immutable: true
92+
url_param_only: true
93+
- !ruby/object:Api::Type::ResourceRef
94+
name: 'routerNat'
95+
resource: 'RouterNat'
96+
imports: 'name'
97+
api_name: 'name'
98+
description: |
99+
The name of the Nat service in which this address will be configured.
100+
required: true
101+
immutable: true
102+
- !ruby/object:Api::Type::ResourceRef
103+
name: region
104+
resource: Region
105+
imports: name
106+
description: Region where the NAT service reside.
107+
immutable: true
108+
required: false
109+
url_param_only: true
110+
default_from_api: true
111+
custom_flatten: 'templates/terraform/custom_flatten/name_from_self_link.erb'
112+
properties:
113+
- !ruby/object:Api::Type::Array
114+
name: 'natIps'
115+
description: |
116+
Self-links of NAT IPs to be used in a Nat service. Only valid if the referenced RouterNat
117+
natIpAllocateOption is set to MANUAL_ONLY.
118+
send_empty_value: true
119+
required: true
120+
is_set: true
121+
set_hash_func: computeRouterNatIPsHash
122+
item_type: !ruby/object:Api::Type::ResourceRef
123+
name: 'address'
124+
resource: 'Address'
125+
imports: 'selfLink'
126+
description: 'A reference to an address to be associated with this NAT'
127+
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'
128+
- !ruby/object:Api::Type::Array
129+
name: 'drainNatIps'
130+
description: |
131+
A list of URLs of the IP resources to be drained. These IPs must be
132+
valid static external IPs that have been assigned to the NAT.
133+
send_empty_value: true
134+
is_set: true
135+
item_type: !ruby/object:Api::Type::ResourceRef
136+
name: 'address'
137+
resource: 'Address'
138+
imports: 'selfLink'
139+
description: 'A reference to an address associated with this NAT'
140+
custom_expand: 'templates/terraform/custom_expand/array_resourceref_with_validation.go.erb'

mmv1/templates/terraform/constants/router_nat.go.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -174,4 +174,4 @@ func computeRouterNatRulesHash(v interface{}) int {
174174
routerNatRulesHash += sourceNatActiveRangeHash + sourceNatDrainRangeHash
175175
<% end -%>
176176
return routerNatRulesHash
177-
}
177+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
<%- # the license inside this block applies to this file
2+
# Copyright 2019 Google Inc.
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
-%>
15+
func addressResourceNameSetFromSelfLinkSet(v interface{}) *schema.Set {
16+
if v == nil {
17+
return schema.NewSet(schema.HashString, nil)
18+
}
19+
vSet := v.(*schema.Set)
20+
ls := make([]interface{}, 0, vSet.Len())
21+
for _, v := range vSet.List() {
22+
if v == nil {
23+
continue
24+
}
25+
ls = append(ls, tpgresource.GetResourceNameFromSelfLink(v.(string)))
26+
}
27+
return schema.NewSet(schema.HashString, ls)
28+
}
29+
30+
// drain_nat_ips MUST be set from (just set) previous values of nat_ips
31+
// so this customizeDiff func makes sure drainNatIps values:
32+
// - aren't set at creation time
33+
// - are in old value of nat_ips but not in new values
34+
func resourceComputeRouterNatAddressDrainNatIpsCustomDiff(_ context.Context, diff *schema.ResourceDiff, meta interface{}) error {
35+
o, n := diff.GetChange("drain_nat_ips")
36+
oSet := addressResourceNameSetFromSelfLinkSet(o)
37+
nSet := addressResourceNameSetFromSelfLinkSet(n)
38+
addDrainIps := nSet.Difference(oSet)
39+
40+
// We don't care if there are no new drainNatIps
41+
if addDrainIps.Len() == 0 {
42+
return nil
43+
}
44+
45+
// Resource hasn't been created yet - return error
46+
if diff.Id() == "" {
47+
return fmt.Errorf("New RouterNat cannot have drain_nat_ips, got values %+v", addDrainIps.List())
48+
}
49+
//
50+
o, n = diff.GetChange("nat_ips")
51+
oNatSet := addressResourceNameSetFromSelfLinkSet(o)
52+
nNatSet := addressResourceNameSetFromSelfLinkSet(n)
53+
54+
// Resource is being updated - make sure new drainNatIps were in natIps prior d and no longer are in natIps.
55+
for _, v := range addDrainIps.List() {
56+
if !oNatSet.Contains(v) {
57+
return fmt.Errorf("drain_nat_ip %q was not previously set in nat_ips %+v", v.(string), oNatSet.List())
58+
}
59+
if nNatSet.Contains(v) {
60+
return fmt.Errorf("drain_nat_ip %q cannot be drained if still set in nat_ips %+v", v.(string), nNatSet.List())
61+
}
62+
}
63+
return nil
64+
}
65+
66+
func resourceComputeRouterNatAddressDeleteOnlyNatIps(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
67+
items, err := resourceComputeRouterNatAddressListForPatch(d, meta)
68+
if err != nil {
69+
return nil, err
70+
}
71+
72+
idx, item, err := resourceComputeRouterNatAddressFindNestedObjectInList(d, meta, items)
73+
if err != nil {
74+
return nil, err
75+
}
76+
77+
// Return error if item to update does not exist.
78+
if item == nil {
79+
return nil, fmt.Errorf("Unable to update RouterNatAddress %q - not found in list", d.Id())
80+
}
81+
82+
if item["natIps"] != nil {
83+
croppedNatIps := item["natIps"].([]interface{})[:1]
84+
item["natIps"] = croppedNatIps
85+
}
86+
87+
items[idx] = item
88+
// Return list with new item added
89+
res := map[string]interface{}{
90+
"nats": items,
91+
}
92+
return res, nil
93+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// 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;
2+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
3+
if err != nil {
4+
return err
5+
}
6+
7+
obj := make(map[string]interface{})
8+
natIpsProp, err := expandNestedComputeRouterNatAddressNatIps(d.Get("nat_ips"), d, config)
9+
if err != nil {
10+
return err
11+
} else if v, ok := d.GetOkExists("nat_ips"); ok || !reflect.DeepEqual(v, natIpsProp) {
12+
obj["natIps"] = natIpsProp
13+
}
14+
drainNatIpsProp, err := expandNestedComputeRouterNatAddressDrainNatIps(d.Get("drain_nat_ips"), d, config)
15+
if err != nil {
16+
return err
17+
} else if v, ok := d.GetOkExists("drain_nat_ips"); ok || !reflect.DeepEqual(v, drainNatIpsProp) {
18+
obj["drainNatIps"] = drainNatIpsProp
19+
}
20+
nameProp, err := expandNestedComputeRouterNatAddressRouterNat(d.Get("router_nat"), d, config)
21+
if err != nil {
22+
return err
23+
} else if v, ok := d.GetOkExists("router_nat"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
24+
obj["name"] = nameProp
25+
}
26+
27+
log.Printf("[DEBUG] Creating new RouterNatAddress: %#v", obj)
28+
29+
obj, err = resourceComputeRouterNatAddressEncoder(d, meta, obj)
30+
if err != nil {
31+
return err
32+
}
33+
34+
lockName, err := tpgresource.ReplaceVars(d, config, "router/{{region}}/{{router}}")
35+
if err != nil {
36+
return err
37+
}
38+
transport_tpg.MutexStore.Lock(lockName)
39+
defer transport_tpg.MutexStore.Unlock(lockName)
40+
41+
url, err := tpgresource.ReplaceVars(d, config, "{{ComputeBasePath}}projects/{{project}}/regions/{{region}}/routers/{{router}}")
42+
if err != nil {
43+
return err
44+
}
45+
46+
billingProject := ""
47+
48+
project, err := tpgresource.GetProject(d, config)
49+
if err != nil {
50+
return fmt.Errorf("Error fetching project for RouterNatAddress: %s", err)
51+
}
52+
billingProject = project
53+
54+
// err == nil indicates that the billing_project value was found
55+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
56+
billingProject = bp
57+
}
58+
59+
headers := make(http.Header)
60+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
61+
Config: config,
62+
Method: "PATCH",
63+
Project: billingProject,
64+
RawURL: url,
65+
UserAgent: userAgent,
66+
Body: obj,
67+
Timeout: d.Timeout(schema.TimeoutCreate),
68+
Headers: headers,
69+
})
70+
if err != nil {
71+
return fmt.Errorf("Error creating RouterNatAddress: %s", err)
72+
}
73+
74+
// Store the ID now
75+
id, err := tpgresource.ReplaceVars(d, config, "projects/{{project}}/regions/{{region}}/routers/{{router}}/{{router_nat}}")
76+
if err != nil {
77+
return fmt.Errorf("Error constructing id: %s", err)
78+
}
79+
d.SetId(id)
80+
81+
err = ComputeOperationWaitTime(
82+
config, res, project, "Creating RouterNatAddress", userAgent,
83+
d.Timeout(schema.TimeoutCreate))
84+
85+
if err != nil {
86+
// The resource didn't actually create
87+
d.SetId("")
88+
return fmt.Errorf("Error waiting to create RouterNatAddress: %s", err)
89+
}
90+
91+
log.Printf("[DEBUG] Finished creating RouterNatAddress %q: %#v", d.Id(), res)
92+
93+
return resourceComputeRouterNatAddressRead(d, meta)

0 commit comments

Comments
 (0)