Skip to content

Commit 280315a

Browse files
feat(apigee): Add support for NatAddress activation (#11698) (#8261)
[upstream:7d76b1ba0d9f366fae4fc5e1159952eee5087661] Signed-off-by: Modular Magician <[email protected]>
1 parent 8ea0746 commit 280315a

6 files changed

+390
-2
lines changed

.changelog/11698.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:enhancement
2+
apigee: Add support for NAT Address activation in `google_apigee_nat_address`.
3+
```
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package apigee
4+
5+
import (
6+
"fmt"
7+
"log"
8+
9+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
10+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"
11+
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
12+
)
13+
14+
func resourceApigeeNatAddressActivate(config *transport_tpg.Config, d *schema.ResourceData, billingProject string, userAgent string) error {
15+
// 1. check prepare for activation
16+
name := d.Get("name").(string)
17+
18+
if d.Get("state").(string) != "RESERVED" {
19+
return fmt.Errorf("Activating NAT address requires the state to become RESERVED")
20+
}
21+
22+
// 2. activation
23+
activateUrl, err := tpgresource.ReplaceVars(d, config, "{{ApigeeBasePath}}{{instance_id}}/natAddresses/{{name}}:activate")
24+
if err != nil {
25+
return err
26+
}
27+
log.Printf("[DEBUG] Activating NAT address: %s", name)
28+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
29+
Config: config,
30+
Method: "POST",
31+
Project: billingProject,
32+
RawURL: activateUrl,
33+
UserAgent: userAgent,
34+
})
35+
if err != nil {
36+
return fmt.Errorf("Error activating NAT address: %s", err)
37+
}
38+
39+
var opRes map[string]interface{}
40+
err = ApigeeOperationWaitTimeWithResponse(
41+
config, res, &opRes, "Activating NAT address", userAgent,
42+
d.Timeout(schema.TimeoutCreate))
43+
if err != nil {
44+
return fmt.Errorf("Error waiting to actiavte NAT address: %s", err)
45+
} else {
46+
log.Printf("[DEBUG] Finished activating NatAddress %q: %#v", d.Id(), res)
47+
}
48+
return nil
49+
}

google-beta/services/apigee/resource_apigee_nat_address.go

+132
Original file line numberDiff line numberDiff line change
@@ -24,16 +24,39 @@ import (
2424
"reflect"
2525
"time"
2626

27+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/retry"
2728
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
2829

2930
"github.com/hashicorp/terraform-provider-google-beta/google-beta/tpgresource"
3031
transport_tpg "github.com/hashicorp/terraform-provider-google-beta/google-beta/transport"
3132
)
3233

34+
// waitForNatAddressReady waits for an NatAddress to leave the
35+
// "CREATING" state and become "RESERVED", to indicate that it's ready.
36+
func waitForNatAddressReserved(d *schema.ResourceData, config *transport_tpg.Config, timeout time.Duration) error {
37+
return retry.Retry(timeout, func() *retry.RetryError {
38+
if err := resourceApigeeNatAddressRead(d, config); err != nil {
39+
return retry.NonRetryableError(err)
40+
}
41+
42+
id := d.Id()
43+
state := d.Get("state").(string)
44+
if state == "CREATING" {
45+
return retry.RetryableError(fmt.Errorf("NatAddress %q has state %q.", id, state))
46+
} else if state == "RESERVED" {
47+
log.Printf("[DEBUG] NatAddress %q has state %q.", id, state)
48+
return nil
49+
} else {
50+
return retry.NonRetryableError(fmt.Errorf("NatAddress %q has state %q.", id, state))
51+
}
52+
})
53+
}
54+
3355
func ResourceApigeeNatAddress() *schema.Resource {
3456
return &schema.Resource{
3557
Create: resourceApigeeNatAddressCreate,
3658
Read: resourceApigeeNatAddressRead,
59+
Update: resourceApigeeNatAddressUpdate,
3760
Delete: resourceApigeeNatAddressDelete,
3861

3962
Importer: &schema.ResourceImporter{
@@ -42,6 +65,7 @@ func ResourceApigeeNatAddress() *schema.Resource {
4265

4366
Timeouts: &schema.ResourceTimeout{
4467
Create: schema.DefaultTimeout(30 * time.Minute),
68+
Update: schema.DefaultTimeout(30 * time.Minute),
4569
Delete: schema.DefaultTimeout(30 * time.Minute),
4670
},
4771

@@ -59,6 +83,12 @@ in the format 'organizations/{{org_name}}/instances/{{instance_name}}'.`,
5983
ForceNew: true,
6084
Description: `Resource ID of the NAT address.`,
6185
},
86+
"activate": {
87+
Type: schema.TypeBool,
88+
Optional: true,
89+
Description: `Flag that specifies whether the reserved NAT address should be activate.`,
90+
Default: false,
91+
},
6292
"ip_address": {
6393
Type: schema.TypeString,
6494
Computed: true,
@@ -88,6 +118,17 @@ func resourceApigeeNatAddressCreate(d *schema.ResourceData, meta interface{}) er
88118
} else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(nameProp)) && (ok || !reflect.DeepEqual(v, nameProp)) {
89119
obj["name"] = nameProp
90120
}
121+
activateProp, err := expandApigeeNatAddressActivate(d.Get("activate"), d, config)
122+
if err != nil {
123+
return err
124+
} else if v, ok := d.GetOkExists("activate"); !tpgresource.IsEmptyValue(reflect.ValueOf(activateProp)) && (ok || !reflect.DeepEqual(v, activateProp)) {
125+
obj["activate"] = activateProp
126+
}
127+
128+
obj, err = resourceApigeeNatAddressEncoder(d, meta, obj)
129+
if err != nil {
130+
return err
131+
}
91132

