Skip to content

Commit

Permalink
add IAM conditions support for generated IAM resources (#2633)
Browse files Browse the repository at this point in the history
Merged PR #2633.
  • Loading branch information
danawillow authored and modular-magician committed Nov 11, 2019
1 parent a365cca commit 36f1209
Show file tree
Hide file tree
Showing 8 changed files with 338 additions and 7 deletions.
4 changes: 4 additions & 0 deletions api/resource/iam_policy.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,9 @@ class IamPolicy < Api::Object
# config with the test/example attributes of the IAM resource.
attr_reader :example_config_body

# How the API supports IAM conditions
attr_reader :iam_conditions_request_type

def validate
super

Expand All @@ -71,6 +74,7 @@ def validate
check :allowed_iam_role, type: String, default: 'roles/viewer'
check :parent_resource_attribute, type: String, default: 'id'
check :test_project_name, type: String
check :iam_conditions_request_type, type: Symbol, allowed: %i[REQUEST_BODY QUERY_PARAM]
check(
:example_config_body,
type: String, default: 'templates/terraform/iam/iam_attributes.tf.erb'
Expand Down
2 changes: 1 addition & 1 deletion build/terraform
2 changes: 1 addition & 1 deletion build/terraform-beta
2 changes: 2 additions & 0 deletions products/iap/terraform.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides
fetch_iam_policy_verb: :POST
allowed_iam_role: 'roles/iap.httpsResourceAccessor'
example_config_body: 'templates/terraform/iam/example_config_body/app_engine_version.tf.erb'
iam_conditions_request_type: :REQUEST_BODY
id_format: 'projects/{{project}}/iap_web/appengine-{{appId}}/services/{{service}}/versions/{{versionId}}'
import_format: [
'projects/{{project}}/iap_web/appengine-{{appId}}/services/{{service}}/versions/{{versionId}}',
Expand Down Expand Up @@ -113,6 +114,7 @@ overrides: !ruby/object:Overrides::ResourceOverrides
parent_resource_attribute: 'web_backend_service'
fetch_iam_policy_verb: :POST
allowed_iam_role: 'roles/iap.httpsResourceAccessor'
iam_conditions_request_type: :REQUEST_BODY
id_format: "projects/{{project}}/iap_web/compute/services/{{name}}"
import_format: ["projects/{{project}}/iap_web/compute/services/{{name}}"]
examples:
Expand Down
229 changes: 229 additions & 0 deletions templates/terraform/examples/base_configs/iam_test_file.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,130 @@ func TestAcc<%= resource_name -%>IamPolicyGenerated(t *testing.T) {
})
}

<% unless version == 'ga' || object.iam_policy.iam_conditions_request_type.nil? -%>
func TestAcc<%= resource_name -%>IamBindingGenerated_withCondition(t *testing.T) {
t.Parallel()

<%= lines(compile('templates/terraform/iam/iam_context.go.erb')) -%>

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAcc<%= resource_name -%>IamBinding_withConditionGenerated(context),
},
{
ResourceName: "<%= resource_ns_iam -%>_binding.foo",
ImportStateId: fmt.Sprintf("<%= import_url -%> <%= object.iam_policy.allowed_iam_role -%> %s"<% unless import_qualifiers.empty? -%>, <% end -%><%= import_qualifiers.join(', ') -%>, <%= example.primary_resource_name -%>, context["condition_title"]),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAcc<%= resource_name -%>IamBindingGenerated_withAndWithoutCondition(t *testing.T) {
t.Parallel()

<%= lines(compile('templates/terraform/iam/iam_context.go.erb')) -%>

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAcc<%= resource_name -%>IamBinding_withAndWithoutConditionGenerated(context),
},
{
ResourceName: "<%= resource_ns_iam -%>_binding.foo",
ImportStateId: fmt.Sprintf("<%= import_url -%> <%= object.iam_policy.allowed_iam_role -%>"<% unless import_qualifiers.empty? -%>, <% end -%><%= import_qualifiers.join(', ') -%>, <%= example.primary_resource_name -%>),
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "<%= resource_ns_iam -%>_binding.foo2",
ImportStateId: fmt.Sprintf("<%= import_url -%> <%= object.iam_policy.allowed_iam_role -%> %s"<% unless import_qualifiers.empty? -%>, <% end -%><%= import_qualifiers.join(', ') -%>, <%= example.primary_resource_name -%>, context["condition_title"]),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAcc<%= resource_name -%>IamMemberGenerated_withCondition(t *testing.T) {
t.Parallel()

<%= lines(compile('templates/terraform/iam/iam_context.go.erb')) -%>

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAcc<%= resource_name -%>IamMember_withConditionGenerated(context),
},
{
ResourceName: "<%= resource_ns_iam -%>_member.foo",
ImportStateId: fmt.Sprintf("<%= import_url -%> <%= object.iam_policy.allowed_iam_role -%> user:[email protected] %s"<% unless import_qualifiers.empty? -%>, <% end -%><%= import_qualifiers.join(', ') -%>, <%= example.primary_resource_name -%>, context["condition_title"]),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAcc<%= resource_name -%>IamMemberGenerated_withAndWithoutCondition(t *testing.T) {
t.Parallel()

<%= lines(compile('templates/terraform/iam/iam_context.go.erb')) -%>

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAcc<%= resource_name -%>IamMember_withAndWithoutConditionGenerated(context),
},
{
ResourceName: "<%= resource_ns_iam -%>_member.foo",
ImportStateId: fmt.Sprintf("<%= import_url -%> <%= object.iam_policy.allowed_iam_role -%> user:[email protected]"<% unless import_qualifiers.empty? -%>, <% end -%><%= import_qualifiers.join(', ') -%>, <%= example.primary_resource_name -%>),
ImportState: true,
ImportStateVerify: true,
},
{
ResourceName: "<%= resource_ns_iam -%>_member.foo2",
ImportStateId: fmt.Sprintf("<%= import_url -%> <%= object.iam_policy.allowed_iam_role -%> user:[email protected] %s"<% unless import_qualifiers.empty? -%>, <% end -%><%= import_qualifiers.join(', ') -%>, <%= example.primary_resource_name -%>, context["condition_title"]),
ImportState: true,
ImportStateVerify: true,
},
},
})
}

