github.com/IBM-Cloud/terraform@v0.6.4-0.20170726051544-8872b87621df/builtin/providers/google/resource_google_service_account.go (about) 1 package google 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "log" 7 8 "github.com/hashicorp/terraform/helper/schema" 9 "google.golang.org/api/iam/v1" 10 ) 11 12 func resourceGoogleServiceAccount() *schema.Resource { 13 return &schema.Resource{ 14 Create: resourceGoogleServiceAccountCreate, 15 Read: resourceGoogleServiceAccountRead, 16 Delete: resourceGoogleServiceAccountDelete, 17 Update: resourceGoogleServiceAccountUpdate, 18 Schema: map[string]*schema.Schema{ 19 "email": &schema.Schema{ 20 Type: schema.TypeString, 21 Computed: true, 22 }, 23 "unique_id": &schema.Schema{ 24 Type: schema.TypeString, 25 Computed: true, 26 }, 27 "name": &schema.Schema{ 28 Type: schema.TypeString, 29 Computed: true, 30 }, 31 "account_id": &schema.Schema{ 32 Type: schema.TypeString, 33 Required: true, 34 ForceNew: true, 35 }, 36 "display_name": &schema.Schema{ 37 Type: schema.TypeString, 38 Optional: true, 39 }, 40 "project": &schema.Schema{ 41 Type: schema.TypeString, 42 Optional: true, 43 ForceNew: true, 44 }, 45 "policy_data": &schema.Schema{ 46 Type: schema.TypeString, 47 Optional: true, 48 }, 49 }, 50 } 51 } 52 53 func resourceGoogleServiceAccountCreate(d *schema.ResourceData, meta interface{}) error { 54 config := meta.(*Config) 55 project, err := getProject(d, config) 56 if err != nil { 57 return err 58 } 59 aid := d.Get("account_id").(string) 60 displayName := d.Get("display_name").(string) 61 62 sa := &iam.ServiceAccount{ 63 DisplayName: displayName, 64 } 65 66 r := &iam.CreateServiceAccountRequest{ 67 AccountId: aid, 68 ServiceAccount: sa, 69 } 70 71 sa, err = config.clientIAM.Projects.ServiceAccounts.Create("projects/"+project, r).Do() 72 if err != nil { 73 return fmt.Errorf("Error creating service account: %s", err) 74 } 75 76 d.SetId(sa.Name) 77 78 // Apply the IAM policy if it is set 79 if pString, ok := d.GetOk("policy_data"); ok { 80 // The policy string is just a marshaled cloudresourcemanager.Policy. 81 // Unmarshal it to a struct. 82 var policy iam.Policy 83 if err = json.Unmarshal([]byte(pString.(string)), &policy); err != nil { 84 return err 85 } 86 87 // Retrieve existing IAM policy from project. This will be merged 88 // with the policy defined here. 89 // TODO(evanbrown): Add an 'authoritative' flag that allows policy 90 // in manifest to overwrite existing policy. 91 p, err := getServiceAccountIamPolicy(sa.Name, config) 92 if err != nil { 93 return fmt.Errorf("Could not find service account %q when applying IAM policy: %s", sa.Name, err) 94 } 95 log.Printf("[DEBUG] Got existing bindings for service account: %#v", p.Bindings) 96 97 // Merge the existing policy bindings with those defined in this manifest. 98 p.Bindings = saMergeBindings(append(p.Bindings, policy.Bindings...)) 99 100 // Apply the merged policy 101 log.Printf("[DEBUG] Setting new policy for service account: %#v", p) 102 _, err = config.clientIAM.Projects.ServiceAccounts.SetIamPolicy(sa.Name, 103 &iam.SetIamPolicyRequest{Policy: p}).Do() 104 105 if err != nil { 106 return fmt.Errorf("Error applying IAM policy for service account %q: %s", sa.Name, err) 107 } 108 } 109 return resourceGoogleServiceAccountRead(d, meta) 110 } 111 112 func resourceGoogleServiceAccountRead(d *schema.ResourceData, meta interface{}) error { 113 config := meta.(*Config) 114 115 // Confirm the service account exists 116 sa, err := config.clientIAM.Projects.ServiceAccounts.Get(d.Id()).Do() 117 if err != nil { 118 return handleNotFoundError(err, d, fmt.Sprintf("Service Account %q", d.Id())) 119 } 120 121 d.Set("email", sa.Email) 122 d.Set("unique_id", sa.UniqueId) 123 d.Set("name", sa.Name) 124 d.Set("display_name", sa.DisplayName) 125 return nil 126 } 127 128 func resourceGoogleServiceAccountDelete(d *schema.ResourceData, meta interface{}) error { 129 config := meta.(*Config) 130 name := d.Id() 131 _, err := config.clientIAM.Projects.ServiceAccounts.Delete(name).Do() 132 if err != nil { 133 return err 134 } 135 d.SetId("") 136 return nil 137 } 138 139 func resourceGoogleServiceAccountUpdate(d *schema.ResourceData, meta interface{}) error { 140 config := meta.(*Config) 141 var err error 142 if ok := d.HasChange("display_name"); ok { 143 sa, err := config.clientIAM.Projects.ServiceAccounts.Get(d.Id()).Do() 144 if err != nil { 145 return fmt.Errorf("Error retrieving service account %q: %s", d.Id(), err) 146 } 147 _, err = config.clientIAM.Projects.ServiceAccounts.Update(d.Id(), 148 &iam.ServiceAccount{ 149 DisplayName: d.Get("display_name").(string), 150 Etag: sa.Etag, 151 }).Do() 152 if err != nil { 153 return fmt.Errorf("Error updating service account %q: %s", d.Id(), err) 154 } 155 } 156 157 if ok := d.HasChange("policy_data"); ok { 158 // The policy string is just a marshaled cloudresourcemanager.Policy. 159 // Unmarshal it to a struct that contains the old and new policies 160 oldP, newP := d.GetChange("policy_data") 161 oldPString := oldP.(string) 162 newPString := newP.(string) 163 164 // JSON Unmarshaling would fail 165 if oldPString == "" { 166 oldPString = "{}" 167 } 168 if newPString == "" { 169 newPString = "{}" 170 } 171 172 log.Printf("[DEBUG]: Old policy: %q\nNew policy: %q", string(oldPString), string(newPString)) 173 174 var oldPolicy, newPolicy iam.Policy 175 if err = json.Unmarshal([]byte(newPString), &newPolicy); err != nil { 176 return err 177 } 178 if err = json.Unmarshal([]byte(oldPString), &oldPolicy); err != nil { 179 return err 180 } 181 182 // Find any Roles and Members that were removed (i.e., those that are present 183 // in the old but absent in the new 184 oldMap := saRolesToMembersMap(oldPolicy.Bindings) 185 newMap := saRolesToMembersMap(newPolicy.Bindings) 186 deleted := make(map[string]map[string]bool) 187 188 // Get each role and its associated members in the old state 189 for role, members := range oldMap { 190 // Initialize map for role 191 if _, ok := deleted[role]; !ok { 192 deleted[role] = make(map[string]bool) 193 } 194 // The role exists in the new state 195 if _, ok := newMap[role]; ok { 196 // Check each memeber 197 for member, _ := range members { 198 // Member does not exist in new state, so it was deleted 199 if _, ok = newMap[role][member]; !ok { 200 deleted[role][member] = true 201 } 202 } 203 } else { 204 // This indicates an entire role was deleted. Mark all members 205 // for delete. 206 for member, _ := range members { 207 deleted[role][member] = true 208 } 209 } 210 } 211 log.Printf("[DEBUG] Roles and Members to be deleted: %#v", deleted) 212 213 // Retrieve existing IAM policy from project. This will be merged 214 // with the policy in the current state 215 // TODO(evanbrown): Add an 'authoritative' flag that allows policy 216 // in manifest to overwrite existing policy. 217 p, err := getServiceAccountIamPolicy(d.Id(), config) 218 if err != nil { 219 return err 220 } 221 log.Printf("[DEBUG] Got existing bindings from service account %q: %#v", d.Id(), p.Bindings) 222 223 // Merge existing policy with policy in the current state 224 log.Printf("[DEBUG] Merging new bindings from service account %q: %#v", d.Id(), newPolicy.Bindings) 225 mergedBindings := saMergeBindings(append(p.Bindings, newPolicy.Bindings...)) 226 227 // Remove any roles and members that were explicitly deleted 228 mergedBindingsMap := saRolesToMembersMap(mergedBindings) 229 for role, members := range deleted { 230 for member, _ := range members { 231 delete(mergedBindingsMap[role], member) 232 } 233 } 234 235 p.Bindings = saRolesToMembersBinding(mergedBindingsMap) 236 log.Printf("[DEBUG] Setting new policy for project: %#v", p) 237 238 dump, _ := json.MarshalIndent(p.Bindings, " ", " ") 239 log.Printf(string(dump)) 240 _, err = config.clientIAM.Projects.ServiceAccounts.SetIamPolicy(d.Id(), 241 &iam.SetIamPolicyRequest{Policy: p}).Do() 242 243 if err != nil { 244 return fmt.Errorf("Error applying IAM policy for service account %q: %s", d.Id(), err) 245 } 246 } 247 return nil 248 } 249 250 // Retrieve the existing IAM Policy for a service account 251 func getServiceAccountIamPolicy(sa string, config *Config) (*iam.Policy, error) { 252 p, err := config.clientIAM.Projects.ServiceAccounts.GetIamPolicy(sa).Do() 253 254 if err != nil { 255 return nil, fmt.Errorf("Error retrieving IAM policy for service account %q: %s", sa, err) 256 } 257 return p, nil 258 } 259 260 // Convert a map of roles->members to a list of Binding 261 func saRolesToMembersBinding(m map[string]map[string]bool) []*iam.Binding { 262 bindings := make([]*iam.Binding, 0) 263 for role, members := range m { 264 b := iam.Binding{ 265 Role: role, 266 Members: make([]string, 0), 267 } 268 for m, _ := range members { 269 b.Members = append(b.Members, m) 270 } 271 bindings = append(bindings, &b) 272 } 273 return bindings 274 } 275 276 // Map a role to a map of members, allowing easy merging of multiple bindings. 277 func saRolesToMembersMap(bindings []*iam.Binding) map[string]map[string]bool { 278 bm := make(map[string]map[string]bool) 279 // Get each binding 280 for _, b := range bindings { 281 // Initialize members map 282 if _, ok := bm[b.Role]; !ok { 283 bm[b.Role] = make(map[string]bool) 284 } 285 // Get each member (user/principal) for the binding 286 for _, m := range b.Members { 287 // Add the member 288 bm[b.Role][m] = true 289 } 290 } 291 return bm 292 } 293 294 // Merge multiple Bindings such that Bindings with the same Role result in 295 // a single Binding with combined Members 296 func saMergeBindings(bindings []*iam.Binding) []*iam.Binding { 297 bm := saRolesToMembersMap(bindings) 298 rb := make([]*iam.Binding, 0) 299 300 for role, members := range bm { 301 var b iam.Binding 302 b.Role = role 303 b.Members = make([]string, 0) 304 for m, _ := range members { 305 b.Members = append(b.Members, m) 306 } 307 rb = append(rb, &b) 308 } 309 310 return rb 311 }