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  }