-
Notifications
You must be signed in to change notification settings - Fork 1.8k
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
Create, update, move and delete a GCP folders. #416
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks pretty good; I like the thorough documentation!
google/resource_google_folder.go
Outdated
// Since we waited above, the operation is guaranteed to have been successful by this point. | ||
waitOp, err := config.clientResourceManager.Operations.Get(op.Name).Do() | ||
if err != nil { | ||
return fmt.Errorf("The folder '%s' has been created but we could not retrieve its id. Delete the folder manually and retry or use 'terraform import'.", displayName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The err is eaten here; should probably include it in the error message
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
google/resource_google_folder.go
Outdated
|
||
Schema: map[string]*schema.Schema{ | ||
// Format is 'folders/{folder_id}. | ||
// The folder id holds the same value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you mean 'id' or 'folder_id' as in terraform id, right? This comment was confusing to me because I don't see folder_id anywhere (this is exacerbated by the fact that other resources have fields like instance_id (which I am responsible for sorry) that are distinct from id).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced 'folder id' by 'terraform id'
google/resource_google_folder.go
Outdated
if err != nil { | ||
return fmt.Errorf("The folder '%s' has been created but we could not retrieve its id. Delete the folder manually and retry or use 'terraform import'.", displayName) | ||
} | ||
response := waitOp.Response.(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm surprised this works, the documentation seems to indicate that there should be some sort of struct here.
Given we don't control the code that is in Response and the type assertion will cause a big nasty error if it fails, how about using the checked type assertion here? eg:
response, ok := waitOp.Response.(map[string]interface{})
if !ok {
// ...
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Done
|
||
d.Partial(true) | ||
if d.HasChange("display_name") { | ||
_, err := config.clientResourceManagerV2Beta1.Folders.Patch(d.Id(), &resourceManagerV2Beta1.Folder{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any way you could combine the patch code for display_name and parent into one api call? This would result in less code and be less likely to fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No we can't. The display_name must be updated with Patch
and the parent must be updated with Move
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I missed that, thanks.
if err != nil { | ||
return fmt.Errorf("Error deleting folder %s", displayName) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing a d.SetId("")
call to indicate successful deletion
Although apparently that isn't needed since the tests passed? I'm curious if we should be doing that or not.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In resource.go in terraform core:
// Destroy the resource since it is created
if err := r.Delete(data, meta); err != nil {
return r.recordCurrentSchemaVersion(data.State()), err
}
// Make sure the ID is gone.
data.SetId("")
It does it for us. Calling it again won't cause harm. Should I add it for consistency?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm 👍 on not setting it, except for in non-Delete methods (Read when not found, etc.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good to know, will omit that on delete in the future.
CheckDestroy: testAccCheckGoogleFolderDestroy, | ||
Steps: []resource.TestStep{ | ||
resource.TestStep{ | ||
Config: testAccGoogleFolder_basis(folderDisplayName, parent), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
minor nit
basis -> basic (unless this config is really the 'basis' for the test?)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant the basis because I use it as a basic for two tests but I guess the intention wasn't clear. I renamed it basic.
newFolderDisplayName := "tf-test-renamed-" + acctest.RandString(10) | ||
org := os.Getenv("GOOGLE_ORG") | ||
parent := "organizations/" + org | ||
folder := &resourceManagerV2Beta1.Folder{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another nit; I've seen other tests declare the resource variable as the struct type, not the pointer type (e.g. resourceManagerV2Beta1.Folder
rather than *resourceManagerV2Beta1.Folder
. The pointer allocation here feels a bit less explicit. But if this was intentional go ahead and leave it in as this is mainly stylistic.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
}) | ||
} | ||
|
||
func TestAccGoogleFolder_move(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I got this initially confused with rename (move in hierarchies is often used to rename the element, like in filesystems). Consider adding the suffix _moveParent
to the test name?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to _moveParent
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckGoogleFolderExists("google_folder.folder1", folder), | ||
testAccCheckGoogleFolderDisplayName(folder, folder1DisplayName), | ||
testAccCheckGoogleFolderExists("google_folder.folder2", folder), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you use a separate variable here for folder 2? Overwriting folder with the contents of folder 2 and then making similar assertions got really hard to read for me
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
* `name` - The resource name of the Folder. Its format is folders/{folder_id}. | ||
* `lifecycle_state` - The lifecycle state of the folder such as `ACTIVE` or `DELETE_REQUESTED`. | ||
* `name` - Timestamp when the Folder was created. Assigned by the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
name -> create_time
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TF_ACC=1 go test ./google -v -run TestAccGoogleFolder_ -timeout 120m
=== RUN TestAccGoogleFolder_import
--- PASS: TestAccGoogleFolder_import (13.79s)
=== RUN TestAccGoogleFolder_rename
--- PASS: TestAccGoogleFolder_rename (14.80s)
=== RUN TestAccGoogleFolder_moveParent
--- PASS: TestAccGoogleFolder_moveParent (37.93s)
PASS
ok github.com/terraform-providers/terraform-provider-google/google 66.645s
google/resource_google_folder.go
Outdated
|
||
Schema: map[string]*schema.Schema{ | ||
// Format is 'folders/{folder_id}. | ||
// The folder id holds the same value. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Replaced 'folder id' by 'terraform id'
google/resource_google_folder.go
Outdated
// Since we waited above, the operation is guaranteed to have been successful by this point. | ||
waitOp, err := config.clientResourceManager.Operations.Get(op.Name).Do() | ||
if err != nil { | ||
return fmt.Errorf("The folder '%s' has been created but we could not retrieve its id. Delete the folder manually and retry or use 'terraform import'.", displayName) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
google/resource_google_folder.go
Outdated
if err != nil { | ||
return fmt.Errorf("The folder '%s' has been created but we could not retrieve its id. Delete the folder manually and retry or use 'terraform import'.", displayName) | ||
} | ||
response := waitOp.Response.(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good idea. Done
|
||
d.Partial(true) | ||
if d.HasChange("display_name") { | ||
_, err := config.clientResourceManagerV2Beta1.Folders.Patch(d.Id(), &resourceManagerV2Beta1.Folder{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No we can't. The display_name must be updated with Patch
and the parent must be updated with Move
if err != nil { | ||
return fmt.Errorf("Error deleting folder %s", displayName) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In resource.go in terraform core:
// Destroy the resource since it is created
if err := r.Delete(data, meta); err != nil {
return r.recordCurrentSchemaVersion(data.State()), err
}
// Make sure the ID is gone.
data.SetId("")
It does it for us. Calling it again won't cause harm. Should I add it for consistency?
newFolderDisplayName := "tf-test-renamed-" + acctest.RandString(10) | ||
org := os.Getenv("GOOGLE_ORG") | ||
parent := "organizations/" + org | ||
folder := &resourceManagerV2Beta1.Folder{} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
CheckDestroy: testAccCheckGoogleFolderDestroy, | ||
Steps: []resource.TestStep{ | ||
resource.TestStep{ | ||
Config: testAccGoogleFolder_basis(folderDisplayName, parent), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant the basis because I use it as a basic for two tests but I guess the intention wasn't clear. I renamed it basic.
}) | ||
} | ||
|
||
func TestAccGoogleFolder_move(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
renamed to _moveParent
Check: resource.ComposeTestCheckFunc( | ||
testAccCheckGoogleFolderExists("google_folder.folder1", folder), | ||
testAccCheckGoogleFolderDisplayName(folder, folder1DisplayName), | ||
testAccCheckGoogleFolderExists("google_folder.folder2", folder), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
|
||
* `name` - The resource name of the Folder. Its format is folders/{folder_id}. | ||
* `lifecycle_state` - The lifecycle state of the folder such as `ACTIVE` or `DELETE_REQUESTED`. | ||
* `name` - Timestamp when the Folder was created. Assigned by the server. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks :)
google/resource_google_folder.go
Outdated
Schema: map[string]*schema.Schema{ | ||
// Format is 'folders/{folder_id}. | ||
// The terraform id holds the same value. | ||
"name": &schema.Schema{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The convention we've mostly been falling into is to list all Required fields first, then Optional, then Computed. I think that would make it more clear that display_name is what the user sets, rather than name (since for so many other resources it's name)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
google/resource_google_folder.go
Outdated
if err != nil { | ||
return fmt.Errorf("The folder '%s' has been created but we could not retrieve its id. Delete the folder manually and retry or use 'terraform import': %s", displayName, err) | ||
} | ||
response, ok := waitOp.Response.(map[string]interface{}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: if you felt like saving a line of code, you could also do
if response, ok := waitOp.Response.(map[string]interface{}); ok {
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer if the ok was negated altogether as it reduces nesting. In general if one of the paths leads to an error (or other early return) that's the one that I nest inside if.
It's also a bit more explicit (it requires a bit of careful reading to realize that when val, ok := response["name"]; ok
evaluates to false, it falls to the error at the bottom.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Discussed offline. Added a comment.
|
||
A folder can contain projects, other folders, or a combination of both. You can use folders to group projects under an organization in a hierarchy. For example, your organization might contain multiple departments, each with its own set of Cloud Platform resources. Folders allows you to group these resources on a per-department basis. Folders are used to group resources that share common IAM policies. | ||
|
||
Folders created lives inside an Organization. See the [Organization documentation](https://cloud.google.com/resource-manager/docs/quickstarts) for more details. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: s/lives/live
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
google/resource_google_folder.go
Outdated
// Format is either folders/{folder_id} or organizations/{org_id}. | ||
"parent": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Optional: true, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be required?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
if err != nil { | ||
return fmt.Errorf("Error deleting folder %s", displayName) | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm 👍 on not setting it, except for in non-Delete methods (Read when not found, etc.)
|
||
A folder can contain projects, other folders, or a combination of both. You can use folders to group projects under an organization in a hierarchy. For example, your organization might contain multiple departments, each with its own set of Cloud Platform resources. Folders allows you to group these resources on a per-department basis. Folders are used to group resources that share common IAM policies. | ||
|
||
Folders created lives inside an Organization. See the [Organization documentation](https://cloud.google.com/resource-manager/docs/quickstarts) for more details. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Created folders live", or some other variation?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
Sorry, meant to change review status to just "comments" without explicit approval but apparently I can only switch from request changes -> approve. This is good to merge with me as soon as everyone else's comments are addressed. |
TF_ACC=1 go test ./google -v -run TestAccGoogleFolder_ -timeout 120m
=== RUN TestAccGoogleFolder_import
--- PASS: TestAccGoogleFolder_import (13.73s)
=== RUN TestAccGoogleFolder_rename
--- PASS: TestAccGoogleFolder_rename (14.40s)
=== RUN TestAccGoogleFolder_moveParent
--- PASS: TestAccGoogleFolder_moveParent (37.29s)
PASS
ok github.com/terraform-providers/terraform-provider-google/google 65.553s |
* Initialize resourcemanager v2beta1 client * Create new google_folder resource supporting create, move, update and delete operations. * Add documentation for folders
* Initialize resourcemanager v2beta1 client * Create new google_folder resource supporting create, move, update and delete operations. * Add documentation for folders
…ashicorp#416) <!-- This change is generated by MagicModules. --> /cc @rileykarson
I'm going to lock this issue because it has been closed for 30 days ⏳. This helps our maintainers find and focus on the active issues. If you feel this issue should be reopened, we encourage creating a new issue linking back to this one for added context. If you feel I made an error 🤖 🙉 , please reach out to my human friends 👉 [email protected]. Thanks! |
Fixes #412
cc/ @danawillow, @srivasta, @ubschmidt2