github.com/pbthorste/terraform@v0.8.6-0.20170127005045-deb56bd93da2/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  //
    18  // This example shows a project with a policy declared in config:
    19  //
    20  // resource "google_project" "my-project" {
    21  //    project = "a-project-id"
    22  //    policy = "${data.google_iam_policy.admin.policy}"
    23  // }
    24  func resourceGoogleProject() *schema.Resource {
    25  	return &schema.Resource{
    26  		SchemaVersion: 1,
    27  
    28  		Create: resourceGoogleProjectCreate,
    29  		Read:   resourceGoogleProjectRead,
    30  		Update: resourceGoogleProjectUpdate,
    31  		Delete: resourceGoogleProjectDelete,
    32  
    33  		Importer: &schema.ResourceImporter{
    34  			State: schema.ImportStatePassthrough,
    35  		},
    36  		MigrateState: resourceGoogleProjectMigrateState,
    37  
    38  		Schema: map[string]*schema.Schema{
    39  			"id": &schema.Schema{
    40  				Type:       schema.TypeString,
    41  				Optional:   true,
    42  				Computed:   true,
    43  				Deprecated: "The id field has unexpected behaviour and probably doesn't do what you expect. See https://www.terraform.io/docs/providers/google/r/google_project.html#id-field for more information. Please use project_id instead; future versions of Terraform will remove the id field.",
    44  			},
    45  			"project_id": &schema.Schema{
    46  				Type:     schema.TypeString,
    47  				Optional: true,
    48  				ForceNew: true,
    49  				DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
    50  					// This suppresses the diff if project_id is not set
    51  					if new == "" {
    52  						return true
    53  					}
    54  					return false
    55  				},
    56  			},
    57  			"skip_delete": &schema.Schema{
    58  				Type:     schema.TypeBool,
    59  				Optional: true,
    60  				Computed: true,
    61  			},
    62  			"name": &schema.Schema{
    63  				Type:     schema.TypeString,
    64  				Optional: true,
    65  				Computed: true,
    66  			},
    67  			"org_id": &schema.Schema{
    68  				Type:     schema.TypeString,
    69  				Optional: true,
    70  				Computed: true,
    71  				ForceNew: true,
    72  			},
    73  			"policy_data": &schema.Schema{
    74  				Type:             schema.TypeString,
    75  				Optional:         true,
    76  				Computed:         true,
    77  				Deprecated:       "Use the 'google_project_iam_policy' resource to define policies for a Google Project",
    78  				DiffSuppressFunc: jsonPolicyDiffSuppress,
    79  			},
    80  			"policy_etag": &schema.Schema{
    81  				Type:       schema.TypeString,
    82  				Computed:   true,
    83  				Deprecated: "Use the the 'google_project_iam_policy' resource to define policies for a Google Project",
    84  			},
    85  			"number": &schema.Schema{
    86  				Type:     schema.TypeString,
    87  				Computed: true,
    88  			},
    89  		},
    90  	}
    91  }
    92  
    93  func resourceGoogleProjectCreate(d *schema.ResourceData, meta interface{}) error {
    94  	config := meta.(*Config)
    95  
    96  	var pid string
    97  	var err error
    98  	pid = d.Get("project_id").(string)
    99  	if pid == "" {
   100  		pid, err = getProject(d, config)
   101  		if err != nil {
   102  			return fmt.Errorf("Error getting project ID: %v", err)
   103  		}
   104  		if pid == "" {
   105  			return fmt.Errorf("'project_id' must be set in the config")
   106  		}
   107  	}
   108  
   109  	// we need to check if name and org_id are set, and throw an error if they aren't
   110  	// we can't just set these as required on the object, however, as that would break
   111  	// all configs that used previous iterations of the resource.
   112  	// TODO(paddy): remove this for 0.9 and set these attributes as required.
   113  	name, org_id := d.Get("name").(string), d.Get("org_id").(string)
   114  	if name == "" {
   115  		return fmt.Errorf("`name` must be set in the config if you're creating a project.")
   116  	}
   117  	if org_id == "" {
   118  		return fmt.Errorf("`org_id` must be set in the config if you're creating a project.")
   119  	}
   120  
   121  	log.Printf("[DEBUG]: Creating new project %q", pid)
   122  	project := &cloudresourcemanager.Project{
   123  		ProjectId: pid,
   124  		Name:      d.Get("name").(string),
   125  		Parent: &cloudresourcemanager.ResourceId{
   126  			Id:   d.Get("org_id").(string),
   127  			Type: "organization",
   128  		},
   129  	}
   130  
   131  	op, err := config.clientResourceManager.Projects.Create(project).Do()
   132  	if err != nil {
   133  		return fmt.Errorf("Error creating project %s (%s): %s.", project.ProjectId, project.Name, err)
   134  	}
   135  
   136  	d.SetId(pid)
   137  
   138  	// Wait for the operation to complete
   139  	waitErr := resourceManagerOperationWait(config, op, "project to create")
   140  	if waitErr != nil {
   141  		return waitErr
   142  	}
   143  
   144  	// Apply the IAM policy if it is set
   145  	if pString, ok := d.GetOk("policy_data"); ok {
   146  		// The policy string is just a marshaled cloudresourcemanager.Policy.
   147  		// Unmarshal it to a struct.
   148  		var policy cloudresourcemanager.Policy
   149  		if err := json.Unmarshal([]byte(pString.(string)), &policy); err != nil {
   150  			return err
   151  		}
   152  		log.Printf("[DEBUG] Got policy from config: %#v", policy.Bindings)
   153  
   154  		// Retrieve existing IAM policy from project. This will be merged
   155  		// with the policy defined here.
   156  		p, err := getProjectIamPolicy(pid, config)
   157  		if err != nil {
   158  			return err
   159  		}
   160  		log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings)
   161  
   162  		// Merge the existing policy bindings with those defined in this manifest.
   163  		p.Bindings = mergeBindings(append(p.Bindings, policy.Bindings...))
   164  
   165  		// Apply the merged policy
   166  		log.Printf("[DEBUG] Setting new policy for project: %#v", p)
   167  		_, err = config.clientResourceManager.Projects.SetIamPolicy(pid,
   168  			&cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do()
   169  
   170  		if err != nil {
   171  			return fmt.Errorf("Error applying IAM policy for project %q: %s", pid, err)
   172  		}
   173  	}
   174  
   175  	return resourceGoogleProjectRead(d, meta)
   176  }
   177  
   178  func resourceGoogleProjectRead(d *schema.ResourceData, meta interface{}) error {
   179  	config := meta.(*Config)
   180  	pid := d.Id()
   181  
   182  	// Read the project
   183  	p, err := config.clientResourceManager.Projects.Get(pid).Do()
   184  	if err != nil {
   185  		if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound {
   186  			return fmt.Errorf("Project %q does not exist.", pid)
   187  		}
   188  		return fmt.Errorf("Error checking project %q: %s", pid, err)
   189  	}
   190  
   191  	d.Set("project_id", pid)
   192  	d.Set("number", strconv.FormatInt(int64(p.ProjectNumber), 10))
   193  	d.Set("name", p.Name)
   194  
   195  	if p.Parent != nil {
   196  		d.Set("org_id", p.Parent.Id)
   197  	}
   198  
   199  	// Read the IAM policy
   200  	pol, err := getProjectIamPolicy(pid, config)
   201  	if err != nil {
   202  		return err
   203  	}
   204  
   205  	polBytes, err := json.Marshal(pol)
   206  	if err != nil {
   207  		return err
   208  	}
   209  
   210  	d.Set("policy_etag", pol.Etag)
   211  	d.Set("policy_data", string(polBytes))
   212  
   213  	return nil
   214  }
   215  
   216  func resourceGoogleProjectUpdate(d *schema.ResourceData, meta interface{}) error {
   217  	config := meta.(*Config)
   218  	pid := d.Id()
   219  
   220  	// Read the project
   221  	// we need the project even though refresh has already been called
   222  	// because the API doesn't support patch, so we need the actual object
   223  	p, err := config.clientResourceManager.Projects.Get(pid).Do()
   224  	if err != nil {
   225  		if v, ok := err.(*googleapi.Error); ok && v.Code == http.StatusNotFound {
   226  			return fmt.Errorf("Project %q does not exist.", pid)
   227  		}
   228  		return fmt.Errorf("Error checking project %q: %s", pid, err)
   229  	}
   230  
   231  	// Project name has changed
   232  	if ok := d.HasChange("name"); ok {
   233  		p.Name = d.Get("name").(string)
   234  		// Do update on project
   235  		p, err = config.clientResourceManager.Projects.Update(p.ProjectId, p).Do()
   236  		if err != nil {
   237  			return fmt.Errorf("Error updating project %q: %s", p.Name, err)
   238  		}
   239  	}
   240  
   241  	return updateProjectIamPolicy(d, config, pid)
   242  }
   243  
   244  func resourceGoogleProjectDelete(d *schema.ResourceData, meta interface{}) error {
   245  	config := meta.(*Config)
   246  	// Only delete projects if skip_delete isn't set
   247  	if !d.Get("skip_delete").(bool) {
   248  		pid := d.Id()
   249  		_, err := config.clientResourceManager.Projects.Delete(pid).Do()
   250  		if err != nil {
   251  			return fmt.Errorf("Error deleting project %q: %s", pid, err)
   252  		}
   253  	}
   254  	d.SetId("")
   255  	return nil
   256  }
   257  
   258  func updateProjectIamPolicy(d *schema.ResourceData, config *Config, pid string) error {
   259  	// Policy has changed
   260  	if ok := d.HasChange("policy_data"); ok {
   261  		// The policy string is just a marshaled cloudresourcemanager.Policy.
   262  		// Unmarshal it to a struct that contains the old and new policies
   263  		oldP, newP := d.GetChange("policy_data")
   264  		oldPString := oldP.(string)
   265  		newPString := newP.(string)
   266  
   267  		// JSON Unmarshaling would fail
   268  		if oldPString == "" {
   269  			oldPString = "{}"
   270  		}
   271  		if newPString == "" {
   272  			newPString = "{}"
   273  		}
   274  
   275  		log.Printf("[DEBUG]: Old policy: %q\nNew policy: %q", oldPString, newPString)
   276  
   277  		var oldPolicy, newPolicy cloudresourcemanager.Policy
   278  		if err := json.Unmarshal([]byte(newPString), &newPolicy); err != nil {
   279  			return err
   280  		}
   281  		if err := json.Unmarshal([]byte(oldPString), &oldPolicy); err != nil {
   282  			return err
   283  		}
   284  
   285  		// Find any Roles and Members that were removed (i.e., those that are present
   286  		// in the old but absent in the new
   287  		oldMap := rolesToMembersMap(oldPolicy.Bindings)
   288  		newMap := rolesToMembersMap(newPolicy.Bindings)
   289  		deleted := make(map[string]map[string]bool)
   290  
   291  		// Get each role and its associated members in the old state
   292  		for role, members := range oldMap {
   293  			// Initialize map for role
   294  			if _, ok := deleted[role]; !ok {
   295  				deleted[role] = make(map[string]bool)
   296  			}
   297  			// The role exists in the new state
   298  			if _, ok := newMap[role]; ok {
   299  				// Check each memeber
   300  				for member, _ := range members {
   301  					// Member does not exist in new state, so it was deleted
   302  					if _, ok = newMap[role][member]; !ok {
   303  						deleted[role][member] = true
   304  					}
   305  				}
   306  			} else {
   307  				// This indicates an entire role was deleted. Mark all members
   308  				// for delete.
   309  				for member, _ := range members {
   310  					deleted[role][member] = true
   311  				}
   312  			}
   313  		}
   314  		log.Printf("[DEBUG] Roles and Members to be deleted: %#v", deleted)
   315  
   316  		// Retrieve existing IAM policy from project. This will be merged
   317  		// with the policy in the current state
   318  		// TODO(evanbrown): Add an 'authoritative' flag that allows policy
   319  		// in manifest to overwrite existing policy.
   320  		p, err := getProjectIamPolicy(pid, config)
   321  		if err != nil {
   322  			return err
   323  		}
   324  		log.Printf("[DEBUG] Got existing bindings from project: %#v", p.Bindings)
   325  
   326  		// Merge existing policy with policy in the current state
   327  		log.Printf("[DEBUG] Merging new bindings from project: %#v", newPolicy.Bindings)
   328  		mergedBindings := mergeBindings(append(p.Bindings, newPolicy.Bindings...))
   329  
   330  		// Remove any roles and members that were explicitly deleted
   331  		mergedBindingsMap := rolesToMembersMap(mergedBindings)
   332  		for role, members := range deleted {
   333  			for member, _ := range members {
   334  				delete(mergedBindingsMap[role], member)
   335  			}
   336  		}
   337  
   338  		p.Bindings = rolesToMembersBinding(mergedBindingsMap)
   339  		dump, _ := json.MarshalIndent(p.Bindings, " ", "  ")
   340  		log.Printf("[DEBUG] Setting new policy for project: %#v:\n%s", p, string(dump))
   341  
   342  		_, err = config.clientResourceManager.Projects.SetIamPolicy(pid,
   343  			&cloudresourcemanager.SetIamPolicyRequest{Policy: p}).Do()
   344  
   345  		if err != nil {
   346  			return fmt.Errorf("Error applying IAM policy for project %q: %s", pid, err)
   347  		}
   348  	}
   349  	return nil
   350  }