func TestAcc<%= resource_name -%>IamPolicyGenerated_withCondition(t *testing.T) {
t.Parallel()

<%= lines(compile('templates/terraform/iam/iam_context.go.erb')) -%>

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAcc<%= resource_name -%>IamPolicy_withConditionGenerated(context),
},
{
ResourceName: "<%= resource_ns_iam -%>_policy.foo",
ImportStateId: fmt.Sprintf("<%= import_url -%>"<% unless import_qualifiers.empty? -%>, <% end -%><%= import_qualifiers.join(', ') -%>, <%= example.primary_resource_name -%>),
ImportState: true,
ImportStateVerify: true,
},
},
})
}
<% end -%>

func testAcc<%= resource_name -%>IamMember_basicGenerated(context map[string]interface{}) string {
return Nprintf(`
<%= example.config_test_body -%>
Expand Down Expand Up @@ -173,3 +297,108 @@ resource "<%= resource_ns_iam -%>_binding" "foo" {
}
`, context)
}

<% unless version == 'ga' || object.iam_policy.iam_conditions_request_type.nil? -%>
func testAcc<%= resource_name -%>IamBinding_withConditionGenerated(context map[string]interface{}) string {
return Nprintf(`
<%= example.config_test_body -%>

resource "<%= resource_ns_iam -%>_binding" "foo" {
<%= lines(compile(object.iam_policy.example_config_body)) -%>
role = "%{role}"
members = ["user:[email protected]"]
condition {
title = "%{condition_title}"
description = "Expiring at midnight of 2019-12-31"
expression = "%{condition_expr}"
}
}
`, context)
}

func testAcc<%= resource_name -%>IamBinding_withAndWithoutConditionGenerated(context map[string]interface{}) string {
return Nprintf(`
<%= example.config_test_body -%>

resource "<%= resource_ns_iam -%>_binding" "foo" {
<%= lines(compile(object.iam_policy.example_config_body)) -%>
role = "%{role}"
members = ["user:[email protected]"]
}

resource "<%= resource_ns_iam -%>_binding" "foo2" {
<%= lines(compile(object.iam_policy.example_config_body)) -%>
role = "%{role}"
members = ["user:[email protected]"]
condition {
title = "%{condition_title}"
description = "Expiring at midnight of 2019-12-31"
expression = "%{condition_expr}"
}
}
`, context)
}

