github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/google/resource_google_project.go (about) 1 package google 2 3 import ( 4 "fmt" 5 "log" 6 "net/http" 7 "strconv" 8 "strings" 9 10 "github.com/hashicorp/terraform/helper/schema" 11 "google.golang.org/api/cloudbilling/v1" 12 "google.golang.org/api/cloudresourcemanager/v1" 13 "google.golang.org/api/googleapi" 14 ) 15 16 // resourceGoogleProject returns a *schema.Resource that allows a customer 17 // to declare a Google Cloud Project resource. 18 func resourceGoogleProject() *schema.Resource { 19 return &schema.Resource{ 20 SchemaVersion: 1, 21 22 Create: resourceGoogleProjectCreate, 23 Read: resourceGoogleProjectRead, 24 Update: resourceGoogleProjectUpdate, 25 Delete: resourceGoogleProjectDelete, 26 27 Importer: &schema.ResourceImporter{ 28 State: schema.ImportStatePassthrough, 29 }, 30 MigrateState: resourceGoogleProjectMigrateState, 31 32 Schema: map[string]*schema.Schema{ 33 "id": &schema.Schema{ 34 Type: schema.TypeString, 35 Optional: true, 36 Computed: true, 37 Removed: "The id field has been removed. Use project_id instead.", 38 }, 39 "project_id": &schema.Schema{ 40 Type: schema.TypeString, 41 Required: true, 42 ForceNew: true, 43 }, 44 "skip_delete": &schema.Schema{ 45 Type: schema.TypeBool, 46 Optional: true, 47 Computed: true, 48 }, 49 "name": &schema.Schema{ 50 Type: schema.TypeString, 51 Required: true, 52 }, 53 "org_id": &schema.Schema{ 54 Type: schema.TypeString, 55 Required: true, 56 ForceNew: true, 57 }, 58 "policy_data": &schema.Schema{ 59 Type: schema.TypeString, 60 Optional: true, 61 Computed: true, 62 Removed: "Use the 'google_project_iam_policy' resource to define policies for a Google Project", 63 }, 64 "policy_etag": &schema.Schema{ 65 Type: schema.TypeString, 66 Computed: true, 67 Removed: "Use the the 'google_project_iam_policy' resource to define policies for a Google Project", 68 }, 69 "number": &schema.Schema{ 70 Type: schema.TypeString, 71 Computed: true, 72 }, 73 "billing_account": &schema.Schema{ 74 Type: schema.TypeString, 75 Optional: true, 76 }, 77 }, 78 } 79 } 80 81 func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error { 82 config := meta.(*Config) 83 84 var pid string 85 var err error 86 pid = d.Get("project_id").(string) 87 88 log.Printf("[DEBUG]: Creating new project %q", pid) 89 project := &cloudresourcemanager.Project{ 90 ProjectId: pid, 91 Name: d.Get("name").(string), 92 Parent: &cloudresourcemanager.ResourceId{ 93 Id: d.Get("org_id").(string), 94 Type: "organization", 95 }, 96 } 97 98 op, err := config.clientResourceManager.Projects.Create(project).Do() 99 if err != nil { 100 return fmt.Errorf("Error creating project %s (%s): %s.", project.ProjectId, project.Name, err) 101 } 102 103 d.SetId(pid) 104 105 // Wait for the operation to complete 106 waitErr := resourceManagerOperationWait(config, op, "project to create") 107 if waitErr != nil { 108 // The resource wasn't actually created 109 d.SetId("") 110 return waitErr 111 } 112 113 // Set the billing account 114 if v, ok := d.GetOk("billing_account"); ok { 115 name := v.(string) 116 ba := cloudbilling.ProjectBillingInfo{ 117 BillingAccountName: "billingAccounts/" + name, 118 } 119 _, err = config.clientBilling.Projects.UpdateBillingInfo(prefixedProject(pid), &ba).Do() 120 if err != nil { 121 d.Set("billing_account", "") 122 if _err, ok := err.(*googleapi.Error); ok { 123 return fmt.Errorf("Error setting billing account %q for project %q: %v", name, prefixedProject(pid), _err) 124 } 125 return fmt.Errorf("Error setting billing account %q for project %q: %v", name, prefixedProject(pid), err) 126 } 127 } 128 129 return resourceGoogleProjectRead(d, meta) 130 } 131 132 func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error { 133 config := meta.(*Config) 134 pid := d.Id() 135 136 // Read the project 137 p, err := config.clientResourceManager.Projects.Get(pid).Do() 138 if err != nil { 139 if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound { 140 return fmt.Errorf("Project %q does not exist.", pid) 141 } 142 return fmt.Errorf("Error checking project %q: %s", pid, err) 143 } 144 145 d.Set("project_id", pid) 146 d.Set("number", strconv.FormatInt(int64(p.ProjectNumber), 10)) 147 d.Set("name", p.Name) 148 149 if p.Parent != nil { 150 d.Set("org_id", p.Parent.Id) 151 } 152 153 // Read the billing account 154 ba, err := config.clientBilling.Projects.GetBillingInfo(prefixedProject(pid)).Do() 155 if err != nil { 156 return fmt.Errorf("Error reading billing account for project %q: %v", prefixedProject(pid), err) 157 } 158 if ba.BillingAccountName != "" { 159 // BillingAccountName is contains the resource name of the billing account 160 // associated with the project, if any. For example, 161 // `billingAccounts/012345-567890-ABCDEF`. We care about the ID and not 162 // the `billingAccounts/` prefix, so we need to remove that. If the 163 // prefix ever changes, we'll validate to make sure it's something we 164 // recognize. 165 _ba := strings.TrimPrefix(ba.BillingAccountName, "billingAccounts/") 166 if ba.BillingAccountName == _ba { 167 return fmt.Errorf("Error parsing billing account for project %q. Expected value to begin with 'billingAccounts/' but got %s", prefixedProject(pid), ba.BillingAccountName) 168 } 169 d.Set("billing_account", _ba) 170 } 171 return nil 172 } 173 174 func prefixedProject(pid string) string { 175 return "projects/" + pid 176 } 177 178 func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error { 179 config := meta.(*Config) 180 pid := d.Id() 181 182 // Read the project 183 // we need the project even though refresh has already been called 184 // because the API doesn't support patch, so we need the actual object 185 p, err := config.clientResourceManager.Projects.Get(pid).Do() 186 if err != nil { 187 if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound { 188 return fmt.Errorf("Project %q does not exist.", pid) 189 } 190 return fmt.Errorf("Error checking project %q: %s", pid, err) 191 } 192 193 // Project name has changed 194 if ok := d.HasChange("name"); ok { 195 p.Name = d.Get("name").(string) 196 // Do update on project 197 p, err = config.clientResourceManager.Projects.Update(p.ProjectId, p).Do() 198 if err != nil { 199 return fmt.Errorf("Error updating project %q: %s", p.Name, err) 200 } 201 } 202 203 // Billing account has changed 204 if ok := d.HasChange("billing_account"); ok { 205 name := d.Get("billing_account").(string) 206 ba := cloudbilling.ProjectBillingInfo{ 207 BillingAccountName: "billingAccounts/" + name, 208 } 209 _, err = config.clientBilling.Projects.UpdateBillingInfo(prefixedProject(pid), &ba).Do() 210 if err != nil { 211 d.Set("billing_account", "") 212 if _err, ok := err.(*googleapi.Error); ok { 213 return fmt.Errorf("Error updating billing account %q for project %q: %v", name, prefixedProject(pid), _err) 214 } 215 return fmt.Errorf("Error updating billing account %q for project %q: %v", name, prefixedProject(pid), err) 216 } 217 } 218 return nil 219 } 220 221 func resourceGoogleProjectDelete(d *schema.ResourceData, meta interface{}) error { 222 config := meta.(*Config) 223 // Only delete projects if skip_delete isn't set 224 if !d.Get("skip_delete").(bool) { 225 pid := d.Id() 226 _, err := config.clientResourceManager.Projects.Delete(pid).Do() 227 if err != nil { 228 return fmt.Errorf("Error deleting project %q: %s", pid, err) 229 } 230 } 231 d.SetId("") 232 return nil 233 }