github.com/vtorhonen/terraform@v0.9.0-beta2.0.20170307220345-5d894e4ffda7/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  func testAccCheckGoogleProjectIamPolicyIsMerged(projectRes, policyRes, pid string) resource.TestCheckFunc {
   258  	return func(s *terraform.State) error {
   259  		// Get the project resource
   260  		project, ok := s.RootModule().Resources[projectRes]
   261  		if !ok {
   262  			return fmt.Errorf("Not found: %s", projectRes)
   263  		}
   264  		// The project ID should match the config's project ID
   265  		if project.Primary.ID != pid {
   266  			return fmt.Errorf("Expected project %q to match ID %q in state", pid, project.Primary.ID)
   267  		}
   268  
   269  		var projectP, policyP cloudresourcemanager.Policy
   270  		// The project should have a policy
   271  		ps, ok := project.Primary.Attributes["policy_data"]
   272  		if !ok {
   273  			return fmt.Errorf("Project resource %q did not have a 'policy_data' attribute. Attributes were %#v", project.Primary.Attributes["id"], project.Primary.Attributes)
   274  		}
   275  		if err := json.Unmarshal([]byte(ps), &projectP); err != nil {
   276  			return fmt.Errorf("Could not unmarshal %s:\n: %v", ps, err)
   277  		}
   278  
   279  		// The data policy resource should have a policy
   280  		policy, ok := s.RootModule().Resources[policyRes]
   281  		if !ok {
   282  			return fmt.Errorf("Not found: %s", policyRes)
   283  		}
   284  		ps, ok = policy.Primary.Attributes["policy_data"]
   285  		if !ok {
   286  			return fmt.Errorf("Data policy resource %q did not have a 'policy_data' attribute. Attributes were %#v", policy.Primary.Attributes["id"], project.Primary.Attributes)
   287  		}
   288  		if err := json.Unmarshal([]byte(ps), &policyP); err != nil {
   289  			return err
   290  		}
   291  
   292  		// The bindings in both policies should be identical
   293  		sort.Sort(sortableBindings(projectP.Bindings))
   294  		sort.Sort(sortableBindings(policyP.Bindings))
   295  		if !reflect.DeepEqual(derefBindings(projectP.Bindings), derefBindings(policyP.Bindings)) {
   296  			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))
   297  		}
   298  
   299  		// Merge the project policy in Terraform state with the policy the project had before the config was applied
   300  		expected := make([]*cloudresourcemanager.Binding, 0)
   301  		expected = append(expected, originalPolicy.Bindings...)
   302  		expected = append(expected, projectP.Bindings...)
   303  		expectedM := mergeBindings(expected)
   304  
   305  		// Retrieve the actual policy from the project
   306  		c := testAccProvider.Meta().(*Config)
   307  		actual, err := getProjectIamPolicy(pid, c)
   308  		if err != nil {
   309  			return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err)
   310  		}
   311  		actualM := mergeBindings(actual.Bindings)
   312  
   313  		sort.Sort(sortableBindings(actualM))
   314  		sort.Sort(sortableBindings(expectedM))
   315  		// The bindings should match, indicating the policy was successfully applied and merged
   316  		if !reflect.DeepEqual(derefBindings(actualM), derefBindings(expectedM)) {
   317  			return fmt.Errorf("Actual and expected project policies do not match: actual policy is %+v, expected policy is  %+v", derefBindings(actualM), derefBindings(expectedM))
   318  		}
   319  
   320  		return nil
   321  	}
   322  }
   323  
   324  func TestIamRolesToMembersBinding(t *testing.T) {
   325  	table := []struct {
   326  		expect []*cloudresourcemanager.Binding
   327  		input  map[string]map[string]bool
   328  	}{
   329  		{
   330  			expect: []*cloudresourcemanager.Binding{
   331  				{
   332  					Role: "role-1",
   333  					Members: []string{
   334  						"member-1",
   335  						"member-2",
   336  					},
   337  				},
   338  			},
   339  			input: map[string]map[string]bool{
   340  				"role-1": map[string]bool{
   341  					"member-1": true,
   342  					"member-2": true,
   343  				},
   344  			},
   345  		},
   346  		{
   347  			expect: []*cloudresourcemanager.Binding{
   348  				{
   349  					Role: "role-1",
   350  					Members: []string{
   351  						"member-1",
   352  						"member-2",
   353  					},
   354  				},
   355  			},
   356  			input: map[string]map[string]bool{
   357  				"role-1": map[string]bool{
   358  					"member-1": true,
   359  					"member-2": true,
   360  				},
   361  			},
   362  		},
   363  		{
   364  			expect: []*cloudresourcemanager.Binding{
   365  				{
   366  					Role:    "role-1",
   367  					Members: []string{},
   368  				},
   369  			},
   370  			input: map[string]map[string]bool{
   371  				"role-1": map[string]bool{},
   372  			},
   373  		},
   374  	}
   375  
   376  	for _, test := range table {
   377  		got := rolesToMembersBinding(test.input)
   378  
   379  		sort.Sort(sortableBindings(got))
   380  		for i, _ := range got {
   381  			sort.Strings(got[i].Members)
   382  		}
   383  
   384  		if !reflect.DeepEqual(derefBindings(got), derefBindings(test.expect)) {
   385  			t.Errorf("got %+v, expected %+v", derefBindings(got), derefBindings(test.expect))
   386  		}
   387  	}
   388  }
   389  func TestIamRolesToMembersMap(t *testing.T) {
   390  	table := []struct {
   391  		input  []*cloudresourcemanager.Binding
   392  		expect map[string]map[string]bool
   393  	}{
   394  		{
   395  			input: []*cloudresourcemanager.Binding{
   396  				{
   397  					Role: "role-1",
   398  					Members: []string{
   399  						"member-1",
   400  						"member-2",
   401  					},
   402  				},
   403  			},
   404  			expect: map[string]map[string]bool{
   405  				"role-1": map[string]bool{
   406  					"member-1": true,
   407  					"member-2": true,
   408  				},
   409  			},
   410  		},
   411  		{
   412  			input: []*cloudresourcemanager.Binding{
   413  				{
   414  					Role: "role-1",
   415  					Members: []string{
   416  						"member-1",
   417  						"member-2",
   418  						"member-1",
   419  						"member-2",
   420  					},
   421  				},
   422  			},
   423  			expect: map[string]map[string]bool{
   424  				"role-1": map[string]bool{
   425  					"member-1": true,
   426  					"member-2": true,
   427  				},
   428  			},
   429  		},
   430  		{
   431  			input: []*cloudresourcemanager.Binding{
   432  				{
   433  					Role: "role-1",
   434  				},
   435  			},
   436  			expect: map[string]map[string]bool{
   437  				"role-1": map[string]bool{},
   438  			},
   439  		},
   440  	}
   441  
   442  	for _, test := range table {
   443  		got := rolesToMembersMap(test.input)
   444  		if !reflect.DeepEqual(got, test.expect) {
   445  			t.Errorf("got %+v, expected %+v", got, test.expect)
   446  		}
   447  	}
   448  }
   449  
   450  func TestIamMergeBindings(t *testing.T) {
   451  	table := []struct {
   452  		input  []*cloudresourcemanager.Binding
   453  		expect []cloudresourcemanager.Binding
   454  	}{
   455  		{
   456  			input: []*cloudresourcemanager.Binding{
   457  				{
   458  					Role: "role-1",
   459  					Members: []string{
   460  						"member-1",
   461  						"member-2",
   462  					},
   463  				},
   464  				{
   465  					Role: "role-1",
   466  					Members: []string{
   467  						"member-3",
   468  					},
   469  				},
   470  			},
   471  			expect: []cloudresourcemanager.Binding{
   472  				{
   473  					Role: "role-1",
   474  					Members: []string{
   475  						"member-1",
   476  						"member-2",
   477  						"member-3",
   478  					},
   479  				},
   480  			},
   481  		},
   482  		{
   483  			input: []*cloudresourcemanager.Binding{
   484  				{
   485  					Role: "role-1",
   486  					Members: []string{
   487  						"member-3",
   488  						"member-4",
   489  					},
   490  				},
   491  				{
   492  					Role: "role-1",
   493  					Members: []string{
   494  						"member-2",
   495  						"member-1",
   496  					},
   497  				},
   498  				{
   499  					Role: "role-2",
   500  					Members: []string{
   501  						"member-1",
   502  					},
   503  				},
   504  				{
   505  					Role: "role-1",
   506  					Members: []string{
   507  						"member-5",
   508  					},
   509  				},
   510  				{
   511  					Role: "role-3",
   512  					Members: []string{
   513  						"member-1",
   514  					},
   515  				},
   516  				{
   517  					Role: "role-2",
   518  					Members: []string{
   519  						"member-2",
   520  					},
   521  				},
   522  			},
   523  			expect: []cloudresourcemanager.Binding{
   524  				{
   525  					Role: "role-1",
   526  					Members: []string{
   527  						"member-1",
   528  						"member-2",
   529  						"member-3",
   530  						"member-4",
   531  						"member-5",
   532  					},
   533  				},
   534  				{
   535  					Role: "role-2",
   536  					Members: []string{
   537  						"member-1",
   538  						"member-2",
   539  					},
   540  				},
   541  				{
   542  					Role: "role-3",
   543  					Members: []string{
   544  						"member-1",
   545  					},
   546  				},
   547  			},
   548  		},
   549  	}
   550  
   551  	for _, test := range table {
   552  		got := mergeBindings(test.input)
   553  		sort.Sort(sortableBindings(got))
   554  		for i, _ := range got {
   555  			sort.Strings(got[i].Members)
   556  		}
   557  
   558  		if !reflect.DeepEqual(derefBindings(got), test.expect) {
   559  			t.Errorf("\ngot %+v\nexpected %+v", derefBindings(got), test.expect)
   560  		}
   561  	}
   562  }
   563  
   564  func derefBindings(b []*cloudresourcemanager.Binding) []cloudresourcemanager.Binding {
   565  	db := make([]cloudresourcemanager.Binding, len(b))
   566  
   567  	for i, v := range b {
   568  		db[i] = *v
   569  		sort.Strings(db[i].Members)
   570  	}
   571  	return db
   572  }
   573  
   574  // Confirm that a project has an IAM policy with at least 1 binding
   575  func testAccGoogleProjectExistingPolicy(pid string) resource.TestCheckFunc {
   576  	return func(s *terraform.State) error {
   577  		c := testAccProvider.Meta().(*Config)
   578  		var err error
   579  		originalPolicy, err = getProjectIamPolicy(pid, c)
   580  		if err != nil {
   581  			return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", pid, err)
   582  		}
   583  		if len(originalPolicy.Bindings) == 0 {
   584  			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.")
   585  		}
   586  		return nil
   587  	}
   588  }
   589  
   590  func testAccGoogleProjectAssociatePolicyBasic(pid, name, org string) string {
   591  	return fmt.Sprintf(`
   592  resource "google_project" "acceptance" {
   593      project_id = "%s"
   594  	name = "%s"
   595  	org_id = "%s"
   596  }
   597  resource "google_project_iam_policy" "acceptance" {
   598      project = "${google_project.acceptance.id}"
   599      policy_data = "${data.google_iam_policy.admin.policy_data}"
   600  }
   601  data "google_iam_policy" "admin" {
   602    binding {
   603      role = "roles/storage.objectViewer"
   604      members = [
   605        "user:evanbrown@google.com",
   606      ]
   607    }
   608    binding {
   609      role = "roles/compute.instanceAdmin"
   610      members = [
   611        "user:evanbrown@google.com",
   612        "user:evandbrown@gmail.com",
   613      ]
   614    }
   615  }
   616  `, pid, name, org)
   617  }
   618  
   619  func testAccGoogleProject_create(pid, name, org string) string {
   620  	return fmt.Sprintf(`
   621  resource "google_project" "acceptance" {
   622      project_id = "%s"
   623  	name = "%s"
   624  	org_id = "%s"
   625  }`, pid, name, org)
   626  }
   627  
   628  func testAccGoogleProject_createBilling(pid, name, org, billing string) string {
   629  	return fmt.Sprintf(`
   630  resource "google_project" "acceptance" {
   631      project_id = "%s"
   632  	name = "%s"
   633  	org_id = "%s"
   634  	billing_account = "%s"
   635  }`, pid, name, org, billing)
   636  }