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 }`