github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/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 // Only the 'policy' property of a project may be updated. All other properties 18 // are computed. 19 // 20 // This example shows a project with a policy declared in config: 21 // 22 // resource "google_project" "my-project" { 23 // project = "a-project-id" 24 // policy = "${data.google_iam_policy.admin.policy}" 25 // } 26 func resourceGoogleProject() *schema.Resource { 27 return &schema.Resource{ 28 Create: resourceGoogleProjectCreate, 29 Read: resourceGoogleProjectRead, 30 Update: resourceGoogleProjectUpdate, 31 Delete: resourceGoogleProjectDelete, 32 Importer: &schema.ResourceImporter{ 33 State: schema.ImportStatePassthrough, 34 }, 35 36 Schema: map[string]*schema.Schema{ 37 "id": &schema.Schema{ 38 Type: schema.TypeString, 39 Required: true, 40 ForceNew: true, 41 }, 42 "policy_data": &schema.Schema{ 43 Type: schema.TypeString, 44 Optional: true, 45 }, 46 "name": &schema.Schema{ 47 Type: schema.TypeString, 48 Computed: true, 49 }, 50 "number": &schema.Schema{ 51 Type: schema.TypeString, 52 Computed: true, 53 }, 54 }, 55 } 56 } 57 58 // This resource supports creation, but not in the traditional sense. 59 // A new Google Cloud Project can not be created. Instead, an existing Project 60 // is initialized and made available as a Terraform resource. 61 func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error { 62 config := meta.(*Config) 63 64 project, err := getProject(d, config) 65 if err != nil { 66 return err 67 } 68 69 d.SetId(project) 70 if err := resourceGoogleProjectRead(d, meta); err != nil { 71 return err 72 } 73 74 // Apply the IAM policy if it is set 75 if pString, ok := d.GetOk("policy_data"); ok { 76 // The policy string is just a marshaled cloudresourcemanager.Policy. 77 // Unmarshal it to a struct. 78 var policy cloudresourcemanager.Policy 79 if err = json.Unmarshal([]byte(pString.(string)), &policy); err != nil { 80 return err 81 } 82 83 // Retrieve existing IAM policy from project. This will be merged 84 // with the policy defined here. 85 // TODO(evanbrown): Add an 'authoritative' flag that allows policy 86 // in manifest to overwrite existing policy. 87 p, err := getProjectIamPolicy(project, config) 88 if err != nil { 89 return err 90 } 91 log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings) 92 93 // Merge the existing policy bindings with those defined in this manifest. 94 p.Bindings = mergeBindings(append(p.Bindings, policy.Bindings...)) 95 96 // Apply the merged policy 97 log.Printf("[DEBUG] Setting new policy for project: %#v", p) 98 _, err = config.clientResourceManager.Projects.SetIamPolicy(project, 99 &cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do() 100 101 if err != nil { 102 return fmt.Errorf("Error applying IAM policy for project %q: %s", project, err) 103 } 104 } 105 return nil 106 } 107 108 func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error { 109 config := meta.(*Config) 110 project, err := getProject(d, config) 111 if err != nil { 112 return err 113 } 114 d.SetId(project) 115 116 // Confirm the project exists. 117 // TODO(evanbrown): Support project creation 118 p, err := config.clientResourceManager.Projects.Get(project).Do() 119 if err != nil { 120 if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound { 121 return fmt.Errorf("Project %q does not exist. The Google provider does not currently support new project creation.", project) 122 } 123 return fmt.Errorf("Error checking project %q: %s", project, err) 124 } 125 126 d.Set("number", strconv.FormatInt(int64(p.ProjectNumber), 10)) 127 d.Set("name", p.Name) 128 129 return nil 130 } 131 132 func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error { 133 config := meta.(*Config) 134 project, err := getProject(d, config) 135 if err != nil { 136 return err 137 } 138 139 // Policy has changed 140 if ok := d.HasChange("policy_data"); ok { 141 // The policy string is just a marshaled cloudresourcemanager.Policy. 142 // Unmarshal it to a struct that contains the old and new policies 143 oldP, newP := d.GetChange("policy_data") 144 oldPString := oldP.(string) 145 newPString := newP.(string) 146 147 // JSON Unmarshaling would fail 148 if oldPString == "" { 149 oldPString = "{}" 150 } 151 if newPString == "" { 152 newPString = "{}" 153 } 154 155 oldPStringf, _ := json.MarshalIndent(oldPString, "", " ") 156 newPStringf, _ := json.MarshalIndent(newPString, "", " ") 157 log.Printf("[DEBUG]: Old policy: %v\nNew policy: %v", string(oldPStringf), string(newPStringf)) 158 159 var oldPolicy, newPolicy cloudresourcemanager.Policy 160 if err = json.Unmarshal([]byte(newPString), &newPolicy); err != nil { 161 return err 162 } 163 if err = json.Unmarshal([]byte(oldPString), &oldPolicy); err != nil { 164 return err 165 } 166 167 // Find any Roles and Members that were removed (i.e., those that are present 168 // in the old but absent in the new 169 oldMap := rolesToMembersMap(oldPolicy.Bindings) 170 newMap := rolesToMembersMap(newPolicy.Bindings) 171 deleted := make(map[string]map[string]bool) 172 173 // Get each role and its associated members in the old state 174 for role, members := range oldMap { 175 // Initialize map for role 176 if _, ok := deleted[role]; !ok { 177 deleted[role] = make(map[string]bool) 178 } 179 // The role exists in the new state 180 if _, ok := newMap[role]; ok { 181 // Check each memeber 182 for member, _ := range members { 183 // Member does not exist in new state, so it was deleted 184 if _, ok = newMap[role][member]; !ok { 185 deleted[role][member] = true 186 } 187 } 188 } else { 189 // This indicates an entire role was deleted. Mark all members 190 // for delete. 191 for member, _ := range members { 192 deleted[role][member] = true 193 } 194 } 195 } 196 log.Printf("[DEBUG] Roles and Members to be deleted: %#v", deleted) 197 198 // Retrieve existing IAM policy from project. This will be merged 199 // with the policy in the current state 200 // TODO(evanbrown): Add an 'authoritative' flag that allows policy 201 // in manifest to overwrite existing policy. 202 p, err := getProjectIamPolicy(project, config) 203 if err != nil { 204 return err 205 } 206 log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings) 207 208 // Merge existing policy with policy in the current state 209 log.Printf("[DEBUG] Merging new bindings from project: %#v", newPolicy.Bindings) 210 mergedBindings := mergeBindings(append(p.Bindings, newPolicy.Bindings...)) 211 212 // Remove any roles and members that were explicitly deleted 213 mergedBindingsMap := rolesToMembersMap(mergedBindings) 214 for role, members := range deleted { 215 for member, _ := range members { 216 delete(mergedBindingsMap[role], member) 217 } 218 } 219 220 p.Bindings = rolesToMembersBinding(mergedBindingsMap) 221 log.Printf("[DEBUG] Setting new policy for project: %#v", p) 222 223 dump, _ := json.MarshalIndent(p.Bindings, " ", " ") 224 log.Printf(string(dump)) 225 _, err = config.clientResourceManager.Projects.SetIamPolicy(project, 226 &cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do() 227 228 if err != nil { 229 return fmt.Errorf("Error applying IAM policy for project %q: %s", project, err) 230 } 231 } 232 233 return nil 234 } 235 236 func resourceGoogleProjectDelete(d *schema.ResourceData, meta interface{}) error { 237 d.SetId("") 238 return nil 239 } 240 241 // Retrieve the existing IAM Policy for a Project 242 func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) { 243 p, err := config.clientResourceManager.Projects.GetIamPolicy(project, 244 &cloudresourcemanager.GetIamPolicyRequest{}).Do() 245 246 if err != nil { 247 return nil, fmt.Errorf("Error retrieving IAM policy for project %q: %s", project, err) 248 } 249 return p, nil 250 } 251 252 // Convert a map of roles->members to a list of Binding 253 func rolesToMembersBinding(m map[string]map[string]bool) []*cloudresourcemanager.Binding { 254 bindings := make([]*cloudresourcemanager.Binding, 0) 255 for role, members := range m { 256 b := cloudresourcemanager.Binding{ 257 Role: role, 258 Members: make([]string, 0), 259 } 260 for m, _ := range members { 261 b.Members = append(b.Members, m) 262 } 263 bindings = append(bindings, &b) 264 } 265 return bindings 266 } 267 268 // Map a role to a map of members, allowing easy merging of multiple bindings. 269 func rolesToMembersMap(bindings []*cloudresourcemanager.Binding) map[string]map[string]bool { 270 bm := make(map[string]map[string]bool) 271 // Get each binding 272 for _, b := range bindings { 273 // Initialize members map 274 if _, ok := bm[b.Role]; !ok { 275 bm[b.Role] = make(map[string]bool) 276 } 277 // Get each member (user/principal) for the binding 278 for _, m := range b.Members { 279 // Add the member 280 bm[b.Role][m] = true 281 } 282 } 283 return bm 284 } 285 286 // Merge multiple Bindings such that Bindings with the same Role result in 287 // a single Binding with combined Members 288 func mergeBindings(bindings []*cloudresourcemanager.Binding) []*cloudresourcemanager.Binding { 289 bm := rolesToMembersMap(bindings) 290 rb := make([]*cloudresourcemanager.Binding, 0) 291 292 for role, members := range bm { 293 var b cloudresourcemanager.Binding 294 b.Role = role 295 b.Members = make([]string, 0) 296 for m, _ := range members { 297 b.Members = append(b.Members, m) 298 } 299 rb = append(rb, &b) 300 } 301 302 return rb 303 }