mirror of
https://github.com/letic/terraform-provider-google.git
synced 2024-09-28 22:16:04 +00:00
Make google_project_iam_policy authoritative. (#2315)
<!-- This change is generated by MagicModules. --> /cc @rileykarson
This commit is contained in:
parent
5a47db4d94
commit
6707290efe
@ -24,8 +24,7 @@ func resourceGoogleProjectIamPolicy() *schema.Resource {
|
||||
Schema: map[string]*schema.Schema{
|
||||
"project": &schema.Schema{
|
||||
Type: schema.TypeString,
|
||||
Optional: true,
|
||||
Computed: true,
|
||||
Required: true,
|
||||
ForceNew: true,
|
||||
},
|
||||
"policy_data": &schema.Schema{
|
||||
@ -33,24 +32,24 @@ func resourceGoogleProjectIamPolicy() *schema.Resource {
|
||||
Required: true,
|
||||
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{
|
||||
Type: schema.TypeString,
|
||||
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{
|
||||
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.",
|
||||
Type: schema.TypeString,
|
||||
Computed: true,
|
||||
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,
|
||||
Computed: true,
|
||||
},
|
||||
"disable_project": &schema.Schema{
|
||||
Deprecated: "This will be removed with the authoritative field. Use lifecycle.prevent_destroy instead.",
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
Removed: "This field was removed alongside the authoritative field. Use lifecycle.prevent_destroy instead.",
|
||||
Type: schema.TypeBool,
|
||||
Optional: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
@ -58,165 +57,67 @@ func resourceGoogleProjectIamPolicy() *schema.Resource {
|
||||
|
||||
func resourceGoogleProjectIamPolicyCreate(d *schema.ResourceData, meta interface{}) error {
|
||||
config := meta.(*Config)
|
||||
pid, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project := d.Get("project").(string)
|
||||
|
||||
mutexKey := getProjectIamPolicyMutexKey(pid)
|
||||
mutexKey := getProjectIamPolicyMutexKey(project)
|
||||
mutexKV.Lock(mutexKey)
|
||||
defer mutexKV.Unlock(mutexKey)
|
||||
|
||||
// Get the policy in the template
|
||||
p, err := getResourceIamPolicy(d)
|
||||
policy, err := getResourceIamPolicy(d)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Could not get valid 'policy_data' from resource: %v", err)
|
||||
}
|
||||
|
||||
// An authoritative policy is applied without regard for any existing IAM
|
||||
// policy.
|
||||
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 {
|
||||
return err
|
||||
}
|
||||
|
||||
// First, subtract the policy defined in the template from the
|
||||
// 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)
|
||||
}
|
||||
log.Printf("[DEBUG] Setting IAM policy for project %q", project)
|
||||
err = setProjectIamPolicy(policy, config, project)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
d.SetId(pid)
|
||||
|
||||
d.SetId(project)
|
||||
return resourceGoogleProjectIamPolicyRead(d, meta)
|
||||
}
|
||||
|
||||
func resourceGoogleProjectIamPolicyRead(d *schema.ResourceData, meta interface{}) error {
|
||||
log.Printf("[DEBUG]: Reading google_project_iam_policy")
|
||||
config := meta.(*Config)
|
||||
pid, err := getProject(d, config)
|
||||
project := d.Get("project").(string)
|
||||
|
||||
policy, err := getProjectIamPolicy(project, config)
|
||||
if err != nil {
|
||||
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
|
||||
pBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: bindings})
|
||||
policyBytes, err := json.Marshal(&cloudresourcemanager.Policy{Bindings: policy.Bindings})
|
||||
if err != nil {
|
||||
return fmt.Errorf("Error marshaling IAM policy: %v", err)
|
||||
}
|
||||
log.Printf("[DEBUG]: Setting etag=%s", p.Etag)
|
||||
d.Set("etag", p.Etag)
|
||||
d.Set("policy_data", string(pBytes))
|
||||
d.Set("project", pid)
|
||||
|
||||
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 {
|
||||
log.Printf("[DEBUG]: Updating google_project_iam_policy")
|
||||
config := meta.(*Config)
|
||||
pid, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project := d.Get("project").(string)
|
||||
|
||||
mutexKey := getProjectIamPolicyMutexKey(pid)
|
||||
mutexKey := getProjectIamPolicyMutexKey(project)
|
||||
mutexKV.Lock(mutexKey)
|
||||
defer mutexKV.Unlock(mutexKey)
|
||||
|
||||
// Get the policy in the template
|
||||
p, err := getResourceIamPolicy(d)
|
||||
policy, err := getResourceIamPolicy(d)
|
||||
if err != nil {
|
||||
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
|
||||
// policy.
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
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)
|
||||
@ -225,42 +126,23 @@ func resourceGoogleProjectIamPolicyUpdate(d *schema.ResourceData, meta interface
|
||||
func resourceGoogleProjectIamPolicyDelete(d *schema.ResourceData, meta interface{}) error {
|
||||
log.Printf("[DEBUG]: Deleting google_project_iam_policy")
|
||||
config := meta.(*Config)
|
||||
pid, err := getProject(d, config)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
project := d.Get("project").(string)
|
||||
|
||||
mutexKey := getProjectIamPolicyMutexKey(pid)
|
||||
mutexKey := getProjectIamPolicyMutexKey(project)
|
||||
mutexKV.Lock(mutexKey)
|
||||
defer mutexKV.Unlock(mutexKey)
|
||||
|
||||
// Get the existing IAM policy from the API
|
||||
ep, err := getProjectIamPolicy(pid, config)
|
||||
// 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)
|
||||
}
|
||||
// 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 {
|
||||
// A non-authoritative policy should set the policy to the value of "restore_policy" in state
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
@ -270,24 +152,6 @@ func resourceGoogleProjectIamPolicyImport(d *schema.ResourceData, meta interface
|
||||
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 {
|
||||
// Apply the policy
|
||||
pbytes, _ := json.Marshal(policy)
|
||||
@ -312,32 +176,6 @@ func getResourceIamPolicy(d *schema.ResourceData) (*cloudresourcemanager.Policy,
|
||||
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
|
||||
func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.Policy, error) {
|
||||
p, err := config.clientResourceManager.Projects.GetIamPolicy(project,
|
||||
@ -349,22 +187,6 @@ func getProjectIamPolicy(project string, config *Config) (*cloudresourcemanager.
|
||||
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 {
|
||||
var oldPolicy, newPolicy cloudresourcemanager.Policy
|
||||
if err := json.Unmarshal([]byte(old), &oldPolicy); err != nil {
|
||||
|
@ -13,213 +13,6 @@ import (
|
||||
"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
|
||||
func TestAccProjectIamPolicy_basic(t *testing.T) {
|
||||
t.Parallel()
|
||||
@ -241,52 +34,10 @@ func TestAccProjectIamPolicy_basic(t *testing.T) {
|
||||
// merges policies, so we validate the expected state.
|
||||
resource.TestStep{
|
||||
Config: testAccProjectAssociatePolicyBasic(pid, pname, org),
|
||||
Check: resource.ComposeTestCheckFunc(
|
||||
testAccCheckGoogleProjectIamPolicyIsMerged("google_project_iam_policy.acceptance", "data.google_iam_policy.admin", pid),
|
||||
),
|
||||
},
|
||||
resource.TestStep{
|
||||
ResourceName: "google_project_iam_policy.acceptance",
|
||||
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) {
|
||||
table := []struct {
|
||||
input []*cloudresourcemanager.Binding
|
||||
@ -631,14 +223,12 @@ func TestIamMergeBindings(t *testing.T) {
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range table {
|
||||
got := mergeBindings(test.input)
|
||||
sort.Sort(sortableBindings(got))
|
||||
for i, _ := range got {
|
||||
sort.Strings(got[i].Members)
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(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 {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
@ -701,10 +268,12 @@ resource "google_project" "acceptance" {
|
||||
name = "%s"
|
||||
org_id = "%s"
|
||||
}
|
||||
|
||||
resource "google_project_iam_policy" "acceptance" {
|
||||
project = "${google_project.acceptance.id}"
|
||||
policy_data = "${data.google_iam_policy.admin.policy_data}"
|
||||
}
|
||||
|
||||
data "google_iam_policy" "admin" {
|
||||
binding {
|
||||
role = "roles/storage.objectViewer"
|
||||
@ -723,14 +292,6 @@ data "google_iam_policy" "admin" {
|
||||
`, 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 {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
@ -740,16 +301,6 @@ resource "google_project" "acceptance" {
|
||||
}`, 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 {
|
||||
return fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
@ -760,8 +311,8 @@ resource "google_project" "acceptance" {
|
||||
resource "google_project_iam_policy" "acceptance" {
|
||||
project = "${google_project.acceptance.id}"
|
||||
policy_data = "${data.google_iam_policy.expanded.policy_data}"
|
||||
authoritative = false
|
||||
}
|
||||
|
||||
data "google_iam_policy" "expanded" {
|
||||
binding {
|
||||
role = "roles/viewer"
|
||||
|
@ -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 {
|
||||
r := fmt.Sprintf(`
|
||||
resource "google_project" "acceptance" {
|
||||
|
@ -21,7 +21,11 @@ Three different resources help you manage your IAM policy for a project. Each of
|
||||
## google\_project\_iam\_policy
|
||||
|
||||
~> **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
|
||||
resource "google_project_iam_policy" "project" {
|
||||
@ -86,28 +90,13 @@ The following arguments are supported:
|
||||
|
||||
Changing this updates the policy.
|
||||
|
||||
Deleting this removes the policy, but leaves the original project policy
|
||||
intact. If there are overlapping `binding` entries between the original
|
||||
project policy and the data source policy, they will be removed.
|
||||
Deleting this removes all policies from the project, locking out users without
|
||||
organization-level access.
|
||||
|
||||
* `project` - (Optional) The project ID. If not specified, uses the
|
||||
ID of the project configured with the provider.
|
||||
|
||||
* `authoritative` - (DEPRECATED) (Optional, only for `google_project_iam_policy`)
|
||||
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.
|
||||
* `project` - (Optional) The project ID. If not specified for `google_project_iam_binding`
|
||||
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
|
||||
will not be inferred from the provider.
|
||||
|
||||
## Attributes Reference
|
||||
|
||||
@ -116,9 +105,6 @@ exported:
|
||||
|
||||
* `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
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user