Make google_project_iam_policy authoritative. (#2315)

<!-- This change is generated by MagicModules. -->
/cc @rileykarson
This commit is contained in:
The Magician 2018-10-30 13:39:07 -07:00 committed by Nathan McKinley
parent 5a47db4d94
commit 6707290efe
4 changed files with 75 additions and 698 deletions

View File

@ -24,8 +24,7 @@ func resourceGoogleProjectIamPolicy() *schema.Resource {
Schema: map[string]*schema.Schema{ Schema: map[string]*schema.Schema{
"project": &schema.Schema{ "project": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Optional: true, Required: true,
Computed: true,
ForceNew: true, ForceNew: true,
}, },
"policy_data": &schema.Schema{ "policy_data": &schema.Schema{
@ -33,22 +32,22 @@ func resourceGoogleProjectIamPolicy() *schema.Resource {
Required: true, Required: true,
DiffSuppressFunc: jsonPolicyDiffSuppress, DiffSuppressFunc: jsonPolicyDiffSuppress,
}, },
"authoritative": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Deprecated: "A future version of Terraform will remove the authoritative field. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information.",
},
"etag": &schema.Schema{ "etag": &schema.Schema{
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"authoritative": &schema.Schema{
Removed: "The authoritative field was removed. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information.",
Type: schema.TypeBool,
Optional: true,
},
"restore_policy": &schema.Schema{ "restore_policy": &schema.Schema{
Deprecated: "This field will be removed alongside the authoritative field. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information.", Removed: "This field was removed alongside the authoritative field. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information.",
Type: schema.TypeString, Type: schema.TypeString,
Computed: true, Computed: true,
}, },
"disable_project": &schema.Schema{ "disable_project": &schema.Schema{
Deprecated: "This will be removed with the authoritative field. Use lifecycle.prevent_destroy instead.", Removed: "This field was removed alongside the authoritative field. Use lifecycle.prevent_destroy instead.",
Type: schema.TypeBool, Type: schema.TypeBool,
Optional: true, Optional: true,
}, },
@ -58,166 +57,68 @@ func resourceGoogleProjectIamPolicy() *schema.Resource {
func resourceGoogleProjectIamPolicyCreate(d *schema.ResourceData, meta interface{}) error { func resourceGoogleProjectIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
config := meta.(*Config) config := meta.(*Config)
pid, err := getProject(d, config) project := d.Get("project").(string)
if err != nil {
return err
}
mutexKey := getProjectIamPolicyMutexKey(pid) mutexKey := getProjectIamPolicyMutexKey(project)
mutexKV.Lock(mutexKey) mutexKV.Lock(mutexKey)
defer mutexKV.Unlock(mutexKey) defer mutexKV.Unlock(mutexKey)
// Get the policy in the template // Get the policy in the template
p, err := getResourceIamPolicy(d) policy, err := getResourceIamPolicy(d)
if err != nil { if err != nil {
return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err) return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err)
} }
// An authoritative policy is applied without regard for any existing IAM log.Printf("[DEBUG] Setting IAM policy for project %q", project)
// policy. err = setProjectIamPolicy(policy, config, project)
if v, ok := d.GetOk("authoritative"); ok && v.(bool) {
log.Printf("[DEBUG] Setting authoritative IAM policy for project %q", pid)
err := setProjectIamPolicy(p, config, pid)
if err != nil {
return err
}
} else {
log.Printf("[DEBUG] Setting non-authoritative IAM policy for project %q", pid)
// This is a non-authoritative policy, meaning it should be merged with
// any existing policy
ep, err := getProjectIamPolicy(pid, config)
if err != nil { if err != nil {
return err return err
} }
// First, subtract the policy defined in the template from the d.SetId(project)
// current policy in the project, and save the result. This will
// allow us to restore the original policy at some point (which
// assumes that Terraform owns any common policy that exists in
// the template and project at create time.
rp := subtractIamPolicy(ep, p)
rps, err := json.Marshal(rp)
if err != nil {
return fmt.Errorf("Error marshaling restorable IAM policy: %v", err)
}
d.Set("restore_policy", string(rps))
// Merge the policies together
mb := mergeBindings(append(p.Bindings, rp.Bindings...))
ep.Bindings = mb
if err = setProjectIamPolicy(ep, config, pid); err != nil {
return fmt.Errorf("Error applying IAM policy to project: %v", err)
}
}
d.SetId(pid)
return resourceGoogleProjectIamPolicyRead(d, meta) return resourceGoogleProjectIamPolicyRead(d, meta)
} }
func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{}) error { func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG]: Reading google_project_iam_policy")
config := meta.(*Config) config := meta.(*Config)
pid, err := getProject(d, config) project := d.Get("project").(string)
policy, err := getProjectIamPolicy(project, config)
if err != nil { if err != nil {
return err return err
} }
p, err := getProjectIamPolicy(pid, config)
if err != nil {
return err
}
var bindings []*cloudresourcemanager.Binding
if v, ok := d.GetOk("restore_policy"); ok {
var restored cloudresourcemanager.Policy
// if there's a restore policy, subtract it from the policy_data
err := json.Unmarshal([]byte(v.(string)), &restored)
if err != nil {
return fmt.Errorf("Error unmarshaling restorable IAM policy: %v", err)
}
subtracted := subtractIamPolicy(p, &restored)
bindings = subtracted.Bindings
} else {
bindings = p.Bindings
}
// we only marshal the bindings, because only the bindings get set in the config // we only marshal the bindings, because only the bindings get set in the config
pBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: bindings}) policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings})
if err != nil { if err != nil {
return fmt.Errorf("Error marshaling IAM policy: %v", err) return fmt.Errorf("Error marshaling IAM policy: %v", err)
} }
log.Printf("[DEBUG]: Setting etag=%s", p.Etag)
d.Set("etag", p.Etag) d.Set("etag", policy.Etag)
d.Set("policy_data", string(pBytes)) d.Set("policy_data", string(policyBytes))
d.Set("project", pid) d.Set("project", project)
return nil return nil
} }
func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error { func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG]: Updating google_project_iam_policy")
config := meta.(*Config) config := meta.(*Config)
pid, err := getProject(d, config) project := d.Get("project").(string)
if err != nil {
return err
}
mutexKey := getProjectIamPolicyMutexKey(pid) mutexKey := getProjectIamPolicyMutexKey(project)
mutexKV.Lock(mutexKey) mutexKV.Lock(mutexKey)
defer mutexKV.Unlock(mutexKey) defer mutexKV.Unlock(mutexKey)
// Get the policy in the template // Get the policy in the template
p, err := getResourceIamPolicy(d) policy, err := getResourceIamPolicy(d)
if err != nil { if err != nil {
return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err) return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err)
} }
pBytes, _ := json.Marshal(p)
log.Printf("[DEBUG] Got policy from config: %s", string(pBytes))
// An authoritative policy is applied without regard for any existing IAM log.Printf("[DEBUG] Updating IAM policy for project %q", project)
// policy. err = setProjectIamPolicy(policy, config, project)
if v, ok := d.GetOk("authoritative"); ok && v.(bool) {
log.Printf("[DEBUG] Updating authoritative IAM policy for project %q", pid)
err := setProjectIamPolicy(p, config, pid)
if err != nil { if err != nil {
return fmt.Errorf("Error setting project IAM policy: %v", err) return fmt.Errorf("Error setting project IAM policy: %v", err)
} }
d.Set("restore_policy", "")
} else {
log.Printf("[DEBUG] Updating non-authoritative IAM policy for project %q", pid)
// Get the previous policy from state
pp, err := getPrevResourceIamPolicy(d)
if err != nil {
return fmt.Errorf("Error retrieving previous version of changed project IAM policy: %v", err)
}
ppBytes, _ := json.Marshal(pp)
log.Printf("[DEBUG] Got previous version of changed project IAM policy: %s", string(ppBytes))
// Get the existing IAM policy from the API
ep, err := getProjectIamPolicy(pid, config)
if err != nil {
return fmt.Errorf("Error retrieving IAM policy from project API: %v", err)
}
epBytes, _ := json.Marshal(ep)
log.Printf("[DEBUG] Got existing version of changed IAM policy from project API: %s", string(epBytes))
// Subtract the previous and current policies from the policy retrieved from the API
rp := subtractIamPolicy(ep, pp)
rpBytes, _ := json.Marshal(rp)
log.Printf("[DEBUG] After subtracting the previous policy from the existing policy, remaining policies: %s", string(rpBytes))
rp = subtractIamPolicy(rp, p)
rpBytes, _ = json.Marshal(rp)
log.Printf("[DEBUG] After subtracting the remaining policies from the config policy, remaining policies: %s", string(rpBytes))
rps, err := json.Marshal(rp)
if err != nil {
return fmt.Errorf("Error marhsaling restorable IAM policy: %v", err)
}
d.Set("restore_policy", string(rps))
// Merge the policies together
mb := mergeBindings(append(p.Bindings, rp.Bindings...))
ep.Bindings = mb
if err = setProjectIamPolicy(ep, config, pid); err != nil {
return fmt.Errorf("Error applying IAM policy to project: %v", err)
}
}
return resourceGoogleProjectIamPolicyRead(d, meta) return resourceGoogleProjectIamPolicyRead(d, meta)
} }
@ -225,42 +126,23 @@ func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface
func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface{}) error { func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG]: Deleting google_project_iam_policy") log.Printf("[DEBUG]: Deleting google_project_iam_policy")
config := meta.(*Config) config := meta.(*Config)
pid, err := getProject(d, config) project := d.Get("project").(string)
if err != nil {
return err
}
mutexKey := getProjectIamPolicyMutexKey(pid) mutexKey := getProjectIamPolicyMutexKey(project)
mutexKV.Lock(mutexKey) mutexKV.Lock(mutexKey)
defer mutexKV.Unlock(mutexKey) defer mutexKV.Unlock(mutexKey)
// Get the existing IAM policy from the API // Get the existing IAM policy from the API so we can repurpose the etag and audit config
ep, err := getProjectIamPolicy(pid, config) ep, err := getProjectIamPolicy(project, config)
if err != nil { if err != nil {
return fmt.Errorf("Error retrieving IAM policy from project API: %v", err) return fmt.Errorf("Error retrieving IAM policy from project API: %v", err)
} }
// Deleting an authoritative policy will leave the project with no policy,
// and unaccessible by anyone without org-level privs. For this reason, the
// "disable_project" property must be set to true, forcing the user to ack
// this outcome
if v, ok := d.GetOk("authoritative"); ok && v.(bool) {
if v, ok := d.GetOk("disable_project"); !ok || !v.(bool) {
return fmt.Errorf("You must set 'disable_project' to true before deleting an authoritative IAM policy")
}
ep.Bindings = make([]*cloudresourcemanager.Binding, 0)
} else { ep.Bindings = make([]*cloudresourcemanager.Binding, 0)
// A non-authoritative policy should set the policy to the value of "restore_policy" in state if err = setProjectIamPolicy(ep, config, project); err != nil {
// Get the previous policy from state
rp, err := getRestoreIamPolicy(d)
if err != nil {
return fmt.Errorf("Error retrieving previous version of changed project IAM policy: %v", err)
}
ep.Bindings = rp.Bindings
}
if err = setProjectIamPolicy(ep, config, pid); err != nil {
return fmt.Errorf("Error applying IAM policy to project: %v", err) return fmt.Errorf("Error applying IAM policy to project: %v", err)
} }
d.SetId("") d.SetId("")
return nil return nil
} }
@ -270,24 +152,6 @@ func resourceGoogleProjectIamPolicyImport(d *schema.ResourceData, meta interface
return []*schema.ResourceData{d}, nil return []*schema.ResourceData{d}, nil
} }
// Subtract all bindings in policy b from policy a, and return the result
func subtractIamPolicy(a, b *cloudresourcemanager.Policy) *cloudresourcemanager.Policy {
am := rolesToMembersMap(a.Bindings)
for _, b := range b.Bindings {
if _, ok := am[b.Role]; ok {
for _, m := range b.Members {
delete(am[b.Role], m)
}
if len(am[b.Role]) == 0 {
delete(am, b.Role)
}
}
}
a.Bindings = rolesToMembersBinding(am)
return a
}
func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pid string) error { func setProjectIamPolicy(policy *cloudresourcemanager.Policy, config *Config, pid string) error {
// Apply the policy // Apply the policy
pbytes, _ := json.Marshal(policy) pbytes, _ := json.Marshal(policy)
@ -312,32 +176,6 @@ func getResourceIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy,
return policy, nil return policy, nil
} }
// Get the previous cloudresourcemanager.Policy from a schema.ResourceData if the
// resource has changed
func getPrevResourceIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy, error) {
var policy *cloudresourcemanager.Policy = &cloudresourcemanager.Policy{}
if d.HasChange("policy_data") {
v, _ := d.GetChange("policy_data")
if err := json.Unmarshal([]byte(v.(string)), policy); err != nil {
return nil, fmt.Errorf("Could not unmarshal previous policy %s:\n: %v", v, err)
}
}
return policy, nil
}
// Get the restore_policy that can be used to restore a project's IAM policy to its
// state before it was adopted into Terraform
func getRestoreIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy, error) {
if v, ok := d.GetOk("restore_policy"); ok {
policy := &cloudresourcemanager.Policy{}
if err := json.Unmarshal([]byte(v.(string)), policy); err != nil {
return nil, fmt.Errorf("Could not unmarshal previous policy %s:\n: %v", v, err)
}
return policy, nil
}
return nil, fmt.Errorf("Resource does not have a 'restore_policy' attribute defined.")
}
// Retrieve the existing IAM Policy for a Project // Retrieve the existing IAM Policy for a Project
func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) { func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) {
p, err := config.clientResourceManager.Projects.GetIamPolicy(project, p, err := config.clientResourceManager.Projects.GetIamPolicy(project,
@ -349,22 +187,6 @@ func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.
return p, nil return p, nil
} }
// Convert a map of roles->members to a list of Binding
func rolesToMembersBinding(m map[string]map[string]bool) []*cloudresourcemanager.Binding {
bindings := make([]*cloudresourcemanager.Binding, 0)
for role, members := range m {
b := cloudresourcemanager.Binding{
Role: role,
Members: make([]string, 0),
}
for m, _ := range members {
b.Members = append(b.Members, m)
}
bindings = append(bindings, &b)
}
return bindings
}
func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool { func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
var oldPolicy, newPolicy cloudresourcemanager.Policy var oldPolicy, newPolicy cloudresourcemanager.Policy
if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil { if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil {

View File

@ -13,213 +13,6 @@ import (
"google.golang.org/api/cloudresourcemanager/v1" "google.golang.org/api/cloudresourcemanager/v1"
) )
func TestSubtractIamPolicy(t *testing.T) {
table := []struct {
a *cloudresourcemanager.Policy
b *cloudresourcemanager.Policy
expect cloudresourcemanager.Policy
}{
{
a: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"2",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
},
},
},
},
b: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"3",
"4",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
},
},
},
},
expect: cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"2",
},
},
},
},
},
{
a: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"2",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
},
},
},
},
b: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"2",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
},
},
},
},
expect: cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{},
},
},
{
a: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"2",
"3",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
"3",
},
},
},
},
b: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"3",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
"3",
},
},
},
},
expect: cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"2",
},
},
},
},
},
{
a: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"2",
"3",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
"3",
},
},
},
},
b: &cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{
{
Role: "a",
Members: []string{
"1",
"2",
"3",
},
},
{
Role: "b",
Members: []string{
"1",
"2",
"3",
},
},
},
},
expect: cloudresourcemanager.Policy{
Bindings: []*cloudresourcemanager.Binding{},
},
},
}
for _, test := range table {
c := subtractIamPolicy(test.a, test.b)
sort.Sort(sortableBindings(c.Bindings))
for i, _ := range c.Bindings {
sort.Strings(c.Bindings[i].Members)
}
if !reflect.DeepEqual(derefBindings(c.Bindings), derefBindings(test.expect.Bindings)) {
t.Errorf("\ngot %+v\nexpected %+v", derefBindings(c.Bindings), derefBindings(test.expect.Bindings))
}
}
}
// Test that an IAM policy can be applied to a project // Test that an IAM policy can be applied to a project
func TestAccProjectIamPolicy_basic(t *testing.T) { func TestAccProjectIamPolicy_basic(t *testing.T) {
t.Parallel() t.Parallel()
@ -241,52 +34,10 @@ func TestAccProjectIamPolicy_basic(t *testing.T) {
// merges policies, so we validate the expected state. // merges policies, so we validate the expected state.
resource.TestStep{ resource.TestStep{
Config: testAccProjectAssociatePolicyBasic(pid, pname, org), Config: testAccProjectAssociatePolicyBasic(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectIamPolicyIsMerged("google_project_iam_policy.acceptance", "data.google_iam_policy.admin", pid),
),
}, },
resource.TestStep{ resource.TestStep{
ResourceName: "google_project_iam_policy.acceptance", ResourceName: "google_project_iam_policy.acceptance",
ImportState: true, ImportState: true,
// Skipping the normal "ImportStateVerify" - Unfortunately, it's not
// really possible to make the imported policy match exactly, since
// the policy depends on the service account being used to create the
// project.
},
// Finally, remove the custom IAM policy from config and apply, then
// confirm that the project is in its original state.
resource.TestStep{
Config: testAccProject_create(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccProjectExistingPolicy(pid),
),
},
},
})
}
// Test that an IAM policy can be applied to a project when no project is set in the resource
func TestAccProjectIamPolicy_defaultProject(t *testing.T) {
t.Parallel()
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
// Create a new project
resource.TestStep{
Config: testAccProjectDefaultAssociatePolicyBasic(),
Check: resource.ComposeTestCheckFunc(
testAccProjectExistingPolicy(getTestProjectFromEnv()),
),
},
// Apply an IAM policy from a data source. The application
// merges policies, so we validate the expected state.
resource.TestStep{
Config: testAccProjectDefaultAssociatePolicyBasic(),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectIamPolicyIsMerged("google_project_iam_policy.acceptance", "data.google_iam_policy.admin", getTestProjectFromEnv()),
),
}, },
}, },
}) })
@ -371,165 +122,6 @@ func testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid string)
} }
} }
func testAccCheckGoogleProjectIamPolicyIsMerged(projectRes, policyRes, pid string) resource.TestCheckFunc {
return func(s *terraform.State) error {
err := testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid)(s)
if err != nil {
return err
}
projectPolicy, err := getGoogleProjectIamPolicyFromState(s, projectRes, pid)
if err != nil {
return fmt.Errorf("Error retrieving IAM policy for project from state: %s", err)
}
// Merge the project policy in Terraform state with the policy the project had before the config was applied
var expected []*cloudresourcemanager.Binding
expected = append(expected, originalPolicy.Bindings...)
expected = append(expected, projectPolicy.Bindings...)
expected = mergeBindings(expected)
// Retrieve the actual policy from the project
c := testAccProvider.Meta().(*Config)
actual, err := getProjectIamPolicy(pid, c)
if err != nil {
return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err)
}
// The bindings should match, indicating the policy was successfully applied and merged
if !compareBindings(actual.Bindings, expected) {
return fmt.Errorf("Actual and expected project policies do not match: actual policy is %+v, expected policy is %+v", derefBindings(actual.Bindings), derefBindings(expected))
}
return nil
}
}
func TestIamRolesToMembersBinding(t *testing.T) {
table := []struct {
expect []*cloudresourcemanager.Binding
input map[string]map[string]bool
}{
{
expect: []*cloudresourcemanager.Binding{
{
Role: "role-1",
Members: []string{
"member-1",
"member-2",
},
},
},
input: map[string]map[string]bool{
"role-1": map[string]bool{
"member-1": true,
"member-2": true,
},
},
},
{
expect: []*cloudresourcemanager.Binding{
{
Role: "role-1",
Members: []string{
"member-1",
"member-2",
},
},
},
input: map[string]map[string]bool{
"role-1": map[string]bool{
"member-1": true,
"member-2": true,
},
},
},
{
expect: []*cloudresourcemanager.Binding{
{
Role: "role-1",
Members: []string{},
},
},
input: map[string]map[string]bool{
"role-1": map[string]bool{},
},
},
}
for _, test := range table {
got := rolesToMembersBinding(test.input)
sort.Sort(sortableBindings(got))
for i, _ := range got {
sort.Strings(got[i].Members)
}
if !reflect.DeepEqual(derefBindings(got), derefBindings(test.expect)) {
t.Errorf("got %+v, expected %+v", derefBindings(got), derefBindings(test.expect))
}
}
}
func TestIamRolesToMembersMap(t *testing.T) {
table := []struct {
input []*cloudresourcemanager.Binding
expect map[string]map[string]bool
}{
{
input: []*cloudresourcemanager.Binding{
{
Role: "role-1",
Members: []string{
"member-1",
"member-2",
},
},
},
expect: map[string]map[string]bool{
"role-1": map[string]bool{
"member-1": true,
"member-2": true,
},
},
},
{
input: []*cloudresourcemanager.Binding{
{
Role: "role-1",
Members: []string{
"member-1",
"member-2",
"member-1",
"member-2",
},
},
},
expect: map[string]map[string]bool{
"role-1": map[string]bool{
"member-1": true,
"member-2": true,
},
},
},
{
input: []*cloudresourcemanager.Binding{
{
Role: "role-1",
},
},
expect: map[string]map[string]bool{
"role-1": map[string]bool{},
},
},
}
for _, test := range table {
got := rolesToMembersMap(test.input)
if !reflect.DeepEqual(got, test.expect) {
t.Errorf("got %+v, expected %+v", got, test.expect)
}
}
}
func TestIamMergeBindings(t *testing.T) { func TestIamMergeBindings(t *testing.T) {
table := []struct { table := []struct {
input []*cloudresourcemanager.Binding input []*cloudresourcemanager.Binding
@ -631,14 +223,12 @@ func TestIamMergeBindings(t *testing.T) {
}, },
}, },
} }
for _, test := range table { for _, test := range table {
got := mergeBindings(test.input) got := mergeBindings(test.input)
sort.Sort(sortableBindings(got)) sort.Sort(sortableBindings(got))
for i, _ := range got { for i, _ := range got {
sort.Strings(got[i].Members) sort.Strings(got[i].Members)
} }
if !reflect.DeepEqual(derefBindings(got), test.expect) { if !reflect.DeepEqual(derefBindings(got), test.expect) {
t.Errorf("\ngot %+v\nexpected %+v", derefBindings(got), test.expect) t.Errorf("\ngot %+v\nexpected %+v", derefBindings(got), test.expect)
} }
@ -671,29 +261,6 @@ func testAccProjectExistingPolicy(pid string) resource.TestCheckFunc {
} }
} }
func testAccProjectDefaultAssociatePolicyBasic() string {
return fmt.Sprintf(`
resource "google_project_iam_policy" "acceptance" {
policy_data = "${data.google_iam_policy.admin.policy_data}"
}
data "google_iam_policy" "admin" {
binding {
role = "roles/storage.objectViewer"
members = [
"user:evanbrown@google.com",
]
}
binding {
role = "roles/compute.instanceAdmin"
members = [
"user:evanbrown@google.com",
"user:evandbrown@gmail.com",
]
}
}
`)
}
func testAccProjectAssociatePolicyBasic(pid, name, org string) string { func testAccProjectAssociatePolicyBasic(pid, name, org string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "google_project" "acceptance" { resource "google_project" "acceptance" {
@ -701,10 +268,12 @@ resource "google_project" "acceptance" {
name = "%s" name = "%s"
org_id = "%s" org_id = "%s"
} }
resource "google_project_iam_policy" "acceptance" { resource "google_project_iam_policy" "acceptance" {
project = "${google_project.acceptance.id}" project = "${google_project.acceptance.id}"
policy_data = "${data.google_iam_policy.admin.policy_data}" policy_data = "${data.google_iam_policy.admin.policy_data}"
} }
data "google_iam_policy" "admin" { data "google_iam_policy" "admin" {
binding { binding {
role = "roles/storage.objectViewer" role = "roles/storage.objectViewer"
@ -723,14 +292,6 @@ data "google_iam_policy" "admin" {
`, pid, name, org) `, pid, name, org)
} }
func testAccProject_createWithoutOrg(pid, name string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
}`, pid, name)
}
func testAccProject_create(pid, name, org string) string { func testAccProject_create(pid, name, org string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "google_project" "acceptance" { resource "google_project" "acceptance" {
@ -740,16 +301,6 @@ resource "google_project" "acceptance" {
}`, pid, name, org) }`, pid, name, org)
} }
func testAccProject_createBilling(pid, name, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}`, pid, name, org, billing)
}
func testAccProjectAssociatePolicyExpanded(pid, name, org string) string { func testAccProjectAssociatePolicyExpanded(pid, name, org string) string {
return fmt.Sprintf(` return fmt.Sprintf(`
resource "google_project" "acceptance" { resource "google_project" "acceptance" {
@ -760,8 +311,8 @@ resource "google_project" "acceptance" {
resource "google_project_iam_policy" "acceptance" { resource "google_project_iam_policy" "acceptance" {
project = "${google_project.acceptance.id}" project = "${google_project.acceptance.id}"
policy_data = "${data.google_iam_policy.expanded.policy_data}" policy_data = "${data.google_iam_policy.expanded.policy_data}"
authoritative = false
} }
data "google_iam_policy" "expanded" { data "google_iam_policy" "expanded" {
binding { binding {
role = "roles/viewer" role = "roles/viewer"

View File

@ -431,6 +431,24 @@ func testAccCheckGoogleProjectHasNoLabels(r, pid string) resource.TestCheckFunc
} }
} }
func testAccProject_createWithoutOrg(pid, name string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
}`, pid, name)
}
func testAccProject_createBilling(pid, name, org, billing string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
billing_account = "%s"
}`, pid, name, org, billing)
}
func testAccProject_labels(pid, name, org string, labels map[string]string) string { func testAccProject_labels(pid, name, org string, labels map[string]string) string {
r := fmt.Sprintf(` r := fmt.Sprintf(`
resource "google_project" "acceptance" { resource "google_project" "acceptance" {

View File

@ -21,7 +21,11 @@ Three different resources help you manage your IAM policy for a project. Each of
## google\_project\_iam\_policy ## google\_project\_iam\_policy
~> **Be careful!** You can accidentally lock yourself out of your project ~> **Be careful!** You can accidentally lock yourself out of your project
using this resource. Proceed with caution. using this resource. Deleting a `google_project_iam_policy` removes access
from anyone without organization-level access to the project. Proceed with caution.
It's not recommended to use `google_project_iam_policy` with your provider project
to avoid locking yourself out, and it should generally only be used with projects
fully managed by Terraform.
```hcl ```hcl
resource "google_project_iam_policy" "project" { resource "google_project_iam_policy" "project" {
@ -86,28 +90,13 @@ The following arguments are supported:
Changing this updates the policy. Changing this updates the policy.
Deleting this removes the policy, but leaves the original project policy Deleting this removes all policies from the project, locking out users without
intact. If there are overlapping `binding` entries between the original organization-level access.
project policy and the data source policy, they will be removed.
* `project` - (Optional) The project ID. If not specified, uses the * `project` - (Optional) The project ID. If not specified for `google_project_iam_binding`
ID of the project configured with the provider. or `google_project_iam_member`, uses the ID of the project configured with the provider.
Required for `google_project_iam_policy` - you must explicitly set the project, and it
* `authoritative` - (DEPRECATED) (Optional, only for `google_project_iam_policy`) will not be inferred from the provider.
A boolean value indicating if this policy
should overwrite any existing IAM policy on the project. When set to true,
**any policies not in your config file will be removed**. This can **lock
you out** of your project until an Organization Administrator grants you
access again, so please exercise caution. If this argument is `true` and you
want to delete the resource, you must set the `disable_project` argument to
`true`, acknowledging that the project will be inaccessible to anyone but the
Organization Admins, as it will no longer have an IAM policy. Rather than using
this, you should use `google_project_iam_binding` and
`google_project_iam_member`.
* `disable_project` - (DEPRECATED) (Optional, only for `google_project_iam_policy`)
A boolean value that must be set to `true`
if you want to delete a `google_project_iam_policy` that is authoritative.
## Attributes Reference ## Attributes Reference
@ -116,9 +105,6 @@ exported:
* `etag` - (Computed) The etag of the project's IAM policy. * `etag` - (Computed) The etag of the project's IAM policy.
* `restore_policy` - (DEPRECATED) (Computed, only for `google_project_iam_policy`)
The IAM policy that will be restored when a
non-authoritative policy resource is deleted.
## Import ## Import