Skip to content

Commit 6f14c59

Browse files
entertvltrodge
authored and
Philip Jonany
committed
feat(apigee): Add support for NatAddress activation (GoogleCloudPlatform#11698)
Co-authored-by: Thomas Rodgers <[email protected]>
1 parent e613ad4 commit 6f14c59

11 files changed

+400
-4
lines changed

mmv1/products/apigee/NatAddress.yaml

+22-3
Original file line numberDiff line numberDiff line change
@@ -34,15 +34,13 @@ async: !ruby/object:Api::OpAsync
3434
error: !ruby/object:Api::OpAsync::Error
3535
path: 'error'
3636
message: 'message'
37-
immutable: true
37+
immutable: false
3838
description: |
3939
Apigee NAT (network address translation) address. A NAT address is a static external IP address used for Internet egress traffic. This is not avaible for Apigee hybrid.
40-
Apigee NAT addresses are not automatically activated because they might require explicit allow entries on the target systems first. See https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.instances.natAddresses/activate
4140
references: !ruby/object:Api::Resource::ReferenceLinks
4241
guides:
4342
'Provisioning NAT IPs': 'https://cloud.google.com/apigee/docs/api-platform/security/nat-provisioning'
4443
api: 'https://cloud.google.com/apigee/docs/reference/apis/apigee/rest/v1/organizations.instances.natAddresses'
45-
4644
autogen_async: true
4745
import_format:
4846
['{{instance_id}}/natAddresses/{{name}}', '{{instance_id}}/{{name}}']
@@ -66,10 +64,23 @@ examples:
6664
true
6765
# Resource creation race
6866
skip_vcr: true
67+
- !ruby/object:Provider::Terraform::Examples
68+
name: 'apigee_nat_address_with_activate'
69+
vars:
70+
nat_address_name: 'my-nat-address'
71+
nat_address_activate: 'true'
72+
skip_test:
73+
true
6974
timeouts: !ruby/object:Api::Timeouts
7075
insert_minutes: 30
76+
update_minutes: 30
7177
delete_minutes: 30
7278
custom_code: !ruby/object:Provider::Terraform::CustomCode
79+
constants: templates/terraform/constants/apigee_nat_address.go.erb
80+
encoder: templates/terraform/encoders/apigee_nat_address.go.erb
81+
decoder: templates/terraform/decoders/apigee_nat_address.go.erb
82+
custom_update: templates/terraform/custom_update/apigee_nat_address.go.erb
83+
post_create: templates/terraform/post_create/apigee_nat_address.go.erb
7384
custom_import: templates/terraform/custom_import/apigee_nat_address.go.erb
7485
parameters:
7586
- !ruby/object:Api::Type::String
@@ -78,13 +89,21 @@ parameters:
7889
The Apigee instance associated with the Apigee environment,
7990
in the format `organizations/{{org_name}}/instances/{{instance_name}}`.
8091
required: true
92+
immutable: true
8193
url_param_only: true
8294
properties:
8395
- !ruby/object:Api::Type::String
8496
name: 'name'
8597
description: |
8698
Resource ID of the NAT address.
8799
required: true
100+
immutable: true
101+
- !ruby/object:Api::Type::Boolean
102+
name: 'activate'
103+
description: |
104+
Flag that specifies whether the reserved NAT address should be activate.
105+
required: false
106+
default_value: false
88107
- !ruby/object:Api::Type::String
89108
name: 'ipAddress'
90109
description: |
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<% unless compiler == "terraformgoogleconversion-codegen" -%>
2+
// waitForNatAddressReady waits for an NatAddress to leave the
3+
// "CREATING" state and become "RESERVED", to indicate that it's ready.
4+
func waitForNatAddressReserved(d *schema.ResourceData, config *transport_tpg.Config, timeout time.Duration) error {
5+
return retry.Retry(timeout, func() *retry.RetryError {
6+
if err := resourceApigeeNatAddressRead(d, config); err != nil {
7+
return retry.NonRetryableError(err)
8+
}
9+
10+
id := d.Id()
11+
state := d.Get("state").(string)
12+
if state == "CREATING" {
13+
return retry.RetryableError(fmt.Errorf("NatAddress %q has state %q.", id, state))
14+
} else if state == "RESERVED" {
15+
log.Printf("[DEBUG] NatAddress %q has state %q.", id, state)
16+
return nil
17+
} else {
18+
return retry.NonRetryableError(fmt.Errorf("NatAddress %q has state %q.", id, state))
19+
}
20+
})
21+
}
22+
<% end -%>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
userAgent, err := tpgresource.GenerateUserAgentString(d, config.UserAgent)
2+
if err != nil {
3+
return err
4+
}
5+
6+
billingProject := ""
7+
8+
obj := make(map[string]interface{})
9+
nameProp, err := expandApigeeNatAddressName(d.Get("name"), d, config)
10+
if err != nil {
11+
return err
12+
} else if v, ok := d.GetOkExists("name"); !tpgresource.IsEmptyValue(reflect.ValueOf(v)) && (ok || !reflect.DeepEqual(v, nameProp)) {
13+
obj["name"] = nameProp
14+
}
15+
16+
log.Printf("[DEBUG] Updating NatAddress %q: %#v", d.Id(), obj)
17+
18+
// err == nil indicates that the billing_project value was found
19+
if bp, err := tpgresource.GetBillingProject(d, config); err == nil {
20+
billingProject = bp
21+
}
22+
23+
if d.HasChange("activate") {
24+
if !d.Get("activate").(bool) {
25+
return fmt.Errorf("NatAddress %q allows only the activation action", d.Id())
26+
} else if d.Get("state").(string) == "RESERVED" {
27+
log.Printf("[DEBUG] Activating for NatAddress %q to become ACTIVE", d.Id())
28+
if err := resourceApigeeNatAddressActivate(config, d, billingProject, userAgent); err != nil {
29+
return fmt.Errorf("Error activating NatAddress: %s", err)
30+
}
31+
}
32+
}
33+
34+
return resourceApigeeNatAddressRead(d, meta)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
res["activate"] = res["state"].(string) == "ACTIVE"
2+
return res, nil
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// cannot include activate prop in the body
2+
delete(obj, "activate")
3+
return obj, nil

mmv1/templates/terraform/examples/apigee_nat_address_basic_test.tf.erb

+1-1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,6 @@ resource "google_apigee_instance" "apigee_instance" {
6060
}
6161

6262
resource "google_apigee_nat_address" "<%= ctx[:primary_resource_id] %>" {
63-
name = "tf-test%{random_suffix}"
63+
name = "tf-test%{random_suffix}"
6464
instance_id = google_apigee_instance.apigee_instance.id
6565
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
data "google_client_config" "current" {}
2+
3+
resource "google_compute_network" "apigee_network" {
4+
name = "apigee-network"
5+
}
6+
7+
resource "google_compute_global_address" "apigee_range" {
8+
name = "apigee-range"
9+
purpose = "VPC_PEERING"
10+
address_type = "INTERNAL"
11+
prefix_length = 21
12+
network = google_compute_network.apigee_network.id
13+
}
14+
15+
resource "google_service_networking_connection" "apigee_vpc_connection" {
16+
network = google_compute_network.apigee_network.id
17+
service = "servicenetworking.googleapis.com"
18+
reserved_peering_ranges = [google_compute_global_address.apigee_range.name]
19+
}
20+
21+
resource "google_kms_key_ring" "apigee_keyring" {
22+
name = "apigee-keyring"
23+
location = "us-central1"
24+
}
25+
26+
resource "google_kms_crypto_key" "apigee_key" {
27+
name = "apigee-key"
28+
key_ring = google_kms_key_ring.apigee_keyring.id
29+
30+
lifecycle {
31+
prevent_destroy = true
32+
}
33+
}
34+
35+
resource "google_project_service_identity" "apigee_sa" {
36+
provider = google-beta
37+
project = google_project.project.project_id
38+
service = google_project_service.apigee.service
39+
}
40+
41+
resource "google_kms_crypto_key_iam_member" "apigee_sa_keyuser" {
42+
crypto_key_id = google_kms_crypto_key.apigee_key.id
43+
role = "roles/cloudkms.cryptoKeyEncrypterDecrypter"
44+
45+
member = google_project_service_identity.apigee_sa.member
46+
}
47+
48+
resource "google_apigee_organization" "apigee_org" {
49+
analytics_region = "us-central1"
50+
display_name = "apigee-org"
51+
description = "Terraform-provisioned Apigee Org."
52+
project_id = data.google_client_config.current.project
53+
authorized_network = google_compute_network.apigee_network.id
54+
runtime_database_encryption_key_name = google_kms_crypto_key.apigee_key.id
55+
56+
depends_on = [
57+
google_service_networking_connection.apigee_vpc_connection,
58+
google_kms_crypto_key_iam_member.apigee_sa_keyuser,
59+
]
60+
}
61+
62+
resource "google_apigee_instance" "apigee_instance" {
63+
name = "apigee-instance"
64+
location = "us-central1"
65+
description = "Terraform-managed Apigee Runtime Instance"
66+
display_name = "apigee-instance"
67+
org_id = google_apigee_organization.apigee_org.id
68+
disk_encryption_key_name = google_kms_crypto_key.apigee_key.id
69+
}
70+
71+
resource "google_apigee_nat_address" "apigee-nat" {
72+
name = "<%= ctx[:vars]['nat_address_name'] %>"
73+
activate = "<%= ctx[:vars]['nat_address_activate'] %>"
74+
instance_id = google_apigee_instance.apigee_instance.id
75+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
resource "google_project" "project" {
2+
project_id = "tf-test%{random_suffix}"
3+
name = "tf-test%{random_suffix}"
4+
org_id = "<%= ctx[:test_env_vars]['org_id'] %>"
5+
billing_account = "<%= ctx[:test_env_vars]['billing_account'] %>"
6+
deletion_policy = "DELETE"
7+
}
8+
9+
resource "google_project_service" "apigee" {
10+
project = google_project.project.project_id
11+
service = "apigee.googleapis.com"
12+
}
13+
14+
resource "google_project_service" "compute" {
15+
project = google_project.project.project_id
16+
service = "compute.googleapis.com"
17+
}
18+
19+
resource "google_project_service" "servicenetworking" {
20+
project = google_project.project.project_id
21+
service = "servicenetworking.googleapis.com"
22+
}
23+
24+
resource "google_compute_network" "apigee_network" {
25+
name = "apigee-network"
26+
project = google_project.project.project_id
27+
depends_on = [google_project_service.compute]
28+
}
29+
30+
resource "google_compute_global_address" "apigee_range" {
31+
name = "apigee-range"
32+
purpose = "VPC_PEERING"
33+
address_type = "INTERNAL"
34+
prefix_length = 21
35+
network = google_compute_network.apigee_network.id
36+
project = google_project.project.project_id
37+
}
38+
39+
resource "google_service_networking_connection" "apigee_vpc_connection" {
40+
network = google_compute_network.apigee_network.id
41+
service = "servicenetworking.googleapis.com"
42+
reserved_peering_ranges = [google_compute_global_address.apigee_range.name]
43+
depends_on = [google_project_service.servicenetworking]
44+
}
45+
46+
resource "google_apigee_organization" "apigee_org" {
47+
analytics_region = "us-central1"
48+
project_id = google_project.project.project_id
49+
authorized_network = google_compute_network.apigee_network.id
50+
depends_on = [
51+
google_service_networking_connection.apigee_vpc_connection,
52+
google_project_service.apigee,
53+
]
54+
}
55+
56+
resource "google_apigee_instance" "apigee_instance" {
57+
name = "apigee-instance"
58+
location = "us-central1"
59+
org_id = google_apigee_organization.apigee_org.id
60+
}
61+
62+
resource "google_apigee_nat_address" "<%= ctx[:primary_resource_id] %>" {
63+
name = "tf-test%{random_suffix}"
64+
activate = true
65+
instance_id = google_apigee_instance.apigee_instance.id
66+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
if d.Get("activate").(bool) {
2+
if err := waitForNatAddressReserved(d, config, d.Timeout(schema.TimeoutCreate) - time.Minute); err != nil {
3+
return fmt.Errorf("Error waiting for NatAddress %q to be RESERVED during creation: %q", d.Id(), err)
4+
}
5+
6+
log.Printf("[DEBUG] Activating for NatAddress %q to become ACTIVE", d.Id())
7+
if err := resourceApigeeNatAddressActivate(config, d, billingProject, userAgent); err != nil {
8+
return fmt.Errorf("Error activating NatAddress: %s", err)
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package apigee
2+
3+
import (
4+
"fmt"
5+
"log"
6+
7+
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
8+
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
9+
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
10+
)
11+
12+
func resourceApigeeNatAddressActivate(config *transport_tpg.Config, d *schema.ResourceData, billingProject string, userAgent string) error {
13+
// 1. check prepare for activation
14+
name := d.Get("name").(string)
15+
16+
if d.Get("state").(string) != "RESERVED" {
17+
return fmt.Errorf("Activating NAT address requires the state to become RESERVED")
18+
}
19+
20+
// 2. activation
21+
activateUrl, err := tpgresource.ReplaceVars(d, config, "{{ApigeeBasePath}}{{instance_id}}/natAddresses/{{name}}:activate")
22+
if err != nil {
23+
return err
24+
}
25+
log.Printf("[DEBUG] Activating NAT address: %s", name)
26+
res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
27+
Config: config,
28+
Method: "POST",
29+
Project: billingProject,
30+
RawURL: activateUrl,
31+
UserAgent: userAgent,
32+
})
33+
if err != nil {
34+
return fmt.Errorf("Error activating NAT address: %s", err)
35+
}
36+
37+
var opRes map[string]interface{}
38+
err = ApigeeOperationWaitTimeWithResponse(
39+
config, res, &opRes, "Activating NAT address", userAgent,
40+
d.Timeout(schema.TimeoutCreate))
41+
if err != nil {
42+
return fmt.Errorf("Error waiting to actiavte NAT address: %s", err)
43+
} else {
44+
log.Printf("[DEBUG] Finished activating NatAddress %q: %#v", d.Id(), res)
45+
}
46+
return nil
47+
}

0 commit comments

Comments
 (0)