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 }