github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/builtin/providers/google/resource_google_project.go (about) 1 package google 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 "net/http" 8 "strconv" 9 10 "github.com/hashicorp/terraform/helper/schema" 11 "google.golang.org/api/cloudresourcemanager/v1" 12 "google.golang.org/api/googleapi" 13 ) 14 15 // resourceGoogleProject returns a *schema.Resource that allows a customer 16 // to declare a Google Cloud Project resource. 17 // 18 // This example shows a project with a policy declared in config: 19 // 20 // resource "google_project" "my-project" { 21 // project = "a-project-id" 22 // policy = "${data.google_iam_policy.admin.policy}" 23 // } 24 func resourceGoogleProject() *schema.Resource { 25 return &schema.Resource{ 26 SchemaVersion: 1, 27 28 Create: resourceGoogleProjectCreate, 29 Read: resourceGoogleProjectRead, 30 Update: resourceGoogleProjectUpdate, 31 Delete: resourceGoogleProjectDelete, 32 33 Importer: &schema.ResourceImporter{ 34 State: schema.ImportStatePassthrough, 35 }, 36 MigrateState: resourceGoogleProjectMigrateState, 37 38 Schema: map[string]*schema.Schema{ 39 "id": &schema.Schema{ 40 Type: schema.TypeString, 41 Optional: true, 42 Computed: true, 43 Deprecated: "The id field has unexpected behaviour and probably doesn't do what you expect. See https://www.terraform.io/docs/providers/google/r/google_project.html#id-field for more information. Please use project_id instead; future versions of Terraform will remove the id field.", 44 }, 45 "project_id": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 ForceNew: true, 49 DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool { 50 // This suppresses the diff if project_id is not set 51 if new == "" { 52 return true 53 } 54 return false 55 }, 56 }, 57 "skip_delete": &schema.Schema{ 58 Type: schema.TypeBool, 59 Optional: true, 60 Computed: true, 61 }, 62 "name": &schema.Schema{ 63 Type: schema.TypeString, 64 Optional: true, 65 Computed: true, 66 }, 67 "org_id": &schema.Schema{ 68 Type: schema.TypeString, 69 Optional: true, 70 Computed: true, 71 ForceNew: true, 72 }, 73 "policy_data": &schema.Schema{ 74 Type: schema.TypeString, 75 Optional: true, 76 Computed: true, 77 Deprecated: "Use the 'google_project_iam_policy' resource to define policies for a Google Project", 78 DiffSuppressFunc: jsonPolicyDiffSuppress, 79 }, 80 "policy_etag": &schema.Schema{ 81 Type: schema.TypeString, 82 Computed: true, 83 Deprecated: "Use the the 'google_project_iam_policy' resource to define policies for a Google Project", 84 }, 85 "number": &schema.Schema{ 86 Type: schema.TypeString, 87 Computed: true, 88 }, 89 }, 90 } 91 } 92 93 func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error { 94 config := meta.(*Config) 95 96 var pid string 97 var err error 98 pid = d.Get("project_id").(string) 99 if pid == "" { 100 pid, err = getProject(d, config) 101 if err != nil { 102 return fmt.Errorf("Error getting project ID: %v", err) 103 } 104 if pid == "" { 105 return fmt.Errorf("'project_id' must be set in the config") 106 } 107 } 108 109 // we need to check if name and org_id are set, and throw an error if they aren't 110 // we can't just set these as required on the object, however, as that would break 111 // all configs that used previous iterations of the resource. 112 // TODO(paddy): remove this for 0.9 and set these attributes as required. 113 name, org_id := d.Get("name").(string), d.Get("org_id").(string) 114 if name == "" { 115 return fmt.Errorf("`name` must be set in the config if you're creating a project.") 116 } 117 if org_id == "" { 118 return fmt.Errorf("`org_id` must be set in the config if you're creating a project.") 119 } 120 121 log.Printf("[DEBUG]: Creating new project %q", pid) 122 project := &cloudresourcemanager.Project{ 123 ProjectId: pid, 124 Name: d.Get("name").(string), 125 Parent: &cloudresourcemanager.ResourceId{ 126 Id: d.Get("org_id").(string), 127 Type: "organization", 128 }, 129 } 130 131 op, err := config.clientResourceManager.Projects.Create(project).Do() 132 if err != nil { 133 return fmt.Errorf("Error creating project %s (%s): %s.", project.ProjectId, project.Name, err) 134 } 135 136 d.SetId(pid) 137 138 // Wait for the operation to complete 139 waitErr := resourceManagerOperationWait(config, op, "project to create") 140 if waitErr != nil { 141 return waitErr 142 } 143 144 // Apply the IAM policy if it is set 145 if pString, ok := d.GetOk("policy_data"); ok { 146 // The policy string is just a marshaled cloudresourcemanager.Policy. 147 // Unmarshal it to a struct. 148 var policy cloudresourcemanager.Policy 149 if err := json.Unmarshal([]byte(pString.(string)), &policy); err != nil { 150 return err 151 } 152 log.Printf("[DEBUG] Got policy from config: %#v", policy.Bindings) 153 154 // Retrieve existing IAM policy from project. This will be merged 155 // with the policy defined here. 156 p, err := getProjectIamPolicy(pid, config) 157 if err != nil { 158 return err 159 } 160 log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings) 161 162 // Merge the existing policy bindings with those defined in this manifest. 163 p.Bindings = mergeBindings(append(p.Bindings, policy.Bindings...)) 164 165 // Apply the merged policy 166 log.Printf("[DEBUG] Setting new policy for project: %#v", p) 167 _, err = config.clientResourceManager.Projects.SetIamPolicy(pid, 168 &cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do() 169 170 if err != nil { 171 return fmt.Errorf("Error applying IAM policy for project %q: %s", pid, err) 172 } 173 } 174 175 return resourceGoogleProjectRead(d, meta) 176 } 177 178 func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error { 179 config := meta.(*Config) 180 pid := d.Id() 181 182 // Read the project 183 p, err := config.clientResourceManager.Projects.Get(pid).Do() 184 if err != nil { 185 if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound { 186 return fmt.Errorf("Project %q does not exist.", pid) 187 } 188 return fmt.Errorf("Error checking project %q: %s", pid, err) 189 } 190 191 d.Set("project_id", pid) 192 d.Set("number", strconv.FormatInt(int64(p.ProjectNumber), 10)) 193 d.Set("name", p.Name) 194 195 if p.Parent != nil { 196 d.Set("org_id", p.Parent.Id) 197 } 198 199 // Read the IAM policy 200 pol, err := getProjectIamPolicy(pid, config) 201 if err != nil { 202 return err 203 } 204 205 polBytes, err := json.Marshal(pol) 206 if err != nil { 207 return err 208 } 209 210 d.Set("policy_etag", pol.Etag) 211 d.Set("policy_data", string(polBytes)) 212 213 return nil 214 } 215 216 func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error { 217 config := meta.(*Config) 218 pid := d.Id() 219 220 // Read the project 221 // we need the project even though refresh has already been called 222 // because the API doesn't support patch, so we need the actual object 223 p, err := config.clientResourceManager.Projects.Get(pid).Do() 224 if err != nil { 225 if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound { 226 return fmt.Errorf("Project %q does not exist.", pid) 227 } 228 return fmt.Errorf("Error checking project %q: %s", pid, err) 229 } 230 231 // Project name has changed 232 if ok := d.HasChange("name"); ok { 233 p.Name = d.Get("name").(string) 234 // Do update on project 235 p, err = config.clientResourceManager.Projects.Update(p.ProjectId, p).Do() 236 if err != nil { 237 return fmt.Errorf("Error updating project %q: %s", p.Name, err) 238 } 239 } 240 241 return updateProjectIamPolicy(d, config, pid) 242 } 243 244 func resourceGoogleProjectDelete(d *schema.ResourceData, meta interface{}) error { 245 config := meta.(*Config) 246 // Only delete projects if skip_delete isn't set 247 if !d.Get("skip_delete").(bool) { 248 pid := d.Id() 249 _, err := config.clientResourceManager.Projects.Delete(pid).Do() 250 if err != nil { 251 return fmt.Errorf("Error deleting project %q: %s", pid, err) 252 } 253 } 254 d.SetId("") 255 return nil 256 } 257 258 func updateProjectIamPolicy(d *schema.ResourceData, config *Config, pid string) error { 259 // Policy has changed 260 if ok := d.HasChange("policy_data"); ok { 261 // The policy string is just a marshaled cloudresourcemanager.Policy. 262 // Unmarshal it to a struct that contains the old and new policies 263 oldP, newP := d.GetChange("policy_data") 264 oldPString := oldP.(string) 265 newPString := newP.(string) 266 267 // JSON Unmarshaling would fail 268 if oldPString == "" { 269 oldPString = "{}" 270 } 271 if newPString == "" { 272 newPString = "{}" 273 } 274 275 log.Printf("[DEBUG]: Old policy: %q\nNew policy: %q", oldPString, newPString) 276 277 var oldPolicy, newPolicy cloudresourcemanager.Policy 278 if err := json.Unmarshal([]byte(newPString), &newPolicy); err != nil { 279 return err 280 } 281 if err := json.Unmarshal([]byte(oldPString), &oldPolicy); err != nil { 282 return err 283 } 284 285 // Find any Roles and Members that were removed (i.e., those that are present 286 // in the old but absent in the new 287 oldMap := rolesToMembersMap(oldPolicy.Bindings) 288 newMap := rolesToMembersMap(newPolicy.Bindings) 289 deleted := make(map[string]map[string]bool) 290 291 // Get each role and its associated members in the old state 292 for role, members := range oldMap { 293 // Initialize map for role 294 if _, ok := deleted[role]; !ok { 295 deleted[role] = make(map[string]bool) 296 } 297 // The role exists in the new state 298 if _, ok := newMap[role]; ok { 299 // Check each memeber 300 for member, _ := range members { 301 // Member does not exist in new state, so it was deleted 302 if _, ok = newMap[role][member]; !ok { 303 deleted[role][member] = true 304 } 305 } 306 } else { 307 // This indicates an entire role was deleted. Mark all members 308 // for delete. 309 for member, _ := range members { 310 deleted[role][member] = true 311 } 312 } 313 } 314 log.Printf("[DEBUG] Roles and Members to be deleted: %#v", deleted) 315 316 // Retrieve existing IAM policy from project. This will be merged 317 // with the policy in the current state 318 // TODO(evanbrown): Add an 'authoritative' flag that allows policy 319 // in manifest to overwrite existing policy. 320 p, err := getProjectIamPolicy(pid, config) 321 if err != nil { 322 return err 323 } 324 log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings) 325 326 // Merge existing policy with policy in the current state 327 log.Printf("[DEBUG] Merging new bindings from project: %#v", newPolicy.Bindings) 328 mergedBindings := mergeBindings(append(p.Bindings, newPolicy.Bindings...)) 329 330 // Remove any roles and members that were explicitly deleted 331 mergedBindingsMap := rolesToMembersMap(mergedBindings) 332 for role, members := range deleted { 333 for member, _ := range members { 334 delete(mergedBindingsMap[role], member) 335 } 336 } 337 338 p.Bindings = rolesToMembersBinding(mergedBindingsMap) 339 dump, _ := json.MarshalIndent(p.Bindings, " ", " ") 340 log.Printf("[DEBUG] Setting new policy for project: %#v:\n%s", p, string(dump)) 341 342 _, err = config.clientResourceManager.Projects.SetIamPolicy(pid, 343 &cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do() 344 345 if err != nil { 346 return fmt.Errorf("Error applying IAM policy for project %q: %s", pid, err) 347 } 348 } 349 return nil 350 }