Merge pull request #12387 from hashicorp/paddy_11763_gcp_iam_diff

provider/google: ignore expanded v collapsed policies in diff
This commit is contained in:
Paddy 2017-03-14 12:48:05 -07:00 committed by GitHub
commit 4f235c870d
2 changed files with 124 additions and 51 deletions

View File

@ -373,6 +373,8 @@ func jsonPolicyDiffSuppress(k, old, new string, d *schema.ResourceData) bool {
log.Printf("[ERROR] Could not unmarshal new policy %s: %v", new, err)
return false
}
oldPolicy.Bindings = mergeBindings(oldPolicy.Bindings)
newPolicy.Bindings = mergeBindings(newPolicy.Bindings)
if newPolicy.Etag != oldPolicy.Etag {
return false
}

View File

@ -254,53 +254,99 @@ func TestAccGoogleProjectIamPolicy_basic(t *testing.T) {
})
}
func testAccCheckGoogleProjectIamPolicyIsMerged(projectRes, policyRes, pid string) resource.TestCheckFunc {
// Test that a non-collapsed IAM policy doesn't perpetually diff
func TestAccGoogleProjectIamPolicy_expanded(t *testing.T) {
pid := "terraform-" + acctest.RandString(10)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccGoogleProjectAssociatePolicyExpanded(pid, pname, org),
Check: resource.ComposeTestCheckFunc(
testAccCheckGoogleProjectIamPolicyExists("google_project_iam_policy.acceptance", "data.google_iam_policy.expanded", pid),
),
},
},
})
}
func getStatePrimaryResource(s *terraform.State, res, expectedID string) (*terraform.InstanceState, error) {
// Get the project resource
resource, ok := s.RootModule().Resources[res]
if !ok {
return nil, fmt.Errorf("Not found: %s", res)
}
if resource.Primary.Attributes["id"] != expectedID && expectedID != "" {
return nil, fmt.Errorf("Expected project %q to match ID %q in state", resource.Primary.ID, expectedID)
}
return resource.Primary, nil
}
func getGoogleProjectIamPolicyFromResource(resource *terraform.InstanceState) (cloudresourcemanager.Policy, error) {
var p cloudresourcemanager.Policy
ps, ok := resource.Attributes["policy_data"]
if !ok {
return p, fmt.Errorf("Resource %q did not have a 'policy_data' attribute. Attributes were %#v", resource.ID, resource.Attributes)
}
if err := json.Unmarshal([]byte(ps), &p); err != nil {
return p, fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err)
}
return p, nil
}
func getGoogleProjectIamPolicyFromState(s *terraform.State, res, expectedID string) (cloudresourcemanager.Policy, error) {
project, err := getStatePrimaryResource(s, res, expectedID)
if err != nil {
return cloudresourcemanager.Policy{}, err
}
return getGoogleProjectIamPolicyFromResource(project)
}
func compareBindings(a, b []*cloudresourcemanager.Binding) bool {
a = mergeBindings(a)
b = mergeBindings(b)
sort.Sort(sortableBindings(a))
sort.Sort(sortableBindings(b))
return reflect.DeepEqual(derefBindings(a), derefBindings(b))
}
func testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid string) resource.TestCheckFunc {
return func(s *terraform.State) error {
// Get the project resource
project, ok := s.RootModule().Resources[projectRes]
if !ok {
return fmt.Errorf("Not found: %s", projectRes)
projectPolicy, err := getGoogleProjectIamPolicyFromState(s, projectRes, pid)
if err != nil {
return fmt.Errorf("Error retrieving IAM policy for project from state: %s", err)
}
// The project ID should match the config's project ID
if project.Primary.ID != pid {
return fmt.Errorf("Expected project %q to match ID %q in state", pid, project.Primary.ID)
}
var projectP, policyP cloudresourcemanager.Policy
// The project should have a policy
ps, ok := project.Primary.Attributes["policy_data"]
if !ok {
return fmt.Errorf("Project resource %q did not have a 'policy_data' attribute. Attributes were %#v", project.Primary.Attributes["id"], project.Primary.Attributes)
}
if err := json.Unmarshal([]byte(ps), &projectP); err != nil {
return fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err)
}
// The data policy resource should have a policy
policy, ok := s.RootModule().Resources[policyRes]
if !ok {
return fmt.Errorf("Not found: %s", policyRes)
}
ps, ok = policy.Primary.Attributes["policy_data"]
if !ok {
return fmt.Errorf("Data policy resource %q did not have a 'policy_data' attribute. Attributes were %#v", policy.Primary.Attributes["id"], project.Primary.Attributes)
}
if err := json.Unmarshal([]byte(ps), &policyP); err != nil {
return err
policyPolicy, err := getGoogleProjectIamPolicyFromState(s, policyRes, "")
if err != nil {
return fmt.Errorf("Error retrieving IAM policy for data_policy from state: %s", err)
}
// The bindings in both policies should be identical
sort.Sort(sortableBindings(projectP.Bindings))
sort.Sort(sortableBindings(policyP.Bindings))
if !reflect.DeepEqual(derefBindings(projectP.Bindings), derefBindings(policyP.Bindings)) {
return fmt.Errorf("Project and data source policies do not match: project policy is %+v, data resource policy is %+v", derefBindings(projectP.Bindings), derefBindings(policyP.Bindings))
if !compareBindings(projectPolicy.Bindings, policyPolicy.Bindings) {
return fmt.Errorf("Project and data source policies do not match: project policy is %+v, data resource policy is %+v", derefBindings(projectPolicy.Bindings), derefBindings(policyPolicy.Bindings))
}
return nil
}
}
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
expected := make([]*cloudresourcemanager.Binding, 0)
var expected []*cloudresourcemanager.Binding
expected = append(expected, originalPolicy.Bindings...)
expected = append(expected, projectP.Bindings...)
expectedM := mergeBindings(expected)
expected = append(expected, projectPolicy.Bindings...)
expected = mergeBindings(expected)
// Retrieve the actual policy from the project
c := testAccProvider.Meta().(*Config)
@ -308,13 +354,9 @@ func testAccCheckGoogleProjectIamPolicyIsMerged(projectRes, policyRes, pid strin
if err != nil {
return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err)
}
actualM := mergeBindings(actual.Bindings)
sort.Sort(sortableBindings(actualM))
sort.Sort(sortableBindings(expectedM))
// The bindings should match, indicating the policy was successfully applied and merged
if !reflect.DeepEqual(derefBindings(actualM), derefBindings(expectedM)) {
return fmt.Errorf("Actual and expected project policies do not match: actual policy is %+v, expected policy is %+v", derefBindings(actualM), derefBindings(expectedM))
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
@ -591,8 +633,8 @@ func testAccGoogleProjectAssociatePolicyBasic(pid, name, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
name = "%s"
org_id = "%s"
}
resource "google_project_iam_policy" "acceptance" {
project = "${google_project.acceptance.id}"
@ -620,8 +662,8 @@ func testAccGoogleProject_create(pid, name, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
name = "%s"
org_id = "%s"
}`, pid, name, org)
}
@ -629,8 +671,37 @@ func testAccGoogleProject_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"
name = "%s"
org_id = "%s"
billing_account = "%s"
}`, pid, name, org, billing)
}
func testAccGoogleProjectAssociatePolicyExpanded(pid, name, org string) string {
return fmt.Sprintf(`
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
}
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"
members = [
"user:paddy@carvers.co",
]
}
binding {
role = "roles/viewer"
members = [
"user:paddy@hashicorp.com",
]
}
}`, pid, name, org)
}