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