92133
url, err := tpgresource.ReplaceVars(d, config, "{{ApigeeBasePath}}{{instance_id}}/natAddresses")
93134
if err != nil {
@@ -137,6 +178,14 @@ func resourceApigeeNatAddressCreate(d *schema.ResourceData, meta interface{}) er
137178
return fmt.Errorf("Error waiting to create NatAddress: %s", err)
138179
}
139180

181+
opRes, err = resourceApigeeNatAddressDecoder(d, meta, opRes)
182+
if err != nil {
183+
return fmt.Errorf("Error decoding response from operation: %s", err)
184+
}
185+
if opRes == nil {
186+
return fmt.Errorf("Error decoding response from operation, could not find object")
187+
}
188+
140189
if err := d.Set("name", flattenApigeeNatAddressName(opRes["name"], d, config)); err != nil {
141190
return err
142191
}
@@ -148,6 +197,17 @@ func resourceApigeeNatAddressCreate(d *schema.ResourceData, meta interface{}) er
148197
}
149198
d.SetId(id)
150199

200+
if d.Get("activate").(bool) {
201+
if err := waitForNatAddressReserved(d, config, d.Timeout(schema.TimeoutCreate)-time.Minute); err != nil {
202+
return fmt.Errorf("Error waiting for NatAddress %q to be RESERVED during creation: %q", d.Id(), err)
203+
}
204+
205+
log.Printf("[DEBUG] Activating for NatAddress %q to become ACTIVE", d.Id())
206+
if err := resourceApigeeNatAddressActivate(config, d, billingProject, userAgent); err != nil {
207+
return fmt.Errorf("Error activating NatAddress: %s", err)
208+
}
209+
}
210+
151211
log.Printf("[DEBUG] Finished creating NatAddress %q: %#v", d.Id(), res)
152212

153213
return resourceApigeeNatAddressRead(d, meta)
@@ -185,9 +245,24 @@ func resourceApigeeNatAddressRead(d *schema.ResourceData, meta interface{}) erro
185245
return transport_tpg.HandleNotFoundError(err, d, fmt.Sprintf("ApigeeNatAddress %q", d.Id()))
186246
}
187247

