mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-28 22:16:04 +00:00
Folder support: Assign/Reassign a google project to a folder. (#438)
+ Make the org_id optional when creating a project. Closes #131 + Mark org_id as computed to allow for GCP automatically assigning the org. + Add an acceptance test for project creation without an organization. + Skip TestAccGoogleProject_createWithoutOrg if GOOGLE_ORG is set. + Add a folder_id to the google_project resource, optionally specifying the ID of the GCP folder in which the GCP project should live. + Document how one can provision a project into a folder, and added a sample configuration to create a project into an existing folder. * Skip test without org if service account is used * Support folders/* or id only for the folder id field
This commit is contained in:
parent
f3ecd1ea1c
commit
c1d0e716d9
@ -52,8 +52,16 @@ func resourceGoogleProject() *schema.Resource {
|
||||
},
|
||||
"org_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ConflictsWith: []string{"folder_id"},
|
||||
},
|
||||
"folder_id": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
ConflictsWith: []string{"org_id"},
|
||||
StateFunc: parseFolderId,
|
||||
},
|
||||
"policy_data": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
@ -95,12 +103,10 @@ func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error
|
||||
project := &cloudresourcemanager.Project{
|
||||
ProjectId: pid,
|
||||
Name: d.Get("name").(string),
|
||||
Parent: &cloudresourcemanager.ResourceId{
|
||||
Id: d.Get("org_id").(string),
|
||||
Type: "organization",
|
||||
},
|
||||
}
|
||||
|
||||
getParentResourceId(d, project)
|
||||
|
||||
if _, ok := d.GetOk("labels"); ok {
|
||||
project.Labels = expandLabels(d)
|
||||
}
|
||||
@ -155,7 +161,14 @@ func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
|
||||
d.Set("labels", p.Labels)
|
||||
|
||||
if p.Parent != nil {
|
||||
switch p.Parent.Type {
|
||||
case "organization":
|
||||
d.Set("org_id", p.Parent.Id)
|
||||
d.Set("folder_id", "")
|
||||
case "folder":
|
||||
d.Set("folder_id", p.Parent.Id)
|
||||
d.Set("org_id", "")
|
||||
}
|
||||
}
|
||||
|
||||
// Read the billing account
|
||||
@ -183,9 +196,36 @@ func prefixedProject(pid string) string {
|
||||
return "projects/" + pid
|
||||
}
|
||||
|
||||
func getParentResourceId(d *schema.ResourceData, p *cloudresourcemanager.Project) error {
|
||||
if v, ok := d.GetOk("org_id"); ok {
|
||||
org_id := v.(string)
|
||||
p.Parent = &cloudresourcemanager.ResourceId{
|
||||
Id: org_id,
|
||||
Type: "organization",
|
||||
}
|
||||
}
|
||||
|
||||
if v, ok := d.GetOk("folder_id"); ok {
|
||||
p.Parent = &cloudresourcemanager.ResourceId{
|
||||
Id: parseFolderId(v),
|
||||
Type: "folder",
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseFolderId(v interface{}) string {
|
||||
folderId := v.(string)
|
||||
if strings.HasPrefix(folderId, "folders/") {
|
||||
return folderId[8:]
|
||||
}
|
||||
return folderId
|
||||
}
|
||||
|
||||
func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
pid := d.Id()
|
||||
project_name := d.Get("name").(string)
|
||||
|
||||
// Read the project
|
||||
// we need the project even though refresh has already been called
|
||||
@ -198,30 +238,46 @@ func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error
|
||||
return fmt.Errorf("Error checking project %q: %s", pid, err)
|
||||
}
|
||||
|
||||
// Project name has changed
|
||||
d.Partial(true)
|
||||
|
||||
// Project display name has changed
|
||||
if ok := d.HasChange("name"); ok {
|
||||
p.Name = d.Get("name").(string)
|
||||
p.Name = project_name
|
||||
// Do update on project
|
||||
p, err = config.clientResourceManager.Projects.Update(p.ProjectId, p).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error updating project %q: %s", p.Name, err)
|
||||
return fmt.Errorf("Error updating project %q: %s", project_name, err)
|
||||
}
|
||||
d.SetPartial("name")
|
||||
}
|
||||
|
||||
// Project parent has changed
|
||||
if d.HasChange("org_id") || d.HasChange("folder_id") {
|
||||
getParentResourceId(d, p)
|
||||
|
||||
// Do update on project
|
||||
p, err = config.clientResourceManager.Projects.Update(p.ProjectId, p).Do()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error updating project %q: %s", project_name, err)
|
||||
}
|
||||
d.SetPartial("org_id")
|
||||
d.SetPartial("folder_id")
|
||||
}
|
||||
|
||||
// Billing account has changed
|
||||
if ok := d.HasChange("billing_account"); ok {
|
||||
name := d.Get("billing_account").(string)
|
||||
billing_name := d.Get("billing_account").(string)
|
||||
ba := cloudbilling.ProjectBillingInfo{}
|
||||
if name != "" {
|
||||
ba.BillingAccountName = "billingAccounts/" + name
|
||||
if billing_name != "" {
|
||||
ba.BillingAccountName = "billingAccounts/" + billing_name
|
||||
}
|
||||
_, err = config.clientBilling.Projects.UpdateBillingInfo(prefixedProject(pid), &ba).Do()
|
||||
if err != nil {
|
||||
d.Set("billing_account", "")
|
||||
if _err, ok := err.(*googleapi.Error); ok {
|
||||
return fmt.Errorf("Error updating billing account %q for project %q: %v", name, prefixedProject(pid), _err)
|
||||
return fmt.Errorf("Error updating billing account %q for project %q: %v", billing_name, prefixedProject(pid), _err)
|
||||
}
|
||||
return fmt.Errorf("Error updating billing account %q for project %q: %v", name, prefixedProject(pid), err)
|
||||
return fmt.Errorf("Error updating billing account %q for project %q: %v", billing_name, prefixedProject(pid), err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,6 +291,8 @@ func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error
|
||||
return fmt.Errorf("Error updating project %q: %s", p.Name, err)
|
||||
}
|
||||
}
|
||||
d.Partial(false)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -658,6 +658,14 @@ data "google_iam_policy" "admin" {
|
||||
`, pid, name, org)
|
||||
}
|
||||
|
||||
func testAccGoogleProject_createWithoutOrg(pid, name string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
project_id = "%s"
|
||||
name = "%s"
|
||||
}`, pid, name)
|
||||
}
|
||||
|
||||
func testAccGoogleProject_create(pid, name, org string) string {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
|
@ -24,15 +24,44 @@ var (
|
||||
originalPolicy *cloudresourcemanager.Policy
|
||||
)
|
||||
|
||||
// Test that a Project resource can be created and an IAM policy
|
||||
// associated
|
||||
func TestAccGoogleProject_create(t *testing.T) {
|
||||
// Test that a Project resource can be created without an organization
|
||||
func TestAccGoogleProject_createWithoutOrg(t *testing.T) {
|
||||
creds := multiEnvSearch(credsEnvVars)
|
||||
if strings.Contains(creds, "iam.gserviceaccount.com") {
|
||||
t.Skip("Service accounts cannot create projects without a parent. Requires user credentials.")
|
||||
}
|
||||
|
||||
pid := "terraform-" + acctest.RandString(10)
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
// This step imports an existing project
|
||||
// This step creates a new project
|
||||
resource.TestStep{
|
||||
Config: testAccGoogleProject_createWithoutOrg(pid, pname),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckGoogleProjectExists("google_project.acceptance", pid),
|
||||
),
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// Test that a Project resource can be created and an IAM policy
|
||||
// associated
|
||||
func TestAccGoogleProject_create(t *testing.T) {
|
||||
skipIfEnvNotSet(t,
|
||||
[]string{
|
||||
"GOOGLE_ORG",
|
||||
}...,
|
||||
)
|
||||
|
||||
pid := "terraform-" + acctest.RandString(10)
|
||||
resource.Test(t, resource.TestCase{
|
||||
PreCheck: func() { testAccPreCheck(t) },
|
||||
Providers: testAccProviders,
|
||||
Steps: []resource.TestStep{
|
||||
// This step creates a new project
|
||||
resource.TestStep{
|
||||
Config: testAccGoogleProject_create(pid, pname, org),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
|
@ -45,6 +45,20 @@ resource "google_project" "my_project" {
|
||||
}
|
||||
```
|
||||
|
||||
To create a project under a specific folder
|
||||
|
||||
```hcl
|
||||
resource "google_project" "my_project-in-a-folder" {
|
||||
project_id = "your-project-id"
|
||||
folder_id = "${google_folder.department1.name}"
|
||||
}
|
||||
|
||||
resource "google_folder" "department1" {
|
||||
display_name = "Department 1"
|
||||
parent = "organizations/1234567"
|
||||
}
|
||||
```
|
||||
|
||||
## Argument Reference
|
||||
|
||||
The following arguments are supported:
|
||||
@ -53,7 +67,7 @@ The following arguments are supported:
|
||||
Changing this forces a new project to be created. If this attribute is not
|
||||
set, `id` must be set. As `id` is deprecated, consider this attribute
|
||||
required. If you are using `project_id` and creating a new project, the
|
||||
`org_id` and `name` attributes are also required.
|
||||
`name` attribute is also required.
|
||||
|
||||
* `id` - (Deprecated) The project ID.
|
||||
This attribute has unexpected behaviour and probably does not work
|
||||
@ -62,8 +76,17 @@ The following arguments are supported:
|
||||
[below](#id-field) for more information about its behaviour.
|
||||
|
||||
* `org_id` - (Optional) The numeric ID of the organization this project belongs to.
|
||||
This is required if you are creating a new project.
|
||||
Changing this forces a new project to be created.
|
||||
Changing this forces a new project to be created. Only one of
|
||||
`org_id` or `folder_id` may be specified. If the `org_id` is
|
||||
specified then the project is created at the top level. Changing
|
||||
this forces the project to be migrated to the newly specified
|
||||
organization.
|
||||
|
||||
* `folder_id` - (Optional) The numeric ID of the folder this project should be
|
||||
created under. Only one of `org_id` or `folder_id` may be
|
||||
specified. If the `folder_id` is specified, then the project is
|
||||
created under the specified folder. Changing this forces the
|
||||
project to be migrated to the newly specified folder.
|
||||
|
||||
* `billing_account` - (Optional) The alphanumeric ID of the billing account this project
|
||||
belongs to. The user or service account performing this operation with Terraform
|
||||
|
Loading…
Reference in New Issue
Block a user