Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat cloud cost credentials data - 218 #317

Merged
merged 16 commits into from
Apr 17, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 92 additions & 0 deletions env0/data_aws_cost_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package env0

import (
"context"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataAwsCostCredentials() *schema.Resource {
return &schema.Resource{
ReadContext: dataAwsCostCredentialsRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "the name of the credential",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
"id": {
Type: schema.TypeString,
Description: "the id of the credential",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
},
}
}

func dataAwsCostCredentialsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var err diag.Diagnostics
var credentials client.Credentials

id, ok := d.GetOk("id")
if ok {
credentials, err = getAwsCostCredentialsById(id.(string), meta)
if err != nil {
return err
}
} else {
name, ok := d.GetOk("name")
if !ok {
return diag.Errorf("Either 'name' or 'id' must be specified")
}
credentials, err = getAwsCostCredentialsByName(name.(string), meta)
if err != nil {
return err
}
}

d.SetId(credentials.Id)
d.Set("name", credentials.Name)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use the new common writeResourceData instead. Have a look at existing usages of it to get the idea.

Note: this comment is relevant to all other resources

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point ill do it, just a little note, the function you mention based on the name of the field in schema against the field name in the struct , in aws creds we have 'arn' in schema but the struct field is 'RoleArn so in some places like that we cant use those functions, or we can just change the name of the field in struct/schema

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So how did you solve that arn -> RoleArn tranform?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this specific code there is no arn attribute so I do used the function you suggested , but in resource(no data resource) for example I cant use it, another problem in this function is when the value of the filed in the struct is struct itself like value in awscredentialscreate but for this I have a solution, anyway its not enough because the problem we talked above, unless we change the schema field name to role_arn

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok understood.
Regarding your last sentence - I do think we should change it to role_arn so:

  1. api and schema are aligned
  2. We can use this util


return nil
}

func getAwsCostCredentialsByName(name interface{}, meta interface{}) (client.Credentials, diag.Diagnostics) {
apiClient := meta.(client.ApiClientInterface)
credentialsList, err := apiClient.CloudCredentialsList()
if err != nil {
return client.Credentials{}, diag.Errorf("Could not query AWS Cost Credentials by name: %v", err)
}

credentialsByNameAndType := make([]client.Credentials, 0)
for _, candidate := range credentialsList {
if candidate.Name == name.(string) && candidate.Type == string(client.AwsCostCredentialsType) {
credentialsByNameAndType = append(credentialsByNameAndType, candidate)
}
}

if len(credentialsByNameAndType) > 1 {
return client.Credentials{}, diag.Errorf("Found multiple AWS Cost Credentials for name: %s", name)
}
if len(credentialsByNameAndType) == 0 {
return client.Credentials{}, diag.Errorf("Could not find AWS Cost Credentials with name: %s", name)
}
return credentialsByNameAndType[0], nil
}

func getAwsCostCredentialsById(id string, meta interface{}) (client.Credentials, diag.Diagnostics) {
apiClient := meta.(client.ApiClientInterface)
credentials, err := apiClient.CloudCredentials(id)
if credentials.Type != string(client.AwsCostCredentialsType) {
return client.Credentials{}, diag.Errorf("Found credentials which are not AWS Cost Credentials: %v", credentials)
samuel-br marked this conversation as resolved.
Show resolved Hide resolved
}
if err != nil {
return client.Credentials{}, diag.Errorf("Could not query AWS Cost Credentials: %v", err)
}
return credentials, nil
}
112 changes: 112 additions & 0 deletions env0/data_aws_cost_credentials_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package env0

import (
"regexp"
"testing"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
)

func TestUnitAwsCostCredentialsData(t *testing.T) {
awsCred := client.Credentials{
Id: "11111",
Name: "testdata",
OrganizationId: "id",
Type: "AWS_ASSUMED_ROLE",
}

credWithInvalidType := client.Credentials{
Id: awsCred.Id,
Name: awsCred.Name,
OrganizationId: awsCred.OrganizationId,
Type: "Invalid-type",
}

otherAwsCred := client.Credentials{
Id: "22222",
Name: "notTestdata",
OrganizationId: "OtherId",
Type: "AWS_ACCESS_KEYS_FOR_DEPLOYMENT",
}

AwsCredFieldsByName := map[string]interface{}{"name": awsCred.Name}
AwsCredFieldsById := map[string]interface{}{"id": awsCred.Id}

resourceType := "env0_aws_cost_credentials"
resourceName := "testdata"
accessor := dataSourceAccessor(resourceType, resourceName)

getValidTestCase := func(input map[string]interface{}) resource.TestCase {
return resource.TestCase{
Steps: []resource.TestStep{
{
Config: dataSourceConfigCreate(resourceType, resourceName, input),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", awsCred.Id),
resource.TestCheckResourceAttr(accessor, "name", awsCred.Name),
),
},
},
}
}

