github.com/danp/terraform@v0.9.5-0.20170426144147-39d740081351/builtin/providers/google/resource_google_project_iam_policy_test.go (about)

     1  package google
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"reflect"
     7  	"sort"
     8  	"testing"
     9  
    10  	"github.com/hashicorp/terraform/helper/acctest"
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/terraform"
    13  	"google.golang.org/api/cloudresourcemanager/v1"
    14  )
    15  
    16  func TestSubtractIamPolicy(t *testing.T) {
    17  	table := []struct {
    18  		a      *cloudresourcemanager.Policy
    19  		b      *cloudresourcemanager.Policy
    20  		expect cloudresourcemanager.Policy
    21  	}{
    22  		{
    23  			a: &cloudresourcemanager.Policy{
    24  				Bindings: []*cloudresourcemanager.Binding{
    25  					{
    26  						Role: "a",
    27  						Members: []string{
    28  							"1",
    29  							"2",
    30  						},
    31  					},
    32  					{
    33  						Role: "b",
    34  						Members: []string{
    35  							"1",
    36  							"2",
    37  						},
    38  					},
    39  				},
    40  			},
    41  			b: &cloudresourcemanager.Policy{
    42  				Bindings: []*cloudresourcemanager.Binding{
    43  					{
    44  						Role: "a",
    45  						Members: []string{
    46  							"3",
    47  							"4",
    48  						},
    49  					},
    50  					{
    51  						Role: "b",
    52  						Members: []string{
    53  							"1",
    54  							"2",
    55  						},
    56  					},
    57  				},
    58  			},
    59  			expect: cloudresourcemanager.Policy{
    60  				Bindings: []*cloudresourcemanager.Binding{
    61  					{
    62  						Role: "a",
    63  						Members: []string{
    64  							"1",
    65  							"2",
    66  						},
    67  					},
    68  				},
    69  			},
    70  		},
    71  		{
    72  			a: &cloudresourcemanager.Policy{
    73  				Bindings: []*cloudresourcemanager.Binding{
    74  					{
    75  						Role: "a",
    76  						Members: []string{
    77  							"1",
    78  							"2",
    79  						},
    80  					},
    81  					{
    82  						Role: "b",
    83  						Members: []string{
    84  							"1",
    85  							"2",
    86  						},
    87  					},
    88  				},
    89  			},
    90  			b: &cloudresourcemanager.Policy{
    91  				Bindings: []*cloudresourcemanager.Binding{
    92  					{
    93  						Role: "a",
    94  						Members: []string{
    95  							"1",
    96  							"2",
    97  						},
    98  					},
    99  					{
   100  						Role: "b",
   101  						Members: []string{
   102  							"1",
   103  							"2",
   104  						},
   105  					},
   106  				},
   107  			},
   108  			expect: cloudresourcemanager.Policy{
   109  				Bindings: []*cloudresourcemanager.Binding{},
   110  			},
   111  		},
   112  		{
   113  			a: &cloudresourcemanager.Policy{
   114  				Bindings: []*cloudresourcemanager.Binding{
   115  					{
   116  						Role: "a",
   117  						Members: []string{
   118  							"1",
   119  							"2",
   120  							"3",
   121  						},
   122  					},
   123  					{
   124  						Role: "b",
   125  						Members: []string{
   126  							"1",
   127  							"2",
   128  							"3",
   129  						},
   130  					},
   131  				},
   132  			},
   133  			b: &cloudresourcemanager.Policy{
   134  				Bindings: []*cloudresourcemanager.Binding{
   135  					{
   136  						Role: "a",
   137  						Members: []string{
   138  							"1",
   139  							"3",
   140  						},
   141  					},
   142  					{
   143  						Role: "b",
   144  						Members: []string{
   145  							"1",
   146  							"2",
   147  							"3",
   148  						},
   149  					},
   150  				},
   151  			},
   152  			expect: cloudresourcemanager.Policy{
   153  				Bindings: []*cloudresourcemanager.Binding{
   154  					{
   155  						Role: "a",
   156  						Members: []string{
   157  							"2",
   158  						},
   159  					},
   160  				},
   161  			},
   162  		},
   163  		{
   164  			a: &cloudresourcemanager.Policy{
   165  				Bindings: []*cloudresourcemanager.Binding{
   166  					{
   167  						Role: "a",
   168  						Members: []string{
   169  							"1",
   170  							"2",
   171  							"3",
   172  						},
   173  					},
   174  					{
   175  						Role: "b",
   176  						Members: []string{
   177  							"1",
   178  							"2",
   179  							"3",
   180  						},
   181  					},
   182  				},
   183  			},
   184  			b: &cloudresourcemanager.Policy{
   185  				Bindings: []*cloudresourcemanager.Binding{
   186  					{
   187  						Role: "a",
   188  						Members: []string{
   189  							"1",
   190  							"2",
   191  							"3",
   192  						},
   193  					},
   194  					{
   195  						Role: "b",
   196  						Members: []string{
   197  							"1",
   198  							"2",
   199  							"3",
   200  						},
   201  					},
   202  				},
   203  			},
   204  			expect: cloudresourcemanager.Policy{
   205  				Bindings: []*cloudresourcemanager.Binding{},
   206  			},
   207  		},
   208  	}
   209  
   210  	for _, test := range table {
   211  		c := subtractIamPolicy(test.a, test.b)
   212  		sort.Sort(sortableBindings(c.Bindings))
   213  		for i, _ := range c.Bindings {
   214  			sort.Strings(c.Bindings[i].Members)
   215  		}
   216  
   217  		if !reflect.DeepEqual(derefBindings(c.Bindings), derefBindings(test.expect.Bindings)) {
   218  			t.Errorf("\ngot %+v\nexpected %+v", derefBindings(c.Bindings), derefBindings(test.expect.Bindings))
   219  		}
   220  	}
   221  }
   222  
   223  // Test that an IAM policy can be applied to a project
   224  func TestAccGoogleProjectIamPolicy_basic(t *testing.T) {
   225  	pid := "terraform-" + acctest.RandString(10)
   226  	resource.Test(t, resource.TestCase{
   227  		PreCheck:  func() { testAccPreCheck(t) },
   228  		Providers: testAccProviders,
   229  		Steps: []resource.TestStep{
   230  			// Create a new project
   231  			resource.TestStep{
   232  				Config: testAccGoogleProject_create(pid, pname, org),
   233  				Check: resource.ComposeTestCheckFunc(
   234  					testAccGoogleProjectExistingPolicy(pid),
   235  				),
   236  			},
   237  			// Apply an IAM policy from a data source. The application
   238  			// merges policies, so we validate the expected state.
   239  			resource.TestStep{
   240  				Config: testAccGoogleProjectAssociatePolicyBasic(pid, pname, org),
   241  				Check: resource.ComposeTestCheckFunc(
   242  					testAccCheckGoogleProjectIamPolicyIsMerged("google_project_iam_policy.acceptance", "data.google_iam_policy.admin", pid),
   243  				),
   244  			},
   245  			// Finally, remove the custom IAM policy from config and apply, then
   246  			// confirm that the project is in its original state.
   247  			resource.TestStep{
   248  				Config: testAccGoogleProject_create(pid, pname, org),
   249  				Check: resource.ComposeTestCheckFunc(
   250  					testAccGoogleProjectExistingPolicy(pid),
   251  				),
   252  			},
   253  		},
   254  	})
   255  }
   256  
   257  // Test that a non-collapsed IAM policy doesn't perpetually diff
   258  func TestAccGoogleProjectIamPolicy_expanded(t *testing.T) {
   259  	pid := "terraform-" + acctest.RandString(10)
   260  	resource.Test(t, resource.TestCase{
   261  		PreCheck:  func() { testAccPreCheck(t) },
   262  		Providers: testAccProviders,
   263  		Steps: []resource.TestStep{
   264  			resource.TestStep{
   265  				Config: testAccGoogleProjectAssociatePolicyExpanded(pid, pname, org),
   266  				Check: resource.ComposeTestCheckFunc(
   267  					testAccCheckGoogleProjectIamPolicyExists("google_project_iam_policy.acceptance", "data.google_iam_policy.expanded", pid),
   268  				),
   269  			},
   270  		},
   271  	})
   272  }
   273  
   274  func getStatePrimaryResource(s *terraform.State, res, expectedID string) (*terraform.InstanceState, error) {
   275  	// Get the project resource
   276  	resource, ok := s.RootModule().Resources[res]
   277  	if !ok {
   278  		return nil, fmt.Errorf("Not found: %s", res)
   279  	}
   280  	if resource.Primary.Attributes["id"] != expectedID && expectedID != "" {
   281  		return nil, fmt.Errorf("Expected project %q to match ID %q in state", resource.Primary.ID, expectedID)
   282  	}
   283  	return resource.Primary, nil
   284  }
   285  
   286  func getGoogleProjectIamPolicyFromResource(resource *terraform.InstanceState) (cloudresourcemanager.Policy, error) {
   287  	var p cloudresourcemanager.Policy
   288  	ps, ok := resource.Attributes["policy_data"]
   289  	if !ok {
   290  		return p, fmt.Errorf("Resource %q did not have a 'policy_data' attribute. Attributes were %#v", resource.ID, resource.Attributes)
   291  	}
   292  	if err := json.Unmarshal([]byte(ps), &p); err != nil {
   293  		return p, fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err)
   294  	}
   295  	return p, nil
   296  }
   297  
   298  func getGoogleProjectIamPolicyFromState(s *terraform.State, res, expectedID string) (cloudresourcemanager.Policy, error) {
   299  	project, err := getStatePrimaryResource(s, res, expectedID)
   300  	if err != nil {
   301  		return cloudresourcemanager.Policy{}, err
   302  	}
   303  	return getGoogleProjectIamPolicyFromResource(project)
   304  }
   305  
   306  func compareBindings(a, b []*cloudresourcemanager.Binding) bool {
   307  	a = mergeBindings(a)
   308  	b = mergeBindings(b)
   309  	sort.Sort(sortableBindings(a))
   310  	sort.Sort(sortableBindings(b))
   311  	return reflect.DeepEqual(derefBindings(a), derefBindings(b))
   312  }
   313  
   314  func testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid string) resource.TestCheckFunc {
   315  	return func(s *terraform.State) error {
   316  		projectPolicy, err := getGoogleProjectIamPolicyFromState(s, projectRes, pid)
   317  		if err != nil {
   318  			return fmt.Errorf("Error retrieving IAM policy for project from state: %s", err)
   319  		}
   320  		policyPolicy, err := getGoogleProjectIamPolicyFromState(s, policyRes, "")
   321  		if err != nil {
   322  			return fmt.Errorf("Error retrieving IAM policy for data_policy from state: %s", err)
   323  		}
   324  
   325  		// The bindings in both policies should be identical
   326  		if !compareBindings(projectPolicy.Bindings, policyPolicy.Bindings) {
   327  			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))
   328  		}
   329  		return nil
   330  	}
   331  }
   332  
   333  func testAccCheckGoogleProjectIamPolicyIsMerged(projectRes, policyRes, pid string) resource.TestCheckFunc {
   334  	return func(s *terraform.State) error {
   335  		err := testAccCheckGoogleProjectIamPolicyExists(projectRes, policyRes, pid)(s)
   336  		if err != nil {
   337  			return err
   338  		}
   339  
   340  		projectPolicy, err := getGoogleProjectIamPolicyFromState(s, projectRes, pid)
   341  		if err != nil {
   342  			return fmt.Errorf("Error retrieving IAM policy for project from state: %s", err)
   343  		}
   344  
   345  		// Merge the project policy in Terraform state with the policy the project had before the config was applied
   346  		var expected []*cloudresourcemanager.Binding
   347  		expected = append(expected, originalPolicy.Bindings...)
   348  		expected = append(expected, projectPolicy.Bindings...)
   349  		expected = mergeBindings(expected)
   350  
   351  		// Retrieve the actual policy from the project
   352  		c := testAccProvider.Meta().(*Config)
   353  		actual, err := getProjectIamPolicy(pid, c)
   354  		if err != nil {
   355  			return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err)
   356  		}
   357  		// The bindings should match, indicating the policy was successfully applied and merged
   358  		if !compareBindings(actual.Bindings, expected) {
   359  			return fmt.Errorf("Actual and expected project policies do not match: actual policy is %+v, expected policy is  %+v", derefBindings(actual.Bindings), derefBindings(expected))
   360  		}
   361  
   362  		return nil
   363  	}
   364  }
   365  
   366  func TestIamRolesToMembersBinding(t *testing.T) {
   367  	table := []struct {
   368  		expect []*cloudresourcemanager.Binding
   369  		input  map[string]map[string]bool
   370  	}{
   371  		{
   372  			expect: []*cloudresourcemanager.Binding{
   373  				{
   374  					Role: "role-1",
   375  					Members: []string{
   376  						"member-1",
   377  						"member-2",
   378  					},
   379  				},
   380  			},
   381  			input: map[string]map[string]bool{
   382  				"role-1": map[string]bool{
   383  					"member-1": true,
   384  					"member-2": true,
   385  				},
   386  			},
   387  		},
   388  		{
   389  			expect: []*cloudresourcemanager.Binding{
   390  				{
   391  					Role: "role-1",
   392  					Members: []string{
   393  						"member-1",
   394  						"member-2",
   395  					},
   396  				},
   397  			},
   398  			input: map[string]map[string]bool{
   399  				"role-1": map[string]bool{
   400  					"member-1": true,
   401  					"member-2": true,
   402  				},
   403  			},
   404  		},
   405  		{
   406  			expect: []*cloudresourcemanager.Binding{
   407  				{
   408  					Role:    "role-1",
   409  					Members: []string{},
   410  				},
   411  			},
   412  			input: map[string]map[string]bool{
   413  				"role-1": map[string]bool{},
   414  			},
   415  		},
   416  	}
   417  
   418  	for _, test := range table {
   419  		got := rolesToMembersBinding(test.input)
   420  
   421  		sort.Sort(sortableBindings(got))
   422  		for i, _ := range got {
   423  			sort.Strings(got[i].Members)
   424  		}
   425  
   426  		if !reflect.DeepEqual(derefBindings(got), derefBindings(test.expect)) {
   427  			t.Errorf("got %+v, expected %+v", derefBindings(got), derefBindings(test.expect))
   428  		}
   429  	}
   430  }
   431  func TestIamRolesToMembersMap(t *testing.T) {
   432  	table := []struct {
   433  		input  []*cloudresourcemanager.Binding
   434  		expect map[string]map[string]bool
   435  	}{
   436  		{
   437  			input: []*cloudresourcemanager.Binding{
   438  				{
   439  					Role: "role-1",
   440  					Members: []string{
   441  						"member-1",
   442  						"member-2",
   443  					},
   444  				},
   445  			},
   446  			expect: map[string]map[string]bool{
   447  				"role-1": map[string]bool{
   448  					"member-1": true,
   449  					"member-2": true,
   450  				},
   451  			},
   452  		},
   453  		{
   454  			input: []*cloudresourcemanager.Binding{
   455  				{
   456  					Role: "role-1",
   457  					Members: []string{
   458  						"member-1",
   459  						"member-2",
   460  						"member-1",
   461  						"member-2",
   462  					},
   463  				},
   464  			},
   465  			expect: map[string]map[string]bool{
   466  				"role-1": map[string]bool{
   467  					"member-1": true,
   468  					"member-2": true,
   469  				},
   470  			},
   471  		},
   472  		{
   473  			input: []*cloudresourcemanager.Binding{
   474  				{
   475  					Role: "role-1",
   476  				},
   477  			},
   478  			expect: map[string]map[string]bool{
   479  				"role-1": map[string]bool{},
   480  			},
   481  		},
   482  	}
   483  
   484  	for _, test := range table {
   485  		got := rolesToMembersMap(test.input)
   486  		if !reflect.DeepEqual(got, test.expect) {
   487  			t.Errorf("got %+v, expected %+v", got, test.expect)
   488  		}
   489  	}
   490  }
   491  
   492  func TestIamMergeBindings(t *testing.T) {
   493  	table := []struct {
   494  		input  []*cloudresourcemanager.Binding
   495  		expect []cloudresourcemanager.Binding
   496  	}{
   497  		{
   498  			input: []*cloudresourcemanager.Binding{
   499  				{
   500  					Role: "role-1",
   501  					Members: []string{
   502  						"member-1",
   503  						"member-2",
   504  					},
   505  				},
   506  				{
   507  					Role: "role-1",
   508  					Members: []string{
   509  						"member-3",
   510  					},
   511  				},
   512  			},
   513  			expect: []cloudresourcemanager.Binding{
   514  				{
   515  					Role: "role-1",
   516  					Members: []string{
   517  						"member-1",
   518  						"member-2",
   519  						"member-3",
   520  					},
   521  				},
   522  			},
   523  		},
   524  		{
   525  			input: []*cloudresourcemanager.Binding{
   526  				{
   527  					Role: "role-1",
   528  					Members: []string{
   529  						"member-3",
   530  						"member-4",
   531  					},
   532  				},
   533  				{
   534  					Role: "role-1",
   535  					Members: []string{
   536  						"member-2",
   537  						"member-1",
   538  					},
   539  				},
   540  				{
   541  					Role: "role-2",
   542  					Members: []string{
   543  						"member-1",
   544  					},
   545  				},
   546  				{
   547  					Role: "role-1",
   548  					Members: []string{
   549  						"member-5",
   550  					},
   551  				},
   552  				{
   553  					Role: "role-3",
   554  					Members: []string{
   555  						"member-1",
   556  					},
   557  				},
   558  				{
   559  					Role: "role-2",
   560  					Members: []string{
   561  						"member-2",
   562  					},
   563  				},
   564  			},
   565  			expect: []cloudresourcemanager.Binding{
   566  				{
   567  					Role: "role-1",
   568  					Members: []string{
   569  						"member-1",
   570  						"member-2",
   571  						"member-3",
   572  						"member-4",
   573  						"member-5",
   574  					},
   575  				},
   576  				{
   577  					Role: "role-2",
   578  					Members: []string{
   579  						"member-1",
   580  						"member-2",
   581  					},
   582  				},
   583  				{
   584  					Role: "role-3",
   585  					Members: []string{
   586  						"member-1",
   587  					},
   588  				},
   589  			},
   590  		},
   591  	}
   592  
   593  	for _, test := range table {
   594  		got := mergeBindings(test.input)
   595  		sort.Sort(sortableBindings(got))
   596  		for i, _ := range got {
   597  			sort.Strings(got[i].Members)
   598  		}
   599  
   600  		if !reflect.DeepEqual(derefBindings(got), test.expect) {
   601  			t.Errorf("\ngot %+v\nexpected %+v", derefBindings(got), test.expect)
   602  		}
   603  	}
   604  }
   605  
   606  func derefBindings(b []*cloudresourcemanager.Binding) []cloudresourcemanager.Binding {
   607  	db := make([]cloudresourcemanager.Binding, len(b))
   608  
   609  	for i, v := range b {
   610  		db[i] = *v
   611  		sort.Strings(db[i].Members)
   612  	}
   613  	return db
   614  }
   615  
   616  // Confirm that a project has an IAM policy with at least 1 binding
   617  func testAccGoogleProjectExistingPolicy(pid string) resource.TestCheckFunc {
   618  	return func(s *terraform.State) error {
   619  		c := testAccProvider.Meta().(*Config)
   620  		var err error
   621  		originalPolicy, err = getProjectIamPolicy(pid, c)
   622  		if err != nil {
   623  			return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err)
   624  		}
   625  		if len(originalPolicy.Bindings) == 0 {
   626  			return fmt.Errorf("Refuse to run test against project with zero IAM Bindings. This is likely an error in the test code that is not properly identifying the IAM policy of a project.")
   627  		}
   628  		return nil
   629  	}
   630  }
   631  
   632  func testAccGoogleProjectAssociatePolicyBasic(pid, name, org string) string {
   633  	return fmt.Sprintf(`
   634  resource "google_project" "acceptance" {
   635      project_id = "%s"
   636      name = "%s"
   637      org_id = "%s"
   638  }
   639  resource "google_project_iam_policy" "acceptance" {
   640      project = "${google_project.acceptance.id}"
   641      policy_data = "${data.google_iam_policy.admin.policy_data}"
   642  }
   643  data "google_iam_policy" "admin" {
   644    binding {
   645      role = "roles/storage.objectViewer"
   646      members = [
   647        "user:evanbrown@google.com",
   648      ]
   649    }
   650    binding {
   651      role = "roles/compute.instanceAdmin"
   652      members = [
   653        "user:evanbrown@google.com",
   654        "user:evandbrown@gmail.com",
   655      ]
   656    }
   657  }
   658  `, pid, name, org)
   659  }
   660  
   661  func testAccGoogleProject_create(pid, name, org string) string {
   662  	return fmt.Sprintf(`
   663  resource "google_project" "acceptance" {
   664      project_id = "%s"
   665      name = "%s"
   666      org_id = "%s"
   667  }`, pid, name, org)
   668  }
   669  
   670  func testAccGoogleProject_createBilling(pid, name, org, billing string) string {
   671  	return fmt.Sprintf(`
   672  resource "google_project" "acceptance" {
   673      project_id = "%s"
   674      name = "%s"
   675      org_id = "%s"
   676      billing_account = "%s"
   677  }`, pid, name, org, billing)
   678  }
   679  
   680  func testAccGoogleProjectAssociatePolicyExpanded(pid, name, org string) string {
   681  	return fmt.Sprintf(`
   682  resource "google_project" "acceptance" {
   683      project_id = "%s"
   684      name = "%s"
   685      org_id = "%s"
   686  }
   687  resource "google_project_iam_policy" "acceptance" {
   688      project = "${google_project.acceptance.id}"
   689      policy_data = "${data.google_iam_policy.expanded.policy_data}"
   690      authoritative = false
   691  }
   692  data "google_iam_policy" "expanded" {
   693      binding {
   694          role = "roles/viewer"
   695          members = [
   696              "user:paddy@carvers.co",
   697          ]
   698      }
   699      
   700      binding {
   701          role = "roles/viewer"
   702          members = [
   703              "user:paddy@hashicorp.com",
   704          ]
   705      }
   706  }`, pid, name, org)
   707  }