func testAcc<%= resource_name -%>IamMember_withConditionGenerated(context map[string]interface{}) string {
return Nprintf(`
<%= example.config_test_body -%>

resource "<%= resource_ns_iam -%>_member" "foo" {
<%= lines(compile(object.iam_policy.example_config_body)) -%>
role = "%{role}"
member = "user:[email protected]"
condition {
title = "%{condition_title}"
description = "Expiring at midnight of 2019-12-31"
expression = "%{condition_expr}"
}
}
`, context)
}

func testAcc<%= resource_name -%>IamMember_withAndWithoutConditionGenerated(context map[string]interface{}) string {
return Nprintf(`
<%= example.config_test_body -%>

resource "<%= resource_ns_iam -%>_member" "foo" {
<%= lines(compile(object.iam_policy.example_config_body)) -%>
role = "%{role}"
member = "user:[email protected]"
}

resource "<%= resource_ns_iam -%>_member" "foo2" {
<%= lines(compile(object.iam_policy.example_config_body)) -%>
role = "%{role}"
member = "user:[email protected]"
condition {
title = "%{condition_title}"
description = "Expiring at midnight of 2019-12-31"
expression = "%{condition_expr}"
}
}
`, context)
}

func testAcc<%= resource_name -%>IamPolicy_withConditionGenerated(context map[string]interface{}) string {
return Nprintf(`
<%= example.config_test_body -%>

data "google_iam_policy" "foo" {
binding {
role = "%{role}"
members = ["user:[email protected]"]
condition {
title = "%{condition_title}"
description = "Expiring at midnight of 2019-12-31"
expression = "%{condition_expr}"
}
}
}

resource "<%= resource_ns_iam -%>_policy" "foo" {
<%= lines(compile(object.iam_policy.example_config_body)) -%>
policy_data = "${data.google_iam_policy.foo.policy_data}"
}
`, context)
}
<% end -%>
4 changes: 4 additions & 0 deletions templates/terraform/iam/iam_context.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,8 @@ context := map[string]interface{}{
"<%= var_name %>": <%= custom_val %>,
<% end -%>
<% end -%>
<% unless version == 'ga' || object.iam_policy.iam_conditions_request_type.nil? -%>
"condition_title": "expires_after_2019_12_31",
"condition_expr": `request.time < timestamp(\"2020-01-01T00:00:00Z\")`,
<% end -%>
}
23 changes: 18 additions & 5 deletions templates/terraform/iam_policy.go.erb
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,6 @@ func <%= resource_name -%>IamUpdaterProducer(d *schema.ResourceData, config *Con
<% end -%>
<% end -%>

d.SetId(u.GetResourceId())

return u, nil
}

Expand Down Expand Up @@ -161,7 +159,7 @@ func <%= resource_name -%>IdParseFunc(d *schema.ResourceData, config *Config) er
<%# Set resource long name in state, this has all the information that we need to identify it -%>
d.Set("<%= resource_params.last.underscore -%>", u.GetResourceId())
<% end -%>
d.SetId(u.GetResourceId())

return nil
}

Expand All @@ -174,8 +172,23 @@ func (u *<%= resource_name -%>IamUpdater) GetResourceIamPolicy() (*cloudresource
return nil, err
}
<% end -%>

policy, err := sendRequest(u.Config, "<%= object.iam_policy.fetch_iam_policy_verb.to_s.upcase -%>", <% if resource_params.include?('project') %>project<% else %>""<% end %>, url, nil)
var obj map[string]interface{}
<% unless version == 'ga' -%>
<% if object.iam_policy.iam_conditions_request_type == :QUERY_PARAM -%>
url, err = addQueryParams(url, map[string]string{"optionsRequestedPolicyVersion": fmt.Sprintf("%d", iamPolicyVersion)})
if err != nil {
return err
}
<% elsif object.iam_policy.iam_conditions_request_type == :REQUEST_BODY -%>
obj = map[string]interface{}{
"options": map[string]interface{}{
"requestedPolicyVersion": iamPolicyVersion,
},
}
<% end -%>
<% end -%>

policy, err := sendRequest(u.Config, "<%= object.iam_policy.fetch_iam_policy_verb.to_s.upcase -%>", <% if resource_params.include?('project') %>project<% else %>""<% end %>, url, obj)
if err != nil {
return nil, errwrap.Wrapf(fmt.Sprintf("Error retrieving IAM policy for %s: {{err}}", u.DescribeResource()), err)
}
Expand Down
Loading

0 comments on commit 36f1209

Please sign in to comment.