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

Create, update, move and delete a GCP folders. #416

Merged
merged 6 commits into from
Sep 14, 2017

Conversation

rosbo
Copy link
Contributor

@rosbo rosbo commented Sep 13, 2017

Copy link
Contributor

@selmanj selmanj left a 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!

// 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)
Copy link
Contributor

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

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.


Schema: map[string]*schema.Schema{
// Format is 'folders/{folder_id}.
// The folder id holds the same value.
Copy link
Contributor

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).

Copy link
Contributor Author

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'

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{})
Copy link
Contributor

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 {
  // ...
}

Copy link
Contributor Author

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{
Copy link
Contributor

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.

Copy link
Contributor Author

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

Copy link
Contributor

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)
}

Copy link
Contributor

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.

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 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?

Copy link
Contributor

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.)

Copy link
Contributor

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),
Copy link
Contributor

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?)

Copy link
Contributor Author

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{}
Copy link
Contributor

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.

Copy link
Contributor Author

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) {
Copy link
Contributor

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?

Copy link
Contributor Author

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),
Copy link
Contributor

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

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

name -> create_time

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks :)

Copy link
Contributor Author

@rosbo rosbo left a 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


Schema: map[string]*schema.Schema{
// Format is 'folders/{folder_id}.
// The folder id holds the same value.
Copy link
Contributor Author

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'

// 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)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Fixed.

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{})
Copy link
Contributor Author

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{
Copy link
Contributor Author

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)
}

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 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{}
Copy link
Contributor Author

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),
Copy link
Contributor Author

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) {
Copy link
Contributor Author

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),
Copy link
Contributor Author

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.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Thanks :)

Schema: map[string]*schema.Schema{
// Format is 'folders/{folder_id}.
// The terraform id holds the same value.
"name": &schema.Schema{
Copy link
Contributor

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)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

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{})
Copy link
Contributor

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 {

Copy link
Contributor

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.

Copy link
Contributor Author

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.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: s/lives/live

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

// Format is either folders/{folder_id} or organizations/{org_id}.
"parent": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this be required?

Copy link
Contributor Author

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)
}

Copy link
Contributor

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.
Copy link
Contributor

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@selmanj
Copy link
Contributor

selmanj commented Sep 14, 2017

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.

@rosbo
Copy link
Contributor Author

rosbo commented Sep 14, 2017

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

@rosbo rosbo merged commit 167abea into hashicorp:master Sep 14, 2017
@rosbo rosbo deleted the crud-folders branch September 14, 2017 18:20
negz pushed a commit to negz/terraform-provider-google that referenced this pull request Oct 17, 2017
* Initialize resourcemanager v2beta1 client
* Create new google_folder resource supporting create, move, update and delete operations.
* Add documentation for folders
chrisst pushed a commit to chrisst/terraform-provider-google that referenced this pull request Nov 9, 2018
* Initialize resourcemanager v2beta1 client
* Create new google_folder resource supporting create, move, update and delete operations.
* Add documentation for folders
luis-silva pushed a commit to luis-silva/terraform-provider-google that referenced this pull request May 21, 2019
@ghost
Copy link

ghost commented Mar 31, 2020

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!

@ghost ghost locked and limited conversation to collaborators Mar 31, 2020
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Create, update, move, delete a GCP folder.
4 participants