2016-11-23 06:55:40 +00:00
package google
import (
"encoding/json"
"fmt"
"reflect"
"sort"
"testing"
"github.com/hashicorp/terraform/helper/acctest"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
"google.golang.org/api/cloudresourcemanager/v1"
)
// Test that an IAM policy can be applied to a project
2018-02-23 23:14:24 +00:00
func TestAccProjectIamPolicy_basic ( t * testing . T ) {
2017-10-12 22:07:29 +00:00
t . Parallel ( )
2017-11-20 23:45:51 +00:00
org := getTestOrgFromEnv ( t )
2016-11-23 06:55:40 +00:00
pid := "terraform-" + acctest . RandString ( 10 )
resource . Test ( t , resource . TestCase {
PreCheck : func ( ) { testAccPreCheck ( t ) } ,
Providers : testAccProviders ,
Steps : [ ] resource . TestStep {
// Create a new project
resource . TestStep {
2018-02-23 23:14:24 +00:00
Config : testAccProject_create ( pid , pname , org ) ,
2016-11-23 06:55:40 +00:00
Check : resource . ComposeTestCheckFunc (
2018-02-23 23:14:24 +00:00
testAccProjectExistingPolicy ( pid ) ,
2016-11-23 06:55:40 +00:00
) ,
} ,
// Apply an IAM policy from a data source. The application
// merges policies, so we validate the expected state.
resource . TestStep {
2018-02-23 23:14:24 +00:00
Config : testAccProjectAssociatePolicyBasic ( pid , pname , org ) ,
2016-11-23 06:55:40 +00:00
} ,
2018-08-14 21:17:01 +00:00
resource . TestStep {
ResourceName : "google_project_iam_policy.acceptance" ,
ImportState : true ,
2017-11-06 21:27:20 +00:00
} ,
} ,
} )
}
2017-03-02 22:00:45 +00:00
// Test that a non-collapsed IAM policy doesn't perpetually diff
2018-02-23 23:14:24 +00:00
func TestAccProjectIamPolicy_expanded ( t * testing . T ) {
2017-10-12 22:07:29 +00:00
t . Parallel ( )
2017-11-20 23:45:51 +00:00
org := getTestOrgFromEnv ( t )
2017-03-02 22:00:45 +00:00
pid := "terraform-" + acctest . RandString ( 10 )
resource . Test ( t , resource . TestCase {
PreCheck : func ( ) { testAccPreCheck ( t ) } ,
Providers : testAccProviders ,
Steps : [ ] resource . TestStep {
resource . TestStep {
2018-02-23 23:14:24 +00:00
Config : testAccProjectAssociatePolicyExpanded ( pid , pname , org ) ,
2017-03-02 22:00:45 +00:00
Check : resource . ComposeTestCheckFunc (
testAccCheckGoogleProjectIamPolicyExists ( "google_project_iam_policy.acceptance" , "data.google_iam_policy.expanded" , pid ) ,
) ,
} ,
} ,
} )
}
2017-03-14 06:20:27 +00:00
func getStatePrimaryResource ( s * terraform . State , res , expectedID string ) ( * terraform . InstanceState , error ) {
// Get the project resource
resource , ok := s . RootModule ( ) . Resources [ res ]
if ! ok {
return nil , fmt . Errorf ( "Not found: %s" , res )
}
if resource . Primary . Attributes [ "id" ] != expectedID && expectedID != "" {
return nil , fmt . Errorf ( "Expected project %q to match ID %q in state" , resource . Primary . ID , expectedID )
}
return resource . Primary , nil
}
2016-11-23 06:55:40 +00:00
2017-03-14 06:20:27 +00:00
func getGoogleProjectIamPolicyFromResource ( resource * terraform . InstanceState ) ( cloudresourcemanager . Policy , error ) {
var p cloudresourcemanager . Policy
ps , ok := resource . Attributes [ "policy_data" ]
if ! ok {
return p , fmt . Errorf ( "Resource %q did not have a 'policy_data' attribute. Attributes were %#v" , resource . ID , resource . Attributes )
}
if err := json . Unmarshal ( [ ] byte ( ps ) , & p ) ; err != nil {
return p , fmt . Errorf ( "Could not unmarshal %s:\n: %v" , ps , err )
}
return p , nil
}
2016-11-23 06:55:40 +00:00
2017-03-14 06:20:27 +00:00
func getGoogleProjectIamPolicyFromState ( s * terraform . State , res , expectedID string ) ( cloudresourcemanager . Policy , error ) {
project , err := getStatePrimaryResource ( s , res , expectedID )
if err != nil {
return cloudresourcemanager . Policy { } , err
}
return getGoogleProjectIamPolicyFromResource ( project )
}
func compareBindings ( a , b [ ] * cloudresourcemanager . Binding ) bool {
a = mergeBindings ( a )
b = mergeBindings ( b )
sort . Sort ( sortableBindings ( a ) )
sort . Sort ( sortableBindings ( b ) )
return reflect . DeepEqual ( derefBindings ( a ) , derefBindings ( b ) )
}
func testAccCheckGoogleProjectIamPolicyExists ( projectRes , policyRes , pid string ) resource . TestCheckFunc {
return func ( s * terraform . State ) error {
projectPolicy , err := getGoogleProjectIamPolicyFromState ( s , projectRes , pid )
if err != nil {
return fmt . Errorf ( "Error retrieving IAM policy for project from state: %s" , err )
2016-11-23 06:55:40 +00:00
}
2017-03-14 06:20:27 +00:00
policyPolicy , err := getGoogleProjectIamPolicyFromState ( s , policyRes , "" )
if err != nil {
return fmt . Errorf ( "Error retrieving IAM policy for data_policy from state: %s" , err )
2016-11-23 06:55:40 +00:00
}
// The bindings in both policies should be identical
2017-03-14 06:20:27 +00:00
if ! compareBindings ( projectPolicy . Bindings , policyPolicy . Bindings ) {
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 ) )
2016-11-23 06:55:40 +00:00
}
2017-03-02 22:00:45 +00:00
return nil
}
}
2016-11-23 06:55:40 +00:00
func TestIamMergeBindings ( t * testing . T ) {
table := [ ] struct {
input [ ] * cloudresourcemanager . Binding
expect [ ] cloudresourcemanager . Binding
} {
{
input : [ ] * cloudresourcemanager . Binding {
{
Role : "role-1" ,
Members : [ ] string {
"member-1" ,
"member-2" ,
} ,
} ,
{
Role : "role-1" ,
Members : [ ] string {
"member-3" ,
} ,
} ,
} ,
expect : [ ] cloudresourcemanager . Binding {
{
Role : "role-1" ,
Members : [ ] string {
"member-1" ,
"member-2" ,
"member-3" ,
} ,
} ,
} ,
} ,
{
input : [ ] * cloudresourcemanager . Binding {
{
Role : "role-1" ,
Members : [ ] string {
"member-3" ,
"member-4" ,
} ,
} ,
{
Role : "role-1" ,
Members : [ ] string {
"member-2" ,
"member-1" ,
} ,
} ,
{
Role : "role-2" ,
Members : [ ] string {
"member-1" ,
} ,
} ,
{
Role : "role-1" ,
Members : [ ] string {
"member-5" ,
} ,
} ,
{
Role : "role-3" ,
Members : [ ] string {
"member-1" ,
} ,
} ,
{
Role : "role-2" ,
Members : [ ] string {
"member-2" ,
} ,
} ,
2018-04-16 20:13:09 +00:00
{ Role : "empty-role" , Members : [ ] string { } } ,
2016-11-23 06:55:40 +00:00
} ,
expect : [ ] cloudresourcemanager . Binding {
{
Role : "role-1" ,
Members : [ ] string {
"member-1" ,
"member-2" ,
"member-3" ,
"member-4" ,
"member-5" ,
} ,
} ,
{
Role : "role-2" ,
Members : [ ] string {
"member-1" ,
"member-2" ,
} ,
} ,
{
Role : "role-3" ,
Members : [ ] string {
"member-1" ,
} ,
} ,
} ,
} ,
}
for _ , test := range table {
got := mergeBindings ( test . input )
sort . Sort ( sortableBindings ( got ) )
for i , _ := range got {
sort . Strings ( got [ i ] . Members )
}
if ! reflect . DeepEqual ( derefBindings ( got ) , test . expect ) {
t . Errorf ( "\ngot %+v\nexpected %+v" , derefBindings ( got ) , test . expect )
}
}
}
func derefBindings ( b [ ] * cloudresourcemanager . Binding ) [ ] cloudresourcemanager . Binding {
db := make ( [ ] cloudresourcemanager . Binding , len ( b ) )
for i , v := range b {
db [ i ] = * v
sort . Strings ( db [ i ] . Members )
}
return db
}
// Confirm that a project has an IAM policy with at least 1 binding
2018-02-23 23:14:24 +00:00
func testAccProjectExistingPolicy ( pid string ) resource . TestCheckFunc {
2016-11-23 06:55:40 +00:00
return func ( s * terraform . State ) error {
c := testAccProvider . Meta ( ) . ( * Config )
var err error
originalPolicy , err = getProjectIamPolicy ( pid , c )
if err != nil {
return fmt . Errorf ( "Failed to retrieve IAM Policy for project %q: %s" , pid , err )
}
if len ( originalPolicy . Bindings ) == 0 {
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." )
}
return nil
}
}
2018-02-23 23:14:24 +00:00
func testAccProjectAssociatePolicyBasic ( pid , name , org string ) string {
2016-11-23 06:55:40 +00:00
return fmt . Sprintf ( `
resource "google_project" "acceptance" {
project_id = "%s"
2017-03-14 06:23:32 +00:00
name = "%s"
org_id = "%s"
2016-11-23 06:55:40 +00:00
}
2018-10-30 20:39:07 +00:00
2016-11-23 06:55:40 +00:00
resource "google_project_iam_policy" "acceptance" {
project = "${google_project.acceptance.id}"
policy_data = "${data.google_iam_policy.admin.policy_data}"
}
2018-10-30 20:39:07 +00:00
2016-11-23 06:55:40 +00:00
data "google_iam_policy" "admin" {
binding {
role = "roles/storage.objectViewer"
members = [
"user:evanbrown@google.com" ,
]
}
binding {
role = "roles/compute.instanceAdmin"
members = [
"user:evanbrown@google.com" ,
"user:evandbrown@gmail.com" ,
]
}
}
` , pid , name , org )
}
2018-02-23 23:14:24 +00:00
func testAccProject_create ( pid , name , org string ) string {
2016-11-23 06:55:40 +00:00
return fmt . Sprintf ( `
resource "google_project" "acceptance" {
project_id = "%s"
2017-03-14 06:23:32 +00:00
name = "%s"
org_id = "%s"
2016-11-23 06:55:40 +00:00
} ` , pid , name , org )
}
2017-02-20 17:32:24 +00:00
2018-02-23 23:14:24 +00:00
func testAccProjectAssociatePolicyExpanded ( pid , name , org string ) string {
2017-03-02 22:00:45 +00:00
return fmt . Sprintf ( `
resource "google_project" "acceptance" {
project_id = "%s"
name = "%s"
org_id = "%s"
}
resource "google_project_iam_policy" "acceptance" {
project = "${google_project.acceptance.id}"
policy_data = "${data.google_iam_policy.expanded.policy_data}"
}
2018-10-30 20:39:07 +00:00
2017-03-02 22:00:45 +00:00
data "google_iam_policy" "expanded" {
binding {
role = "roles/viewer"
2017-03-14 06:23:32 +00:00
members = [
"user:paddy@carvers.co" ,
]
2017-03-02 22:00:45 +00:00
}
binding {
role = "roles/viewer"
2017-03-14 06:23:32 +00:00
members = [
"user:paddy@hashicorp.com" ,
]
2017-03-02 22:00:45 +00:00
}
} ` , pid , name , org )
}