From 693da462f749d35bf6a28c3cbb3eab9add2b7f3d Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Thu, 3 Sep 2026 16:10:02 -0700 Subject: [PATCH 1/7] accesscontextmanager: in-place updates for ingress policy --- .../ServicePerimeterIngressPolicy.yaml | 7 +- ...r_service_perimeter_ingress_policy_test.go | 111 +++++++++++++++--- 2 files changed, 97 insertions(+), 21 deletions(-) diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml index dd1b3d2f2763..a99bdb3c5be8 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml @@ -35,9 +35,9 @@ references: docs: base_url: "" self_link: '{{perimeter}}' -create_url: '{{perimeter}}?updateMask=status.ingressPolicies' -delete_url: '{{perimeter}}?updateMask=status.ingressPolicies' -update_url: '{{perimeter}}?updateMask=status.ingressPolicies' +create_url: '{{perimeter}}' +delete_url: '{{perimeter}}' +update_url: '{{perimeter}}' create_verb: PATCH update_verb: PATCH delete_verb: PATCH @@ -76,6 +76,7 @@ custom_code: constants: templates/terraform/constants/access_context_manager.go.tmpl pre_create: templates/terraform/pre_create/access_context_manager_service_perimeter_ingress_policy.go.tmpl post_read: templates/terraform/post_read/access_context_manager_service_perimeter_resource.go.tmpl + pre_update: templates/terraform/pre_create/access_context_manager_service_perimeter_ingress_policy.go.tmpl pre_delete: templates/terraform/pre_delete/access_context_manager_service_perimeter_ingress_policy.go.tmpl custom_import: templates/terraform/custom_import/access_context_manager_service_perimeter_ingress_policy.go.tmpl samples: diff --git a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go index b64339ef4de4..75175b9717b4 100644 --- a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go +++ b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go @@ -190,7 +190,7 @@ func testAccAccessContextManagerServicePerimeterIngressPolicy_updateTest(t *test Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step1(org, policyTitle, perimeterTitle), }, { - Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitle), + Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitle, projectNumber), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction( @@ -211,6 +211,17 @@ func testAccAccessContextManagerServicePerimeterIngressPolicy_updateTest(t *test }, }, }, + { + Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step4(org, policyTitle, perimeterTitle), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction( + "google_access_context_manager_service_perimeter_ingress_policy.test-access1", + plancheck.ResourceActionUpdate, + ), + }, + }, + }, { Config: testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitle), Check: testAccCheckAccessContextManagerServicePerimeterIngressPolicyDestroyProducer(t), @@ -219,7 +230,7 @@ func testAccAccessContextManagerServicePerimeterIngressPolicy_updateTest(t *test }) } -// step 1: create +// step 1: create with identity_type, operations with method and permission selectors func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step1(org, policyTitle, perimeterTitleName string) string { return fmt.Sprintf(` %s @@ -237,22 +248,25 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- method_selectors { method = "*" } + method_selectors { + permission = "storage.objects.get" + } } } } `, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName)) } -// step 2: change identity_type, swap to access_level source and roles -func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitleName string) string { +// step 2: change identity_type, add access_level source, update resources, add roles, omit operations +func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitleName, projectNumber string) string { return fmt.Sprintf(` %s -resource "google_access_context_manager_access_level" "update-test" { +resource "google_access_context_manager_access_level" "update-test1" { parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" - name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/updatetestlevel" - title = "updatetestlevel" - description = "Access level for ingress update test" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/updatetestlevel1" + title = "updatetestlevel1" + description = "Access level 1 for ingress update test" basic { conditions { ip_subnetworks = ["192.0.4.0/24"] @@ -266,27 +280,27 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- ingress_from { identity_type = "ANY_USER_ACCOUNT" sources { - access_level = google_access_context_manager_access_level.update-test.name + access_level = google_access_context_manager_access_level.update-test1.name } } ingress_to { - resources = ["*"] + resources = ["projects/%s"] roles = ["roles/bigquery.admin"] } } -`, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName)) +`, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName), projectNumber) } -// step 3: switch to project resource source and permission selector +// step 3: omit identity_type, add identities, update access_level source and add resource source, update resources and roles func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step3(org, policyTitle, perimeterTitleName, projectNumber string) string { return fmt.Sprintf(` %s -resource "google_access_context_manager_access_level" "update-test" { +resource "google_access_context_manager_access_level" "update-test1" { parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" - name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/updatetestlevel" - title = "updatetestlevel" - description = "Access level for ingress update test" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/updatetestlevel1" + title = "updatetestlevel1" + description = "Access level 1 for ingress update test" basic { conditions { ip_subnetworks = ["192.0.4.0/24"] @@ -294,17 +308,78 @@ resource "google_access_context_manager_access_level" "update-test" { } } +resource "google_access_context_manager_access_level" "update-test2" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/updatetestlevel2" + title = "updatetestlevel2" + description = "Access level 2 for ingress update test" + basic { + conditions { + ip_subnetworks = ["192.0.5.0/24"] + } + } +} + resource "google_access_context_manager_service_perimeter_ingress_policy" "test-access1" { perimeter = google_access_context_manager_service_perimeter.test-access.name title = "ingress policy update test" ingress_from { - identity_type = "ANY_IDENTITY" + identities = ["user:test@google.com"] + sources { + access_level = google_access_context_manager_access_level.update-test2.name + } sources { resource = "projects/%s" } } ingress_to { resources = ["*"] + roles = ["roles/storage.admin"] + } +} +`, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName), projectNumber) +} + +// step 4: update identities, update resource source and omit access_level source, update resources, omit roles and add updated operations +func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step4(org, policyTitle, perimeterTitleName string) string { + return fmt.Sprintf(` +%s + +resource "google_access_context_manager_access_level" "update-test1" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/updatetestlevel1" + title = "updatetestlevel1" + description = "Access level 1 for ingress update test" + basic { + conditions { + ip_subnetworks = ["192.0.4.0/24"] + } + } +} + +resource "google_access_context_manager_access_level" "update-test2" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/accessLevels/updatetestlevel2" + title = "updatetestlevel2" + description = "Access level 2 for ingress update test" + basic { + conditions { + ip_subnetworks = ["192.0.5.0/24"] + } + } +} + +resource "google_access_context_manager_service_perimeter_ingress_policy" "test-access1" { + perimeter = google_access_context_manager_service_perimeter.test-access.name + title = "ingress policy update test" + ingress_from { + identities = ["user:test2@google.com"] + sources { + resource = "projects/123456789" + } + } + ingress_to { + resources = ["projects/123456789"] operations { service_name = "bigquery.googleapis.com" method_selectors { @@ -316,7 +391,7 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- } } } -`, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName), projectNumber) +`, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName)) } func testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName string) string { From 276e58d9554b36bcd8184e07ee67f864c68417b9 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Sep 2026 11:05:21 -0700 Subject: [PATCH 2/7] Update acceptance test for service perimeter ingress policy update --- ...r_service_perimeter_ingress_policy_test.go | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go index 75175b9717b4..58777187687f 100644 --- a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go +++ b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_ingress_policy_test.go @@ -180,7 +180,7 @@ func testAccAccessContextManagerServicePerimeterIngressPolicy_updateTest(t *test policyTitle := acctest.RandString(t, 10) perimeterTitle := "perimeter" - projectNumber := envvar.GetTestProjectNumberFromEnv() + projects := BootstrapServicePerimeterProjects(t, 2) acctest.VcrTest(t, resource.TestCase{ PreCheck: func() { acctest.AccTestPreCheck(t) }, @@ -190,7 +190,7 @@ func testAccAccessContextManagerServicePerimeterIngressPolicy_updateTest(t *test Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step1(org, policyTitle, perimeterTitle), }, { - Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitle, projectNumber), + Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitle, projects[0].ProjectNumber), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction( @@ -201,7 +201,7 @@ func testAccAccessContextManagerServicePerimeterIngressPolicy_updateTest(t *test }, }, { - Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step3(org, policyTitle, perimeterTitle, projectNumber), + Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step3(org, policyTitle, perimeterTitle, projects[0].ProjectNumber), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction( @@ -212,7 +212,7 @@ func testAccAccessContextManagerServicePerimeterIngressPolicy_updateTest(t *test }, }, { - Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step4(org, policyTitle, perimeterTitle), + Config: testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step4(org, policyTitle, perimeterTitle, projects[1].ProjectNumber), ConfigPlanChecks: resource.ConfigPlanChecks{ PreApply: []plancheck.PlanCheck{ plancheck.ExpectResourceAction( @@ -248,9 +248,6 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- method_selectors { method = "*" } - method_selectors { - permission = "storage.objects.get" - } } } } @@ -258,7 +255,7 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- } // step 2: change identity_type, add access_level source, update resources, add roles, omit operations -func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitleName, projectNumber string) string { +func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step2(org, policyTitle, perimeterTitleName string, projectNumber int64) string { return fmt.Sprintf(` %s @@ -284,7 +281,7 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- } } ingress_to { - resources = ["projects/%s"] + resources = ["projects/%d"] roles = ["roles/bigquery.admin"] } } @@ -292,7 +289,7 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- } // step 3: omit identity_type, add identities, update access_level source and add resource source, update resources and roles -func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step3(org, policyTitle, perimeterTitleName, projectNumber string) string { +func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step3(org, policyTitle, perimeterTitleName string, projectNumber int64) string { return fmt.Sprintf(` %s @@ -324,24 +321,24 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- perimeter = google_access_context_manager_service_perimeter.test-access.name title = "ingress policy update test" ingress_from { - identities = ["user:test@google.com"] + identities = ["group:test@google.com"] sources { access_level = google_access_context_manager_access_level.update-test2.name } sources { - resource = "projects/%s" + resource = "projects/%d" } } ingress_to { resources = ["*"] - roles = ["roles/storage.admin"] + roles = ["roles/bigquery.dataViewer"] } } `, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName), projectNumber) } // step 4: update identities, update resource source and omit access_level source, update resources, omit roles and add updated operations -func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step4(org, policyTitle, perimeterTitleName string) string { +func testAccAccessContextManagerServicePerimeterIngressPolicy_ingressPolicyUpdate_step4(org, policyTitle, perimeterTitleName string, projectNumber int64) string { return fmt.Sprintf(` %s @@ -373,13 +370,13 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- perimeter = google_access_context_manager_service_perimeter.test-access.name title = "ingress policy update test" ingress_from { - identities = ["user:test2@google.com"] + identities = ["group:test2@google.com"] sources { - resource = "projects/123456789" + resource = "projects/%d" } } ingress_to { - resources = ["projects/123456789"] + resources = ["projects/%d"] operations { service_name = "bigquery.googleapis.com" method_selectors { @@ -391,7 +388,7 @@ resource "google_access_context_manager_service_perimeter_ingress_policy" "test- } } } -`, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName)) +`, testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName), projectNumber, projectNumber) } func testAccAccessContextManagerServicePerimeterIngressPolicy_destroy(org, policyTitle, perimeterTitleName string) string { From 823d717b7e50cf0e53368e2beadaf8cabd56b5b3 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Sep 2026 12:30:50 -0700 Subject: [PATCH 3/7] Fixed identity for ingress / egress policies --- .../ServicePerimeterDryRunEgressPolicy.yaml | 2 -- .../ServicePerimeterDryRunIngressPolicy.yaml | 2 -- .../accesscontextmanager/ServicePerimeterEgressPolicy.yaml | 2 -- .../accesscontextmanager/ServicePerimeterIngressPolicy.yaml | 2 -- 4 files changed, 8 deletions(-) diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml index 35819145d2ce..e43c88a4a150 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml @@ -41,8 +41,6 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: - - egressFrom - - egressTo - title nested_query: keys: diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml index 485a8083812e..07597c39115d 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml @@ -42,8 +42,6 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: - - ingressFrom - - ingressTo - title nested_query: keys: diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml index 823fa6969d00..17cd93b92623 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml @@ -41,8 +41,6 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: - - egressFrom - - egressTo - title nested_query: keys: diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml index a99bdb3c5be8..1ecf676bd5b7 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml @@ -42,8 +42,6 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: - - ingressFrom - - ingressTo - title nested_query: keys: From f915f35097ee8446e325c19cec0156108121cd45 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Sep 2026 13:25:34 -0700 Subject: [PATCH 4/7] Added etag field to acm serviceperimeter resource --- mmv1/products/accesscontextmanager/ServicePerimeter.yaml | 5 +++++ ...access_context_manager_service_perimeter_test.go.tmpl | 9 +++++++++ 2 files changed, 14 insertions(+) diff --git a/mmv1/products/accesscontextmanager/ServicePerimeter.yaml b/mmv1/products/accesscontextmanager/ServicePerimeter.yaml index 9200f3588ef7..38e368f09398 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeter.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeter.yaml @@ -138,6 +138,11 @@ properties: description: | Time the AccessPolicy was updated in UTC. output: true + - name: etag + type: Fingerprint + description: | + The etag for the version of the ServicePerimeter that this request is based on. + output: true - name: perimeterType type: Enum description: | diff --git a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_test.go.tmpl b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_test.go.tmpl index be8c50bb5ace..573810ebe0dc 100644 --- a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_test.go.tmpl +++ b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_test.go.tmpl @@ -27,6 +27,9 @@ func testAccAccessContextManagerServicePerimeter_basicTest(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccAccessContextManagerServicePerimeter_basic(org, "my policy", "level", "perimeter"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("google_access_context_manager_service_perimeter.test-access", "etag"), + ), }, { ResourceName: "google_access_context_manager_service_perimeter.test-access", @@ -48,6 +51,9 @@ func testAccAccessContextManagerServicePerimeter_updateTest(t *testing.T) { Steps: []resource.TestStep{ { Config: testAccAccessContextManagerServicePerimeter_basic(org, "my policy", "level", "perimeter"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("google_access_context_manager_service_perimeter.test-access", "etag"), + ), }, { ResourceName: "google_access_context_manager_service_perimeter.test-access", @@ -56,6 +62,9 @@ func testAccAccessContextManagerServicePerimeter_updateTest(t *testing.T) { }, { Config: testAccAccessContextManagerServicePerimeter_update(org, "my policy", "level", "perimeter"), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttrSet("google_access_context_manager_service_perimeter.test-access", "etag"), + ), }, { ResourceName: "google_access_context_manager_service_perimeter.test-access", From 31c4fe6040675fd22da6f92a7653eb82e60be460 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Sep 2026 15:34:49 -0700 Subject: [PATCH 5/7] Added ingressFrom and ingressTo back to the policy resources --- .../ServicePerimeterDryRunEgressPolicy.yaml | 2 ++ .../ServicePerimeterDryRunIngressPolicy.yaml | 2 ++ .../accesscontextmanager/ServicePerimeterEgressPolicy.yaml | 2 ++ .../accesscontextmanager/ServicePerimeterIngressPolicy.yaml | 2 ++ 4 files changed, 8 insertions(+) diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml index e43c88a4a150..35819145d2ce 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunEgressPolicy.yaml @@ -41,6 +41,8 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: + - egressFrom + - egressTo - title nested_query: keys: diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml index 07597c39115d..485a8083812e 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterDryRunIngressPolicy.yaml @@ -42,6 +42,8 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: + - ingressFrom + - ingressTo - title nested_query: keys: diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml index 17cd93b92623..823fa6969d00 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterEgressPolicy.yaml @@ -41,6 +41,8 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: + - egressFrom + - egressTo - title nested_query: keys: diff --git a/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml b/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml index 1ecf676bd5b7..a99bdb3c5be8 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeterIngressPolicy.yaml @@ -42,6 +42,8 @@ create_verb: PATCH update_verb: PATCH delete_verb: PATCH identity: + - ingressFrom + - ingressTo - title nested_query: keys: From 940bc4a52c7d6e9bafd54761795d5f546ebde0cb Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Sep 2026 15:51:06 -0700 Subject: [PATCH 6/7] Narrowed the ACM service perimeter update mask for status / spec subfield updates --- .../ServicePerimeter.yaml | 1 + ..._context_manager_service_perimeter.go.tmpl | 41 +++++++++++++++++++ 2 files changed, 42 insertions(+) create mode 100644 mmv1/templates/terraform/pre_update/access_context_manager_service_perimeter.go.tmpl diff --git a/mmv1/products/accesscontextmanager/ServicePerimeter.yaml b/mmv1/products/accesscontextmanager/ServicePerimeter.yaml index 38e368f09398..14e83c95cd62 100644 --- a/mmv1/products/accesscontextmanager/ServicePerimeter.yaml +++ b/mmv1/products/accesscontextmanager/ServicePerimeter.yaml @@ -68,6 +68,7 @@ custom_code: encoder: templates/terraform/encoders/access_level_never_send_parent.go.tmpl constants: templates/terraform/constants/access_context_manager.go.tmpl custom_import: templates/terraform/custom_import/set_access_policy_parent_from_self_link.go.tmpl + pre_update: templates/terraform/pre_update/access_context_manager_service_perimeter.go.tmpl samples: - name: access_context_manager_service_perimeter_basic primary_resource_id: service-perimeter diff --git a/mmv1/templates/terraform/pre_update/access_context_manager_service_perimeter.go.tmpl b/mmv1/templates/terraform/pre_update/access_context_manager_service_perimeter.go.tmpl new file mode 100644 index 000000000000..1821167f118a --- /dev/null +++ b/mmv1/templates/terraform/pre_update/access_context_manager_service_perimeter.go.tmpl @@ -0,0 +1,41 @@ +{{/* + The license inside this block applies to this file + Copyright 2026 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. +*/ -}} +newUpdateMask := []string{} +for _, mask := range updateMask { + switch mask { +{{- range $topProp := $.RootProperties }} +{{- if or (eq $topProp.Name "status") (eq $topProp.Name "spec") }} + case "{{ $topProp.ApiName }}": + {{ underscore $topProp.Name }}SubfieldsChanged := false +{{- range $subProp := $topProp.UserProperties }} +{{- if and (not $subProp.Output) (not $subProp.ClientSide) }} + if d.HasChange("{{ underscore $topProp.Name }}.0.{{ underscore $subProp.Name }}") { + newUpdateMask = append(newUpdateMask, "{{ $topProp.ApiName }}.{{ $subProp.ApiName }}") + {{ underscore $topProp.Name }}SubfieldsChanged = true + } +{{- end }} +{{- end }} + if !{{ underscore $topProp.Name }}SubfieldsChanged { + newUpdateMask = append(newUpdateMask, "{{ $topProp.ApiName }}") + } +{{- end }} +{{- end }} + default: + newUpdateMask = append(newUpdateMask, mask) + } +} +updateMask = newUpdateMask +url, err = transport_tpg.AddQueryParams(url, map[string]string{"updateMask": strings.Join(updateMask, ",")}) +if err != nil { + return err +} From de20a7074058f4a91ae0cf5a0ff18f7dc7530307 Mon Sep 17 00:00:00 2001 From: Stephen Lewis Date: Fri, 4 Sep 2026 16:36:17 -0700 Subject: [PATCH 7/7] Added regression test --- ...context_manager_access_policy_test.go.tmpl | 1 + ...ce_perimeter_dry_run_egress_policy_test.go | 3 +- ...er_service_perimeter_egress_policy_test.go | 132 ++++++++++++++++++ 3 files changed, 135 insertions(+), 1 deletion(-) diff --git a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_access_policy_test.go.tmpl b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_access_policy_test.go.tmpl index 698595227f53..fa6ed1860eff 100644 --- a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_access_policy_test.go.tmpl +++ b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_access_policy_test.go.tmpl @@ -38,6 +38,7 @@ func TestAccAccessContextManager(t *testing.T) { "service_perimeter_psc_endpoint": testAccAccessContextManagerServicePerimeter_pscEndpointTest, "service_perimeter_egress_policy": testAccAccessContextManagerServicePerimeterEgressPolicy_basicTest, "service_perimeter_egress_policy_update": testAccAccessContextManagerServicePerimeterEgressPolicy_updateTest, + "service_perimeter_egress_policy_parent_update": testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdateTest, "service_perimeter_dry_run_egress_policy": testAccAccessContextManagerServicePerimeterDryRunEgressPolicy_basicTest, "service_perimeter_dry_run_egress_policy_update": testAccAccessContextManagerServicePerimeterDryRunEgressPolicy_updateTest, "service_perimeter_egress_policy_psc_endpoint": testAccAccessContextManagerServicePerimeterEgressPolicy_pscEndpointTest, diff --git a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy_test.go b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy_test.go index c1eabe3278b1..1ab4b4343a79 100644 --- a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy_test.go +++ b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_dry_run_egress_policy_test.go @@ -4,6 +4,7 @@ import ( "fmt" "strings" "testing" + "time" "github.com/hashicorp/terraform-plugin-testing/helper/resource" "github.com/hashicorp/terraform-plugin-testing/plancheck" @@ -60,7 +61,7 @@ func BootstrapServicePerimeterProjects(t *testing.T, desiredProjects int) []*clo t.Fatalf("Error bootstrapping shared test project: %s", err) } - err = resourcemanager.ResourceManagerOperationWaitTime(config, opAsMap, "creating project", config.UserAgent, 4) + err = resourcemanager.ResourceManagerOperationWaitTime(config, opAsMap, "creating project", config.UserAgent, 4*time.Minute) if err != nil { t.Fatalf("Error bootstrapping shared test project: %s", err) } diff --git a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy_test.go b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy_test.go index cc5f62bf4a43..a4f0cc938dbe 100644 --- a/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy_test.go +++ b/mmv1/third_party/terraform/services/accesscontextmanager/resource_access_context_manager_service_perimeter_egress_policy_test.go @@ -405,3 +405,135 @@ resource "google_access_context_manager_service_perimeter" "test-access" { } `, org, policyTitle, perimeterTitleName, perimeterTitleName) } + +// This is a regression test for https://github.com/hashicorp/terraform-provider-google/issues/29005 +func testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdateTest(t *testing.T) { + org := envvar.GetTestOrgFromEnv(t) + projects := BootstrapServicePerimeterProjects(t, 7) + + policyTitle := acctest.RandString(t, 10) + perimeterTitle := "perimeter" + + perimeterProject := projects[0].ProjectNumber + var egressProjects []int64 + for i := 1; i <= 6; i++ { + egressProjects = append(egressProjects, projects[i].ProjectNumber) + } + + acctest.VcrTest(t, resource.TestCase{ + PreCheck: func() { acctest.AccTestPreCheck(t) }, + ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t), + Steps: []resource.TestStep{ + { + Config: testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdate_step(org, policyTitle, perimeterTitle, "", egressProjects), + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_access_context_manager_service_perimeter_egress_policy.test-access1", "egress_to.0.resources.#", "6"), + ), + }, + { + Config: testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdate_step(org, policyTitle, perimeterTitle, fmt.Sprintf(`["projects/%d"]`, perimeterProject), egressProjects), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction( + "google_access_context_manager_service_perimeter.test-access", + plancheck.ResourceActionUpdate, + ), + plancheck.ExpectResourceAction( + "google_access_context_manager_service_perimeter_egress_policy.test-access1", + plancheck.ResourceActionNoop, + ), + }, + }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_access_context_manager_service_perimeter.test-access", "status.0.resources.#", "1"), + resource.TestCheckResourceAttr("google_access_context_manager_service_perimeter_egress_policy.test-access1", "egress_to.0.resources.#", "6"), + ), + }, + { + Config: testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdate_step(org, policyTitle, perimeterTitle, fmt.Sprintf(`["projects/%d"]`, perimeterProject), egressProjects[:5]), + ConfigPlanChecks: resource.ConfigPlanChecks{ + PreApply: []plancheck.PlanCheck{ + plancheck.ExpectResourceAction( + "google_access_context_manager_service_perimeter.test-access", + plancheck.ResourceActionNoop, + ), + plancheck.ExpectResourceAction( + "google_access_context_manager_service_perimeter_egress_policy.test-access1", + plancheck.ResourceActionUpdate, + ), + }, + }, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr("google_access_context_manager_service_perimeter_egress_policy.test-access1", "egress_to.0.resources.#", "5"), + ), + }, + { + Config: testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdate_base(org, policyTitle, perimeterTitle, fmt.Sprintf(`["projects/%d"]`, perimeterProject)), + Check: testAccCheckAccessContextManagerServicePerimeterEgressPolicyDestroyProducer(t), + }, + }, + }) +} + +func formatProjectsHCL(projectNumbers []int64) string { + var formatted []string + for _, num := range projectNumbers { + formatted = append(formatted, fmt.Sprintf(`"projects/%d"`, num)) + } + return strings.Join(formatted, ",\n ") +} + +func testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdate_base(org, policyTitle, perimeterTitleName, perimeterResources string) string { + resourcesBlock := "" + if perimeterResources != "" { + resourcesBlock = fmt.Sprintf("resources = %s", perimeterResources) + } + return fmt.Sprintf(` +resource "google_access_context_manager_access_policy" "test-access" { + parent = "organizations/%s" + title = "%s" +} + +resource "google_access_context_manager_service_perimeter" "test-access" { + parent = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}" + name = "accessPolicies/${google_access_context_manager_access_policy.test-access.name}/servicePerimeters/%s" + title = "%s" + status { + restricted_services = ["storage.googleapis.com"] + %s + } + + lifecycle { + ignore_changes = [ + status[0].egress_policies, + status[0].ingress_policies, + ] + } +} +`, org, policyTitle, perimeterTitleName, perimeterTitleName, resourcesBlock) +} + +func testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdate_step(org, policyTitle, perimeterTitleName string, perimeterResources string, egressProjects []int64) string { + return fmt.Sprintf(` +%s + +resource "google_access_context_manager_service_perimeter_egress_policy" "test-access1" { + perimeter = google_access_context_manager_service_perimeter.test-access.name + title = "egress policy update test" + egress_from { + identity_type = "ANY_USER_ACCOUNT" + } + egress_to { + resources = [ + %s, + ] + operations { + service_name = "storage.googleapis.com" + method_selectors { + method = "google.storage.objects.get" + } + } + } +} +`, testAccAccessContextManagerServicePerimeterEgressPolicy_parentPerimeterUpdate_base(org, policyTitle, perimeterTitleName, perimeterResources), formatProjectsHCL(egressProjects)) +}