-
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
Changes from all commits
134116a
ccd8855
883c252
9646380
71d1edd
600e890
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package google | ||
|
||
import ( | ||
"github.com/hashicorp/terraform/helper/acctest" | ||
"github.com/hashicorp/terraform/helper/resource" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func TestAccGoogleFolder_import(t *testing.T) { | ||
skipIfEnvNotSet(t, "GOOGLE_ORG") | ||
|
||
folderDisplayName := "tf-test-" + acctest.RandString(10) | ||
org := os.Getenv("GOOGLE_ORG") | ||
parent := "organizations/" + org | ||
|
||
resource.Test(t, resource.TestCase{ | ||
PreCheck: func() { testAccPreCheck(t) }, | ||
Providers: testAccProviders, | ||
CheckDestroy: testAccCheckGoogleFolderDestroy, | ||
Steps: []resource.TestStep{ | ||
resource.TestStep{ | ||
Config: testAccGoogleFolder_basic(folderDisplayName, parent), | ||
}, | ||
resource.TestStep{ | ||
ResourceName: "google_folder.folder1", | ||
ImportState: true, | ||
ImportStateVerify: true, | ||
}, | ||
}, | ||
}) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,168 @@ | ||
package google | ||
|
||
import ( | ||
"fmt" | ||
"github.com/hashicorp/terraform/helper/schema" | ||
resourceManagerV2Beta1 "google.golang.org/api/cloudresourcemanager/v2beta1" | ||
"strings" | ||
) | ||
|
||
func resourceGoogleFolder() *schema.Resource { | ||
return &schema.Resource{ | ||
Create: resourceGoogleFolderCreate, | ||
Read: resourceGoogleFolderRead, | ||
Update: resourceGoogleFolderUpdate, | ||
Delete: resourceGoogleFolderDelete, | ||
|
||
Importer: &schema.ResourceImporter{ | ||
State: resourceGoogleFolderImportState, | ||
}, | ||
|
||
Schema: map[string]*schema.Schema{ | ||
// Format is either folders/{folder_id} or organizations/{org_id}. | ||
"parent": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
// Must be unique amongst its siblings. | ||
"display_name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Required: true, | ||
}, | ||
|
||
// Format is 'folders/{folder_id}. | ||
// The terraform id holds the same value. | ||
"name": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"lifecycle_state": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
"create_time": &schema.Schema{ | ||
Type: schema.TypeString, | ||
Computed: true, | ||
}, | ||
}, | ||
} | ||
} | ||
|
||
func resourceGoogleFolderCreate(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
|
||
displayName := d.Get("display_name").(string) | ||
parent := d.Get("parent").(string) | ||
|
||
op, err := config.clientResourceManagerV2Beta1.Folders.Create(&resourceManagerV2Beta1.Folder{ | ||
DisplayName: displayName, | ||
}).Parent(parent).Do() | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error creating folder '%s' in '%s': %s", displayName, parent, err) | ||
} | ||
|
||
err = resourceManagerV2Beta1OperationWait(config, op, "creating folder") | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error creating folder '%s' in '%s': %s", displayName, parent, err) | ||
} | ||
|
||
// 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': %s", displayName, err) | ||
} | ||
|
||
// Requires 3 successive checks for safety. Nested IFs are used to avoid 3 error statement with the same message. | ||
if response, ok := waitOp.Response.(map[string]interface{}); ok { | ||
if val, ok := response["name"]; ok { | ||
if name, ok := val.(string); ok { | ||
d.SetId(name) | ||
return resourceGoogleFolderRead(d, meta) | ||
} | ||
} | ||
} | ||
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) | ||
} | ||
|
||
func resourceGoogleFolderRead(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
|
||
folder, err := config.clientResourceManagerV2Beta1.Folders.Get(d.Id()).Do() | ||
if err != nil { | ||
return handleNotFoundError(err, d, d.Id()) | ||
} | ||
|
||
d.Set("name", folder.Name) | ||
d.Set("parent", folder.Parent) | ||
d.Set("display_name", folder.DisplayName) | ||
d.Set("lifecycle_state", folder.LifecycleState) | ||
d.Set("create_time", folder.CreateTime) | ||
|
||
return nil | ||
} | ||
|
||
func resourceGoogleFolderUpdate(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
displayName := d.Get("display_name").(string) | ||
|
||
d.Partial(true) | ||
if d.HasChange("display_name") { | ||
_, err := config.clientResourceManagerV2Beta1.Folders.Patch(d.Id(), &resourceManagerV2Beta1.Folder{ | ||
DisplayName: displayName, | ||
}).Do() | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error updating display_name to '%s': %s", displayName, err) | ||
} | ||
|
||
d.SetPartial("display_name") | ||
} | ||
|
||
if d.HasChange("parent") { | ||
newParent := d.Get("parent").(string) | ||
op, err := config.clientResourceManagerV2Beta1.Folders.Move(d.Id(), &resourceManagerV2Beta1.MoveFolderRequest{ | ||
DestinationParent: newParent, | ||
}).Do() | ||
|
||
if err != nil { | ||
return fmt.Errorf("Error moving folder '%s' to '%s': %s", displayName, newParent, err) | ||
} | ||
|
||
err = resourceManagerV2Beta1OperationWait(config, op, "move folder") | ||
if err != nil { | ||
return fmt.Errorf("Error moving folder '%s' to '%s': %s", displayName, newParent, err) | ||
} | ||
|
||
d.SetPartial("parent") | ||
} | ||
|
||
d.Partial(false) | ||
|
||
return nil | ||
} | ||
|
||
func resourceGoogleFolderDelete(d *schema.ResourceData, meta interface{}) error { | ||
config := meta.(*Config) | ||
displayName := d.Get("display_name").(string) | ||
|
||
_, err := config.clientResourceManagerV2Beta1.Folders.Delete(d.Id()).Do() | ||
if err != nil { | ||
return fmt.Errorf("Error deleting folder %s", displayName) | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Missing a 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 commentThe 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 commentThe 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 commentThe reason will be displayed to describe this comment to others. Learn more. Good to know, will omit that on delete in the future. |
||
return nil | ||
} | ||
|
||
func resourceGoogleFolderImportState(d *schema.ResourceData, m interface{}) ([]*schema.ResourceData, error) { | ||
id := d.Id() | ||
|
||
if !strings.HasPrefix(d.Id(), "folders/") { | ||
id = fmt.Sprintf("folders/%s", id) | ||
} | ||
|
||
d.SetId(id) | ||
|
||
return []*schema.ResourceData{d}, nil | ||
} |
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 withMove
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.