github.com/recobe182/terraform@v0.8.5-0.20170117231232-49ab22a935b7/builtin/providers/google/resource_google_project_test.go (about)

     1  package google
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"os"
     7  	"reflect"
     8  	"sort"
     9  	"testing"
    10  
    11  	"github.com/hashicorp/terraform/helper/resource"
    12  	"github.com/hashicorp/terraform/terraform"
    13  	"google.golang.org/api/cloudresourcemanager/v1"
    14  )
    15  
    16  var (
    17  	projectId = multiEnvSearch([]string{
    18  		"GOOGLE_PROJECT",
    19  		"GCLOUD_PROJECT",
    20  		"CLOUDSDK_CORE_PROJECT",
    21  	})
    22  )
    23  
    24  func multiEnvSearch(ks []string) string {
    25  	for _, k := range ks {
    26  		if v := os.Getenv(k); v != "" {
    27  			return v
    28  		}
    29  	}
    30  	return ""
    31  }
    32  
    33  // Test that a Project resource can be created and destroyed
    34  func TestAccGoogleProject_associate(t *testing.T) {
    35  	resource.Test(t, resource.TestCase{
    36  		PreCheck:  func() { testAccPreCheck(t) },
    37  		Providers: testAccProviders,
    38  		Steps: []resource.TestStep{
    39  			resource.TestStep{
    40  				Config: fmt.Sprintf(testAccGoogleProject_basic, projectId),
    41  				Check: resource.ComposeTestCheckFunc(
    42  					testAccCheckGoogleProjectExists("google_project.acceptance"),
    43  				),
    44  			},
    45  		},
    46  	})
    47  }
    48  
    49  // Test that a Project resource can be created, an IAM Policy
    50  // associated with it, and then destroyed
    51  func TestAccGoogleProject_iamPolicy1(t *testing.T) {
    52  	var policy *cloudresourcemanager.Policy
    53  	resource.Test(t, resource.TestCase{
    54  		PreCheck:     func() { testAccPreCheck(t) },
    55  		Providers:    testAccProviders,
    56  		CheckDestroy: testAccCheckGoogleProjectDestroy,
    57  		Steps: []resource.TestStep{
    58  			// First step inventories the project's existing IAM policy
    59  			resource.TestStep{
    60  				Config: fmt.Sprintf(testAccGoogleProject_basic, projectId),
    61  				Check: resource.ComposeTestCheckFunc(
    62  					testAccGoogleProjectExistingPolicy(policy),
    63  				),
    64  			},
    65  			// Second step applies an IAM policy from a data source. The application
    66  			// merges policies, so we validate the expected state.
    67  			resource.TestStep{
    68  				Config: fmt.Sprintf(testAccGoogleProject_policy1, projectId),
    69  				Check: resource.ComposeTestCheckFunc(
    70  					testAccCheckGoogleProjectExists("google_project.acceptance"),
    71  					testAccCheckGoogleProjectIamPolicyIsMerged("google_project.acceptance", "data.google_iam_policy.admin", policy),
    72  				),
    73  			},
    74  			// Finally, remove the custom IAM policy from config and apply, then
    75  			// confirm that the project is in its original state.
    76  			resource.TestStep{
    77  				Config: fmt.Sprintf(testAccGoogleProject_basic, projectId),
    78  			},
    79  		},
    80  	})
    81  }
    82  
    83  func testAccCheckGoogleProjectDestroy(s *terraform.State) error {
    84  	return nil
    85  }
    86  
    87  // Retrieve the existing policy (if any) for a GCP Project
    88  func testAccGoogleProjectExistingPolicy(p *cloudresourcemanager.Policy) resource.TestCheckFunc {
    89  	return func(s *terraform.State) error {
    90  		c := testAccProvider.Meta().(*Config)
    91  		var err error
    92  		p, err = getProjectIamPolicy(projectId, c)
    93  		if err != nil {
    94  			return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", projectId, err)
    95  		}
    96  		if len(p.Bindings) == 0 {
    97  			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.")
    98  		}
    99  		return nil
   100  	}
   101  }
   102  
   103  func testAccCheckGoogleProjectExists(r string) resource.TestCheckFunc {
   104  	return func(s *terraform.State) error {
   105  		rs, ok := s.RootModule().Resources[r]
   106  		if !ok {
   107  			return fmt.Errorf("Not found: %s", r)
   108  		}
   109  
   110  		if rs.Primary.ID == "" {
   111  			return fmt.Errorf("No ID is set")
   112  		}
   113  
   114  		if rs.Primary.ID != projectId {
   115  			return fmt.Errorf("Expected project %q to match ID %q in state", projectId, rs.Primary.ID)
   116  		}
   117  
   118  		return nil
   119  	}
   120  }
   121  
   122  func testAccCheckGoogleProjectIamPolicyIsMerged(projectRes, policyRes string, original *cloudresourcemanager.Policy) resource.TestCheckFunc {
   123  	return func(s *terraform.State) error {
   124  		// Get the project resource
   125  		project, ok := s.RootModule().Resources[projectRes]
   126  		if !ok {
   127  			return fmt.Errorf("Not found: %s", projectRes)
   128  		}
   129  		// The project ID should match the config's project ID
   130  		if project.Primary.ID != projectId {
   131  			return fmt.Errorf("Expected project %q to match ID %q in state", projectId, project.Primary.ID)
   132  		}
   133  
   134  		var projectP, policyP cloudresourcemanager.Policy
   135  		// The project should have a policy
   136  		ps, ok := project.Primary.Attributes["policy_data"]
   137  		if !ok {
   138  			return fmt.Errorf("Project resource %q did not have a 'policy_data' attribute. Attributes were %#v", project.Primary.Attributes["id"], project.Primary.Attributes)
   139  		}
   140  		if err := json.Unmarshal([]byte(ps), &projectP); err != nil {
   141  			return err
   142  		}
   143  
   144  		// The data policy resource should have a policy
   145  		policy, ok := s.RootModule().Resources[policyRes]
   146  		if !ok {
   147  			return fmt.Errorf("Not found: %s", policyRes)
   148  		}
   149  		ps, ok = policy.Primary.Attributes["policy_data"]
   150  		if !ok {
   151  			return fmt.Errorf("Data policy resource %q did not have a 'policy_data' attribute. Attributes were %#v", policy.Primary.Attributes["id"], project.Primary.Attributes)
   152  		}
   153  		if err := json.Unmarshal([]byte(ps), &policyP); err != nil {
   154  			return err
   155  		}
   156  
   157  		// The bindings in both policies should be identical
   158  		if !reflect.DeepEqual(derefBindings(projectP.Bindings), derefBindings(policyP.Bindings)) {
   159  			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))
   160  		}
   161  
   162  		// Merge the project policy in Terrafomr state with the policy the project had before the config was applied
   163  		expected := make([]*cloudresourcemanager.Binding, 0)
   164  		expected = append(expected, original.Bindings...)
   165  		expected = append(expected, projectP.Bindings...)
   166  		expectedM := mergeBindings(expected)
   167  
   168  		// Retrieve the actual policy from the project
   169  		c := testAccProvider.Meta().(*Config)
   170  		actual, err := getProjectIamPolicy(projectId, c)
   171  		if err != nil {
   172  			return fmt.Errorf("Failed to retrieve IAM Policy for project %q: %s", projectId, err)
   173  		}
   174  		actualM := mergeBindings(actual.Bindings)
   175  
   176  		// The bindings should match, indicating the policy was successfully applied and merged
   177  		if !reflect.DeepEqual(derefBindings(actualM), derefBindings(expectedM)) {
   178  			return fmt.Errorf("Actual and expected project policies do not match: actual policy is %+v, expected policy is  %+v", derefBindings(actualM), derefBindings(expectedM))
   179  		}
   180  
   181  		return nil
   182  	}
   183  }
   184  
   185  func TestIamRolesToMembersBinding(t *testing.T) {
   186  	table := []struct {
   187  		expect []*cloudresourcemanager.Binding
   188  		input  map[string]map[string]bool
   189  	}{
   190  		{
   191  			expect: []*cloudresourcemanager.Binding{
   192  				{
   193  					Role: "role-1",
   194  					Members: []string{
   195  						"member-1",
   196  						"member-2",
   197  					},
   198  				},
   199  			},
   200  			input: map[string]map[string]bool{
   201  				"role-1": map[string]bool{
   202  					"member-1": true,
   203  					"member-2": true,
   204  				},
   205  			},
   206  		},
   207  		{
   208  			expect: []*cloudresourcemanager.Binding{
   209  				{
   210  					Role: "role-1",
   211  					Members: []string{
   212  						"member-1",
   213  						"member-2",
   214  					},
   215  				},
   216  			},
   217  			input: map[string]map[string]bool{
   218  				"role-1": map[string]bool{
   219  					"member-1": true,
   220  					"member-2": true,
   221  				},
   222  			},
   223  		},
   224  		{
   225  			expect: []*cloudresourcemanager.Binding{
   226  				{
   227  					Role:    "role-1",
   228  					Members: []string{},
   229  				},
   230  			},
   231  			input: map[string]map[string]bool{
   232  				"role-1": map[string]bool{},
   233  			},
   234  		},
   235  	}
   236  
   237  	for _, test := range table {
   238  		got := rolesToMembersBinding(test.input)
   239  
   240  		sort.Sort(Binding(got))
   241  		for i, _ := range got {
   242  			sort.Strings(got[i].Members)
   243  		}
   244  
   245  		if !reflect.DeepEqual(derefBindings(got), derefBindings(test.expect)) {
   246  			t.Errorf("got %+v, expected %+v", derefBindings(got), derefBindings(test.expect))
   247  		}
   248  	}
   249  }
   250  func TestIamRolesToMembersMap(t *testing.T) {
   251  	table := []struct {
   252  		input  []*cloudresourcemanager.Binding
   253  		expect map[string]map[string]bool
   254  	}{
   255  		{
   256  			input: []*cloudresourcemanager.Binding{
   257  				{
   258  					Role: "role-1",
   259  					Members: []string{
   260  						"member-1",
   261  						"member-2",
   262  					},
   263  				},
   264  			},
   265  			expect: map[string]map[string]bool{
   266  				"role-1": map[string]bool{
   267  					"member-1": true,
   268  					"member-2": true,
   269  				},
   270  			},
   271  		},
   272  		{
   273  			input: []*cloudresourcemanager.Binding{
   274  				{
   275  					Role: "role-1",
   276  					Members: []string{
   277  						"member-1",
   278  						"member-2",
   279  						"member-1",
   280  						"member-2",
   281  					},
   282  				},
   283  			},
   284  			expect: map[string]map[string]bool{
   285  				"role-1": map[string]bool{
   286  					"member-1": true,
   287  					"member-2": true,
   288  				},
   289  			},
   290  		},
   291  		{
   292  			input: []*cloudresourcemanager.Binding{
   293  				{
   294  					Role: "role-1",
   295  				},
   296  			},
   297  			expect: map[string]map[string]bool{
   298  				"role-1": map[string]bool{},
   299  			},
   300  		},
   301  	}
   302  
   303  	for _, test := range table {
   304  		got := rolesToMembersMap(test.input)
   305  		if !reflect.DeepEqual(got, test.expect) {
   306  			t.Errorf("got %+v, expected %+v", got, test.expect)
   307  		}
   308  	}
   309  }
   310  
   311  func TestIamMergeBindings(t *testing.T) {
   312  	table := []struct {
   313  		input  []*cloudresourcemanager.Binding
   314  		expect []cloudresourcemanager.Binding
   315  	}{
   316  		{
   317  			input: []*cloudresourcemanager.Binding{
   318  				{
   319  					Role: "role-1",
   320  					Members: []string{
   321  						"member-1",
   322  						"member-2",
   323  					},
   324  				},
   325  				{
   326  					Role: "role-1",
   327  					Members: []string{
   328  						"member-3",
   329  					},
   330  				},
   331  			},
   332  			expect: []cloudresourcemanager.Binding{
   333  				{
   334  					Role: "role-1",
   335  					Members: []string{
   336  						"member-1",
   337  						"member-2",
   338  						"member-3",
   339  					},
   340  				},
   341  			},
   342  		},
   343  		{
   344  			input: []*cloudresourcemanager.Binding{
   345  				{
   346  					Role: "role-1",
   347  					Members: []string{
   348  						"member-3",
   349  						"member-4",
   350  					},
   351  				},
   352  				{
   353  					Role: "role-1",
   354  					Members: []string{
   355  						"member-2",
   356  						"member-1",
   357  					},
   358  				},
   359  				{
   360  					Role: "role-2",
   361  					Members: []string{
   362  						"member-1",
   363  					},
   364  				},
   365  				{
   366  					Role: "role-1",
   367  					Members: []string{
   368  						"member-5",
   369  					},
   370  				},
   371  				{
   372  					Role: "role-3",
   373  					Members: []string{
   374  						"member-1",
   375  					},
   376  				},
   377  				{
   378  					Role: "role-2",
   379  					Members: []string{
   380  						"member-2",
   381  					},
   382  				},
   383  			},
   384  			expect: []cloudresourcemanager.Binding{
   385  				{
   386  					Role: "role-1",
   387  					Members: []string{
   388  						"member-1",
   389  						"member-2",
   390  						"member-3",
   391  						"member-4",
   392  						"member-5",
   393  					},
   394  				},
   395  				{
   396  					Role: "role-2",
   397  					Members: []string{
   398  						"member-1",
   399  						"member-2",
   400  					},
   401  				},
   402  				{
   403  					Role: "role-3",
   404  					Members: []string{
   405  						"member-1",
   406  					},
   407  				},
   408  			},
   409  		},
   410  	}
   411  
   412  	for _, test := range table {
   413  		got := mergeBindings(test.input)
   414  		sort.Sort(Binding(got))
   415  		for i, _ := range got {
   416  			sort.Strings(got[i].Members)
   417  		}
   418  
   419  		if !reflect.DeepEqual(derefBindings(got), test.expect) {
   420  			t.Errorf("\ngot %+v\nexpected %+v", derefBindings(got), test.expect)
   421  		}
   422  	}
   423  }
   424  
   425  func derefBindings(b []*cloudresourcemanager.Binding) []cloudresourcemanager.Binding {
   426  	db := make([]cloudresourcemanager.Binding, len(b))
   427  
   428  	for i, v := range b {
   429  		db[i] = *v
   430  	}
   431  	return db
   432  }
   433  
   434  type Binding []*cloudresourcemanager.Binding
   435  
   436  func (b Binding) Len() int {
   437  	return len(b)
   438  }
   439  func (b Binding) Swap(i, j int) {
   440  	b[i], b[j] = b[j], b[i]
   441  }
   442  func (b Binding) Less(i, j int) bool {
   443  	return b[i].Role < b[j].Role
   444  }
   445  
   446  var testAccGoogleProject_basic = `
   447  resource "google_project" "acceptance" {
   448      id = "%v"
   449  }`
   450  
   451  var testAccGoogleProject_policy1 = `
   452  resource "google_project" "acceptance" {
   453      id = "%v"
   454      policy_data = "${data.google_iam_policy.admin.policy_data}"
   455  }
   456  
   457  data "google_iam_policy" "admin" {
   458    binding {
   459      role = "roles/storage.objectViewer"
   460      members = [
   461        "user:evanbrown@google.com",
   462      ]
   463    }
   464    binding {
   465      role = "roles/compute.instanceAdmin"
   466      members = [
   467        "user:evanbrown@google.com",
   468        "user:evandbrown@gmail.com",
   469      ]
   470    }
   471  }`