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

feat(HNS folders): add new resources to support HNS folders #12101

Open
wants to merge 63 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 60 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
ad3cefc
add hns folder logic
gurusai-voleti Oct 23, 2024
b522bcd
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Oct 23, 2024
de6f2ea
update
gurusai-voleti Oct 23, 2024
376eead
Merge branch 'hns_folders' of github.com:gurusai-voleti/magic-modules…
gurusai-voleti Oct 23, 2024
0646523
add example and yaml
gurusai-voleti Oct 24, 2024
454d6d1
added test case
gurusai-voleti Oct 24, 2024
8eb8111
yaml changes
gurusai-voleti Oct 24, 2024
69d2966
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Oct 30, 2024
ddd5b51
update
gurusai-voleti Oct 30, 2024
90599d5
update
gurusai-voleti Oct 30, 2024
9efe5a8
remove example
gurusai-voleti Oct 30, 2024
9b5195d
update test case
gurusai-voleti Oct 30, 2024
736407c
update test case
gurusai-voleti Oct 30, 2024
ad19c6a
updates
gurusai-voleti Oct 30, 2024
1d6b68c
updated example
gurusai-voleti Oct 30, 2024
d81ca9e
update example test case
gurusai-voleti Oct 30, 2024
e7b1ff6
update example test case
gurusai-voleti Oct 30, 2024
06c78d3
update error msgs
gurusai-voleti Oct 30, 2024
a142e22
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Nov 7, 2024
4650cd6
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Nov 11, 2024
6098a8f
addressed review comments
gurusai-voleti Nov 11, 2024
2a6d49d
updated test case
gurusai-voleti Nov 11, 2024
6f767b7
added test cases
gurusai-voleti Nov 11, 2024
6384419
updated test case
gurusai-voleti Nov 11, 2024
ccebe43
updated yaml
gurusai-voleti Nov 13, 2024
1030aa1
update
gurusai-voleti Nov 13, 2024
f8786d8
review comments
gurusai-voleti Nov 13, 2024
8a86c0a
update test cases
gurusai-voleti Nov 13, 2024
f20a949
update test cases
gurusai-voleti Nov 13, 2024
a156f38
update testcase
gurusai-voleti Nov 14, 2024
220bd1f
update testcase
gurusai-voleti Nov 14, 2024
0756c9e
update test case
gurusai-voleti Nov 14, 2024
5c3b7cd
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Nov 14, 2024
caac4ff
update to handwritten resource
gurusai-voleti Nov 14, 2024
453aed3
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Dec 3, 2024
7267414
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Dec 9, 2024
92bd5fc
generate storage folder
gurusai-voleti Dec 9, 2024
772d7b2
generate storage folder
gurusai-voleti Dec 9, 2024
73a5bc8
generate storage folder
gurusai-voleti Dec 9, 2024
cce471f
update yaml
gurusai-voleti Dec 10, 2024
547875d
update yaml
gurusai-voleti Dec 10, 2024
e6bbaac
auto generate create and read
gurusai-voleti Dec 10, 2024
d026479
fix yaml
gurusai-voleti Dec 10, 2024
d2f7d28
update delete
gurusai-voleti Dec 11, 2024
9221d77
update
gurusai-voleti Dec 11, 2024
2fa4f7a
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Dec 11, 2024
f00da94
Merge branch 'GoogleCloudPlatform:main' into hns_folders
gurusai-voleti Dec 17, 2024
807016d
update regex to support valid folder paths
gurusai-voleti Dec 17, 2024
16f31c0
update example
gurusai-voleti Dec 17, 2024
881bb8a
update regex
gurusai-voleti Dec 17, 2024
6c059e5
update nit
gurusai-voleti Dec 17, 2024
86bf3c0
review comments
gurusai-voleti Dec 18, 2024
f31d49a
review comments
gurusai-voleti Dec 18, 2024
3d83330
review comments
gurusai-voleti Dec 18, 2024
e5bd04f
update example
gurusai-voleti Dec 18, 2024
62096c9
update message
gurusai-voleti Dec 18, 2024
c5c00df
update message
gurusai-voleti Dec 18, 2024
28b0e37
update logic
gurusai-voleti Dec 18, 2024
ce8deca
updated test case to tf config
gurusai-voleti Dec 18, 2024
10f329f
fix
gurusai-voleti Dec 18, 2024
0043ae2
update test case
gurusai-voleti Dec 19, 2024
e0c43cd
to avoid list permission issue
gurusai-voleti Dec 19, 2024
8775946
update
gurusai-voleti Dec 19, 2024
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
89 changes: 89 additions & 0 deletions mmv1/products/storage/Folder.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 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.
---
name: 'Folder'
kind: 'storage#folder'
base_url: 'b/{{bucket}}/folders'
self_link: 'b/{{bucket}}/folders/{{%name}}'
id_format: '{{bucket}}/{{name}}'
delete_url: 'b/{{bucket}}/folders/{{%name}}'
create_url: 'b/{{bucket}}/folders'
has_self_link: true
timeouts:
insert_minutes: 20
update_minutes: 20
delete_minutes: 20
exclude_sweeper: true
import_format:
- '{{bucket}}/folders/{{%name}}'
- '{{bucket}}/{{%name}}'
examples:
- name: 'storage_folder_basic'
primary_resource_id: 'folder'
vars:
bucket_name: 'my-bucket'
ignore_read_extra:
- 'force_destroy'
description: |
A Google Cloud Storage Folder.

