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  }