Skip to content

Commit

Permalink
Add support for GCS managed folders (GoogleCloudPlatform#10786)
Browse files Browse the repository at this point in the history
Co-authored-by: Stephen Lewis (Burrows) <stephen.r.burrows@gmail.com>
  • Loading branch information
2 people authored and vijaykanthm committed Jul 22, 2024
1 parent 1b36b93 commit 142d5df
Show file tree
Hide file tree
Showing 7 changed files with 1,137 additions and 0 deletions.
83 changes: 83 additions & 0 deletions mmv1/products/storage/ManagedFolder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Copyright 2024 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: 'ManagedFolder'
kind: 'storage#managedFolder'
base_url: 'b/{{bucket}}/managedFolders'
self_link: 'b/{{bucket}}/managedFolders/{{%name}}'
id_format: '{{bucket}}/{{name}}'
delete_url: 'b/{{bucket}}/managedFolders/{{%name}}'
has_self_link: true
immutable: true
skip_sweeper: true # Skipping sweeper since this is a child resource.
description: |
A Google Cloud Storage Managed Folder.
You can apply Identity and Access Management (IAM) policies to
managed folders to grant principals access only to the objects
within the managed folder, which lets you more finely control access
for specific data sets and tables within a bucket. You can nest
managed folders up to 15 levels deep, including the parent managed
folder.
Managed folders can only be created in buckets that have uniform
bucket-level access enabled.
references: !ruby/object:Api::Resource::ReferenceLinks
guides:
'Official Documentation': 'https://cloud.google.com/storage/docs/managed-folders'
api: 'https://cloud.google.com/storage/docs/json_api/v1/managedFolder'
# iam_policy: handwritten in mmv1/third_party/terraform/services/storage/iam_storage_managed_folder.go
import_format:
- '{{bucket}}/managedFolders/{{%name}}'
- '{{bucket}}/{{%name}}'
examples:
- !ruby/object:Provider::Terraform::Examples
name: 'storage_managed_folder_basic'
primary_resource_id: 'folder'
vars:
bucket_name: 'my-bucket'
parameters:
- !ruby/object:Api::Type::ResourceRef
name: 'bucket'
resource: 'Bucket'
imports: 'name'
description: 'The name of the bucket that contains the managed folder.'
required: true
- !ruby/object:Api::Type::String
name: 'name'
description: |
The name of the managed folder expressed as a path. Must include
trailing '/'. For example, `example_dir/example_dir2/`.
required: true
# The API returns values with trailing slashes, even if not
# provided. Enforcing trailing slashes prevents diffs and ensures
# consistent output.
validation: !ruby/object:Provider::Terraform::Validation
regex: '/$'
properties:
- !ruby/object:Api::Type::String
name: createTime
description: |
The timestamp at which this managed folder was created.
output: true
- !ruby/object:Api::Type::String
name: updateTime
description: |
The timestamp at which this managed folder was most recently updated.
output: true
- !ruby/object:Api::Type::String
name: metageneration
description: |
The metadata generation of the managed folder.
output: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
resource "google_storage_bucket" "bucket" {
name = "<%= ctx[:vars]['bucket_name'] %>"
location = "EU"
uniform_bucket_level_access = true
}

resource "google_storage_managed_folder" "<%= ctx[:primary_resource_id] %>" {
bucket = google_storage_bucket.bucket.name
name = "managed/folder/name/"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
bucket = google_storage_managed_folder.folder.bucket
managed_folder = google_storage_managed_folder.folder.name
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,7 @@ var handwrittenIAMDatasources = map[string]*schema.Resource{
"google_kms_crypto_key_iam_policy": tpgiamresource.DataSourceIamPolicy(kms.IamKmsCryptoKeySchema, kms.NewKmsCryptoKeyIamUpdater),
"google_spanner_instance_iam_policy": tpgiamresource.DataSourceIamPolicy(spanner.IamSpannerInstanceSchema, spanner.NewSpannerInstanceIamUpdater),
"google_spanner_database_iam_policy": tpgiamresource.DataSourceIamPolicy(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater),
"google_storage_managed_folder_iam_policy": tpgiamresource.DataSourceIamPolicy(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer),
"google_organization_iam_policy": tpgiamresource.DataSourceIamPolicy(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater),
"google_project_iam_policy": tpgiamresource.DataSourceIamPolicy(resourcemanager.IamProjectSchema, resourcemanager.NewProjectIamUpdater),
"google_pubsub_subscription_iam_policy": tpgiamresource.DataSourceIamPolicy(pubsub.IamPubsubSubscriptionSchema, pubsub.NewPubsubSubscriptionIamUpdater),
Expand Down Expand Up @@ -426,6 +427,9 @@ var handwrittenIAMResources = map[string]*schema.Resource{
"google_spanner_database_iam_binding": tpgiamresource.ResourceIamBinding(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater, spanner.SpannerDatabaseIdParseFunc),
"google_spanner_database_iam_member": tpgiamresource.ResourceIamMember(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater, spanner.SpannerDatabaseIdParseFunc),
"google_spanner_database_iam_policy": tpgiamresource.ResourceIamPolicy(spanner.IamSpannerDatabaseSchema, spanner.NewSpannerDatabaseIamUpdater, spanner.SpannerDatabaseIdParseFunc),
"google_storage_managed_folder_iam_binding": tpgiamresource.ResourceIamBinding(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc),
"google_storage_managed_folder_iam_member": tpgiamresource.ResourceIamMember(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc),
"google_storage_managed_folder_iam_policy": tpgiamresource.ResourceIamPolicy(storage.StorageManagedFolderIamSchema, storage.StorageManagedFolderIamUpdaterProducer, storage.StorageManagedFolderIdParseFunc),
"google_organization_iam_binding": tpgiamresource.ResourceIamBinding(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc),
"google_organization_iam_member": tpgiamresource.ResourceIamMember(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc),
"google_organization_iam_policy": tpgiamresource.ResourceIamPolicy(resourcemanager.IamOrganizationSchema, resourcemanager.NewOrganizationIamUpdater, resourcemanager.OrgIdParseFunc),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package storage

import (
"fmt"
"net/url"

"github.com/hashicorp/errwrap"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"

"github.com/hashicorp/terraform-provider-google/google/tpgiamresource"
"github.com/hashicorp/terraform-provider-google/google/tpgresource"
transport_tpg "github.com/hashicorp/terraform-provider-google/google/transport"
"github.com/hashicorp/terraform-provider-google/google/verify"
)

var StorageManagedFolderIamSchema = map[string]*schema.Schema{
"bucket": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
},
"managed_folder": {
Type: schema.TypeString,
Required: true,
ForceNew: true,
DiffSuppressFunc: tpgresource.CompareSelfLinkOrResourceName,
ValidateFunc: verify.ValidateRegexp(`/$`),
},
}

type StorageManagedFolderIamUpdater struct {
bucket string
managedFolder string
d tpgresource.TerraformResourceData
Config *transport_tpg.Config
}

func StorageManagedFolderIamUpdaterProducer(d tpgresource.TerraformResourceData, config *transport_tpg.Config) (tpgiamresource.ResourceIamUpdater, error) {
values := make(map[string]string)

if v, ok := d.GetOk("bucket"); ok {
values["bucket"] = v.(string)
}

if v, ok := d.GetOk("managed_folder"); ok {
values["managed_folder"] = v.(string)
}

u := &StorageManagedFolderIamUpdater{
bucket: values["bucket"],
managedFolder: values["managed_folder"],
d: d,
Config: config,
}

if err := d.Set("bucket", u.bucket); err != nil {
return nil, fmt.Errorf("Error setting bucket: %s", err)
}
if err := d.Set("managed_folder", u.managedFolder); err != nil {
return nil, fmt.Errorf("Error setting managed_folder: %s", err)
}

return u, nil
}

func StorageManagedFolderIdParseFunc(d *schema.ResourceData, config *transport_tpg.Config) error {
values := make(map[string]string)

m, err := tpgresource.GetImportIdQualifiers([]string{"(?P<bucket>[^/]+)/managedFolders/(?P<managed_folder>.+)", "(?P<bucket>[^/]+)/(?P<managed_folder>.+)"}, d, config, d.Id())
if err != nil {
return err
}

for k, v := range m {
values[k] = v
}

u := &StorageManagedFolderIamUpdater{
bucket: values["bucket"],
managedFolder: values["managed_folder"],
d: d,
Config: config,
}
if err := d.Set("bucket", u.bucket); err != nil {
return fmt.Errorf("Error setting bucket: %s", err)
}
if err := d.Set("managed_folder", u.managedFolder); err != nil {
return fmt.Errorf("Error setting managed_folder: %s", err)
}
d.SetId(u.GetResourceId())
return nil
}

func (u *StorageManagedFolderIamUpdater) GetResourceIamPolicy() (*cloudresourcemanager.Policy, error) {
url, err := u.qualifyManagedFolderUrl("iam")
if err != nil {
return nil, err
}

var obj map[string]interface{}
url, err = transport_tpg.AddQueryParams(url, map[string]string{"optionsRequestedPolicyVersion": fmt.Sprintf("%d", tpgiamresource.IamPolicyVersion)})
if err != nil {
return nil, err
}

userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent)
if err != nil {
return nil, err
}

policy, err := transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: u.Config,
Method: "GET",
RawURL: url,
UserAgent: userAgent,
Body: obj,
})
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err)
}

