Skip to content
Merged
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
6 changes: 6 additions & 0 deletions mmv1/products/accesscontextmanager/ServicePerimeter.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -138,6 +139,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: |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"fmt"
"strings"
"testing"
"time"

"github.com/hashicorp/terraform-plugin-testing/helper/resource"
"github.com/hashicorp/terraform-plugin-testing/plancheck"
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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))
}
Loading
Loading