248+
res, err = resourceApigeeNatAddressDecoder(d, meta, res)
249+
if err != nil {
250+
return err
251+
}
252+
253+
if res == nil {
254+
// Decoding the object has resulted in it being gone. It may be marked deleted
255+
log.Printf("[DEBUG] Removing ApigeeNatAddress because it no longer exists.")
256+
d.SetId("")
257+
return nil
258+
}
259+
188260
if err := d.Set("name", flattenApigeeNatAddressName(res["name"], d, config)); err != nil {
189261
return fmt.Errorf("Error reading NatAddress: %s", err)
190262
}
263+
if err := d.Set("activate", flattenApigeeNatAddressActivate(res["activate"], d, config)); err != nil {
264+
return fmt.Errorf("Error reading NatAddress: %s", err)
265+
}
191266
if err := d.Set("ip_address", flattenApigeeNatAddressIpAddress(res["ipAddress"], d, config)); err != nil {
192267
return fmt.Errorf("Error reading NatAddress: %s", err)
193268
}
@@ -198,6 +273,44 @@ func resourceApigeeNatAddressRead(d *schema.ResourceData, meta interface{}) erro
198273
return nil
199274
}
200275

276+
func resourceApigeeNatAddressUpdate(d *schema.ResourceData, meta interface{}) error {
277+
config := meta.(*transport_tpg.Config)
278+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
279+
if err != nil {
280+
return err
281+
}
282+
283+
billingProject := ""
284+
285+
obj := make(map[string]interface{})
286+
nameProp, err := expandApigeeNatAddressName(d.Get("name"), d, config)
287+
if err != nil {
288+
return err
289+
} else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, nameProp)) {
290+
obj["name"] = nameProp
291+
}
292+
293+
log.Printf("[DEBUG] Updating NatAddress %q: %#v", d.Id(), obj)
294+
295+
// err == nil indicates that the billing_project value was found
296+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
297+
billingProject = bp
298+
}
299+
300+
if d.HasChange("activate") {
301+
if !d.Get("activate").(bool) {
302+
return fmt.Errorf("NatAddress %q allows only the activation action", d.Id())
303+
} else if d.Get("state").(string) == "RESERVED" {
304+
log.Printf("[DEBUG] Activating for NatAddress %q to become ACTIVE", d.Id())
305+
if err := resourceApigeeNatAddressActivate(config, d, billingProject, userAgent); err != nil {
306+
return fmt.Errorf("Error activating NatAddress: %s", err)
307+
}
308+
}
309+
}
310+
311+
return resourceApigeeNatAddressRead(d, meta)
312+
}
313+
201314
func resourceApigeeNatAddressDelete(d *schema.ResourceData, meta interface{}) error {
202315
config := meta.(*transport_tpg.Config)
203316
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
@@ -273,6 +386,10 @@ func flattenApigeeNatAddressName(v interface{}, d *schema.ResourceData, config *
273386
return v
274387
}
275388

389+
func flattenApigeeNatAddressActivate(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
390+
return v
391+
}
392+
276393
func flattenApigeeNatAddressIpAddress(v interface{}, d *schema.ResourceData, config *transport_tpg.Config) interface{} {
277394
return v
278395
}
@@ -284,3 +401,18 @@ func flattenApigeeNatAddressState(v interface{}, d *schema.ResourceData, config
284401
func expandApigeeNatAddressName(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
285402
return v, nil
286403
}
404+
405+
func expandApigeeNatAddressActivate(v interface{}, d tpgresource.TerraformResourceData, config *transport_tpg.Config) (interface{}, error) {
406+
return v, nil
407+
}
408+
409+
func resourceApigeeNatAddressEncoder(d *schema.ResourceData, meta interface{}, obj map[string]interface{}) (map[string]interface{}, error) {
410+
// cannot include activate prop in the body
411+
delete(obj, "activate")
412+
return obj, nil
413+
}
414+
415+
func resourceApigeeNatAddressDecoder(d *schema.ResourceData, meta interface{}, res map[string]interface{}) (map[string]interface{}, error) {
416+
res["activate"] = res["state"].(string) == "ACTIVE"
417+
return res, nil
418+
}

google-beta/services/apigee/resource_apigee_nat_address_generated_test.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ resource "google_apigee_instance" "apigee_instance" {
123123
}
124124
125125
resource "google_apigee_nat_address" "apigee_nat_address" {
126-
name = "tf-test%{random_suffix}"
126+
name = "tf-test%{random_suffix}"
127127
instance_id = google_apigee_instance.apigee_instance.id
128128
}
129129
`, context)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: MPL-2.0
3+
package apigee_test
4+
5+
import (
6+
"testing"
7+
8+
"github.com/hashicorp/terraform-plugin-testing/helper/resource"
9+
10+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/acctest"
11+
"github.com/hashicorp/terraform-provider-google-beta/google-beta/envvar"
12+
)
13+
14+
func TestAccApigeeNatAddress_apigeeNatAddressUpdateTest(t *testing.T) {
15+
acctest.SkipIfVcr(t)
16+
t.Parallel()
17+
18+
context := map[string]interface{}{
19+
"billing_account": envvar.GetTestBillingAccountFromEnv(t),
20+
"org_id": envvar.GetTestOrgFromEnv(t),
21+
"random_suffix": acctest.RandString(t, 10),
22+
}
23+
24+
acctest.VcrTest(t, resource.TestCase{
25+
PreCheck: func() { acctest.AccTestPreCheck(t) },
26+
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
27+
CheckDestroy: testAccCheckApigeeNatAddressDestroyProducer(t),
28+
Steps: []resource.TestStep{
29+
{
30+
Config: testAccApigeeNatAddress_apigeeNatAddressBasicTestExample(context),
31+
},
32+
{
33+
ResourceName: "google_apigee_nat_address.apigee_nat_address",
34+
ImportState: true,
35+
ImportStateVerify: true,
36+
ImportStateVerifyIgnore: []string{"activate", "instance_id"},
37+
},
38+
{
39+
Config: testAccApigeeNatAddress_apigeeNatAddressUpdateTest(context),
40+
},
41+
{
42+
ResourceName: "google_apigee_nat_address.apigee_nat_address",
43+
ImportState: true,
44+
ImportStateVerify: true,
45+
ImportStateVerifyIgnore: []string{"activate", "instance_id"},
46+
},
47+
},
48+
})
49+
}
50+
51+
func testAccApigeeNatAddress_apigeeNatAddressUpdateTest(context map[string]interface{}) string {
52+
return acctest.Nprintf(`
53+
resource "google_project" "project" {
54+
project_id = "tf-test%{random_suffix}"
55+
name = "tf-test%{random_suffix}"
56+
org_id = "%{org_id}"
57+
billing_account = "%{billing_account}"
58+
deletion_policy = "DELETE"
59+
}
60+
61+
resource "google_project_service" "apigee" {
62+
project = google_project.project.project_id
63+
service = "apigee.googleapis.com"
64+
}
65+
66+
resource "google_project_service" "compute" {
67+
project = google_project.project.project_id
68+
service = "compute.googleapis.com"
69+
}
70+
71+
resource "google_project_service" "servicenetworking" {
72+
project = google_project.project.project_id
73+
service = "servicenetworking.googleapis.com"
74+
}
75+
76+
resource "google_compute_network" "apigee_network" {
77+
name = "apigee-network"
78+
project = google_project.project.project_id
79+
depends_on = [google_project_service.compute]
80+
}
81+
82+
resource "google_compute_global_address" "apigee_range" {
83+
name = "apigee-range"
84+
purpose = "VPC_PEERING"
85+
address_type = "INTERNAL"
86+
prefix_length = 21
87+
network = google_compute_network.apigee_network.id
88+
project = google_project.project.project_id
89+
}
90+
91+
resource "google_service_networking_connection" "apigee_vpc_connection" {
92+
network = google_compute_network.apigee_network.id
93+
service = "servicenetworking.googleapis.com"
94+
reserved_peering_ranges = [google_compute_global_address.apigee_range.name]
95+
depends_on = [google_project_service.servicenetworking]
96+
}
97+
98+
resource "google_apigee_organization" "apigee_org" {
99+
analytics_region = "us-central1"
100+
project_id = google_project.project.project_id
101+
authorized_network = google_compute_network.apigee_network.id
102+
depends_on = [
103+
google_service_networking_connection.apigee_vpc_connection,
104+
google_project_service.apigee,
105+
]
106+
}
107+
108+
resource "google_apigee_instance" "apigee_instance" {
109+
name = "apigee-instance"
110+
location = "us-central1"
111+
org_id = google_apigee_organization.apigee_org.id
112+
}
113+
114+
resource "google_apigee_nat_address" "apigee_nat_address" {
115+
name = "tf-test%{random_suffix}"
116+
activate = true
117+
instance_id = google_apigee_instance.apigee_instance.id
118+
}
119+
`, context)
120+
}

0 commit comments

Comments
 (0)