out := &cloudresourcemanager.Policy{}
err = tpgresource.Convert(policy, out)
if err != nil {
return nil, errwrap.Wrapf("Cannot convert a policy to a resource manager policy: {{err}}", err)
}

return out, nil
}

func (u *StorageManagedFolderIamUpdater) SetResourceIamPolicy(policy *cloudresourcemanager.Policy) error {
json, err := tpgresource.ConvertToMap(policy)
if err != nil {
return err
}

obj := json

url, err := u.qualifyManagedFolderUrl("iam")
if err != nil {
return err
}

userAgent, err := tpgresource.GenerateUserAgentString(u.d, u.Config.UserAgent)
if err != nil {
return err
}

_, err = transport_tpg.SendRequest(transport_tpg.SendRequestOptions{
Config: u.Config,
Method: "PUT",
RawURL: url,
UserAgent: userAgent,
Body: obj,
Timeout: u.d.Timeout(schema.TimeoutCreate),
})
if err != nil {
return errwrap.Wrapf(fmt.Sprintf("Error setting IAM policy for %s: {{err}}", u.DescribeResource()), err)
}

return nil
}

func (u *StorageManagedFolderIamUpdater) qualifyManagedFolderUrl(methodIdentifier string) (string, error) {
urlTemplate := fmt.Sprintf("{{StorageBasePath}}b/%s/managedFolders/%s/%s", u.bucket, url.PathEscape(u.managedFolder), methodIdentifier)
url, err := tpgresource.ReplaceVars(u.d, u.Config, urlTemplate)
if err != nil {
return "", err
}
return url, nil
}

func (u *StorageManagedFolderIamUpdater) GetResourceId() string {
return fmt.Sprintf("b/%s/managedFolders/%s", u.bucket, u.managedFolder)
}

func (u *StorageManagedFolderIamUpdater) GetMutexKey() string {
return fmt.Sprintf("iam-storage-managedfolder-%s", u.GetResourceId())
}

func (u *StorageManagedFolderIamUpdater) DescribeResource() string {
return fmt.Sprintf("storage managedfolder %q", u.GetResourceId())
}
Loading

0 comments on commit 142d5df

Please sign in to comment.