diff --git a/client/api_client.go b/client/api_client.go index b5d2eeda..06cd2b84 100644 --- a/client/api_client.go +++ b/client/api_client.go @@ -42,6 +42,7 @@ type ApiClientInterface interface { SshKeyCreate(payload SshKeyCreatePayload) (*SshKey, error) SshKeyDelete(id string) error CredentialsCreate(request CredentialCreatePayload) (Credentials, error) + CredentialsUpdate(id string, request CredentialCreatePayload) (Credentials, error) CloudCredentials(id string) (Credentials, error) CloudCredentialsList() ([]Credentials, error) CloudCredentialsDelete(id string) error diff --git a/client/api_client_mock.go b/client/api_client_mock.go index 0956c62a..62af3eec 100644 --- a/client/api_client_mock.go +++ b/client/api_client_mock.go @@ -434,6 +434,21 @@ func (mr *MockApiClientInterfaceMockRecorder) CredentialsCreate(arg0 interface{} return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CredentialsCreate", reflect.TypeOf((*MockApiClientInterface)(nil).CredentialsCreate), arg0) } +// CredentialsUpdate mocks base method. +func (m *MockApiClientInterface) CredentialsUpdate(arg0 string, arg1 CredentialCreatePayload) (Credentials, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CredentialsUpdate", arg0, arg1) + ret0, _ := ret[0].(Credentials) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CredentialsUpdate indicates an expected call of CredentialsUpdate. +func (mr *MockApiClientInterfaceMockRecorder) CredentialsUpdate(arg0, arg1 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CredentialsUpdate", reflect.TypeOf((*MockApiClientInterface)(nil).CredentialsUpdate), arg0, arg1) +} + // CustomFlow mocks base method. func (m *MockApiClientInterface) CustomFlow(arg0 string) (*CustomFlow, error) { m.ctrl.T.Helper() diff --git a/client/cloud_credentials.go b/client/cloud_credentials.go index 3fa759c6..636db3a2 100644 --- a/client/cloud_credentials.go +++ b/client/cloud_credentials.go @@ -143,10 +143,27 @@ func (client *ApiClient) CredentialsCreate(request CredentialCreatePayload) (Cre request.SetOrganizationId(organizationId) var result Credentials - err = client.http.Post("/credentials", request, &result) + if err := client.http.Post("/credentials", request, &result); err != nil { + return Credentials{}, err + } + + return result, nil +} + +func (client *ApiClient) CredentialsUpdate(id string, request CredentialCreatePayload) (Credentials, error) { + organizationId, err := client.OrganizationId() if err != nil { return Credentials{}, err } + + request.SetOrganizationId(organizationId) + + var result Credentials + + if err := client.http.Patch("/credentials/"+id, request, &result); err != nil { + return Credentials{}, err + } + return result, nil } diff --git a/client/cloud_credentials_test.go b/client/cloud_credentials_test.go index 8877fc3d..c43f1e3c 100644 --- a/client/cloud_credentials_test.go +++ b/client/cloud_credentials_test.go @@ -115,6 +115,46 @@ var _ = Describe("CloudCredentials", func() { }) }) + Describe("AwsCredentialsUpdate", func() { + BeforeEach(func() { + mockOrganizationIdCall(organizationId) + + payloadValue := AwsCredentialsValuePayload{ + RoleArn: "role", + } + + httpCall = mockHttpClient.EXPECT(). + Patch("/credentials/"+mockCredentials.Id, &AwsCredentialsCreatePayload{ + Name: credentialsName, + OrganizationId: organizationId, + Type: "AWS_ASSUMED_ROLE_FOR_DEPLOYMENT", + Value: payloadValue, + }, + gomock.Any()). + Do(func(path string, request interface{}, response *Credentials) { + *response = mockCredentials + }) + + credentials, _ = apiClient.CredentialsUpdate(credentials.Id, &AwsCredentialsCreatePayload{ + Name: credentialsName, + Value: payloadValue, + Type: "AWS_ASSUMED_ROLE_FOR_DEPLOYMENT", + }) + }) + + It("Should get organization id", func() { + organizationIdCall.Times(1) + }) + + It("Should send PATCH request with params", func() { + httpCall.Times(1) + }) + + It("Should return key", func() { + Expect(credentials).To(Equal(mockCredentials)) + }) + }) + Describe("GcpCredentialsCreate", func() { const gcpRequestType = "GCP_SERVICE_ACCOUNT_FOR_DEPLOYMENT" mockGcpCredentials := mockCredentials diff --git a/env0/resource_cost_credentials.go b/env0/resource_cost_credentials.go index f5350021..a40ee9fa 100644 --- a/env0/resource_cost_credentials.go +++ b/env0/resource_cost_credentials.go @@ -2,7 +2,6 @@ package env0 import ( "context" - "errors" "fmt" "github.com/env0/terraform-provider-env0/client" @@ -15,104 +14,152 @@ const AZURE = "azure" const GOOGLE = "google" func resourceCostCredentials(providerName string) *schema.Resource { - - awsSchema := map[string]*schema.Schema{ - "arn": { - Type: schema.TypeString, - Description: "the aws role arn", - ForceNew: true, - Required: true, - }, + getSchema := func() map[string]*schema.Schema { + switch providerName { + case AWS: + return map[string]*schema.Schema{ + "arn": { + Type: schema.TypeString, + Description: "the aws role arn", + Required: true, + }, + } + case AZURE: + return map[string]*schema.Schema{ + "client_id": { + Type: schema.TypeString, + Description: "the azure client id", + Required: true, + }, + "client_secret": { + Type: schema.TypeString, + Description: "the azure client secret", + Sensitive: true, + Required: true, + }, + "tenant_id": { + Type: schema.TypeString, + Description: "the azure tenant id", + Required: true, + }, + "subscription_id": { + Type: schema.TypeString, + Description: "the azure subscription id", + Required: true, + }, + } + case GOOGLE: + return map[string]*schema.Schema{ + "table_id": { + Type: schema.TypeString, + Description: "the full BigQuery table id of the exported billing data", + Required: true, + }, + "secret": { + Type: schema.TypeString, + Description: "the GCP service account key", + Sensitive: true, + Required: true, + }, + } + default: + panic(fmt.Sprintf("unhandled provider name: %s", providerName)) + } } - azureSchema := map[string]*schema.Schema{ - "client_id": { - Type: schema.TypeString, - Description: "the azure client id", - ForceNew: true, - Required: true, - }, - "client_secret": { - Type: schema.TypeString, - Description: "the azure client secret", - Sensitive: true, - ForceNew: true, - Required: true, - }, - "tenant_id": { - Type: schema.TypeString, - Description: "the azure tenant id", - ForceNew: true, - Required: true, - }, - "subscription_id": { - Type: schema.TypeString, - Description: "the azure subscription id", - ForceNew: true, - Required: true, - }, - } + getPayload := func(d *schema.ResourceData) (client.CredentialCreatePayload, error) { + var payload client.CredentialCreatePayload + var value interface{} + + switch providerName { + case AWS: + payload = &client.AwsCredentialsCreatePayload{ + Type: client.AwsCostCredentialsType, + } + value = &payload.(*client.AwsCredentialsCreatePayload).Value + case AZURE: + payload = &client.AzureCredentialsCreatePayload{ + Type: client.AzureCostCredentialsType, + } + value = &payload.(*client.AzureCredentialsCreatePayload).Value + case GOOGLE: + payload = &client.GoogleCostCredentialsCreatePayload{ + Type: client.GoogleCostCredentialsType, + } + value = &payload.(*client.GoogleCostCredentialsCreatePayload).Value + default: + panic(fmt.Sprintf("unhandled provider name: %s", providerName)) + } - googleSchema := map[string]*schema.Schema{ - "table_id": { - Type: schema.TypeString, - Description: "the full BigQuery table id of the exported billing data", - ForceNew: true, - Required: true, - }, - "secret": { - Type: schema.TypeString, - Description: "the GCP service account key", - Sensitive: true, - ForceNew: true, - Required: true, - }, - } + if err := readResourceData(value, d); err != nil { + return nil, fmt.Errorf("schema resource data deserialization failed: %w", err) + } - schemaMap := map[string]map[string]*schema.Schema{AWS: awsSchema, AZURE: azureSchema, GOOGLE: googleSchema} + if err := readResourceData(payload, d); err != nil { + return nil, fmt.Errorf("schema resource data deserialization failed: %w", err) + } - return &schema.Resource{ - CreateContext: resourceCostCredentialsCreate, - ReadContext: resourceCostCredentialsRead, - DeleteContext: resourceCostCredentialsDelete, - Schema: extendSchema(schemaMap[providerName]), + return payload, nil } -} -func extendSchema(schemaToReadFrom map[string]*schema.Schema) map[string]*schema.Schema { + getResourceCreate := func() func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + payload, err := getPayload(d) + if err != nil { + return diag.FromErr(err) + } + + apiClient := meta.(client.ApiClientInterface) + + res, err := apiClient.CredentialsCreate(payload) + if err != nil { + return diag.FromErr(err) + } + + d.SetId(res.Id) - resultsSchema := map[string]*schema.Schema{ - "name": { - Type: schema.TypeString, - Description: "the name for the credentials", - Required: true, - ForceNew: true, - }, + return nil + } } - for index, element := range schemaToReadFrom { - resultsSchema[index] = element + getResourceUpdate := func() func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + payload, err := getPayload(d) + if err != nil { + return diag.FromErr(err) + } + + apiClient := meta.(client.ApiClientInterface) + + if _, err := apiClient.CredentialsUpdate(d.Id(), payload); err != nil { + return diag.FromErr(err) + } + + return nil + } } - return resultsSchema -} + resourceSchema := getSchema() -func resourceCostCredentialsCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + resourceSchema["name"] = &schema.Schema{ + Type: schema.TypeString, + Description: "the name for the credentials", + Required: true, + } - apiKey, err := sendApiCallToCreateCred(d, meta) - if err != nil { - return diag.Errorf("Cost credential failed: %v", err) + return &schema.Resource{ + CreateContext: getResourceCreate(), + UpdateContext: getResourceUpdate(), + ReadContext: resourceCostCredentialsRead, + DeleteContext: resourceCostCredentialsDelete, + Schema: resourceSchema, } - d.SetId(apiKey.Id) - return nil } func resourceCostCredentialsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { apiClient := meta.(client.ApiClientInterface) - id := d.Id() - _, err := apiClient.CloudCredentials(id) - if err != nil { + if _, err := apiClient.CloudCredentials(d.Id()); err != nil { return ResourceGetFailure(ctx, "cost credentials", d, err) } return nil @@ -128,47 +175,3 @@ func resourceCostCredentialsDelete(ctx context.Context, d *schema.ResourceData, return nil } - -func sendApiCallToCreateCred(d *schema.ResourceData, meta interface{}) (client.Credentials, error) { - apiClient := meta.(client.ApiClientInterface) - _, awsOk := d.GetOk("arn") - _, azureOk := d.GetOk("client_id") - _, googleOk := d.GetOk("table_id") - switch { - case awsOk: - var value client.AwsCredentialsValuePayload - if err := readResourceData(&value, d); err != nil { - return client.Credentials{}, fmt.Errorf("schema resource data deserialization failed: %v", err) - } - - return apiClient.CredentialsCreate(&client.AwsCredentialsCreatePayload{ - Name: d.Get("name").(string), - Type: client.AwsCostCredentialsType, - Value: value, - }) - case azureOk: - var value client.AzureCredentialsValuePayload - if err := readResourceData(&value, d); err != nil { - return client.Credentials{}, fmt.Errorf("schema resource data deserialization failed: %v", err) - } - - return apiClient.CredentialsCreate(&client.AzureCredentialsCreatePayload{ - Name: d.Get("name").(string), - Type: client.AzureCostCredentialsType, - Value: value, - }) - case googleOk: - var value client.GoogleCostCredentialsValuePayload - if err := readResourceData(&value, d); err != nil { - return client.Credentials{}, fmt.Errorf("schema resource data deserialization failed: %v", err) - } - - return apiClient.CredentialsCreate(&client.GoogleCostCredentialsCreatePayload{ - Name: d.Get("name").(string), - Type: client.GoogleCostCredentialsType, - Value: value, - }) - default: - return client.Credentials{}, errors.New("error in schema, no required value defined") - } -} diff --git a/env0/resource_cost_credentials_test.go b/env0/resource_cost_credentials_test.go index 3debc958..e6443a87 100644 --- a/env0/resource_cost_credentials_test.go +++ b/env0/resource_cost_credentials_test.go @@ -50,7 +50,7 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { } updateReturnValues := client.Credentials{ - Id: "id2", + Id: "id", Name: "update", OrganizationId: "id", Type: "AWS_ASSUMED_ROLE", @@ -114,8 +114,7 @@ func TestUnitAwsCostCredentialsResource(t *testing.T) { runUnitTest(t, testCaseForUpdate, func(mock *client.MockApiClientInterface) { gomock.InOrder( mock.EXPECT().CredentialsCreate(&awsCredCreatePayload).Times(1).Return(returnValues, nil), - mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), - mock.EXPECT().CredentialsCreate(&updateAwsCredCreatePayload).Times(1).Return(updateReturnValues, nil), + mock.EXPECT().CredentialsUpdate(returnValues.Id, &updateAwsCredCreatePayload).Times(1).Return(updateReturnValues, nil), ) gomock.InOrder( mock.EXPECT().CloudCredentials(returnValues.Id).Times(2).Return(returnValues, nil), @@ -183,7 +182,7 @@ func TestUnitAzureCostCredentialsResource(t *testing.T) { } updateReturnValues := client.Credentials{ - Id: "id2", + Id: "id", Name: "update", OrganizationId: "id", Type: string(client.AzureCostCredentialsType), @@ -244,8 +243,7 @@ func TestUnitAzureCostCredentialsResource(t *testing.T) { runUnitTest(t, testCaseForUpdate, func(mock *client.MockApiClientInterface) { gomock.InOrder( mock.EXPECT().CredentialsCreate(&azureCredCreatePayload).Times(1).Return(returnValues, nil), - mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), - mock.EXPECT().CredentialsCreate(&updateAzureCredCreatePayload).Times(1).Return(updateReturnValues, nil), + mock.EXPECT().CredentialsUpdate(returnValues.Id, &updateAzureCredCreatePayload).Times(1).Return(updateReturnValues, nil), ) gomock.InOrder( mock.EXPECT().CloudCredentials(returnValues.Id).Times(2).Return(returnValues, nil), @@ -318,7 +316,7 @@ func TestUnitGoogleCostCredentialsResource(t *testing.T) { } updateReturnValues := client.Credentials{ - Id: "id2", + Id: "id", Name: "update", OrganizationId: "id", Type: string(client.GoogleCostCredentialsType), @@ -373,8 +371,7 @@ func TestUnitGoogleCostCredentialsResource(t *testing.T) { runUnitTest(t, testCaseForUpdate, func(mock *client.MockApiClientInterface) { gomock.InOrder( mock.EXPECT().CredentialsCreate(&googleCostCredCreatePayload).Times(1).Return(returnValues, nil), - mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), - mock.EXPECT().CredentialsCreate(&updateGoogleCostCredCreatePayload).Times(1).Return(updateReturnValues, nil), + mock.EXPECT().CredentialsUpdate(returnValues.Id, &updateGoogleCostCredCreatePayload).Times(1).Return(updateReturnValues, nil), ) gomock.InOrder( mock.EXPECT().CloudCredentials(returnValues.Id).Times(2).Return(returnValues, nil),