Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for GCS managed folders #10786

Merged
merged 19 commits into from
Jun 26, 2024
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
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'
juliocc marked this conversation as resolved.
Show resolved Hide resolved
vars:
bucket_name: 'my-bucket'
melinath marked this conversation as resolved.
Show resolved Hide resolved
parameters:
- !ruby/object:Api::Type::ResourceRef
name: 'bucket'
resource: 'Bucket'
juliocc marked this conversation as resolved.
Show resolved Hide resolved
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,
juliocc marked this conversation as resolved.
Show resolved Hide resolved
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
Loading