getErrorTestCase := func(input map[string]interface{}, expectedError string) resource.TestCase {
return resource.TestCase{
Steps: []resource.TestStep{
{
Config: dataSourceConfigCreate(resourceType, resourceName, input),
ExpectError: regexp.MustCompile(expectedError),
},
},
}
}

mockGetAwsCredCall := func(returnValue client.Credentials) func(mockFunc *client.MockApiClientInterface) {
return func(mock *client.MockApiClientInterface) {
mock.EXPECT().CloudCredentials(awsCred.Id).AnyTimes().Return(returnValue, nil)
}
}

mockListAwsCredCall := func(returnValue []client.Credentials) func(mockFunc *client.MockApiClientInterface) {
return func(mock *client.MockApiClientInterface) {
mock.EXPECT().CloudCredentialsList().AnyTimes().Return(returnValue, nil)
}
}

t.Run("By ID", func(t *testing.T) {
runUnitTest(t,
getValidTestCase(AwsCredFieldsById),
mockGetAwsCredCall(awsCred),
)
})

t.Run("By Name", func(t *testing.T) {
runUnitTest(t,
getValidTestCase(AwsCredFieldsByName),
mockListAwsCredCall([]client.Credentials{awsCred, otherAwsCred, credWithInvalidType}),
)
})

t.Run("Throw error when no name or id is supplied", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase(map[string]interface{}{}, "one of `id,name` must be specified"),
func(mock *client.MockApiClientInterface) {},
)
})

t.Run("Throw error when by name and more than one aws-credential exists with the relevant name", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase(AwsCredFieldsByName, "Found multiple AWS Cost Credentials for name: testdata"),
mockListAwsCredCall([]client.Credentials{awsCred, awsCred, awsCred}),
)
})

t.Run("Throw error when by name and no aws-credential found with that name", func(t *testing.T) {
runUnitTest(t,
getErrorTestCase(AwsCredFieldsByName, "Could not find AWS Cost Credentials with name: testdata"),
mockListAwsCredCall([]client.Credentials{otherAwsCred, credWithInvalidType}),
)
})

}
93 changes: 93 additions & 0 deletions env0/data_azure_cost_credentials.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package env0

import (
"context"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func dataAzureCostCredentials() *schema.Resource {
return &schema.Resource{
ReadContext: dataAzureCostCredentialsRead,

Schema: map[string]*schema.Schema{
"name": {
Type: schema.TypeString,
Description: "the name of the credential",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
"id": {
Type: schema.TypeString,
Description: "the id of the credential",
Optional: true,
ExactlyOneOf: []string{"name", "id"},
},
},
}
}

func dataAzureCostCredentialsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
var err diag.Diagnostics
var credentials client.Credentials

id, ok := d.GetOk("id")
if ok {
credentials, err = getAzureCostCredentialsById(id.(string), meta)
if err != nil {
return err
}
} else {
name, _ := d.Get("name").(string) // name must be specified here
credentials, err = getAzureCostCredentialsByName(name, meta)
if err != nil {
return err
}
}

d.SetId(credentials.Id)
d.Set("name", credentials.Name)

return nil
}

func getAzureCostCredentialsByName(name interface{}, meta interface{}) (client.Credentials, diag.Diagnostics) {
apiClient := meta.(client.ApiClientInterface)
credentialsList, err := apiClient.CloudCredentialsList()
if err != nil {
return client.Credentials{}, diag.Errorf("Could not query Azure Credentials by name: %v", err)
}

credentialsByNameAndType := make([]client.Credentials, 0)
for _, candidate := range credentialsList {
if candidate.Name == name.(string) && isValidAzureCostCredentialsType(candidate.Type) {
credentialsByNameAndType = append(credentialsByNameAndType, candidate)
}
}

if len(credentialsByNameAndType) > 1 {
return client.Credentials{}, diag.Errorf("Found multiple Azure cost Credentials for name: %s", name)
}
if len(credentialsByNameAndType) == 0 {
return client.Credentials{}, diag.Errorf("Could not find Azure cost Credentials with name: %s", name)
}
return credentialsByNameAndType[0], nil
}

func getAzureCostCredentialsById(id string, meta interface{}) (client.Credentials, diag.Diagnostics) {
apiClient := meta.(client.ApiClientInterface)
credentials, err := apiClient.CloudCredentials(id)
if !isValidAzureCostCredentialsType(credentials.Type) {
return client.Credentials{}, diag.Errorf("Found credentials which are not Azure Credentials: %v", credentials)
}
if err != nil {
return client.Credentials{}, diag.Errorf("Could not query Azure Credentials: %v", err)
}
return credentials, nil
}

func isValidAzureCostCredentialsType(credentialsType string) bool {
return client.AzureCredentialsType(credentialsType) == client.AzureCostCredentialsType
}
Loading