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  }