From 93be30be7de4031e82b6360314504c9fc331e4cb Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Tue, 19 Sep 2023 12:02:41 -0500 Subject: [PATCH 1/2] Feat: add support for Duration for AWS Assume Roles in env0_aws_credentials resource --- client/cloud_credentials.go | 1 + client/cloud_credentials_test.go | 6 ++++-- env0/resource_cost_credentials.go | 6 ++++++ env0/resource_cost_credentials_test.go | 12 +++++++++--- env0/validators.go | 13 +++++++++++++ .../023_cost_credentials_project_assignment/main.tf | 5 +++-- 6 files changed, 36 insertions(+), 7 deletions(-) diff --git a/client/cloud_credentials.go b/client/cloud_credentials.go index 636db3a2..f5d6ab21 100644 --- a/client/cloud_credentials.go +++ b/client/cloud_credentials.go @@ -50,6 +50,7 @@ type AwsCredentialsCreatePayload struct { type AwsCredentialsValuePayload struct { RoleArn string `json:"roleArn" tfschema:"arn"` + Duration int `json:"duration,omitempty"` AccessKeyId string `json:"accessKeyId"` SecretAccessKey string `json:"secretAccessKey"` } diff --git a/client/cloud_credentials_test.go b/client/cloud_credentials_test.go index c43f1e3c..e948f5a3 100644 --- a/client/cloud_credentials_test.go +++ b/client/cloud_credentials_test.go @@ -80,7 +80,8 @@ var _ = Describe("CloudCredentials", func() { mockOrganizationIdCall(organizationId) payloadValue := AwsCredentialsValuePayload{ - RoleArn: "role", + RoleArn: "role", + Duration: 1, } httpCall = mockHttpClient.EXPECT(). @@ -120,7 +121,8 @@ var _ = Describe("CloudCredentials", func() { mockOrganizationIdCall(organizationId) payloadValue := AwsCredentialsValuePayload{ - RoleArn: "role", + RoleArn: "role", + Duration: 1, } httpCall = mockHttpClient.EXPECT(). diff --git a/env0/resource_cost_credentials.go b/env0/resource_cost_credentials.go index a40ee9fa..c1d84fdc 100644 --- a/env0/resource_cost_credentials.go +++ b/env0/resource_cost_credentials.go @@ -23,6 +23,12 @@ func resourceCostCredentials(providerName string) *schema.Resource { Description: "the aws role arn", Required: true, }, + "duration": { + Type: schema.TypeInt, + Description: "the session duration in seconds. If set must be one of the following: 3600 (1h), 7200 (2h), 14400 (4h), 18000 (5h default), 28800 (8h), 43200 (12h)", + Optional: true, + ValidateDiagFunc: NewIntInValidator([]int{3600, 7200, 14400, 18000, 28800, 43200}), + }, } case AZURE: return map[string]*schema.Schema{ diff --git a/env0/resource_cost_credentials_test.go b/env0/resource_cost_credentials_test.go index e6443a87..021b0a0f 100644 --- a/env0/resource_cost_credentials_test.go +++ b/env0/resource_cost_credentials_test.go @@ -2,6 +2,7 @@ package env0 import ( "regexp" + "strconv" "testing" "github.com/env0/terraform-provider-env0/client" @@ -22,8 +23,9 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { } updatedAwsCredentialResource := map[string]interface{}{ - "name": "update", - "arn": "33333", + "name": "update", + "arn": "33333", + "duration": 3600, } awsCredCreatePayload := client.AwsCredentialsCreatePayload{ @@ -37,7 +39,8 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { updateAwsCredCreatePayload := client.AwsCredentialsCreatePayload{ Name: updatedAwsCredentialResource["name"].(string), Value: client.AwsCredentialsValuePayload{ - RoleArn: updatedAwsCredentialResource["arn"].(string), + RoleArn: updatedAwsCredentialResource["arn"].(string), + Duration: updatedAwsCredentialResource["duration"].(int), }, Type: client.AwsCostCredentialsType, } @@ -64,6 +67,7 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "name", awsCredentialResource["name"].(string)), resource.TestCheckResourceAttr(accessor, "arn", awsCredentialResource["arn"].(string)), resource.TestCheckResourceAttr(accessor, "id", "id"), + resource.TestCheckNoResourceAttr(accessor, "duration"), ), }, }, @@ -77,6 +81,7 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "name", awsCredentialResource["name"].(string)), resource.TestCheckResourceAttr(accessor, "arn", awsCredentialResource["arn"].(string)), resource.TestCheckResourceAttr(accessor, "id", returnValues.Id), + resource.TestCheckNoResourceAttr(accessor, "duration"), ), }, { @@ -85,6 +90,7 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "name", updatedAwsCredentialResource["name"].(string)), resource.TestCheckResourceAttr(accessor, "arn", updatedAwsCredentialResource["arn"].(string)), resource.TestCheckResourceAttr(accessor, "id", updateReturnValues.Id), + resource.TestCheckResourceAttr(accessor, "duration", strconv.Itoa(updatedAwsCredentialResource["duration"].(int))), ), }, }, diff --git a/env0/validators.go b/env0/validators.go index 3eb05f50..ecc6509e 100644 --- a/env0/validators.go +++ b/env0/validators.go @@ -91,6 +91,19 @@ func NewStringInValidator(allowedValues []string) schema.SchemaValidateDiagFunc } } +func NewIntInValidator(allowedValues []int) schema.SchemaValidateDiagFunc { + return func(i interface{}, p cty.Path) diag.Diagnostics { + value := i.(int) + for _, allowedValue := range allowedValues { + if value == allowedValue { + return nil + } + } + + return diag.Errorf("must be one of: %s", fmt.Sprint(allowedValues)) + } +} + func NewGreaterThanValidator(greaterThan int) schema.SchemaValidateDiagFunc { return func(i interface{}, p cty.Path) diag.Diagnostics { value := i.(int) diff --git a/tests/integration/023_cost_credentials_project_assignment/main.tf b/tests/integration/023_cost_credentials_project_assignment/main.tf index afcc860c..3425c50e 100644 --- a/tests/integration/023_cost_credentials_project_assignment/main.tf +++ b/tests/integration/023_cost_credentials_project_assignment/main.tf @@ -12,8 +12,9 @@ resource "env0_project" "project" { } resource "env0_aws_cost_credentials" "cost" { - name = "cost-${random_string.random.result}" - arn = "arn" + name = "cost-${random_string.random.result}" + arn = "arn" + duration = 3600 } resource "env0_cost_credentials_project_assignment" "cost_project_assignment" { From c5c91d171bb5ec09292dc8327607a906c93a9de4 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Tue, 26 Sep 2023 19:14:31 -0500 Subject: [PATCH 2/2] updated tests --- env0/resource_cost_credentials_test.go | 30 +++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/env0/resource_cost_credentials_test.go b/env0/resource_cost_credentials_test.go index 021b0a0f..fee66e01 100644 --- a/env0/resource_cost_credentials_test.go +++ b/env0/resource_cost_credentials_test.go @@ -18,8 +18,9 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { accessor := resourceAccessor(resourceType, resourceName) awsCredentialResource := map[string]interface{}{ - "name": "test", - "arn": "11111", + "name": "test", + "arn": "11111", + "duration": 7200, } updatedAwsCredentialResource := map[string]interface{}{ @@ -28,10 +29,17 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { "duration": 3600, } + invalidDurationAwsCredentialResource := map[string]interface{}{ + "name": "update", + "arn": "33333", + "duration": 1234, + } + awsCredCreatePayload := client.AwsCredentialsCreatePayload{ Name: awsCredentialResource["name"].(string), Value: client.AwsCredentialsValuePayload{ - RoleArn: awsCredentialResource["arn"].(string), + RoleArn: awsCredentialResource["arn"].(string), + Duration: awsCredentialResource["duration"].(int), }, Type: client.AwsCostCredentialsType, } @@ -67,7 +75,7 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "name", awsCredentialResource["name"].(string)), resource.TestCheckResourceAttr(accessor, "arn", awsCredentialResource["arn"].(string)), resource.TestCheckResourceAttr(accessor, "id", "id"), - resource.TestCheckNoResourceAttr(accessor, "duration"), + resource.TestCheckResourceAttr(accessor, "duration", strconv.Itoa(awsCredentialResource["duration"].(int))), ), }, }, @@ -81,7 +89,7 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { resource.TestCheckResourceAttr(accessor, "name", awsCredentialResource["name"].(string)), resource.TestCheckResourceAttr(accessor, "arn", awsCredentialResource["arn"].(string)), resource.TestCheckResourceAttr(accessor, "id", returnValues.Id), - resource.TestCheckNoResourceAttr(accessor, "duration"), + resource.TestCheckResourceAttr(accessor, "duration", strconv.Itoa(awsCredentialResource["duration"].(int))), ), }, { @@ -135,6 +143,18 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { }) }) + t.Run("throw error when don't enter duration valid values", func(t *testing.T) { + runUnitTest(t, resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, invalidDurationAwsCredentialResource), + ExpectError: regexp.MustCompile("Error: must be one of"), + }, + }, + }, func(mock *client.MockApiClientInterface) { + }) + }) + } func TestUnitAzureCostCredentialsResource(t *testing.T) {