diff --git a/third_party/terraform/resources/resource_google_project_iam_policy.go.erb b/third_party/terraform/resources/resource_google_project_iam_policy.go.erb deleted file mode 100644 index 820937a43c41..000000000000 --- a/third_party/terraform/resources/resource_google_project_iam_policy.go.erb +++ /dev/null @@ -1,187 +0,0 @@ -<% autogen_exception -%> -package google - -import ( - "encoding/json" - "fmt" - "github.com/hashicorp/errwrap" - "github.com/hashicorp/terraform-plugin-sdk/helper/schema" - "google.golang.org/api/cloudresourcemanager/v1" - "log" -) - -func resourceGoogleProjectIamPolicy() *schema.Resource { - return &schema.Resource{ - Create: resourceGoogleProjectIamPolicyCreate, - Read: resourceGoogleProjectIamPolicyRead, - Update: resourceGoogleProjectIamPolicyUpdate, - Delete: resourceGoogleProjectIamPolicyDelete, - Importer: &schema.ResourceImporter{ - State: resourceGoogleProjectIamPolicyImport, - }, - - Schema: map[string]*schema.Schema{ - "project": { - Type: schema.TypeString, - Required: true, - ForceNew: true, - DiffSuppressFunc: compareProjectName, - }, - "policy_data": { - Type: schema.TypeString, - Required: true, - DiffSuppressFunc: jsonPolicyDiffSuppress, - }, - "etag": { - Type: schema.TypeString, - Computed: true, - }, - }, - } -} - -func compareProjectName(_, old, new string, _ *schema.ResourceData) bool { - // We can either get "projects/project-id" or "project-id", so strip any prefixes - return GetResourceNameFromSelfLink(old) == GetResourceNameFromSelfLink(new) -} - -func resourceGoogleProjectIamPolicyCreate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - project := GetResourceNameFromSelfLink(d.Get("project").(string)) - - mutexKey := getProjectIamPolicyMutexKey(project) - mutexKV.Lock(mutexKey) - defer mutexKV.Unlock(mutexKey) - - // Get the policy in the template - policy, err := getResourceIamPolicy(d) - if err != nil { - return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err) - } - - log.Printf("[DEBUG] Setting IAM policy for project %q", project) - err = setProjectIamPolicy(policy, config, project) - if err != nil { - return err - } - - d.SetId(project) - return resourceGoogleProjectIamPolicyRead(d, meta) -} - -func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - project := GetResourceNameFromSelfLink(d.Get("project").(string)) - - policy, err := getProjectIamPolicy(project, config) - if err != nil { - return err - } - - policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings, AuditConfigs: policy.AuditConfigs}) - if err != nil { - return fmt.Errorf("Error marshaling IAM policy: %v", err) - } - - d.Set("etag", policy.Etag) - d.Set("policy_data", string(policyBytes)) - d.Set("project", project) - return nil -} - -func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error { - config := meta.(*Config) - project := GetResourceNameFromSelfLink(d.Get("project").(string)) - - mutexKey := getProjectIamPolicyMutexKey(project) - mutexKV.Lock(mutexKey) - defer mutexKV.Unlock(mutexKey) - - // Get the policy in the template - policy, err := getResourceIamPolicy(d) - if err != nil { - return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err) - } - - log.Printf("[DEBUG] Updating IAM policy for project %q", project) - err = setProjectIamPolicy(policy, config, project) - if err != nil { - return fmt.Errorf("Error setting project IAM policy: %v", err) - } - - return resourceGoogleProjectIamPolicyRead(d, meta) -} - -func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface{}) error { - log.Printf("[DEBUG]: Deleting google_project_iam_policy") - config := meta.(*Config) - project := GetResourceNameFromSelfLink(d.Get("project").(string)) - - mutexKey := getProjectIamPolicyMutexKey(project) - mutexKV.Lock(mutexKey) - defer mutexKV.Unlock(mutexKey) - - // Get the existing IAM policy from the API so we can repurpose the etag and audit config - ep, err := getProjectIamPolicy(project, config) - if err != nil { - return fmt.Errorf("Error retrieving IAM policy from project API: %v", err) - } - - ep.Bindings = make([]*cloudresourcemanager.Binding, 0) - if err = setProjectIamPolicy(ep, config, project); err != nil { - return fmt.Errorf("Error applying IAM policy to project: %v", err) - } - - d.SetId("") - return nil -} - -func resourceGoogleProjectIamPolicyImport(d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { - d.Set("project", d.Id()) - return []*schema.ResourceData{d}, nil -} - -func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pid string) error { - policy.Version = iamPolicyVersion - - // Apply the policy - pbytes, _ := json.Marshal(policy) - log.Printf("[DEBUG] Setting policy %#v for project: %s", string(pbytes), pid) - _, err := config.clientResourceManager.Projects.SetIamPolicy(pid, - &cloudresourcemanager.SetIamPolicyRequest{Policy: policy, UpdateMask: "bindings,etag,auditConfigs"}).Do() - - if err != nil { - return errwrap.Wrapf(fmt.Sprintf("Error applying IAM policy for project %q. Policy is %#v, error is {{err}}", pid, policy), err) - } - return nil -} - -// Get a cloudresourcemanager.Policy from a schema.ResourceData -func getResourceIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy, error) { - ps := d.Get("policy_data").(string) - // The policy string is just a marshaled cloudresourcemanager.Policy. - policy := &cloudresourcemanager.Policy{} - if err := json.Unmarshal([]byte(ps), policy); err != nil { - return nil, fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err) - } - return policy, nil -} - -// Retrieve the existing IAM Policy for a Project -func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) { - p, err := config.clientResourceManager.Projects.GetIamPolicy(project, - &cloudresourcemanager.GetIamPolicyRequest{ - Options: &cloudresourcemanager.GetPolicyOptions{ - RequestedPolicyVersion: iamPolicyVersion, - }, - }).Do() - - if err != nil { - return nil, fmt.Errorf("Error retrieving IAM policy for project %q: %s", project, err) - } - return p, nil -} - -func getProjectIamPolicyMutexKey(pid string) string { - return fmt.Sprintf("iam-project-%s", pid) -} diff --git a/third_party/terraform/resources/resource_google_project_migrate.go b/third_party/terraform/resources/resource_google_project_migrate.go index 5735e8803cef..0d6cc996fe72 100644 --- a/third_party/terraform/resources/resource_google_project_migrate.go +++ b/third_party/terraform/resources/resource_google_project_migrate.go @@ -5,6 +5,7 @@ import ( "log" "github.com/hashicorp/terraform-plugin-sdk/terraform" + "google.golang.org/api/cloudresourcemanager/v1" ) func resourceGoogleProjectMigrateState(v int, s *terraform.InstanceState, meta interface{}) (*terraform.InstanceState, error) { @@ -45,3 +46,18 @@ func migrateGoogleProjectStateV0toV1(s *terraform.InstanceState, config *Config) log.Printf("[DEBUG] Attributes after migration: %#v", s.Attributes) return s, nil } + +// Retrieve the existing IAM Policy for a Project +func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) { + p, err := config.clientResourceManager.Projects.GetIamPolicy(project, + &cloudresourcemanager.GetIamPolicyRequest{ + Options: &cloudresourcemanager.GetPolicyOptions{ + RequestedPolicyVersion: iamPolicyVersion, + }, + }).Do() + + if err != nil { + return nil, fmt.Errorf("Error retrieving IAM policy for project %q: %s", project, err) + } + return p, nil +} diff --git a/third_party/terraform/tests/resource_google_project_iam_policy_test.go.erb b/third_party/terraform/tests/resource_google_project_iam_policy_test.go.erb index e43ffc59ab87..bfaa971d4931 100644 --- a/third_party/terraform/tests/resource_google_project_iam_policy_test.go.erb +++ b/third_party/terraform/tests/resource_google_project_iam_policy_test.go.erb @@ -165,7 +165,7 @@ func getStatePrimaryResource(s *terraform.State, res, expectedID string) (*terra if !ok { return nil, fmt.Errorf("Not found: %s", res) } - if resource.Primary.Attributes["id"] != expectedID && expectedID != "" { + if expectedID != "" && !compareProjectName("", resource.Primary.Attributes["id"], expectedID, nil) { return nil, fmt.Errorf("Expected project %q to match ID %q in state", resource.Primary.ID, expectedID) } return resource.Primary, nil diff --git a/third_party/terraform/utils/iam_project.go b/third_party/terraform/utils/iam_project.go index aa2841d0cb93..fb61769df91d 100644 --- a/third_party/terraform/utils/iam_project.go +++ b/third_party/terraform/utils/iam_project.go @@ -18,6 +18,17 @@ var IamProjectSchema = map[string]*schema.Schema{ }, } +// In google_project_iam_policy, project is required and not inferred by +// getProject. +var IamPolicyProjectSchema = map[string]*schema.Schema{ + "project": { + Type: schema.TypeString, + Required: true, + ForceNew: true, + DiffSuppressFunc: compareProjectName, + }, +} + type ProjectIamUpdater struct { resourceId string Config *Config @@ -37,6 +48,15 @@ func NewProjectIamUpdater(d *schema.ResourceData, config *Config) (ResourceIamUp }, nil } +// NewProjectIamPolicyUpdater is similar to NewProjectIamUpdater, except that it +// doesn't call getProject and only uses an explicitly set project. +func NewProjectIamPolicyUpdater(d *schema.ResourceData, config *Config) (ResourceIamUpdater, error) { + return &ProjectIamUpdater{ + resourceId: d.Get("project").(string), + Config: config, + }, nil +} + func ProjectIdParseFunc(d *schema.ResourceData, _ *Config) error { d.Set("project", d.Id()) return nil @@ -84,3 +104,12 @@ func (u *ProjectIamUpdater) GetMutexKey() string { func (u *ProjectIamUpdater) DescribeResource() string { return fmt.Sprintf("project %q", u.resourceId) } + +func compareProjectName(_, old, new string, _ *schema.ResourceData) bool { + // We can either get "projects/project-id" or "project-id", so strip any prefixes + return GetResourceNameFromSelfLink(old) == GetResourceNameFromSelfLink(new) +} + +func getProjectIamPolicyMutexKey(pid string) string { + return fmt.Sprintf("iam-project-%s", pid) +} diff --git a/third_party/terraform/utils/provider.go.erb b/third_party/terraform/utils/provider.go.erb index 6c41df8f7069..39e9746d93f2 100644 --- a/third_party/terraform/utils/provider.go.erb +++ b/third_party/terraform/utils/provider.go.erb @@ -372,11 +372,11 @@ end # products.each do "google_organization_iam_binding": ResourceIamBinding(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc), "google_organization_iam_custom_role": resourceGoogleOrganizationIamCustomRole(), "google_organization_iam_member": ResourceIamMember(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc), - "google_organization_iam_policy": ResourceIamPolicy(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc), - "google_organization_iam_audit_config": ResourceIamAuditConfig(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc), + "google_organization_iam_policy": ResourceIamPolicy(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc), + "google_organization_iam_audit_config": ResourceIamAuditConfig(IamOrganizationSchema, NewOrganizationIamUpdater, OrgIdParseFunc), "google_organization_policy": resourceGoogleOrganizationPolicy(), "google_project": resourceGoogleProject(), - "google_project_iam_policy": resourceGoogleProjectIamPolicy(), + "google_project_iam_policy": ResourceIamPolicy(IamPolicyProjectSchema, NewProjectIamPolicyUpdater, ProjectIdParseFunc), "google_project_iam_binding": ResourceIamBindingWithBatching(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc, IamBatchingEnabled), "google_project_iam_member": ResourceIamMemberWithBatching(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc, IamBatchingEnabled), "google_project_iam_audit_config": ResourceIamAuditConfigWithBatching(IamProjectSchema, NewProjectIamUpdater, ProjectIdParseFunc, IamBatchingEnabled),