Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions mmv1/products/apigee/Organization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ async:
resource_inside_response: true
sweeper:
identifier_field: "organization"
read_error_transform: 'transformApigeeOrganizationReadError'
custom_code:
encoder: 'templates/terraform/encoders/apigee_organization.go.tmpl'
custom_import: 'templates/terraform/custom_import/apigee_organization.go.tmpl'
Expand Down
1 change: 1 addition & 0 deletions mmv1/products/apigee/TargetServer.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ properties:
description: |
Enabling/disabling a TargetServer is useful when TargetServers are used in load balancing configurations, and one or more TargetServers need to taken out of rotation periodically. Defaults to true.
default_value: true
send_empty_value: true
- name: 'sSLInfo'
type: NestedObject
description: Specifies TLS configuration info for this TargetServer. The JSON name is sSLInfo for legacy/backwards compatibility reasons -- Edge originally supported SSL, and the name is still used for TLS configuration.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package apigee

import (
"log"
"strings"

"github.com/hashicorp/errwrap"
"google.golang.org/api/googleapi"
)

// transformApigeeOrganizationReadError converts a 403 "permission denied (or
// it may not exist)" error into a 404 so that Terraform's standard
// HandleNotFoundError logic can detect that the organization has been deleted
// out-of-band and propose re-creation rather than surfacing an opaque
// permission error.
//
// Background: when an Apigee organization is deleted via the management API
// (not through Terraform), Terraform still holds state for the resource. On
// the next plan/apply Terraform calls the Read function which GETs
// /v1/organizations/<name>. Because the organization no longer exists the
// Apigee API returns HTTP 403 with a message containing "(or it may not
// exist)" instead of the more intuitive 404 — this is intentional API
// behaviour to avoid leaking existence information to callers that lack
// read access. From Terraform's perspective this ambiguous 403 must be
// treated as "resource is gone" so that the plan shows an add rather than
// failing with an access-denied error.
func transformApigeeOrganizationReadError(err error) error {
if gErr, ok := errwrap.GetType(err, &googleapi.Error{}).(*googleapi.Error); ok {
if gErr.Code == 403 && strings.Contains(gErr.Message, "(or it may not exist)") {
// Rewrite the status code so HandleNotFoundError treats this as a
// deleted resource and schedules re-creation on the next apply.
gErr.Code = 404
}

log.Printf("[DEBUG] Transformed ApigeeOrganization read error: %v", gErr)
return gErr
}

return err
}
Original file line number Diff line number Diff line change
Expand Up @@ -478,3 +478,208 @@ resource "google_apigee_target_server" "apigee_target_server"{
}
`, context)
}

func TestAccApigeeTargetServer_apigeeTargetServerIsEnabledFalse(t *testing.T) {
t.Parallel()

context := map[string]interface{}{
"org_id": envvar.GetTestOrgFromEnv(t),
"billing_account": envvar.GetTestBillingAccountFromEnv(t),
"random_suffix": acctest.RandString(t, 10),
}

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
ExternalProviders: map[string]resource.ExternalProvider{
"time": {},
},
CheckDestroy: testAccCheckApigeeTargetServerDestroyProducer(t),
Steps: []resource.TestStep{
{
// Create with is_enabled = false; previously the field was never sent
// due to the IsEmptyValue guard treating false as empty, causing the
// API to default is_enabled to true regardless.
Config: testAccApigeeTargetServer_isEnabledFalse(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("google_apigee_target_server.apigee_target_server", "is_enabled", "false"),
),
},
{
ResourceName: "google_apigee_target_server.apigee_target_server",
ImportState: true,
ImportStateVerify: true,
ImportStateVerifyIgnore: []string{"env_id"},
},
{
// Update is_enabled back to true
Config: testAccApigeeTargetServer_isEnabledTrue(context),
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("google_apigee_target_server.apigee_target_server", "is_enabled", "true"),
),
},
},
})
}

func testAccApigeeTargetServer_isEnabledFalse(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_project" "project" {
project_id = "tf-test%{random_suffix}"
name = "tf-test%{random_suffix}"
org_id = "%{org_id}"
billing_account = "%{billing_account}"
deletion_policy = "DELETE"
}

resource "google_project_service" "apigee" {
project = google_project.project.project_id
service = "apigee.googleapis.com"
}

resource "google_project_service" "servicenetworking" {
project = google_project.project.project_id
service = "servicenetworking.googleapis.com"
depends_on = [google_project_service.apigee]
}

resource "google_project_service" "compute" {
project = google_project.project.project_id
service = "compute.googleapis.com"
depends_on = [google_project_service.servicenetworking]
}

resource "time_sleep" "wait_300_seconds" {
create_duration = "300s"
depends_on = [google_project_service.compute]
}

resource "google_compute_network" "apigee_network" {
name = "apigee-network"
project = google_project.project.project_id
depends_on = [time_sleep.wait_300_seconds]
}

resource "google_compute_global_address" "apigee_range" {
name = "apigee-range"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.apigee_network.id
project = google_project.project.project_id
}

resource "google_service_networking_connection" "apigee_vpc_connection" {
network = google_compute_network.apigee_network.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.apigee_range.name]
depends_on = [google_project_service.servicenetworking]
}

resource "google_apigee_organization" "apigee_org" {
analytics_region = "us-central1"
project_id = google_project.project.project_id
authorized_network = google_compute_network.apigee_network.id
depends_on = [
google_service_networking_connection.apigee_vpc_connection,
google_project_service.apigee,
]
}

resource "google_apigee_environment" "apigee_environment" {
org_id = google_apigee_organization.apigee_org.id
name = "tf-test%{random_suffix}"
description = "Apigee Environment"
display_name = "environment-1"
}

resource "google_apigee_target_server" "apigee_target_server" {
name = "tf-test-target-server%{random_suffix}"
host = "abc.foo.com"
port = 8080
is_enabled = false
env_id = google_apigee_environment.apigee_environment.id
}
`, context)
}

