2016-11-23 06:55:40 +00:00
package google
import (
"encoding/json"
"fmt"
"log"
2018-12-21 05:36:11 +00:00
"reflect"
2016-11-23 06:55:40 +00:00
"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 {
2018-12-14 20:51:11 +00:00
"project" : {
2016-11-23 06:55:40 +00:00
Type : schema . TypeString ,
2018-10-30 20:39:07 +00:00
Required : true ,
2016-11-23 06:55:40 +00:00
ForceNew : true ,
} ,
2018-12-14 20:51:11 +00:00
"policy_data" : {
2016-11-23 06:55:40 +00:00
Type : schema . TypeString ,
Required : true ,
DiffSuppressFunc : jsonPolicyDiffSuppress ,
} ,
2018-12-14 20:51:11 +00:00
"etag" : {
2016-11-23 06:55:40 +00:00
Type : schema . TypeString ,
Computed : true ,
} ,
2018-12-14 20:51:11 +00:00
"authoritative" : {
2018-10-30 20:39:07 +00:00
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 ,
} ,
2018-12-14 20:51:11 +00:00
"restore_policy" : {
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
} ,
2018-12-14 20:51:11 +00:00
"disable_project" : {
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
}
2018-12-21 05:36:11 +00:00
policyBytes , err := json . Marshal ( & cloudresourcemanager . Policy { Bindings : policy . Bindings , AuditConfigs : policy . AuditConfigs } )
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 ,
2018-12-21 05:36:11 +00:00
& cloudresourcemanager . SetIamPolicyRequest { Policy : policy , UpdateMask : "bindings,etag,auditConfigs" } ) . Do ( )
2016-11-23 06:55:40 +00:00
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
}
if newPolicy . Etag != oldPolicy . Etag {
return false
}
if newPolicy . Version != oldPolicy . Version {
return false
}
2018-12-21 05:36:11 +00:00
if ! compareBindings ( oldPolicy . Bindings , newPolicy . Bindings ) {
2016-11-23 06:55:40 +00:00
return false
}
2018-12-21 05:36:11 +00:00
if ! compareAuditConfigs ( oldPolicy . AuditConfigs , newPolicy . AuditConfigs ) {
return false
}
return true
}
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
}
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 compareAuditConfigs ( a , b [ ] * cloudresourcemanager . AuditConfig ) bool {
a = mergeAuditConfigs ( a )
b = mergeAuditConfigs ( b )
sort . Sort ( sortableAuditConfigs ( a ) )
sort . Sort ( sortableAuditConfigs ( b ) )
if len ( a ) != len ( b ) {
return false
}
for i , v := range a {
if len ( v . AuditLogConfigs ) != len ( b [ i ] . AuditLogConfigs ) {
2016-11-23 06:55:40 +00:00
return false
}
2018-12-21 05:36:11 +00:00
sort . Sort ( sortableAuditLogConfigs ( v . AuditLogConfigs ) )
sort . Sort ( sortableAuditLogConfigs ( b [ i ] . AuditLogConfigs ) )
for x , logConfig := range v . AuditLogConfigs {
if b [ i ] . AuditLogConfigs [ x ] . LogType != logConfig . LogType {
return false
}
sort . Strings ( logConfig . ExemptedMembers )
sort . Strings ( b [ i ] . AuditLogConfigs [ x ] . ExemptedMembers )
if len ( logConfig . ExemptedMembers ) != len ( b [ i ] . AuditLogConfigs [ x ] . ExemptedMembers ) {
2016-11-23 06:55:40 +00:00
return false
}
2018-12-21 05:36:11 +00:00
for pos , exemptedMember := range logConfig . ExemptedMembers {
if b [ i ] . AuditLogConfigs [ x ] . ExemptedMembers [ pos ] != exemptedMember {
return false
}
}
2016-11-23 06:55:40 +00:00
}
}
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
2018-12-21 05:36:11 +00:00
type sortableAuditConfigs [ ] * cloudresourcemanager . AuditConfig
func ( b sortableAuditConfigs ) Len ( ) int {
return len ( b )
}
func ( b sortableAuditConfigs ) Swap ( i , j int ) {
b [ i ] , b [ j ] = b [ j ] , b [ i ]
}
func ( b sortableAuditConfigs ) Less ( i , j int ) bool {
return b [ i ] . Service < b [ j ] . Service
}
type sortableAuditLogConfigs [ ] * cloudresourcemanager . AuditLogConfig
func ( b sortableAuditLogConfigs ) Len ( ) int {
return len ( b )
}
func ( b sortableAuditLogConfigs ) Swap ( i , j int ) {
b [ i ] , b [ j ] = b [ j ] , b [ i ]
}
func ( b sortableAuditLogConfigs ) Less ( i , j int ) bool {
return b [ i ] . LogType < b [ j ] . LogType
}
2018-06-13 19:35:49 +00:00
func getProjectIamPolicyMutexKey ( pid string ) string {
return fmt . Sprintf ( "iam-project-%s" , pid )
}