Skip to content

Commit

Permalink
AccessContextManager - Add dry run service perimeter resource (Google…
Browse files Browse the repository at this point in the history
…CloudPlatform#10145)

Co-authored-by: Charlesleonius <[email protected]>
  • Loading branch information
2 people authored and balanaguharsha committed May 2, 2024
1 parent ae4b0df commit 463cc99
Show file tree
Hide file tree
Showing 6 changed files with 269 additions and 16 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# Copyright 2018 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.

--- !ruby/object:Api::Resource
name: 'ServicePerimeterDryRunResource'
create_url: '{{perimeter_name}}'
base_url: ''
self_link: '{{perimeter_name}}'
create_verb: :PATCH
delete_verb: :PATCH
immutable: true
update_mask: true
identity:
- resource
nested_query: !ruby/object:Api::Resource::NestedQuery
modify_by_patch: true
is_list_of_ids: true
keys:
- spec
- resources
references: !ruby/object:Api::Resource::ReferenceLinks
guides:
'Service Perimeter Quickstart': 'https://cloud.google.com/vpc-service-controls/docs/quickstart'
api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters'
description: |
Allows configuring a single GCP resource that should be inside of the `spec` block of a dry run service perimeter.
This resource is intended to be used in cases where it is not possible to compile a full list
of projects to include in a `google_access_context_manager_service_perimeter` resource,
to enable them to be added separately.
If your perimeter is NOT in dry-run mode use `google_access_context_manager_service_perimeter_resource` instead.
~> **Note:** If this resource is used alongside a `google_access_context_manager_service_perimeter` resource,
the service perimeter resource must have a `lifecycle` block with `ignore_changes = [spec[0].resources]` so
they don't fight over which resources should be in the policy.
docs: !ruby/object:Provider::Terraform::Docs
warning: |
If you are using User ADCs (Application Default Credentials) with this resource,
you must specify a `billing_project` and set `user_project_override` to true
in the provider configuration. Otherwise the ACM API will return a 403 error.
Your account must have the `serviceusage.services.use` permission on the
`billing_project` you defined.
autogen_async: true
exclude_tgc: true
# Skipping the sweeper due to the non-standard base_url and because this is fine-grained under ServicePerimeter
skip_sweeper: true
id_format: '{{perimeter_name}}/{{resource}}'
import_format: ['{{perimeter_name}}/{{resource}}']
mutex: '{{perimeter_name}}'
examples:
- !ruby/object:Provider::Terraform::Examples
name: 'access_context_manager_service_perimeter_dry_run_resource_basic'
skip_test: true
primary_resource_id: 'service-perimeter-dry-run-resource'
vars:
service_perimeter_name: 'restrict_all'
custom_code: !ruby/object:Provider::Terraform::CustomCode
custom_import: templates/terraform/custom_import/access_context_manager_service_perimeter_resource.go.erb
pre_update: templates/terraform/pre_create/access_context_manager_service_perimeter_dry_run_resource.go.erb
pre_create: templates/terraform/pre_create/access_context_manager_service_perimeter_dry_run_resource.go.erb
pre_delete: templates/terraform/pre_create/access_context_manager_service_perimeter_dry_run_resource.go.erb
parameters:
- !ruby/object:Api::Type::ResourceRef
name: 'perimeterName'
resource: 'ServicePerimeter'
imports: 'name'
description: |
The name of the Service Perimeter to add this resource to.
required: true
immutable: true
url_param_only: true
properties:
- !ruby/object:Api::Type::String
name: 'resource'
description: |
A GCP resource that is inside of the service perimeter.
Currently only projects are allowed.
Format: projects/{project_number}
required: true
immutable: true
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,11 @@ references: !ruby/object:Api::Resource::ReferenceLinks
'Service Perimeter Quickstart': 'https://cloud.google.com/vpc-service-controls/docs/quickstart'
api: 'https://cloud.google.com/access-context-manager/docs/reference/rest/v1/accessPolicies.servicePerimeters'
description: |
Allows configuring a single GCP resource that should be inside of a service perimeter.
Allows configuring a single GCP resource that should be inside the `status` block of a service perimeter.
This resource is intended to be used in cases where it is not possible to compile a full list
of projects to include in a `google_access_context_manager_service_perimeter` resource,
to enable them to be added separately.
If your perimeter is in dry-run mode use `google_access_context_manager_service_perimeter_dry_run_resource` instead.
~> **Note:** If this resource is used alongside a `google_access_context_manager_service_perimeter` resource,
the service perimeter resource must have a `lifecycle` block with `ignore_changes = [status[0].resources]` so
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
resource "google_access_context_manager_service_perimeter_dry_run_resource" "<%= ctx[:primary_resource_id] %>" {
perimeter_name = google_access_context_manager_service_perimeter.<%= ctx[:primary_resource_id] %>.name
resource = "projects/987654321"
}

resource "google_access_context_manager_service_perimeter" "<%= ctx[:primary_resource_id] %>" {
parent = "accessPolicies/${google_access_context_manager_access_policy.access-policy.name}"
name = "accessPolicies/${google_access_context_manager_access_policy.access-policy.name}/servicePerimeters/<%= ctx[:vars]['service_perimeter_name'] %>"
title = "<%= ctx[:vars]['service_perimeter_name'] %>"
spec {
restricted_services = ["storage.googleapis.com"]
}
use_explicit_dry_run_spec = true
lifecycle {
ignore_changes = [spec[0].resources]
}
}

resource "google_access_context_manager_access_policy" "access-policy" {
parent = "organizations/123456789"
title = "my policy"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
obj["use_explicit_dry_run_spec"] = true
Original file line number Diff line number Diff line change
Expand Up @@ -17,21 +17,22 @@ import (
// can exist, they need to be run serially
func TestAccAccessContextManager(t *testing.T) {
testCases := map[string]func(t *testing.T){
"access_policy": testAccAccessContextManagerAccessPolicy_basicTest,
"access_policy_scoped": testAccAccessContextManagerAccessPolicy_scopedTest,
"service_perimeter": testAccAccessContextManagerServicePerimeter_basicTest,
"service_perimeter_update": testAccAccessContextManagerServicePerimeter_updateTest,
"service_perimeter_resource": testAccAccessContextManagerServicePerimeterResource_basicTest,
"access_level": testAccAccessContextManagerAccessLevel_basicTest,
"access_level_full": testAccAccessContextManagerAccessLevel_fullTest,
"access_level_custom": testAccAccessContextManagerAccessLevel_customTest,
"access_levels": testAccAccessContextManagerAccessLevels_basicTest,
"access_level_condition": testAccAccessContextManagerAccessLevelCondition_basicTest,
"service_perimeter_egress_policy": testAccAccessContextManagerServicePerimeterEgressPolicy_basicTest,
"service_perimeter_ingress_policy": testAccAccessContextManagerServicePerimeterIngressPolicy_basicTest,
"service_perimeters": testAccAccessContextManagerServicePerimeters_basicTest,
"gcp_user_access_binding": testAccAccessContextManagerGcpUserAccessBinding_basicTest,
"authorized_orgs_desc": testAccAccessContextManagerAuthorizedOrgsDesc_basicTest,
"access_policy": testAccAccessContextManagerAccessPolicy_basicTest,
"access_policy_scoped": testAccAccessContextManagerAccessPolicy_scopedTest,
"service_perimeter": testAccAccessContextManagerServicePerimeter_basicTest,
"service_perimeter_update": testAccAccessContextManagerServicePerimeter_updateTest,
"service_perimeter_resource": testAccAccessContextManagerServicePerimeterResource_basicTest,
"service_perimeter_dry_run_resource": testAccAccessContextManagerServicePerimeterResource_basicTest,
"access_level": testAccAccessContextManagerAccessLevel_basicTest,
"access_level_full": testAccAccessContextManagerAccessLevel_fullTest,
"access_level_custom": testAccAccessContextManagerAccessLevel_customTest,
"access_levels": testAccAccessContextManagerAccessLevels_basicTest,
"access_level_condition": testAccAccessContextManagerAccessLevelCondition_basicTest,
"service_perimeter_egress_policy": testAccAccessContextManagerServicePerimeterEgressPolicy_basicTest,
"service_perimeter_ingress_policy": testAccAccessContextManagerServicePerimeterIngressPolicy_basicTest,
"service_perimeters": testAccAccessContextManagerServicePerimeters_basicTest,
"gcp_user_access_binding": testAccAccessContextManagerGcpUserAccessBinding_basicTest,
"authorized_orgs_desc": testAccAccessContextManagerAuthorizedOrgsDesc_basicTest,
}

for name, tc := range testCases {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
package accesscontextmanager_test

import (
"fmt"
"testing"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
"github.com/hashicorp/terraform-provider-google/google/acctest"
"github.com/hashicorp/terraform-provider-google/google/envvar"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
)

// Since each test here is acting on the same organization and only one AccessPolicy
// can exist, they need to be run serially. See AccessPolicy for the test runner.

func testAccAccessContextManagerServicePerimeterDryRunResource_basicTest(t *testing.T) {
// Multiple fine-grained resources
acctest.SkipIfVcr(t)
org := envvar.GetTestOrgFromEnv(t)
projects := acctest.BootstrapServicePerimeterProjects(t, 2)
policyTitle := "my policy"
perimeterTitle := "perimeter"

acctest.VcrTest(t, resource.TestCase{
PreCheck: func() { acctest.AccTestPreCheck(t) },
ProtoV5ProviderFactories: acctest.ProtoV5ProviderFactories(t),
Steps: []resource.TestStep{
{
Config: testAccAccessContextManagerServicePerimeterDryRunResource_basic(org, policyTitle, perimeterTitle, projects[0].ProjectNumber, projects[1].ProjectNumber),
},
{
ResourceName: "google_access_context_manager_service_perimeter_dry_run_resource.test-access1",
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "google_access_context_manager_service_perimeter_dry_run_resource.test-access2",
ImportState: true,
ImportStateVerify: true,
},
// Use a separate TestStep rather than a CheckDestroy because we need the service perimeter to still exist
{
Config: testAccAccessContextManagerServicePerimeterDryRunResource_destroy(org, policyTitle, perimeterTitle),
Check: testAccCheckAccessContextManagerServicePerimeterDryRunResourceDestroyProducer(t),
},
},
})
}

func testAccCheckAccessContextManagerServicePerimeterDryRunResourceDestroyProducer(t *testing.T) func(s *terraform.State) error {
return func(s *terraform.State) error {
for _, rs := range s.RootModule().Resources {
if rs.Type != "google_access_context_manager_service_perimeter_dry_run_resource" {
continue
}

config := acctest.GoogleProviderConfig(t)

url, err := tpgresource.ReplaceVarsForTest(config, rs, "{{AccessContextManagerBasePath}}{{perimeter_name}}")
if err != nil {
return err
}

res, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: config,
Method: "GET",
RawURL: url,
UserAgent: config.UserAgent,
})
if err != nil {
return err
}

v, ok := res["spec"]
if !ok || v == nil {
return nil
}

res = v.(map[string]interface{})
v, ok = res["resources"]
if !ok || v == nil {
return nil
}

resources := v.([]interface{})
if len(resources) == 0 {
return nil
}

return fmt.Errorf("expected 0 resources in perimeter, found %d: %v", len(resources), resources)
}

return nil
}
}

func testAccAccessContextManagerServicePerimeterDryRunResource_basic(org, policyTitle, perimeterTitleName string, projectNumber1, projectNumber2 int64) string {
return fmt.Sprintf(`
%s
resource "google_access_context_manager_service_perimeter_dry_run_resource" "test-access1" {
perimeter_name = google_access_context_manager_service_perimeter.test-access.name
resource = "projects/%d"
}
resource "google_access_context_manager_service_perimeter_dry_run_resource" "test-access2" {
perimeter_name = google_access_context_manager_service_perimeter.test-access.name
resource = "projects/%d"
}
`, testAccAccessContextManagerServicePerimeterDryRunResource_destroy(org, policyTitle, perimeterTitleName), projectNumber1, projectNumber2)
}

func testAccAccessContextManagerServicePerimeterDryRunResource_destroy(org, policyTitle, perimeterTitleName string) string {
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"
perimeter_type = "PERIMETER_TYPE_REGULAR"
status {
restricted_services = ["storage.googleapis.com"]
}
spec {
restricted_services = ["storage.googleapis.com"]
}
use_explicit_dry_run_spec = true
lifecycle {
ignore_changes = [spec[0].resources]
}
}
`, org, policyTitle, perimeterTitleName, perimeterTitleName)
}

0 comments on commit 463cc99

Please sign in to comment.