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 }