The Folder resource represents a folder in a Cloud Storage bucket with hierarchical namespace enabled
references:
guides:
'Official Documentation': 'https://cloud.google.com/storage/docs/folders-overview'
api: 'https://cloud.google.com/storage/docs/json_api/v1/folders'
custom_code:
custom_import: templates/terraform/custom_import/storage_folder.go.tmpl
custom_update: templates/terraform/custom_update/storage_folder_update.go.tmpl
custom_delete: templates/terraform/custom_delete/storage_folder_delete.go.tmpl
virtual_fields:
- name: 'force_destroy'
description:
If set to true, items within folder if any will be force destroyed.
type: Boolean
default_value: false
parameters:
- name: 'bucket'
resource: 'Bucket'
imports: 'name'
description: 'The name of the bucket that contains the folder.'
required: true
immutable: true
url_param_only: true
- name: 'name'
gurusai-voleti marked this conversation as resolved.
Show resolved Hide resolved
description: |
The name of the folder expressed as a path. Must include
trailing '/'. For example, `example_dir/example_dir2/`, `example@#/`, `a-b/d-f/`.
required: true
immutable: true
# The API returns values with trailing slashes, even if not
# provided. Enforcing trailing slashes prevents diffs and ensures
# consistent output.
validation:
gurusai-voleti marked this conversation as resolved.
Show resolved Hide resolved
regex: '/$'
properties:
- name: createTime
type: String
description: |
The timestamp at which this folder was created.
output: true
- name: updateTime
type: String
description: |
The timestamp at which this folder was most recently updated.
output: true
- name: metageneration
type: String
description: |
The metadata generation of the folder.
output: true
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
bucket := d.Get("bucket").(string)
name := d.Get("name").(string)

var listError, deleteObjectError error
for deleteObjectError == nil {
res, err := config.NewStorageClient(userAgent).Objects.List(bucket).Prefix(name).Do()
if err != nil {
log.Printf("Error listing contents of folder %s: %v", bucket, err)
listError = err
break
}

if len(res.Items) == 0 {
break // 0 items, folder empty
}

if !d.Get("force_destroy").(bool) {
gurusai-voleti marked this conversation as resolved.
Show resolved Hide resolved
deleteErr := fmt.Errorf("Error trying to delete folder %s containing objects without force_destroy set to true", bucket)
log.Printf("Error! %s : %s\n\n", bucket, deleteErr)
return deleteErr
}
// GCS requires that a folder be empty (have no objects or object
// versions) before it can be deleted.
log.Printf("[DEBUG] GCS Folder attempting to forceDestroy\n\n")

// Create a workerpool for parallel deletion of resources. In the
// future, it would be great to expose Terraform's global parallelism
// flag here, but that's currently reserved for core use. Testing
// shows that NumCPUs-1 is the most performant on average networks.
//
// The challenge with making this user-configurable is that the
// configuration would reside in the Terraform configuration file,
// decreasing its portability. Ideally we'd want this to connect to
// Terraform's top-level -parallelism flag, but that's not plumbed nor
// is it scheduled to be plumbed to individual providers.
wp := workerpool.New(runtime.NumCPU() - 1)

for _, object := range res.Items {
log.Printf("[DEBUG] Found %s", object.Name)
object := object

wp.Submit(func() {
log.Printf("[TRACE] Attempting to delete %s", object.Name)
if err := config.NewStorageClient(userAgent).Objects.Delete(bucket, object.Name).Generation(object.Generation).Do(); err != nil {
deleteObjectError = err
log.Printf("[ERR] Failed to delete storage object %s: %s", object.Name, err)
} else {
log.Printf("[TRACE] Successfully deleted %s", object.Name)
}
})
}

// Wait for everything to finish.
wp.StopWait()
}

