2016-11-23 06:55:40 +00:00
package google
import (
"encoding/json"
"fmt"
"log"
"sort"
2017-07-04 02:01:08 +00:00
"github.com/hashicorp/errwrap"
2016-11-23 06:55:40 +00:00
"github.com/hashicorp/terraform/helper/schema"
"google.golang.org/api/cloudresourcemanager/v1"
)
func resourceGoogleProjectIamPolicy ( ) * schema . Resource {
return & schema . Resource {
Create : resourceGoogleProjectIamPolicyCreate ,
Read : resourceGoogleProjectIamPolicyRead ,
Update : resourceGoogleProjectIamPolicyUpdate ,
Delete : resourceGoogleProjectIamPolicyDelete ,
2017-12-11 18:24:53 +00:00
Importer : & schema . ResourceImporter {
2018-08-14 21:17:01 +00:00
State : resourceGoogleProjectIamPolicyImport ,
2017-12-11 18:24:53 +00:00
} ,
2016-11-23 06:55:40 +00:00
Schema : map [ string ] * schema . Schema {
"project" : & schema . Schema {
Type : schema . TypeString ,
2018-10-30 20:39:07 +00:00
Required : true ,
2016-11-23 06:55:40 +00:00
ForceNew : true ,
} ,
"policy_data" : & schema . Schema {
Type : schema . TypeString ,
Required : true ,
DiffSuppressFunc : jsonPolicyDiffSuppress ,
} ,
"etag" : & schema . Schema {
Type : schema . TypeString ,
Computed : true ,
} ,
2018-10-30 20:39:07 +00:00
"authoritative" : & schema . Schema {
Removed : "The authoritative field was removed. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information." ,
Type : schema . TypeBool ,
Optional : true ,
} ,
2016-11-23 06:55:40 +00:00
"restore_policy" : & schema . Schema {
2018-10-30 20:39:07 +00:00
Removed : "This field was removed alongside the authoritative field. To ignore changes not managed by Terraform, use google_project_iam_binding and google_project_iam_member instead. See https://www.terraform.io/docs/providers/google/r/google_project_iam.html for more information." ,
Type : schema . TypeString ,
Computed : true ,
2016-11-23 06:55:40 +00:00
} ,
"disable_project" : & schema . Schema {
2018-10-30 20:39:07 +00:00
Removed : "This field was removed alongside the authoritative field. Use lifecycle.prevent_destroy instead." ,
Type : schema . TypeBool ,
Optional : true ,
2016-11-23 06:55:40 +00:00
} ,
} ,
}
}
func resourceGoogleProjectIamPolicyCreate ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2018-10-30 20:39:07 +00:00
project := d . Get ( "project" ) . ( string )
2018-06-13 19:35:49 +00:00
2018-10-30 20:39:07 +00:00
mutexKey := getProjectIamPolicyMutexKey ( project )
2018-06-13 19:35:49 +00:00
mutexKV . Lock ( mutexKey )
defer mutexKV . Unlock ( mutexKey )
2016-11-23 06:55:40 +00:00
// Get the policy in the template
2018-10-30 20:39:07 +00:00
policy , err := getResourceIamPolicy ( d )
2016-11-23 06:55:40 +00:00
if err != nil {
return fmt . Errorf ( "Could not get valid 'policy_data' from resource: %v" , err )
}
2018-10-30 20:39:07 +00:00
log . Printf ( "[DEBUG] Setting IAM policy for project %q" , project )
err = setProjectIamPolicy ( policy , config , project )
if err != nil {
return err
2016-11-23 06:55:40 +00:00
}
2018-10-30 20:39:07 +00:00
d . SetId ( project )
2016-11-23 06:55:40 +00:00
return resourceGoogleProjectIamPolicyRead ( d , meta )
}
func resourceGoogleProjectIamPolicyRead ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2018-10-30 20:39:07 +00:00
project := d . Get ( "project" ) . ( string )
2016-11-23 06:55:40 +00:00
2018-10-30 20:39:07 +00:00
policy , err := getProjectIamPolicy ( project , config )
2016-11-23 06:55:40 +00:00
if err != nil {
return err
}
// we only marshal the bindings, because only the bindings get set in the config
2018-10-30 20:39:07 +00:00
policyBytes , err := json . Marshal ( & cloudresourcemanager . Policy { Bindings : policy . Bindings } )
2016-11-23 06:55:40 +00:00
if err != nil {
return fmt . Errorf ( "Error marshaling IAM policy: %v" , err )
}
2018-10-30 20:39:07 +00:00
d . Set ( "etag" , policy . Etag )
d . Set ( "policy_data" , string ( policyBytes ) )
d . Set ( "project" , project )
2016-11-23 06:55:40 +00:00
return nil
}
func resourceGoogleProjectIamPolicyUpdate ( d * schema . ResourceData , meta interface { } ) error {
config := meta . ( * Config )
2018-10-30 20:39:07 +00:00
project := d . Get ( "project" ) . ( string )
2016-11-23 06:55:40 +00:00
2018-10-30 20:39:07 +00:00
mutexKey := getProjectIamPolicyMutexKey ( project )
2018-06-13 19:35:49 +00:00
mutexKV . Lock ( mutexKey )
defer mutexKV . Unlock ( mutexKey )
2016-11-23 06:55:40 +00:00
// Get the policy in the template
2018-10-30 20:39:07 +00:00
policy , err := getResourceIamPolicy ( d )
2016-11-23 06:55:40 +00:00
if err != nil {
return fmt . Errorf ( "Could not get valid 'policy_data' from resource: %v" , err )
}
2018-10-30 20:39:07 +00:00
log . Printf ( "[DEBUG] Updating IAM policy for project %q" , project )
err = setProjectIamPolicy ( policy , config , project )
if err != nil {
return fmt . Errorf ( "Error setting project IAM policy: %v" , err )
2016-11-23 06:55:40 +00:00
}
return resourceGoogleProjectIamPolicyRead ( d , meta )
}
func resourceGoogleProjectIamPolicyDelete ( d * schema . ResourceData , meta interface { } ) error {
log . Printf ( "[DEBUG]: Deleting google_project_iam_policy" )
config := meta . ( * Config )
2018-10-30 20:39:07 +00:00
project := d . Get ( "project" ) . ( string )
2016-11-23 06:55:40 +00:00
2018-10-30 20:39:07 +00:00
mutexKey := getProjectIamPolicyMutexKey ( project )
2018-06-13 19:35:49 +00:00
mutexKV . Lock ( mutexKey )
defer mutexKV . Unlock ( mutexKey )
2018-10-30 20:39:07 +00:00
// Get the existing IAM policy from the API so we can repurpose the etag and audit config
ep , err := getProjectIamPolicy ( project , config )
2016-11-23 06:55:40 +00:00
if err != nil {
return fmt . Errorf ( "Error retrieving IAM policy from project API: %v" , err )
}
2018-10-30 20:39:07 +00:00
ep . Bindings = make ( [ ] * cloudresourcemanager . Binding , 0 )
if err = setProjectIamPolicy ( ep , config , project ) ; err != nil {
2016-11-23 06:55:40 +00:00
return fmt . Errorf ( "Error applying IAM policy to project: %v" , err )
}
2018-10-30 20:39:07 +00:00
2016-11-23 06:55:40 +00:00
d . SetId ( "" )
return nil
}
2018-08-14 21:17:01 +00:00
func resourceGoogleProjectIamPolicyImport ( d * schema . ResourceData , meta interface { } ) ( [ ] * schema . ResourceData , error ) {
d . Set ( "project" , d . Id ( ) )
return [ ] * schema . ResourceData { d } , nil
}
2016-11-23 06:55:40 +00:00
func setProjectIamPolicy ( policy * cloudresourcemanager . Policy , config * Config , pid string ) error {
// Apply the policy
pbytes , _ := json . Marshal ( policy )
log . Printf ( "[DEBUG] Setting policy %#v for project: %s" , string ( pbytes ) , pid )
_ , err := config . clientResourceManager . Projects . SetIamPolicy ( pid ,
& cloudresourcemanager . SetIamPolicyRequest { Policy : policy } ) . Do ( )
if err != nil {
2017-09-14 16:30:06 +00:00
return errwrap . Wrapf ( fmt . Sprintf ( "Error applying IAM policy for project %q. Policy is %#v, error is {{err}}" , pid , policy ) , err )
2016-11-23 06:55:40 +00:00
}
return nil
}
// Get a cloudresourcemanager.Policy from a schema.ResourceData
func getResourceIamPolicy ( d * schema . ResourceData ) ( * cloudresourcemanager . Policy , error ) {
ps := d . Get ( "policy_data" ) . ( string )
// The policy string is just a marshaled cloudresourcemanager.Policy.
policy := & cloudresourcemanager . Policy { }
if err := json . Unmarshal ( [ ] byte ( ps ) , policy ) ; err != nil {
return nil , fmt . Errorf ( "Could not unmarshal %s:\n: %v" , ps , err )
}
return policy , nil
}
// Retrieve the existing IAM Policy for a Project
func getProjectIamPolicy ( project string , config * Config ) ( * cloudresourcemanager . Policy , error ) {
p , err := config . clientResourceManager . Projects . GetIamPolicy ( project ,
& cloudresourcemanager . GetIamPolicyRequest { } ) . Do ( )
if err != nil {
return nil , fmt . Errorf ( "Error retrieving IAM policy for project %q: %s" , project , err )
}
return p , nil
}
func jsonPolicyDiffSuppress ( k , old , new string , d * schema . ResourceData ) bool {
var oldPolicy , newPolicy cloudresourcemanager . Policy
if err := json . Unmarshal ( [ ] byte ( old ) , & oldPolicy ) ; err != nil {
log . Printf ( "[ERROR] Could not unmarshal old policy %s: %v" , old , err )
return false
}
if err := json . Unmarshal ( [ ] byte ( new ) , & newPolicy ) ; err != nil {
log . Printf ( "[ERROR] Could not unmarshal new policy %s: %v" , new , err )
return false
}
2017-03-02 22:00:45 +00:00
oldPolicy . Bindings = mergeBindings ( oldPolicy . Bindings )
newPolicy . Bindings = mergeBindings ( newPolicy . Bindings )
2016-11-23 06:55:40 +00:00
if newPolicy . Etag != oldPolicy . Etag {
return false
}
if newPolicy . Version != oldPolicy . Version {
return false
}
if len ( newPolicy . Bindings ) != len ( oldPolicy . Bindings ) {
return false
}
sort . Sort ( sortableBindings ( newPolicy . Bindings ) )
sort . Sort ( sortableBindings ( oldPolicy . Bindings ) )
for pos , newBinding := range newPolicy . Bindings {
oldBinding := oldPolicy . Bindings [ pos ]
if oldBinding . Role != newBinding . Role {
return false
}
if len ( oldBinding . Members ) != len ( newBinding . Members ) {
return false
}
sort . Strings ( oldBinding . Members )
sort . Strings ( newBinding . Members )
for i , newMember := range newBinding . Members {
oldMember := oldBinding . Members [ i ]
if newMember != oldMember {
return false
}
}
}
return true
}
type sortableBindings [ ] * cloudresourcemanager . Binding
func ( b sortableBindings ) Len ( ) int {
return len ( b )
}
func ( b sortableBindings ) Swap ( i , j int ) {
b [ i ] , b [ j ] = b [ j ] , b [ i ]
}
func ( b sortableBindings ) Less ( i , j int ) bool {
return b [ i ] . Role < b [ j ] . Role
}
2018-06-13 19:35:49 +00:00
func getProjectIamPolicyMutexKey ( pid string ) string {
return fmt . Sprintf ( "iam-project-%s" , pid )
}