From 038bdc163d96af96d55df428e7a15cd3aaedb20c Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Tue, 9 Jan 2024 17:37:45 -0600 Subject: [PATCH 1/5] Feat: add new OIDC credentials creation and assigment (gcp resource) --- client/cloud_credentials.go | 6 +- env0/credentials.go | 2 + env0/data_aws_oidc_credentials_test.go | 119 --------- env0/data_azure_oidc_credentials_test.go | 119 --------- env0/data_oidc_credentials_test.go | 128 ++++++++++ env0/provider.go | 4 +- env0/resource_gcp_oidc_credentials.go | 89 +++++++ env0/resource_gcp_oidc_credentials_test.go | 229 ++++++++++++++++++ .../env0_aws_oidc_credentials/data-source.tf | 4 - .../data-source.tf | 14 ++ .../env0_gcp_oidc_credentials/data-source.tf | 15 ++ .../env0_gcp_oidc_credentials/import.sh | 2 + .../env0_gcp_oidc_credentials/resource.tf | 12 + tests/integration/015_gcp_credentials/main.tf | 13 + 14 files changed, 511 insertions(+), 245 deletions(-) delete mode 100644 env0/data_aws_oidc_credentials_test.go delete mode 100644 env0/data_azure_oidc_credentials_test.go create mode 100644 env0/data_oidc_credentials_test.go create mode 100644 env0/resource_gcp_oidc_credentials.go create mode 100644 env0/resource_gcp_oidc_credentials_test.go create mode 100644 examples/data-sources/env0_azure_oidc_credentials/data-source.tf create mode 100644 examples/data-sources/env0_gcp_oidc_credentials/data-source.tf create mode 100644 examples/resources/env0_gcp_oidc_credentials/import.sh create mode 100644 examples/resources/env0_gcp_oidc_credentials/resource.tf diff --git a/client/cloud_credentials.go b/client/cloud_credentials.go index e2d93247..c6ca75a4 100644 --- a/client/cloud_credentials.go +++ b/client/cloud_credentials.go @@ -75,8 +75,9 @@ type GcpCredentialsCreatePayload struct { } type GcpCredentialsValuePayload struct { - ProjectId string `json:"projectId"` - ServiceAccountKey string `json:"serviceAccountKey"` + ProjectId string `json:"projectId,omitempty"` + ServiceAccountKey string `json:"serviceAccountKey,omitempty"` + CredentialConfigurationFileContent string `json:"credentialConfigurationFileContent"` } func (c *GoogleCostCredentialsCreatePayload) SetOrganizationId(organizationId string) { @@ -102,6 +103,7 @@ const ( AwsOidcCredentialsType AwsCredentialsType = "AWS_OIDC" GoogleCostCredentialsType GcpCredentialsType = "GCP_CREDENTIALS" GcpServiceAccountCredentialsType GcpCredentialsType = "GCP_SERVICE_ACCOUNT_FOR_DEPLOYMENT" + GcpOidcCredentialsType GcpCredentialsType = "GCP_OIDC" AzureCostCredentialsType AzureCredentialsType = "AZURE_CREDENTIALS" AzureServicePrincipalCredentialsType AzureCredentialsType = "AZURE_SERVICE_PRINCIPAL_FOR_DEPLOYMENT" AzureOidcCredentialsType AzureCredentialsType = "AZURE_OIDC" diff --git a/env0/credentials.go b/env0/credentials.go index 47d94037..4e78c93e 100644 --- a/env0/credentials.go +++ b/env0/credentials.go @@ -22,6 +22,7 @@ const ( AZURE_COST_TYPE CloudType = "azure_cost" AZURE_OIDC_TYPE CloudType = "azure_oidc" GCP_TYPE CloudType = "gcp" + GCP_OIDC_TYPE CloudType = "gcp_oidc" GCP_COST_TYPE CloudType = "google_cost" ) @@ -34,6 +35,7 @@ var credentialsTypeToPrefixList map[CloudType][]string = map[CloudType][]string{ AZURE_OIDC_TYPE: {string(client.AzureOidcCredentialsType)}, GCP_TYPE: {string(client.GcpServiceAccountCredentialsType)}, GCP_COST_TYPE: {string(client.GoogleCostCredentialsType)}, + GCP_OIDC_TYPE: {string(client.GcpOidcCredentialsType)}, } func getCredentialsByName(name string, prefixList []string, meta interface{}) (client.Credentials, error) { diff --git a/env0/data_aws_oidc_credentials_test.go b/env0/data_aws_oidc_credentials_test.go deleted file mode 100644 index 40f18c13..00000000 --- a/env0/data_aws_oidc_credentials_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package env0 - -import ( - "fmt" - "regexp" - "testing" - - "github.com/env0/terraform-provider-env0/client" - "github.com/env0/terraform-provider-env0/client/http" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAwsOidcCredentialDataSource(t *testing.T) { - credentials := client.Credentials{ - Id: "id0", - Name: "name0", - Type: string(client.AwsOidcCredentialsType), - } - - credentialsOther1 := client.Credentials{ - Id: "id1", - Name: "name1", - Type: string(client.AwsOidcCredentialsType), - } - - credentialsOther2 := client.Credentials{ - Id: "id2", - Name: "name2", - Type: string(client.AwsAssumedRoleCredentialsType), - } - - byName := map[string]interface{}{"name": credentials.Name} - byId := map[string]interface{}{"id": credentials.Id} - - resourceType := "env0_aws_oidc_credentials" - resourceName := "test_aws_oidc_credentials" - 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", credentials.Id), - resource.TestCheckResourceAttr(accessor, "name", credentials.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), - }, - }, - } - } - - mockGetCredentials := func(returnValue client.Credentials) func(mockFunc *client.MockApiClientInterface) { - return func(mock *client.MockApiClientInterface) { - mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(returnValue, nil) - } - } - - mockListCredentials := 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(byId), - mockGetCredentials(credentials), - ) - }) - - t.Run("by name", func(t *testing.T) { - runUnitTest(t, - getValidTestCase(byName), - mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2}), - ) - }) - - 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 is returned", func(t *testing.T) { - runUnitTest(t, - getErrorTestCase(byName, "found multiple credentials"), - mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2, credentials}), - ) - }) - - t.Run("Throw error when by name and not found", func(t *testing.T) { - runUnitTest(t, - getErrorTestCase(byName, "not found"), - mockListCredentials([]client.Credentials{credentialsOther1, credentialsOther2}), - ) - }) - - t.Run("Throw error when by id and not found", func(t *testing.T) { - runUnitTest(t, - getErrorTestCase(byId, fmt.Sprintf("id %s not found", credentials.Id)), - func(mock *client.MockApiClientInterface) { - mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(client.Credentials{}, http.NewMockFailedResponseError(404)) - }, - ) - }) -} diff --git a/env0/data_azure_oidc_credentials_test.go b/env0/data_azure_oidc_credentials_test.go deleted file mode 100644 index 3d3165ef..00000000 --- a/env0/data_azure_oidc_credentials_test.go +++ /dev/null @@ -1,119 +0,0 @@ -package env0 - -import ( - "fmt" - "regexp" - "testing" - - "github.com/env0/terraform-provider-env0/client" - "github.com/env0/terraform-provider-env0/client/http" - "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" -) - -func TestAzureOidcCredentialDataSource(t *testing.T) { - credentials := client.Credentials{ - Id: "id0", - Name: "name0", - Type: string(client.AzureOidcCredentialsType), - } - - credentialsOther1 := client.Credentials{ - Id: "id1", - Name: "name1", - Type: string(client.AzureOidcCredentialsType), - } - - credentialsOther2 := client.Credentials{ - Id: "id2", - Name: "name2", - Type: string(client.AzureServicePrincipalCredentialsType), - } - - byName := map[string]interface{}{"name": credentials.Name} - byId := map[string]interface{}{"id": credentials.Id} - - resourceType := "env0_azure_oidc_credentials" - resourceName := "test_azure_oidc_credentials" - 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", credentials.Id), - resource.TestCheckResourceAttr(accessor, "name", credentials.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), - }, - }, - } - } - - mockGetCredentials := func(returnValue client.Credentials) func(mockFunc *client.MockApiClientInterface) { - return func(mock *client.MockApiClientInterface) { - mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(returnValue, nil) - } - } - - mockListCredentials := 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(byId), - mockGetCredentials(credentials), - ) - }) - - t.Run("by name", func(t *testing.T) { - runUnitTest(t, - getValidTestCase(byName), - mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2}), - ) - }) - - 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 is returned", func(t *testing.T) { - runUnitTest(t, - getErrorTestCase(byName, "found multiple credentials"), - mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2, credentials}), - ) - }) - - t.Run("Throw error when by name and not found", func(t *testing.T) { - runUnitTest(t, - getErrorTestCase(byName, "not found"), - mockListCredentials([]client.Credentials{credentialsOther1, credentialsOther2}), - ) - }) - - t.Run("Throw error when by id and not found", func(t *testing.T) { - runUnitTest(t, - getErrorTestCase(byId, fmt.Sprintf("id %s not found", credentials.Id)), - func(mock *client.MockApiClientInterface) { - mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(client.Credentials{}, http.NewMockFailedResponseError(404)) - }, - ) - }) -} diff --git a/env0/data_oidc_credentials_test.go b/env0/data_oidc_credentials_test.go new file mode 100644 index 00000000..04970fd2 --- /dev/null +++ b/env0/data_oidc_credentials_test.go @@ -0,0 +1,128 @@ +package env0 + +import ( + "fmt" + "regexp" + "testing" + + "github.com/env0/terraform-provider-env0/client" + "github.com/env0/terraform-provider-env0/client/http" + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" +) + +func TestOidcCredentialDataSource(t *testing.T) { + tests := [][]string{ + {"env0_aws_oidc_credentials", string(client.AwsOidcCredentialsType)}, + {"env0_azure_oidc_credentials", string(client.AzureOidcCredentialsType)}, + {"env0_gcp_oidc_credentials", string(client.GcpOidcCredentialsType)}, + } + + for _, test := range tests { + credentials := client.Credentials{ + Id: "id0", + Name: "name0", + Type: test[1], + } + + credentialsOther1 := client.Credentials{ + Id: "id1", + Name: "name1", + Type: test[1], + } + + credentialsOther2 := client.Credentials{ + Id: "id2", + Name: "name2", + Type: test[1], + } + + byName := map[string]interface{}{"name": credentials.Name} + byId := map[string]interface{}{"id": credentials.Id} + + resourceType := test[0] + resourceName := "test" + 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", credentials.Id), + resource.TestCheckResourceAttr(accessor, "name", credentials.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), + }, + }, + } + } + + mockGetCredentials := func(returnValue client.Credentials) func(mockFunc *client.MockApiClientInterface) { + return func(mock *client.MockApiClientInterface) { + mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(returnValue, nil) + } + } + + mockListCredentials := 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(byId), + mockGetCredentials(credentials), + ) + }) + + t.Run("by name - "+test[0], func(t *testing.T) { + runUnitTest(t, + getValidTestCase(byName), + mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2}), + ) + }) + + t.Run("throw error when no name or id is supplied - "+test[0], 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 is returned - "+test[0], func(t *testing.T) { + runUnitTest(t, + getErrorTestCase(byName, "found multiple credentials"), + mockListCredentials([]client.Credentials{credentials, credentialsOther1, credentialsOther2, credentials}), + ) + }) + + t.Run("Throw error when by name and not found - "+test[0], func(t *testing.T) { + runUnitTest(t, + getErrorTestCase(byName, "not found"), + mockListCredentials([]client.Credentials{credentialsOther1, credentialsOther2}), + ) + }) + + t.Run("Throw error when by id and not found - "+test[0], func(t *testing.T) { + runUnitTest(t, + getErrorTestCase(byId, fmt.Sprintf("id %s not found", credentials.Id)), + func(mock *client.MockApiClientInterface) { + mock.EXPECT().CloudCredentials(credentials.Id).AnyTimes().Return(client.Credentials{}, http.NewMockFailedResponseError(404)) + }, + ) + }) + } + +} diff --git a/env0/provider.go b/env0/provider.go index e023edf6..2777f527 100644 --- a/env0/provider.go +++ b/env0/provider.go @@ -71,6 +71,7 @@ func Provider(version string) plugin.ProviderFunc { "env0_aws_credentials": dataCredentials(AWS_TYPE), "env0_aws_oidc_credentials": dataOidcCredentials(AWS_OIDC_TYPE), "env0_gcp_credentials": dataCredentials(GCP_TYPE), + "env0_gcp_oidc_credentials": dataOidcCredentials(GCP_OIDC_TYPE), "env0_azure_credentials": dataCredentials(AZURE_TYPE), "env0_azure_oidc_credentials": dataOidcCredentials(AZURE_OIDC_TYPE), "env0_team": dataTeam(), @@ -104,11 +105,12 @@ func Provider(version string) plugin.ProviderFunc { "env0_aws_credentials": resourceAwsCredentials(), "env0_aws_oidc_credentials": resourceAwsOidcCredentials(), "env0_aws_cost_credentials": resourceCostCredentials("aws"), + "env0_azure_credentials": resourceAzureCredentials(), "env0_azure_cost_credentials": resourceCostCredentials("azure"), "env0_azure_oidc_credentials": resourceAzureOidcCredentials(), "env0_gcp_cost_credentials": resourceCostCredentials("google"), "env0_gcp_credentials": resourceGcpCredentials(), - "env0_azure_credentials": resourceAzureCredentials(), + "env0_gcp_oidc_credentials": resourceGcpOidcCredentials(), "env0_template_project_assignment": resourceTemplateProjectAssignment(), "env0_cloud_credentials_project_assignment": resourceCloudCredentialsProjectAssignment(), "env0_cost_credentials_project_assignment": resourceCostCredentialsProjectAssignment(), diff --git a/env0/resource_gcp_oidc_credentials.go b/env0/resource_gcp_oidc_credentials.go new file mode 100644 index 00000000..3623dfd6 --- /dev/null +++ b/env0/resource_gcp_oidc_credentials.go @@ -0,0 +1,89 @@ +package env0 + +import ( + "context" + "fmt" + + "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 resourceGcpOidcCredentials() *schema.Resource { + return &schema.Resource{ + CreateContext: resourceGcpOidcCredentialsCreate, + UpdateContext: resourceGcpOidcCredentialsUpdate, + ReadContext: resourceCredentialsRead(GCP_OIDC_TYPE), + DeleteContext: resourceCredentialsDelete, + + Importer: &schema.ResourceImporter{StateContext: resourceCredentialsImport(GCP_OIDC_TYPE)}, + + Schema: map[string]*schema.Schema{ + "name": { + Type: schema.TypeString, + Description: "name for the oidc credentials", + Required: true, + ForceNew: true, + }, + "credential_configuration_file_content": { + Type: schema.TypeString, + Description: "the JSON content of the JWT configuration file", + Required: true, + }, + }, + } +} + +func gcpOidcCredentialsGetValue(d *schema.ResourceData) (client.GcpCredentialsValuePayload, error) { + value := client.GcpCredentialsValuePayload{} + + if err := readResourceData(&value, d); err != nil { + return value, fmt.Errorf("schema resource data deserialization failed: %w", err) + } + + return value, nil +} + +func resourceGcpOidcCredentialsCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + value, err := gcpOidcCredentialsGetValue(d) + if err != nil { + return diag.FromErr(err) + } + + request := client.GcpCredentialsCreatePayload{ + Name: d.Get("name").(string), + Value: value, + Type: client.GcpOidcCredentialsType, + } + + credentials, err := apiClient.CredentialsCreate(&request) + if err != nil { + return diag.Errorf("could not create gcp oidc credentials: %v", err) + } + + d.SetId(credentials.Id) + + return nil +} + +func resourceGcpOidcCredentialsUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { + apiClient := meta.(client.ApiClientInterface) + + value, err := gcpOidcCredentialsGetValue(d) + if err != nil { + return diag.FromErr(err) + } + + request := client.GcpCredentialsCreatePayload{ + Value: value, + Type: client.GcpOidcCredentialsType, + } + + if _, err := apiClient.CredentialsUpdate(d.Id(), &request); err != nil { + return diag.Errorf("could not update gcp oidc credentials: %s %v", d.Id(), err) + } + + return nil +} diff --git a/env0/resource_gcp_oidc_credentials_test.go b/env0/resource_gcp_oidc_credentials_test.go new file mode 100644 index 00000000..425c62bc --- /dev/null +++ b/env0/resource_gcp_oidc_credentials_test.go @@ -0,0 +1,229 @@ +package env0 + +import ( + "fmt" + "regexp" + "testing" + + "github.com/env0/terraform-provider-env0/client" + "github.com/env0/terraform-provider-env0/client/http" + + "github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource" + "go.uber.org/mock/gomock" +) + +func TestUnitGcpOidcCredentialsResource(t *testing.T) { + resourceType := "env0_gcp_oidc_credentials" + resourceName := "test" + resourceNameImport := resourceType + "." + resourceName + accessor := resourceAccessor(resourceType, resourceName) + + gcpCredentialsResource := map[string]interface{}{ + "name": "test", + "credential_configuration_file_content": "content1", + } + + updatedGcpCredentialsResource := map[string]interface{}{ + "name": "test", + "credential_configuration_file_content": "content2", + } + + createPayload := client.GcpCredentialsCreatePayload{ + Name: gcpCredentialsResource["name"].(string), + Value: client.GcpCredentialsValuePayload{ + CredentialConfigurationFileContent: gcpCredentialsResource["credential_configuration_file_content"].(string), + }, + Type: client.GcpOidcCredentialsType, + } + + updatePayload := client.GcpCredentialsCreatePayload{ + Value: client.GcpCredentialsValuePayload{ + CredentialConfigurationFileContent: updatedGcpCredentialsResource["credential_configuration_file_content"].(string), + }, + Type: client.GcpOidcCredentialsType, + } + + returnValues := client.Credentials{ + Id: "f595c4b6-0a24-4c22-89f7-7030045de30f", + Name: "test", + OrganizationId: "id", + Type: string(client.GcpOidcCredentialsType), + } + + otherTypeReturnValues := client.Credentials{ + Id: "f595c4b6-0a24-4c22-89f7-7030045de30a", + Name: "test", + OrganizationId: "id", + Type: "AWS_....", + } + + updateReturnValues := client.Credentials{ + Id: returnValues.Id, + Name: returnValues.Name, + OrganizationId: "id", + Type: string(client.GcpOidcCredentialsType), + } + + testCaseForCreateAndUpdate := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, gcpCredentialsResource), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", returnValues.Id), + resource.TestCheckResourceAttr(accessor, "name", gcpCredentialsResource["name"].(string)), + resource.TestCheckResourceAttr(accessor, "credential_configuration_file_content", gcpCredentialsResource["credential_configuration_file_content"].(string)), + ), + }, + { + Config: resourceConfigCreate(resourceType, resourceName, updatedGcpCredentialsResource), + Check: resource.ComposeAggregateTestCheckFunc( + resource.TestCheckResourceAttr(accessor, "id", updateReturnValues.Id), + resource.TestCheckResourceAttr(accessor, "name", updatedGcpCredentialsResource["name"].(string)), + resource.TestCheckResourceAttr(accessor, "credential_configuration_file_content", updatedGcpCredentialsResource["credential_configuration_file_content"].(string)), + ), + }, + }, + } + + t.Run("create and update", func(t *testing.T) { + runUnitTest(t, testCaseForCreateAndUpdate, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().CredentialsCreate(&createPayload).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(2).Return(returnValues, nil), + mock.EXPECT().CredentialsUpdate(returnValues.Id, &updatePayload).Times(1).Return(updateReturnValues, nil), + mock.EXPECT().CloudCredentials(updateReturnValues.Id).Times(1).Return(updateReturnValues, nil), + mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), + ) + }) + }) + + t.Run("drift", func(t *testing.T) { + stepConfig := resourceConfigCreate(resourceType, resourceName, gcpCredentialsResource) + + createTestCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: stepConfig, + }, + { + Config: stepConfig, + }, + }, + } + + runUnitTest(t, createTestCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().CredentialsCreate(&createPayload).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(1).Return(returnValues, http.NewMockFailedResponseError(404)), + mock.EXPECT().CredentialsCreate(&createPayload).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), + ) + }) + }) + + t.Run("import by name", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, gcpCredentialsResource), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: gcpCredentialsResource["name"].(string), + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"credential_configuration_file_content"}, + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().CredentialsCreate(&createPayload).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentialsList().Times(1).Return([]client.Credentials{otherTypeReturnValues, returnValues}, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), + ) + }) + }) + + t.Run("import by id", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, gcpCredentialsResource), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: returnValues.Id, + ImportStateVerify: true, + ImportStateVerifyIgnore: []string{"credential_configuration_file_content"}, + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().CredentialsCreate(&createPayload).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(3).Return(returnValues, nil), + mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), + ) + }) + }) + + t.Run("import by id not found", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, gcpCredentialsResource), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: otherTypeReturnValues.Id, + ImportStateVerify: true, + ExpectError: regexp.MustCompile("credentials not found"), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().CredentialsCreate(&createPayload).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(otherTypeReturnValues.Id).Times(1).Return(client.Credentials{}, &client.NotFoundError{}), + mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), + ) + }) + }) + + t.Run("import by name not found", func(t *testing.T) { + testCase := resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, gcpCredentialsResource), + }, + { + ResourceName: resourceNameImport, + ImportState: true, + ImportStateId: gcpCredentialsResource["name"].(string), + ImportStateVerify: true, + ExpectError: regexp.MustCompile(fmt.Sprintf("credentials with name %v not found", gcpCredentialsResource["name"].(string))), + }, + }, + } + + runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { + gomock.InOrder( + mock.EXPECT().CredentialsCreate(&createPayload).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentials(returnValues.Id).Times(1).Return(returnValues, nil), + mock.EXPECT().CloudCredentialsList().Times(1).Return([]client.Credentials{otherTypeReturnValues}, nil), + mock.EXPECT().CloudCredentialsDelete(returnValues.Id).Times(1).Return(nil), + ) + }) + }) +} diff --git a/examples/data-sources/env0_aws_oidc_credentials/data-source.tf b/examples/data-sources/env0_aws_oidc_credentials/data-source.tf index 9359c096..febd95fe 100644 --- a/examples/data-sources/env0_aws_oidc_credentials/data-source.tf +++ b/examples/data-sources/env0_aws_oidc_credentials/data-source.tf @@ -10,7 +10,3 @@ data "env0_aws_oidc_credentials" "by_id" { data "env0_aws_oidc_credentials" "by_name" { name = env0_aws_oidc_credentials.example.name } - -output "oidc_sub" { - value = data.env0_aws_oidc_credentials.by_name.oidc_sub -} diff --git a/examples/data-sources/env0_azure_oidc_credentials/data-source.tf b/examples/data-sources/env0_azure_oidc_credentials/data-source.tf new file mode 100644 index 00000000..37c7219e --- /dev/null +++ b/examples/data-sources/env0_azure_oidc_credentials/data-source.tf @@ -0,0 +1,14 @@ +resource "env0_gcp_oidc_credentials" "credentials" { + name = "example" + credential_configuration_file_content = jsonencode({ + "key" : "value" + }) +} + +data "env0_gcp_oidc_credentials" "by_id" { + id = env0_gcp_oidc_credentials.example.id +} + +data "env0_gcp_oidc_credentials" "by_name" { + name = env0_gcp_oidc_credentials.example.name +} diff --git a/examples/data-sources/env0_gcp_oidc_credentials/data-source.tf b/examples/data-sources/env0_gcp_oidc_credentials/data-source.tf new file mode 100644 index 00000000..c7239cf0 --- /dev/null +++ b/examples/data-sources/env0_gcp_oidc_credentials/data-source.tf @@ -0,0 +1,15 @@ +resource "env0_azure_oidc_credentials" "credentials" { + name = "example" + tenant_id = "4234-2343-24234234234-42343" + client_id = "fff333-345555-4444" + subscription_id = "f1111-222-2222" +} + + +data "env0_azure_oidc_credentials" "by_id" { + id = env0_azure_oidc_credentials.example.id +} + +data "env0_azure_oidc_credentials" "by_name" { + name = env0_azure_oidc_credentials.example.name +} diff --git a/examples/resources/env0_gcp_oidc_credentials/import.sh b/examples/resources/env0_gcp_oidc_credentials/import.sh new file mode 100644 index 00000000..932970e5 --- /dev/null +++ b/examples/resources/env0_gcp_oidc_credentials/import.sh @@ -0,0 +1,2 @@ +terraform import env0_gcp_oidc_credentials.by_id d31a6b30-5f69-4d24-937c-22322754934e +terraform import env0_gcp_oidc_credentials.by_name "credentials name" diff --git a/examples/resources/env0_gcp_oidc_credentials/resource.tf b/examples/resources/env0_gcp_oidc_credentials/resource.tf new file mode 100644 index 00000000..abb86f97 --- /dev/null +++ b/examples/resources/env0_gcp_oidc_credentials/resource.tf @@ -0,0 +1,12 @@ +resource "env0_aws_oidc_credentials" "credentials" { + name = "example" + role_arn = "arn::role::34" + duration = 3600 +} + +resource "env0_gcp_oidc_credentials" "credentials" { + name = "example" + credential_configuration_file_content = jsonencode({ + "key" : "value" + }) +} diff --git a/tests/integration/015_gcp_credentials/main.tf b/tests/integration/015_gcp_credentials/main.tf index ac81e1de..08d5c7f6 100644 --- a/tests/integration/015_gcp_credentials/main.tf +++ b/tests/integration/015_gcp_credentials/main.tf @@ -25,6 +25,19 @@ data "env0_gcp_credentials" "gcp_cred_with_project_id" { name = env0_gcp_credentials.gcp_cred_with_project_id.name } +resource "env0_gcp_oidc_credentials" "gcp_credentials" { + name = "test azure oidc credentials ${random_string.random.result}" + credential_configuration_file_content = jsonencode({ + "key" : "value" + }) +} + +data "env0_gcp_oidc_credentials" "gcp_credentials" { + name = "test azure oidc credentials ${random_string.random.result}" + depends_on = [env0_gcp_oidc_credentials.oidc_credentials] +} + + output "gcp_cred_name" { value = data.env0_gcp_credentials.gcp_cred.name From 310c6ee1abff191ef8023da877122b57e35e2fc7 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Tue, 9 Jan 2024 17:40:24 -0600 Subject: [PATCH 2/5] added missing omitempty --- client/cloud_credentials.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/cloud_credentials.go b/client/cloud_credentials.go index c6ca75a4..f240c929 100644 --- a/client/cloud_credentials.go +++ b/client/cloud_credentials.go @@ -77,7 +77,7 @@ type GcpCredentialsCreatePayload struct { type GcpCredentialsValuePayload struct { ProjectId string `json:"projectId,omitempty"` ServiceAccountKey string `json:"serviceAccountKey,omitempty"` - CredentialConfigurationFileContent string `json:"credentialConfigurationFileContent"` + CredentialConfigurationFileContent string `json:"credentialConfigurationFileContent,omitempty"` } func (c *GoogleCostCredentialsCreatePayload) SetOrganizationId(organizationId string) { From e7b3412bfb48ef349f65ca699a97f3a9fabe8844 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Tue, 9 Jan 2024 17:43:57 -0600 Subject: [PATCH 3/5] Fixed example --- examples/resources/env0_gcp_oidc_credentials/resource.tf | 6 ------ 1 file changed, 6 deletions(-) diff --git a/examples/resources/env0_gcp_oidc_credentials/resource.tf b/examples/resources/env0_gcp_oidc_credentials/resource.tf index abb86f97..36cbb59a 100644 --- a/examples/resources/env0_gcp_oidc_credentials/resource.tf +++ b/examples/resources/env0_gcp_oidc_credentials/resource.tf @@ -1,9 +1,3 @@ -resource "env0_aws_oidc_credentials" "credentials" { - name = "example" - role_arn = "arn::role::34" - duration = 3600 -} - resource "env0_gcp_oidc_credentials" "credentials" { name = "example" credential_configuration_file_content = jsonencode({ From bf97044c47bade277a8fbf74ca2ee0e139351f01 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Tue, 9 Jan 2024 17:46:27 -0600 Subject: [PATCH 4/5] fix test --- tests/integration/015_gcp_credentials/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/integration/015_gcp_credentials/main.tf b/tests/integration/015_gcp_credentials/main.tf index 08d5c7f6..cf9ff933 100644 --- a/tests/integration/015_gcp_credentials/main.tf +++ b/tests/integration/015_gcp_credentials/main.tf @@ -25,7 +25,7 @@ data "env0_gcp_credentials" "gcp_cred_with_project_id" { name = env0_gcp_credentials.gcp_cred_with_project_id.name } -resource "env0_gcp_oidc_credentials" "gcp_credentials" { +resource "env0_gcp_oidc_credentials" "oidc_credentials" { name = "test azure oidc credentials ${random_string.random.result}" credential_configuration_file_content = jsonencode({ "key" : "value" From 91c705cd3de7288b92a42b60159a9247c0f86762 Mon Sep 17 00:00:00 2001 From: Tomer Heber Date: Fri, 12 Jan 2024 10:15:36 -0600 Subject: [PATCH 5/5] fix mixup between examples --- .../data-source.tf | 19 ++++++++++--------- .../env0_gcp_oidc_credentials/data-source.tf | 19 +++++++++---------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/examples/data-sources/env0_azure_oidc_credentials/data-source.tf b/examples/data-sources/env0_azure_oidc_credentials/data-source.tf index 37c7219e..c7239cf0 100644 --- a/examples/data-sources/env0_azure_oidc_credentials/data-source.tf +++ b/examples/data-sources/env0_azure_oidc_credentials/data-source.tf @@ -1,14 +1,15 @@ -resource "env0_gcp_oidc_credentials" "credentials" { - name = "example" - credential_configuration_file_content = jsonencode({ - "key" : "value" - }) +resource "env0_azure_oidc_credentials" "credentials" { + name = "example" + tenant_id = "4234-2343-24234234234-42343" + client_id = "fff333-345555-4444" + subscription_id = "f1111-222-2222" } -data "env0_gcp_oidc_credentials" "by_id" { - id = env0_gcp_oidc_credentials.example.id + +data "env0_azure_oidc_credentials" "by_id" { + id = env0_azure_oidc_credentials.example.id } -data "env0_gcp_oidc_credentials" "by_name" { - name = env0_gcp_oidc_credentials.example.name +data "env0_azure_oidc_credentials" "by_name" { + name = env0_azure_oidc_credentials.example.name } diff --git a/examples/data-sources/env0_gcp_oidc_credentials/data-source.tf b/examples/data-sources/env0_gcp_oidc_credentials/data-source.tf index c7239cf0..37c7219e 100644 --- a/examples/data-sources/env0_gcp_oidc_credentials/data-source.tf +++ b/examples/data-sources/env0_gcp_oidc_credentials/data-source.tf @@ -1,15 +1,14 @@ -resource "env0_azure_oidc_credentials" "credentials" { - name = "example" - tenant_id = "4234-2343-24234234234-42343" - client_id = "fff333-345555-4444" - subscription_id = "f1111-222-2222" +resource "env0_gcp_oidc_credentials" "credentials" { + name = "example" + credential_configuration_file_content = jsonencode({ + "key" : "value" + }) } - -data "env0_azure_oidc_credentials" "by_id" { - id = env0_azure_oidc_credentials.example.id +data "env0_gcp_oidc_credentials" "by_id" { + id = env0_gcp_oidc_credentials.example.id } -data "env0_azure_oidc_credentials" "by_name" { - name = env0_azure_oidc_credentials.example.name +data "env0_gcp_oidc_credentials" "by_name" { + name = env0_gcp_oidc_credentials.example.name }