if listError != nil {
return fmt.Errorf("could not delete non-empty folder due to error when listing contents: %v", listError)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if I missed this: the bucket implementation appears to attempt the bucket deletion even if there is a listError, and then only surface the error if the deletion was not successful. Was there a reason we didn't do the same here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in bucket deletion, we are not attempting to delete the bucket if the listError occurs

Copy link
Member

@kautikdk kautikdk Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we do try to delete bucket even if the listError occurs,
Here we are only breaking object deletion loop in case of error. We try to delete bucket regardless of object deletion, Reference.

Copy link
Contributor Author

@gurusai-voleti gurusai-voleti Dec 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

here I see there is no need to attempt to delete the folder,

foldersList, err := config.NewStorageClient(userAgent).Folders.List(bucket).Prefix(name).Do()
if len(foldersList.Items) == 1 || d.Get("force_destroy").(bool)

the API above call will return only the folder if it doesn't contain any sub folders if any sub folders exists it will return all the matching items so the logic is

if its the only folder exists we delete the folder irrespective of force destroy set to true or false,
if any subfolders exists and force_destroy set to true we delete the sub folders and the folder also, so in any case folder will be deleted

if subfolders exist and force destroy is false we throw an error in else case

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this was added to the bucket implementation here: #2755. You can read the PR description, but it appears this was added to get around permissions (a user may be able to delete the bucket, but not list buckets).

I would defer to the two of you if the same issue will be present for folders.

}
if deleteObjectError != nil {
return fmt.Errorf("could not delete non-empty folder due to error when deleting contents: %v", deleteObjectError)
}

// attempts to delete any sub folders and folder
foldersList, err := config.NewStorageClient(userAgent).Folders.List(bucket).Prefix(name).Do()
if err != nil {
return err
}
if len(foldersList.Items) == 1 || d.Get("force_destroy").(bool) {
log.Printf("[DEBUG] folder names to delete: %#v", name)
items := foldersList.Items
for i := len(items) - 1; i >= 0; i-- {
err = transport_tpg.Retry(transport_tpg.RetryOptions{
RetryFunc: func() error {
err = config.NewStorageClient(userAgent).Folders.Delete(bucket, items[i].Name).Do()
return err
},
Timeout: d.Timeout(schema.TimeoutDelete),
ErrorRetryPredicates: []transport_tpg.RetryErrorPredicateFunc{transport_tpg.Is429RetryableQuotaError},
})
if err != nil {
return err
}
}

log.Printf("[DEBUG] Finished deleting Folder %q: %#v", d.Id(), name)
} else {
deleteErr := fmt.Errorf("Sub folders exist within folder, use force_destroy to true to delete all subfolders")
log.Printf("Error! %s : %s\n\n", name, deleteErr)
return deleteErr
}
return nil
19 changes: 19 additions & 0 deletions mmv1/templates/terraform/custom_import/storage_folder.go.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
config := meta.(*transport_tpg.Config)
if err := tpgresource.ParseImportId([]string{
"^(?P<bucket>[^/]+)/folders/(?P<name>.+)$",
"^(?P<bucket>[^/]+)/(?P<name>.+)$",
}, d, config); err != nil {
return nil, err
}

// Replace import id for the resource id
id, err := tpgresource.ReplaceVars(d, config, "{{"{{"}}bucket{{"}}"}}/{{"{{"}}name{{"}}"}}")
if err != nil {
return nil, fmt.Errorf("Error constructing id: %s", err)
}
d.SetId(id)
if err := d.Set("force_destroy", false); err != nil {
return nil, fmt.Errorf("Error setting force_destroy: %s", err)
}

return []*schema.ResourceData{d}, nil
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
_ = config
// we can only get here if force_destroy was updated
if d.Get("force_destroy") != nil {
if err := d.Set("force_destroy", d.Get("force_destroy")); err != nil {
return fmt.Errorf("Error updating force_destroy: %s", err)
}
}
gurusai-voleti marked this conversation as resolved.
Show resolved Hide resolved

// all other fields are immutable, don't do anything else
return nil
18 changes: 18 additions & 0 deletions mmv1/templates/terraform/examples/storage_folder_basic.tf.tmpl
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
resource "google_storage_bucket" "bucket" {
name = "{{index $.Vars "bucket_name"}}"
location = "EU"
uniform_bucket_level_access = true
hierarchical_namespace {
enabled = true
}
}

resource "google_storage_folder" "{{$.PrimaryResourceId}}" {
bucket = google_storage_bucket.bucket.name
name = "parent-folder/"
}

resource "google_storage_folder" "subfolder" {
bucket = google_storage_bucket.bucket.name
name = "${google_storage_folder.{{$.PrimaryResourceId}}.name}subfolder/"
}
Loading
Loading