func testAccApigeeTargetServer_isEnabledTrue(context map[string]interface{}) string {
return acctest.Nprintf(`
resource "google_project" "project" {
project_id = "tf-test%{random_suffix}"
name = "tf-test%{random_suffix}"
org_id = "%{org_id}"
billing_account = "%{billing_account}"
deletion_policy = "DELETE"
}

resource "google_project_service" "apigee" {
project = google_project.project.project_id
service = "apigee.googleapis.com"
}

resource "google_project_service" "servicenetworking" {
project = google_project.project.project_id
service = "servicenetworking.googleapis.com"
depends_on = [google_project_service.apigee]
}

resource "google_project_service" "compute" {
project = google_project.project.project_id
service = "compute.googleapis.com"
depends_on = [google_project_service.servicenetworking]
}

resource "time_sleep" "wait_300_seconds" {
create_duration = "300s"
depends_on = [google_project_service.compute]
}

resource "google_compute_network" "apigee_network" {
name = "apigee-network"
project = google_project.project.project_id
depends_on = [time_sleep.wait_300_seconds]
}

resource "google_compute_global_address" "apigee_range" {
name = "apigee-range"
purpose = "VPC_PEERING"
address_type = "INTERNAL"
prefix_length = 16
network = google_compute_network.apigee_network.id
project = google_project.project.project_id
}

resource "google_service_networking_connection" "apigee_vpc_connection" {
network = google_compute_network.apigee_network.id
service = "servicenetworking.googleapis.com"
reserved_peering_ranges = [google_compute_global_address.apigee_range.name]
depends_on = [google_project_service.servicenetworking]
}

resource "google_apigee_organization" "apigee_org" {
analytics_region = "us-central1"
project_id = google_project.project.project_id
authorized_network = google_compute_network.apigee_network.id
depends_on = [
google_service_networking_connection.apigee_vpc_connection,
google_project_service.apigee,
]
}

resource "google_apigee_environment" "apigee_environment" {
org_id = google_apigee_organization.apigee_org.id
name = "tf-test%{random_suffix}"
description = "Apigee Environment"
display_name = "environment-1"
}

resource "google_apigee_target_server" "apigee_target_server" {
name = "tf-test-target-server%{random_suffix}"
host = "abc.foo.com"
port = 8080
is_enabled = true
env_id = google_apigee_environment.apigee_environment.id
}
`, context)
}
Loading