diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 36494a16..44cb4f1a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,51 +1,55 @@ -name: CI - -on: - pull_request: - types: [opened, synchronize] - -env: - ENV0_API_ENDPOINT: ${{ secrets.ENV0_API_ENDPOINT }} - ENV0_API_KEY: ${{ secrets.TF_PROVIDER_INTEGRATION_TEST_API_KEY }} # API Key for organization 'TF-provider-integration-tests' @ dev - ENV0_API_SECRET: ${{ secrets.TF_PROVIDER_INTEGRATION_TEST_API_SECRET }} - GO_VERSION: "1.21" - TERRAFORM_VERSION: 1.1.7 - -jobs: - unit-tests: - name: Unit Tests - timeout-minutes: 10 - runs-on: ubuntu-20.04 - steps: - - name: Install Go - uses: actions/setup-go@v5 - with: - go-version: ${{ env.GO_VERSION }} - - name: Checkout code - uses: actions/checkout@v4 - - name: Generate mocks - run: | - go install go.uber.org/mock/mockgen@v0.3.0 - go generate client/api_client.go - - name: Go fmt - run: | - ! go fmt ./... | read - - name: Go vet - run: | - ! go vet ./... | read - - name: Go Test - run: go test -v ./... - - # See terraform-provider-env0 README for integration tests prerequisites - integration-tests: - name: Integration Tests - runs-on: ubuntu-20.04 - container: golang:1.21-alpine3.18 - timeout-minutes: 20 - steps: - - name: Install Terraform - run: apk add terraform - - name: Checkout code - uses: actions/checkout@v4 - - name: Run Harness tests - run: go run tests/harness.go +name: CI + +on: + pull_request: + types: [opened, synchronize] + +env: + ENV0_API_ENDPOINT: ${{ secrets.ENV0_API_ENDPOINT }} + ENV0_API_KEY: ${{ secrets.TF_PROVIDER_INTEGRATION_TEST_API_KEY }} # API Key for organization 'TF-provider-integration-tests' @ dev + ENV0_API_SECRET: ${{ secrets.TF_PROVIDER_INTEGRATION_TEST_API_SECRET }} + GO_VERSION: "1.21" + TERRAFORM_VERSION: 1.1.7 + +jobs: + unit-tests: + name: Unit Tests + timeout-minutes: 10 + runs-on: ubuntu-20.04 + steps: + - name: Install Go + uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + - name: Checkout code + uses: actions/checkout@v4 + - name: Generate mocks + run: | + go install go.uber.org/mock/mockgen@v0.3.0 + go generate client/api_client.go + - name: Go fmt + run: | + ! go fmt ./... | read + - name: Go vet + run: | + ! go vet ./... | read + - name: golangci-lint + uses: golangci/golangci-lint-action@v6 + with: + version: v1.60 + - name: Go Test + run: go test -v ./... + + # See terraform-provider-env0 README for integration tests prerequisites + integration-tests: + name: Integration Tests + runs-on: ubuntu-20.04 + container: golang:1.21-alpine3.18 + timeout-minutes: 20 + steps: + - name: Install Terraform + run: apk add terraform + - name: Checkout code + uses: actions/checkout@v4 + - name: Run Harness tests + run: go run tests/harness.go diff --git a/.golangci.yaml b/.golangci.yaml index c2815a27..e6b031b6 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -3,7 +3,6 @@ linters: - errname - errorlint - gocheckcompilerdirectives - - gochecknoglobals - gochecknoinits - goconst - gocritic diff --git a/client/agent.go b/client/agent.go index 46475c42..62806837 100644 --- a/client/agent.go +++ b/client/agent.go @@ -11,8 +11,8 @@ func (client *ApiClient) Agents() ([]Agent, error) { } var result []Agent - err = client.http.Get("/agents", map[string]string{"organizationId": organizationId}, &result) - if err != nil { + + if err := client.http.Get("/agents", map[string]string{"organizationId": organizationId}, &result); err != nil { return nil, err } diff --git a/client/agent_project_assignment.go b/client/agent_project_assignment.go index 80ca6141..0351dc0e 100644 --- a/client/agent_project_assignment.go +++ b/client/agent_project_assignment.go @@ -30,6 +30,7 @@ func (client *ApiClient) ProjectsAgentsAssignments() (*ProjectsAgentsAssignments } var result ProjectsAgentsAssignments + err = client.http.Get("/agents/projects-assignments", map[string]string{"organizationId": organizationId}, &result) if err != nil { return nil, err diff --git a/client/agent_project_assignment_test.go b/client/agent_project_assignment_test.go index 8490a2b9..a055850c 100644 --- a/client/agent_project_assignment_test.go +++ b/client/agent_project_assignment_test.go @@ -34,7 +34,7 @@ var _ = Describe("Agent Project Assignment", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) httpCall = mockHttpClient.EXPECT(). Post("/agents/projects-assignments?organizationId="+organizationId, mapping, gomock.Any()).Times(1). @@ -59,7 +59,7 @@ var _ = Describe("Agent Project Assignment", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) httpCall = mockHttpClient.EXPECT(). Post("/agents/projects-assignments?organizationId="+organizationId, mapping, gomock.Any()).Times(1).Return(errorMock) @@ -83,7 +83,7 @@ var _ = Describe("Agent Project Assignment", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) httpCall = mockHttpClient.EXPECT(). Get("/agents/projects-assignments", map[string]string{"organizationId": organizationId}, gomock.Any()).Times(1). @@ -107,7 +107,7 @@ var _ = Describe("Agent Project Assignment", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) httpCall = mockHttpClient.EXPECT(). Get("/agents/projects-assignments", map[string]string{"organizationId": organizationId}, gomock.Any()).Times(1). diff --git a/client/agent_test.go b/client/agent_test.go index 561245e5..746f1a87 100644 --- a/client/agent_test.go +++ b/client/agent_test.go @@ -24,7 +24,7 @@ var _ = Describe("Agent Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/agents", gomock.Any(), gomock.Any()). @@ -56,7 +56,7 @@ var _ = Describe("Agent Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/agents", gomock.Any(), gomock.Any()). diff --git a/client/api_key_test.go b/client/api_key_test.go index a3648160..5fa8d5a6 100644 --- a/client/api_key_test.go +++ b/client/api_key_test.go @@ -21,7 +21,7 @@ var _ = Describe("ApiKey Client", func() { mockApiKeys := []ApiKey{mockApiKey} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/api-keys", map[string]string{"organizationId": organizationId}, gomock.Any()). Do(func(path string, request interface{}, response *[]ApiKey) { @@ -48,10 +48,11 @@ var _ = Describe("ApiKey Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() createApiKeyPayload := ApiKeyCreatePayload{} - copier.Copy(&createApiKeyPayload, &mockApiKey) + + _ = copier.Copy(&createApiKeyPayload, &mockApiKey) expectedCreateRequest := ApiKeyCreatePayloadWith{ ApiKeyCreatePayload: createApiKeyPayload, @@ -85,14 +86,20 @@ var _ = Describe("ApiKey Client", func() { }) Describe("Delete ApiKey", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/api-keys/"+mockApiKey.Id, nil) - apiClient.ApiKeyDelete(mockApiKey.Id) + err = apiClient.ApiKeyDelete(mockApiKey.Id) }) It("Should send DELETE request with ApiKey id", func() { httpCall.Times(1) }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("Get Oidc Sub", func() { @@ -101,7 +108,7 @@ var _ = Describe("ApiKey Client", func() { mockedOidcSub := "oidc sub 1234" BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/api-keys/oidc-sub", map[string]string{"organizationId": organizationId}, gomock.Any()). Do(func(path string, request interface{}, response *string) { diff --git a/client/approval_policy_test.go b/client/approval_policy_test.go index 9eb745c6..7bf0d633 100644 --- a/client/approval_policy_test.go +++ b/client/approval_policy_test.go @@ -36,7 +36,7 @@ var _ = Describe("Approval Policy Client", func() { mockApprovalPolicies := []ApprovalPolicy{mockApprovalPolicy} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/approval-policy", map[string]string{"organizationId": organizationId, "name": mockApprovalPolicy.Name}, gomock.Any()). Do(func(path string, request interface{}, response *[]ApprovalPolicy) { @@ -125,10 +125,10 @@ var _ = Describe("Approval Policy Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) createApprovalPolicyPayload := ApprovalPolicyCreatePayload{} - copier.Copy(&createApprovalPolicyPayload, &mockApprovalPolicy) + _ = copier.Copy(&createApprovalPolicyPayload, &mockApprovalPolicy) expectedCreateRequest := struct { ApprovalPolicyCreatePayload @@ -162,7 +162,7 @@ var _ = Describe("Approval Policy Client", func() { BeforeEach(func() { updateApprovalPolicyPayload := ApprovalPolicyUpdatePayload{} - copier.Copy(&updateApprovalPolicyPayload, &mockApprovalPolicy) + _ = copier.Copy(&updateApprovalPolicyPayload, &mockApprovalPolicy) httpCall = mockHttpClient.EXPECT(). Put("/approval-policy", &updateApprovalPolicyPayload, gomock.Any()). diff --git a/client/client_test.go b/client/client_test.go index 3aa1239c..9535f780 100644 --- a/client/client_test.go +++ b/client/client_test.go @@ -39,7 +39,7 @@ var _ = AfterSuite(func() { ctrl.Finish() }) -func mockOrganizationIdCall(organizationId string) *gomock.Call { +func mockOrganizationIdCall() *gomock.Call { organizations := []Organization{{ Id: organizationId, }} diff --git a/client/cloud_account_test.go b/client/cloud_account_test.go index acbf78cf..0ac00722 100644 --- a/client/cloud_account_test.go +++ b/client/cloud_account_test.go @@ -47,7 +47,7 @@ var _ = Describe("CloudAccount", func() { Describe("create", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() payload := CloudAccountCreatePayload{ Provider: account1.Provider, @@ -151,7 +151,7 @@ var _ = Describe("CloudAccount", func() { } BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/cloud/configurations", map[string]string{"organizationId": organizationId}, gomock.Any()). diff --git a/client/cloud_credentials.go b/client/cloud_credentials.go index c439f906..8e174379 100644 --- a/client/cloud_credentials.go +++ b/client/cloud_credentials.go @@ -152,8 +152,8 @@ func (client *ApiClient) CloudCredentialsList() ([]Credentials, error) { } var credentials []Credentials - err = client.http.Get("/credentials", map[string]string{"organizationId": organizationId}, &credentials) - if err != nil { + + if err := client.http.Get("/credentials", map[string]string{"organizationId": organizationId}, &credentials); err != nil { return []Credentials{}, err } diff --git a/client/cloud_credentials_project_assignment.go b/client/cloud_credentials_project_assignment.go index ee1d0b28..e9d00673 100644 --- a/client/cloud_credentials_project_assignment.go +++ b/client/cloud_credentials_project_assignment.go @@ -13,10 +13,10 @@ type CloudCredentialsProjectAssignment struct { func (client *ApiClient) AssignCloudCredentialsToProject(projectId string, credentialId string) (CloudCredentialsProjectAssignment, error) { var result CloudCredentialsProjectAssignment - err := client.http.Put("/credentials/deployment/"+credentialId+"/project/"+projectId, nil, &result) - if err != nil { + if err := client.http.Put("/credentials/deployment/"+credentialId+"/project/"+projectId, nil, &result); err != nil { return result, err } + return result, nil } @@ -26,10 +26,10 @@ func (client *ApiClient) RemoveCloudCredentialsFromProject(projectId string, cre func (client *ApiClient) CloudCredentialIdsInProject(projectId string) ([]string, error) { var result CloudCredentialIdsInProjectResponse - err := client.http.Get("/credentials/deployment/project/"+projectId, nil, &result) - if err != nil { + if err := client.http.Get("/credentials/deployment/project/"+projectId, nil, &result); err != nil { return nil, err } + return result.CredentialIds, nil } diff --git a/client/cloud_credentials_project_assignment_test.go b/client/cloud_credentials_project_assignment_test.go index 17489de6..ce0752f0 100644 --- a/client/cloud_credentials_project_assignment_test.go +++ b/client/cloud_credentials_project_assignment_test.go @@ -38,38 +38,38 @@ var _ = Describe("Credentials Project Assignment", func() { }) Describe("On Error", func() { errorInfo := "error" - var actualError error + var err error BeforeEach(func() { httpCall = mockHttpClient.EXPECT(). Put("/credentials/deployment/"+credentialId+"/project/"+projectId, nil, gomock.Any()). Return(errors.New(errorInfo)). Times(1) - _, actualError = apiClient.AssignCloudCredentialsToProject(projectId, credentialId) + _, err = apiClient.AssignCloudCredentialsToProject(projectId, credentialId) }) It("should return the error from the api call", func() { - Expect(actualError).ShouldNot(BeNil()) - Expect(actualError.Error()).Should(Equal(errorInfo)) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(Equal(errorInfo)) }) }) }) Describe("RemoveCloudCredentialsFromProject", func() { errorInfo := "error" - var actualError error + var err error BeforeEach(func() { httpCall = mockHttpClient.EXPECT(). Delete("/credentials/deployment/"+credentialId+"/project/"+projectId, nil). Return(errors.New(errorInfo)). Times(1) - actualError = apiClient.RemoveCloudCredentialsFromProject(projectId, credentialId) + err = apiClient.RemoveCloudCredentialsFromProject(projectId, credentialId) }) It("should return the error from the api call", func() { - Expect(actualError).ShouldNot(BeNil()) - Expect(actualError.Error()).Should(Equal(errorInfo)) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(Equal(errorInfo)) }) }) @@ -96,19 +96,19 @@ var _ = Describe("Credentials Project Assignment", func() { }) Describe("On Error", func() { errorInfo := "error" - var actualError error + var err error BeforeEach(func() { httpCall = mockHttpClient.EXPECT(). Get("/credentials/deployment/project/"+projectId, nil, gomock.Any()). Return(errors.New(errorInfo)). Times(1) - _, actualError = apiClient.CloudCredentialIdsInProject(projectId) + _, err = apiClient.CloudCredentialIdsInProject(projectId) }) It("should return the error from the api call", func() { - Expect(actualError).ShouldNot(BeNil()) - Expect(actualError.Error()).Should(Equal(errorInfo)) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(Equal(errorInfo)) }) }) diff --git a/client/cloud_credentials_test.go b/client/cloud_credentials_test.go index e948f5a3..3888ffa3 100644 --- a/client/cloud_credentials_test.go +++ b/client/cloud_credentials_test.go @@ -36,7 +36,7 @@ var _ = Describe("CloudCredentials", func() { Describe("GoogleCostCredentialsCreate", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() payloadValue := GoogleCostCredentialsValuePayload{ TableId: "table", @@ -77,7 +77,7 @@ var _ = Describe("CloudCredentials", func() { Describe("AwsCredentialsCreate", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() payloadValue := AwsCredentialsValuePayload{ RoleArn: "role", @@ -118,7 +118,7 @@ var _ = Describe("CloudCredentials", func() { Describe("AwsCredentialsUpdate", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() payloadValue := AwsCredentialsValuePayload{ RoleArn: "role", @@ -162,7 +162,7 @@ var _ = Describe("CloudCredentials", func() { mockGcpCredentials := mockCredentials mockGcpCredentials.Type = gcpRequestType BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() payloadValue := GcpCredentialsValuePayload{ ProjectId: "projectId", @@ -207,7 +207,7 @@ var _ = Describe("CloudCredentials", func() { mockAzureCredentials.Type = azureRequestType BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) payloadValue := AzureCredentialsValuePayload{ ClientId: "fakeClientId", @@ -241,19 +241,25 @@ var _ = Describe("CloudCredentials", func() { }) Describe("CloudCredentialsDelete", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/credentials/"+mockCredentials.Id, nil) - apiClient.CloudCredentialsDelete(mockCredentials.Id) + err = apiClient.CloudCredentialsDelete(mockCredentials.Id) }) It("Should send DELETE request with project id", func() { httpCall.Times(1) }) + + It("should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("CloudCredentials", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/credentials", map[string]string{"organizationId": organizationId}, gomock.Any()). @@ -276,7 +282,7 @@ var _ = Describe("CloudCredentials", func() { var credentials []Credentials BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/credentials", map[string]string{"organizationId": organizationId}, gomock.Any()). diff --git a/client/configuration_set.go b/client/configuration_set.go index ad912984..b2f06170 100644 --- a/client/configuration_set.go +++ b/client/configuration_set.go @@ -101,5 +101,6 @@ func (client *ApiClient) ConfigurationVariablesBySetId(setId string) ([]Configur }, &result); err != nil { return nil, err } + return result, nil } diff --git a/client/configuration_set_assignment_test.go b/client/configuration_set_assignment_test.go index 977a78ad..20a6649d 100644 --- a/client/configuration_set_assignment_test.go +++ b/client/configuration_set_assignment_test.go @@ -24,27 +24,39 @@ var _ = Describe("Configuration Set", func() { } Describe("assign configuration sets", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Post("/configuration-sets/assignments/environment/12345?setIds=1,2,3", nil, nil). Do(func(path string, request interface{}, response *interface{}) {}). Times(1) - apiClient.AssignConfigurationSets(scope, scopeId, setIds) + err = apiClient.AssignConfigurationSets(scope, scopeId, setIds) }) It("Should send post request", func() {}) + + It("should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("unassign configuration sets", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/configuration-sets/assignments/environment/12345", map[string]string{ "setIds": "1,2,3", }). Do(func(path string, request interface{}) {}). Times(1) - apiClient.UnassignConfigurationSets(scope, scopeId, setIds) + err = apiClient.UnassignConfigurationSets(scope, scopeId, setIds) }) It("Should send delete request", func() {}) + + It("should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("get configuration sets by scope and scope id", func() { diff --git a/client/configuration_set_test.go b/client/configuration_set_test.go index 93d509ae..299a8995 100644 --- a/client/configuration_set_test.go +++ b/client/configuration_set_test.go @@ -21,7 +21,7 @@ var _ = Describe("Configuration Set", func() { Describe("create organization configuration set", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) createPayload := CreateConfigurationSetPayload{ Name: "name1", @@ -111,16 +111,22 @@ var _ = Describe("Configuration Set", func() { }) Describe("delete configuration set", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT(). Delete("/configuration-sets/"+id, nil). Do(func(path string, request interface{}) {}). Times(1) - apiClient.ConfigurationSetDelete(id) + err = apiClient.ConfigurationSetDelete(id) }) It("Should call delete once", func() {}) + + It("should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("get configuration variables by set id", func() { @@ -136,7 +142,7 @@ var _ = Describe("Configuration Set", func() { var variables []ConfigurationVariable BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/configuration", map[string]string{ diff --git a/client/configuration_variable.go b/client/configuration_variable.go index f3f68c03..313d2c8e 100644 --- a/client/configuration_variable.go +++ b/client/configuration_variable.go @@ -37,6 +37,7 @@ func (c *ConfigurationVariableSchema) ResourceDataSliceStructValueWrite(values m if len(c.Format) > 0 { values["format"] = c.Format } + return nil } @@ -105,6 +106,7 @@ func (client *ApiClient) ConfigurationVariablesById(id string) (ConfigurationVar if err != nil { return ConfigurationVariable{}, err } + return result, nil } @@ -184,10 +186,11 @@ func (client *ApiClient) ConfigurationVariableCreate(params ConfigurationVariabl request["schema"] = getSchema(params) requestInArray := []map[string]interface{}{request} - err = client.http.Post("configuration", requestInArray, &result) - if err != nil { + + if err := client.http.Post("configuration", requestInArray, &result); err != nil { return ConfigurationVariable{}, err } + return result[0], nil } diff --git a/client/configuration_variable_test.go b/client/configuration_variable_test.go index b95a2cd7..b8760fa8 100644 --- a/client/configuration_variable_test.go +++ b/client/configuration_variable_test.go @@ -123,6 +123,7 @@ var _ = Describe("Configuration Variable", func() { "isRequired": *mockConfig.IsRequired, "regex": mockConfig.Regex, }} + return request } @@ -155,7 +156,7 @@ var _ = Describe("Configuration Variable", func() { } BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() SetCreateRequestExpectation(mockConfigurationVariable) DoCreateRequest(mockConfigurationVariable) }) @@ -174,7 +175,7 @@ var _ = Describe("Configuration Variable", func() { DescribeTable("Create with different schema format", func(schemaFormat Format) { var mockWithFormat = ConfigurationVariable{} - copier.Copy(&mockWithFormat, &mockConfigurationVariable) + _ = copier.Copy(&mockWithFormat, &mockConfigurationVariable) mockWithFormat.Schema.Format = schemaFormat SetCreateRequestExpectation(mockWithFormat) @@ -205,7 +206,7 @@ var _ = Describe("Configuration Variable", func() { var updatedConfigurationVariable ConfigurationVariable BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() newName := "new-" + mockConfigurationVariable.Name newDescription := "new-" + mockConfigurationVariable.Description @@ -272,7 +273,7 @@ var _ = Describe("Configuration Variable", func() { expectedParams := map[string]string{"organizationId": organizationId, "blueprintId": scopeId} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/configuration", expectedParams, gomock.Any()). diff --git a/client/cost_credentials_project_assignment.go b/client/cost_credentials_project_assignment.go index 1ae7aaab..eeb805a8 100644 --- a/client/cost_credentials_project_assignment.go +++ b/client/cost_credentials_project_assignment.go @@ -12,9 +12,11 @@ func (client *ApiClient) AssignCostCredentialsToProject(projectId string, creden err := client.http.Put("/costs/project/"+projectId+"/credentials", map[string]string{ "credentialsId": credentialId, }, &result) + if err != nil { return result, err } + return result, nil } @@ -29,5 +31,6 @@ func (client *ApiClient) CostCredentialIdsInProject(projectId string) ([]CostCre if err != nil { return nil, err } + return result, nil } diff --git a/client/cost_credentials_project_assignment_test.go b/client/cost_credentials_project_assignment_test.go index c70e4594..af015a15 100644 --- a/client/cost_credentials_project_assignment_test.go +++ b/client/cost_credentials_project_assignment_test.go @@ -38,38 +38,38 @@ var _ = Describe(" Cost Credentials Project Assignment", func() { }) Describe("On Error", func() { errorInfo := "error" - var actualError error + var err error BeforeEach(func() { httpCall = mockHttpClient.EXPECT(). Put("/costs/project/"+projectId+"/credentials", map[string]string{"credentialsId": credentialId}, gomock.Any()). Return(errors.New(errorInfo)). Times(1) - _, actualError = apiClient.AssignCostCredentialsToProject(projectId, credentialId) + _, err = apiClient.AssignCostCredentialsToProject(projectId, credentialId) }) It("should return the error from the api call", func() { - Expect(actualError).ShouldNot(BeNil()) - Expect(actualError.Error()).Should(Equal(errorInfo)) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(Equal(errorInfo)) }) }) }) Describe("RemoveCostCredentialsFromProject", func() { errorInfo := "error" - var actualError error + var err error BeforeEach(func() { httpCall = mockHttpClient.EXPECT(). Delete("/costs/project/"+projectId+"/credentials/"+credentialId, nil). Return(errors.New(errorInfo)). Times(1) - actualError = apiClient.RemoveCostCredentialsFromProject(projectId, credentialId) + err = apiClient.RemoveCostCredentialsFromProject(projectId, credentialId) }) It("should return the error from the api call", func() { - Expect(actualError).ShouldNot(BeNil()) - Expect(actualError.Error()).Should(Equal(errorInfo)) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(Equal(errorInfo)) }) }) @@ -107,19 +107,19 @@ var _ = Describe(" Cost Credentials Project Assignment", func() { }) Describe("On Error", func() { errorInfo := "error" - var actualError error + var err error BeforeEach(func() { httpCall = mockHttpClient.EXPECT(). Get("/costs/project/"+projectId+"/credentials", nil, gomock.Any()). Return(errors.New(errorInfo)). Times(1) - _, actualError = apiClient.CostCredentialIdsInProject(projectId) + _, err = apiClient.CostCredentialIdsInProject(projectId) }) It("should return the error from the api call", func() { - Expect(actualError).ShouldNot(BeNil()) - Expect(actualError.Error()).Should(Equal(errorInfo)) + Expect(err).ShouldNot(BeNil()) + Expect(err.Error()).Should(Equal(errorInfo)) }) }) diff --git a/client/custom_flow_test.go b/client/custom_flow_test.go index 5115c144..8705bc36 100644 --- a/client/custom_flow_test.go +++ b/client/custom_flow_test.go @@ -52,7 +52,7 @@ var _ = Describe("Custom Flow Client", func() { mockCustomFlows := []CustomFlow{mockCustomFlow} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/custom-flows", map[string]string{"organizationId": organizationId, "name": mockCustomFlow.Name}, gomock.Any()). Do(func(path string, request interface{}, response *[]CustomFlow) { @@ -73,10 +73,10 @@ var _ = Describe("Custom Flow Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() var createCustomFlowPayload CustomFlowCreatePayload - copier.Copy(&createCustomFlowPayload, &mockCustomFlow) + _ = copier.Copy(&createCustomFlowPayload, &mockCustomFlow) expectedCreateRequest := struct { OrganizationId string `json:"organizationId"` @@ -106,10 +106,16 @@ var _ = Describe("Custom Flow Client", func() { }) Describe("Delete Custom Flow", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/custom-flow/"+mockCustomFlow.Id, nil) httpCall.Times(1) - apiClient.CustomFlowDelete(mockCustomFlow.Id) + err = apiClient.CustomFlowDelete(mockCustomFlow.Id) + }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) }) }) @@ -121,7 +127,7 @@ var _ = Describe("Custom Flow Client", func() { updatedMockCustomFlow.Path = "updated-path" var updateCustomFlowPayload CustomFlowCreatePayload - copier.Copy(&updateCustomFlowPayload, &updatedMockCustomFlow) + _ = copier.Copy(&updateCustomFlowPayload, &updatedMockCustomFlow) BeforeEach(func() { expectedUpdateRequest := struct { diff --git a/client/environment.go b/client/environment.go index 498dd159..fbf67859 100644 --- a/client/environment.go +++ b/client/environment.go @@ -188,6 +188,7 @@ func (create EnvironmentCreateWithoutTemplate) MarshalJSON() ([]byte, error) { if err != nil { return nil, err } + tcb, err := json.Marshal(&create.TemplateCreate) if err != nil { return nil, err @@ -256,19 +257,23 @@ func (client *ApiClient) ProjectEnvironments(projectId string) ([]Environment, e func (client *ApiClient) Environment(id string) (Environment, error) { var result Environment + err := client.http.Get("/environments/"+id, nil, &result) if err != nil { return Environment{}, err } + return result, nil } func (client *ApiClient) EnvironmentDeploymentLog(id string) (*DeploymentLog, error) { var result DeploymentLog + err := client.http.Get("/environments/deployments/"+id, nil, &result) if err != nil { return nil, err } + return &result, nil } @@ -279,6 +284,7 @@ func (client *ApiClient) EnvironmentCreate(payload EnvironmentCreate) (Environme if err != nil { return Environment{}, err } + return result, nil } @@ -301,20 +307,23 @@ func (client *ApiClient) EnvironmentCreateWithoutTemplate(payload EnvironmentCre func (client *ApiClient) EnvironmentDestroy(id string) (*EnvironmentDestroyResponse, error) { var result EnvironmentDestroyResponse + err := client.http.Post("/environments/"+id+"/destroy", nil, &result) if err != nil { return nil, err } + return &result, nil } func (client *ApiClient) EnvironmentUpdate(id string, payload EnvironmentUpdate) (Environment, error) { var result Environment - err := client.http.Put("/environments/"+id, payload, &result) + err := client.http.Put("/environments/"+id, payload, &result) if err != nil { return Environment{}, err } + return result, nil } @@ -328,21 +337,23 @@ func (client *ApiClient) EnvironmentMarkAsArchived(id string) error { func (client *ApiClient) EnvironmentUpdateTTL(id string, payload TTL) (Environment, error) { var result Environment - err := client.http.Put("/environments/"+id+"/ttl", payload, &result) + err := client.http.Put("/environments/"+id+"/ttl", payload, &result) if err != nil { return Environment{}, err } + return result, nil } func (client *ApiClient) EnvironmentDeploy(id string, payload DeployRequest) (EnvironmentDeployResponse, error) { var result EnvironmentDeployResponse - err := client.http.Post("/environments/"+id+"/deployments", payload, &result) + err := client.http.Post("/environments/"+id+"/deployments", payload, &result) if err != nil { return EnvironmentDeployResponse{}, err } + return result, nil } diff --git a/client/environment_discovery_test.go b/client/environment_discovery_test.go index d1f2f278..3a897833 100644 --- a/client/environment_discovery_test.go +++ b/client/environment_discovery_test.go @@ -105,12 +105,18 @@ var _ = Describe("Environment Discovery", func() { Describe("DELETE", func() { Describe("success", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/environment-discovery/projects/"+projectId, nil).Times(1) - apiClient.DeleteEnvironmentDiscovery(projectId) + err = apiClient.DeleteEnvironmentDiscovery(projectId) }) It("Should send DELETE request", func() {}) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("failure", func() { diff --git a/client/environment_drift_detection.go b/client/environment_drift_detection.go index 2f7477d5..5cdc639d 100644 --- a/client/environment_drift_detection.go +++ b/client/environment_drift_detection.go @@ -7,6 +7,7 @@ func (client *ApiClient) EnvironmentDriftDetection(environmentId string) (Enviro if err != nil { return result, err } + return result, nil } diff --git a/client/environment_scheduling.go b/client/environment_scheduling.go index 073e36b7..bb9356b7 100644 --- a/client/environment_scheduling.go +++ b/client/environment_scheduling.go @@ -46,6 +46,7 @@ func (client *ApiClient) EnvironmentScheduling(environmentId string) (Environmen if err != nil { return result, err } + return result, nil } diff --git a/client/environment_scheduling_test.go b/client/environment_scheduling_test.go index 5d26a9bf..845b9282 100644 --- a/client/environment_scheduling_test.go +++ b/client/environment_scheduling_test.go @@ -115,14 +115,20 @@ var _ = Describe("EnvironmentScheduling", func() { Describe("Delete", func() { Describe("Success", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Put("/scheduling/environments/"+mockEnvironmentId, EnvironmentScheduling{}, &EnvironmentScheduling{}) - apiClient.EnvironmentSchedulingDelete(mockEnvironmentId) + err = apiClient.EnvironmentSchedulingDelete(mockEnvironmentId) }) It("Should send PUT request with empty environment scheduling object", func() { httpCall.Times(1) }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("Fail", func() { diff --git a/client/environment_test.go b/client/environment_test.go index 34e95a04..9fecbd63 100644 --- a/client/environment_test.go +++ b/client/environment_test.go @@ -39,7 +39,7 @@ var _ = Describe("Environment Client", func() { Describe("Success", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/environments", map[string]string{ "limit": "100", @@ -74,7 +74,7 @@ var _ = Describe("Environment Client", func() { } BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/environments", map[string]string{ "offset": "0", @@ -148,7 +148,7 @@ var _ = Describe("Environment Client", func() { Describe("Failure", func() { It("On error from server return the error", func() { expectedErr := errors.New("some error") - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/environments", map[string]string{ "limit": "100", @@ -207,7 +207,7 @@ var _ = Describe("Environment Client", func() { BeforeEach(func() { createEnvironmentPayload := EnvironmentCreate{} - copier.Copy(&createEnvironmentPayload, &mockEnvironment) + _ = copier.Copy(&createEnvironmentPayload, &mockEnvironment) expectedCreateRequest := createEnvironmentPayload @@ -238,11 +238,11 @@ var _ = Describe("Environment Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() createEnvironmentPayload := EnvironmentCreate{} - copier.Copy(&createEnvironmentPayload, &mockEnvironment) + _ = copier.Copy(&createEnvironmentPayload, &mockEnvironment) createTemplatePayload := TemplateCreatePayload{} - copier.Copy(&createTemplatePayload, &mockTemplate) + _ = copier.Copy(&createTemplatePayload, &mockTemplate) createRequest := EnvironmentCreateWithoutTemplate{ EnvironmentCreate: createEnvironmentPayload, diff --git a/client/git_token_test.go b/client/git_token_test.go index 4b8f0ac9..fbff4b2a 100644 --- a/client/git_token_test.go +++ b/client/git_token_test.go @@ -42,7 +42,7 @@ var _ = Describe("GitToken Client", func() { mockGitTokens := []GitToken{mockGitToken} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/tokens", map[string]string{"organizationId": organizationId, "type": "GIT"}, gomock.Any()). Do(func(path string, request interface{}, response *[]GitToken) { @@ -69,10 +69,10 @@ var _ = Describe("GitToken Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() createGitTokenPayload := GitTokenCreatePayload{} - copier.Copy(&createGitTokenPayload, &mockGitToken) + _ = copier.Copy(&createGitTokenPayload, &mockGitToken) expectedCreateRequest := GitTokenCreatePayloadWith{ GitTokenCreatePayload: createGitTokenPayload, @@ -107,13 +107,19 @@ var _ = Describe("GitToken Client", func() { }) Describe("Delete GitToken", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/tokens/"+mockGitToken.Id, nil) - apiClient.GitTokenDelete(mockGitToken.Id) + err = apiClient.GitTokenDelete(mockGitToken.Id) }) It("Should send DELETE request with GitToken id", func() { httpCall.Times(1) }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) }) diff --git a/client/gpg_key_test.go b/client/gpg_key_test.go index 87b85ccf..9258bc60 100644 --- a/client/gpg_key_test.go +++ b/client/gpg_key_test.go @@ -20,7 +20,7 @@ var _ = Describe("Gpg Token Client", func() { mockGpgKeys := []GpgKey{mockGpgKey} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() mockHttpClient.EXPECT(). Get("/gpg-keys", map[string]string{"organizationId": organizationId}, gomock.Any()). Do(func(path string, request interface{}, response *[]GpgKey) { @@ -56,7 +56,7 @@ var _ = Describe("Gpg Token Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() payload := struct { OrganizationId string `json:"organizationId"` diff --git a/client/http/client.go b/client/http/client.go index 35768fcb..0a647936 100644 --- a/client/http/client.go +++ b/client/http/client.go @@ -49,9 +49,11 @@ func (client *HttpClient) httpResult(response *resty.Response, err error) error if err != nil { return err } + if !response.IsSuccess() { return &FailedResponseError{res: response} } + return nil } @@ -98,6 +100,7 @@ func (client *HttpClient) Get(path string, params map[string]string, response in func (client *HttpClient) Delete(path string, params map[string]string) error { result, err := client.request().SetQueryParams(params).Delete(path) + return client.httpResult(result, err) } @@ -106,5 +109,6 @@ func (client *HttpClient) Patch(path string, request interface{}, response inter SetBody(request). SetResult(response). Patch(path) + return client.httpResult(result, err) } diff --git a/client/http/client_test.go b/client/http/client_test.go index 956e5eac..6ce182f3 100644 --- a/client/http/client_test.go +++ b/client/http/client_test.go @@ -116,7 +116,7 @@ var _ = Describe("Http Client", func() { // Get actual request body actualBodyBuffer := new(strings.Builder) - io.Copy(actualBodyBuffer, httpRequest.Body) + _, _ = io.Copy(actualBodyBuffer, httpRequest.Body) Expect(actualBodyBuffer.String()).To(Equal(string(mockRequestJson)), "Should send payload as HTTP request body") } @@ -127,10 +127,12 @@ var _ = Describe("Http Client", func() { for _, methodType := range []string{"GET", "POST", "PUT", "DELETE"} { httpmock.RegisterResponder(methodType, successUrl, func(req *http.Request) (*http.Response, error) { httpRequest = req + return httpmock.NewJsonResponse(200, mockedResponse) }) httpmock.RegisterResponder(methodType, failureUrl, func(req *http.Request) (*http.Response, error) { httpRequest = req + return httpmock.NewStringResponse(ErrorStatusCode, ErrorMessage), nil }) } diff --git a/client/kubernetes_credentials_test.go b/client/kubernetes_credentials_test.go index 757fecb3..109e4c4f 100644 --- a/client/kubernetes_credentials_test.go +++ b/client/kubernetes_credentials_test.go @@ -39,7 +39,7 @@ var _ = Describe("Kubernetes Credentials", func() { } BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Post("/credentials", &createPayloadWithOrganizationId, gomock.Any()). diff --git a/client/module_test.go b/client/module_test.go index 6535c8d6..3350a2a8 100644 --- a/client/module_test.go +++ b/client/module_test.go @@ -43,7 +43,7 @@ var _ = Describe("Module Client", func() { mockModules := []Module{mockModule} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/modules", map[string]string{"organizationId": organizationId}, gomock.Any()). Do(func(path string, request interface{}, response *[]Module) { @@ -70,10 +70,10 @@ var _ = Describe("Module Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() createModulePayload := ModuleCreatePayload{} - copier.Copy(&createModulePayload, &mockModule) + _ = copier.Copy(&createModulePayload, &mockModule) expectedCreateRequest := ModuleCreatePayloadWith{ ModuleCreatePayload: createModulePayload, @@ -108,14 +108,20 @@ var _ = Describe("Module Client", func() { }) Describe("Delete Module", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/modules/"+mockModule.Id, nil) - apiClient.ModuleDelete(mockModule.Id) + err = apiClient.ModuleDelete(mockModule.Id) }) It("Should send DELETE request with module id", func() { httpCall.Times(1) }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("Update Module", func() { diff --git a/client/notification.go b/client/notification.go index dd0cae29..000222bb 100644 --- a/client/notification.go +++ b/client/notification.go @@ -45,9 +45,11 @@ func (client *ApiClient) Notifications() ([]Notification, error) { } var result []Notification + if err := client.http.Get("/notifications/endpoints", map[string]string{"organizationId": organizationId}, &result); err != nil { return nil, err } + return result, nil } @@ -67,6 +69,7 @@ func (client *ApiClient) NotificationCreate(payload NotificationCreatePayload) ( if err = client.http.Post("/notifications/endpoints", payloadWithOrganizationId, &result); err != nil { return nil, err } + return &result, nil } @@ -74,6 +77,7 @@ func (client *ApiClient) NotificationDelete(id string) error { if err := client.http.Delete("/notifications/endpoints/"+id, nil); err != nil { return err } + return nil } @@ -83,5 +87,6 @@ func (client *ApiClient) NotificationUpdate(id string, payload NotificationUpdat if err := client.http.Patch("/notifications/endpoints/"+id, payload, &result); err != nil { return nil, err } + return &result, nil } diff --git a/client/notification_project_assignment.go b/client/notification_project_assignment.go index 3eb74cc7..28b6373c 100644 --- a/client/notification_project_assignment.go +++ b/client/notification_project_assignment.go @@ -16,17 +16,22 @@ type NotificationProjectAssignmentUpdatePayload struct { func (client *ApiClient) NotificationProjectAssignments(projectId string) ([]NotificationProjectAssignment, error) { var result []NotificationProjectAssignment + if err := client.http.Get("/notifications/projects/"+projectId, nil, &result); err != nil { return nil, err } + return result, nil } func (client *ApiClient) NotificationProjectAssignmentUpdate(projectId string, endpointId string, payload NotificationProjectAssignmentUpdatePayload) (*NotificationProjectAssignment, error) { var result NotificationProjectAssignment + url := fmt.Sprintf("/notifications/projects/%s/endpoints/%s", projectId, endpointId) + if err := client.http.Put(url, payload, &result); err != nil { return nil, err } + return &result, nil } diff --git a/client/notification_test.go b/client/notification_test.go index 419ca231..2345328c 100644 --- a/client/notification_test.go +++ b/client/notification_test.go @@ -22,7 +22,7 @@ var _ = Describe("Notification Client", func() { mockNotifications := []Notification{mockNotification} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/notifications/endpoints", map[string]string{"organizationId": organizationId}, gomock.Any()). Do(func(path string, request interface{}, response *[]Notification) { @@ -50,10 +50,10 @@ var _ = Describe("Notification Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() createNotificationPayload := NotificationCreatePayload{} - copier.Copy(&createNotificationPayload, &mockNotification) + _ = copier.Copy(&createNotificationPayload, &mockNotification) expectedCreateRequest := NotificationCreatePayloadWith{ NotificationCreatePayload: createNotificationPayload, @@ -88,14 +88,20 @@ var _ = Describe("Notification Client", func() { }) Describe("NotificationDelete", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/notifications/endpoints/"+mockNotification.Id, nil) - apiClient.NotificationDelete(mockNotification.Id) + err = apiClient.NotificationDelete(mockNotification.Id) }) It("Should send DELETE request with notification id", func() { httpCall.Times(1) }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("NotificationUpdate", func() { diff --git a/client/organization.go b/client/organization.go index a714d747..80512655 100644 --- a/client/organization.go +++ b/client/organization.go @@ -34,10 +34,12 @@ type OrganizationPolicyUpdatePayload struct { func (client *ApiClient) Organization() (Organization, error) { var result []Organization + err := client.http.Get("/organizations", nil, &result) if err != nil { return Organization{}, err } + if len(result) != 1 { if client.defaultOrganizationId != "" { for _, organization := range result { @@ -51,6 +53,7 @@ func (client *ApiClient) Organization() (Organization, error) { return Organization{}, errors.New("server responded with too many organizations (set a default organization id in the provider settings)") } + return result[0], nil } @@ -58,11 +61,14 @@ func (client *ApiClient) OrganizationId() (string, error) { if client.cachedOrganizationId != "" { return client.cachedOrganizationId, nil } + organization, err := client.Organization() if err != nil { return "", err } + client.cachedOrganizationId = organization.Id + return client.cachedOrganizationId, nil } diff --git a/client/organization_test.go b/client/organization_test.go index 7e14abe2..9c8b9cdf 100644 --- a/client/organization_test.go +++ b/client/organization_test.go @@ -108,7 +108,7 @@ var _ = Describe("Organization", func() { Describe("Success", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() updateOrganizationPolicyPayload := OrganizationPolicyUpdatePayload{ DefaultTtl: &hour12, DoNotConsiderMergeCommitsForPrPlans: &t, @@ -139,7 +139,7 @@ var _ = Describe("Organization", func() { }) }) - Describe("Emtpy string is passed as null", func() { + Describe("Empty string is passed as null", func() { updatedMockOrganization := mockOrganization updatedMockOrganization.DefaultTtl = nil updatedMockOrganization.MaxTtl = nil @@ -150,7 +150,7 @@ var _ = Describe("Organization", func() { emptyString := "" BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() originalUpdatePayload := OrganizationPolicyUpdatePayload{ DefaultTtl: &emptyString, MaxTtl: &emptyString, @@ -187,7 +187,7 @@ var _ = Describe("Organization", func() { Describe("Success", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Put("/organizations/"+organizationId+"/users/"+userId+"/role", roleId, nil) diff --git a/client/pagination.go b/client/pagination.go index 4f9ae909..b2869049 100644 --- a/client/pagination.go +++ b/client/pagination.go @@ -24,6 +24,7 @@ func (p *Pagination) getParams() map[string]string { // returns true if there is more data. func (p *Pagination) next(currentPageSize int) bool { p.offset += currentPageSize + return currentPageSize == limit } diff --git a/client/project.go b/client/project.go index 76d3fbc9..e160b3be 100644 --- a/client/project.go +++ b/client/project.go @@ -36,25 +36,29 @@ func (client *ApiClient) Projects() ([]Project, error) { if err != nil { return nil, err } + var result []Project - err = client.http.Get("/projects", map[string]string{"organizationId": organizationId}, &result) - if err != nil { + + if err := client.http.Get("/projects", map[string]string{"organizationId": organizationId}, &result); err != nil { return []Project{}, err } + return result, nil } func (client *ApiClient) Project(id string) (Project, error) { var result Project - err := client.http.Get("/projects/"+id, nil, &result) - if err != nil { + + if err := client.http.Get("/projects/"+id, nil, &result); err != nil { return Project{}, err } + return result, nil } func (client *ApiClient) ProjectCreate(payload ProjectCreatePayload) (Project, error) { var result Project + organizationId, err := client.OrganizationId() if err != nil { return Project{}, err @@ -72,6 +76,7 @@ func (client *ApiClient) ProjectCreate(payload ProjectCreatePayload) (Project, e if err != nil { return Project{}, err } + return result, nil } @@ -86,6 +91,7 @@ func (client *ApiClient) ProjectUpdate(id string, payload ProjectUpdatePayload) if err != nil { return Project{}, err } + return result, nil } diff --git a/client/project_budget_test.go b/client/project_budget_test.go index cd228286..264a9eed 100644 --- a/client/project_budget_test.go +++ b/client/project_budget_test.go @@ -36,13 +36,19 @@ var _ = Describe("Project Budget Client", func() { }) Describe("Delete Project Budget", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/costs/project/"+mockProjectBudget.ProjectId+"/budget", gomock.Nil()) httpCall.Times(1) - apiClient.ProjectBudgetDelete(mockProjectBudget.ProjectId) + err = apiClient.ProjectBudgetDelete(mockProjectBudget.ProjectId) }) It("Should delete project budget", func() {}) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("Update Project Budget", func() { @@ -50,7 +56,7 @@ var _ = Describe("Project Budget Client", func() { BeforeEach(func() { var updateProjectBudgetPayload ProjectBudgetUpdatePayload - copier.Copy(&updateProjectBudgetPayload, &mockProjectBudget) + _ = copier.Copy(&updateProjectBudgetPayload, &mockProjectBudget) httpCall = mockHttpClient.EXPECT(). Put("/costs/project/"+mockProjectBudget.ProjectId+"/budget", &updateProjectBudgetPayload, gomock.Any()). diff --git a/client/project_policy.go b/client/project_policy.go index 5770c976..7139ee7b 100644 --- a/client/project_policy.go +++ b/client/project_policy.go @@ -50,6 +50,7 @@ func (client *ApiClient) Policy(projectId string) (Policy, error) { } var result Policy + err = client.http.Get(u.String(), nil, &result) if err != nil { return Policy{}, err @@ -61,9 +62,11 @@ func (client *ApiClient) Policy(projectId string) (Policy, error) { // PolicyUpdate updates a policy through the API func (client *ApiClient) PolicyUpdate(payload PolicyUpdatePayload) (Policy, error) { var result Policy + err := client.http.Put("/policies", payload, &result) if err != nil { return Policy{}, err } + return result, nil } diff --git a/client/project_policy_test.go b/client/project_policy_test.go index 3e004516..4aff70f2 100644 --- a/client/project_policy_test.go +++ b/client/project_policy_test.go @@ -2,7 +2,6 @@ package client_test import ( "errors" - "fmt" . "github.com/env0/terraform-provider-env0/client" . "github.com/onsi/ginkgo" @@ -23,7 +22,7 @@ var _ = Describe("Policy", func() { var policy Policy var err error - path := fmt.Sprintf("/policies?projectId=%s", mockPolicy.ProjectId) + path := "/policies?projectId=" + mockPolicy.ProjectId Describe("Success", func() { BeforeEach(func() { diff --git a/client/project_test.go b/client/project_test.go index 06d84933..22fcdf35 100644 --- a/client/project_test.go +++ b/client/project_test.go @@ -28,7 +28,7 @@ var _ = Describe("Project", func() { Describe("ProjectCreate", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() payload := struct { ProjectCreatePayload @@ -69,14 +69,20 @@ var _ = Describe("Project", func() { }) Describe("ProjectDelete", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/projects/"+mockProject.Id, nil) - apiClient.ProjectDelete(mockProject.Id) + err = apiClient.ProjectDelete(mockProject.Id) }) It("Should send DELETE request with project id", func() { httpCall.Times(1) }) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("ProjectUpdate", func() { @@ -132,7 +138,7 @@ var _ = Describe("Project", func() { mockProjects := []Project{mockProject} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/projects", map[string]string{"organizationId": organizationId}, gomock.Any()). @@ -156,6 +162,8 @@ var _ = Describe("Project", func() { }) Describe("ProjectMove", func() { + var err error + targetProjectId := "targetid" payload := struct { @@ -166,13 +174,19 @@ var _ = Describe("Project", func() { BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Post("/projects/"+mockProject.Id+"/move", payload, nil).Times(1) - apiClient.ProjectMove(mockProject.Id, targetProjectId) + err = apiClient.ProjectMove(mockProject.Id, targetProjectId) }) It("Should send POST request with project id and target project id", func() {}) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("ProjectMove with no target project id", func() { + var err error + payload := struct { TargetProjectId *string `json:"targetProjectId"` }{ @@ -181,15 +195,19 @@ var _ = Describe("Project", func() { BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Post("/projects/"+mockProject.Id+"/move", payload, nil).Times(1) - apiClient.ProjectMove(mockProject.Id, "") + err = apiClient.ProjectMove(mockProject.Id, "") }) It("Should send POST request with project id and nil target project id", func() {}) + + It("Should not return error", func() { + Expect(err).To(BeNil()) + }) }) Describe("ModuleTestingProject", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/projects/modules/testing/"+organizationId, nil, gomock.Any()). diff --git a/client/provider_test.go b/client/provider_test.go index e4051d5f..e08c41a8 100644 --- a/client/provider_test.go +++ b/client/provider_test.go @@ -36,7 +36,7 @@ var _ = Describe("Provider Client", func() { mockProviders := []Provider{mockProvider} BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) httpCall = mockHttpClient.EXPECT(). Get("/providers", map[string]string{"organizationId": organizationId}, gomock.Any()). Do(func(path string, request interface{}, response *[]Provider) { @@ -54,7 +54,7 @@ var _ = Describe("Provider Client", func() { var createdProvider *Provider BeforeEach(func() { - mockOrganizationIdCall(organizationId).Times(1) + mockOrganizationIdCall().Times(1) createProviderPayload := ProviderCreatePayload{ Type: mockProvider.Type, @@ -83,12 +83,18 @@ var _ = Describe("Provider Client", func() { }) Describe("Delete Provider", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/providers/"+mockProvider.Id, nil).Times(1) - apiClient.ProviderDelete(mockProvider.Id) + err = apiClient.ProviderDelete(mockProvider.Id) }) It("Should send DELETE request with provider id", func() {}) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) Describe("Update Provider", func() { diff --git a/client/role_test.go b/client/role_test.go index de5df4ab..473935b4 100644 --- a/client/role_test.go +++ b/client/role_test.go @@ -36,7 +36,7 @@ var _ = Describe("Role", func() { Describe("RoleCreate", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Post("/roles", RoleCreatePayload{ @@ -71,14 +71,20 @@ var _ = Describe("Role", func() { }) Describe("RoleDelete", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/roles/"+mockRole.Id, nil) - apiClient.RoleDelete(mockRole.Id) + err = apiClient.RoleDelete(mockRole.Id) }) It("Should send DELETE request with role id", func() { httpCall.Times(1) }) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) Describe("RoleUpdate", func() { @@ -130,7 +136,7 @@ var _ = Describe("Role", func() { mockRoles := []Role{mockRole} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/roles", map[string]string{"organizationId": organizationId}, gomock.Any()). diff --git a/client/sshkey.go b/client/sshkey.go index 2ef6fd24..442e76da 100644 --- a/client/sshkey.go +++ b/client/sshkey.go @@ -27,12 +27,14 @@ func (client *ApiClient) SshKeyCreate(payload SshKeyCreatePayload) (*SshKey, err if err != nil { return nil, err } + payload.OrganizationId = organizationId var result SshKey if err := client.http.Post("/ssh-keys", payload, &result); err != nil { return nil, err } + return &result, nil } @@ -42,6 +44,7 @@ func (client *ApiClient) SshKeyUpdate(id string, payload *SshKeyUpdatePayload) ( if err := client.http.Put("/ssh-keys/"+id, payload, &result); err != nil { return nil, err } + return &result, nil } @@ -54,10 +57,13 @@ func (client *ApiClient) SshKeys() ([]SshKey, error) { if err != nil { return nil, err } + var result []SshKey + err = client.http.Get("/ssh-keys", map[string]string{"organizationId": organizationId}, &result) if err != nil { return nil, err } + return result, err } diff --git a/client/sshkey_test.go b/client/sshkey_test.go index d7ed1768..606c777c 100644 --- a/client/sshkey_test.go +++ b/client/sshkey_test.go @@ -21,7 +21,7 @@ var _ = Describe("SshKey", func() { var sshKey *SshKey BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() expectedPayload := SshKeyCreatePayload{Name: sshKeyName, Value: sshKeyValue, OrganizationId: organizationId} httpCall = mockHttpClient.EXPECT(). Post("/ssh-keys", expectedPayload, gomock.Any()). @@ -59,7 +59,7 @@ var _ = Describe("SshKey", func() { Describe("SshKeys", func() { var sshKeys []SshKey BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/ssh-keys", map[string]string{"organizationId": organizationId}, diff --git a/client/team.go b/client/team.go index 242bdbbd..64189843 100644 --- a/client/team.go +++ b/client/team.go @@ -26,29 +26,36 @@ func (client *ApiClient) TeamCreate(payload TeamCreatePayload) (Team, error) { if payload.Name == "" { return Team{}, errors.New("must specify team name on creation") } + if payload.OrganizationId != "" { return Team{}, errors.New("must not specify organizationId") } + organizationId, err := client.OrganizationId() if err != nil { return Team{}, err } + payload.OrganizationId = organizationId var result Team + err = client.http.Post("/teams", payload, &result) if err != nil { return Team{}, err } + return result, nil } func (client *ApiClient) Team(id string) (Team, error) { var result Team + err := client.http.Get("/teams/"+id, nil, &result) if err != nil { return Team{}, err } + return result, nil } @@ -62,10 +69,12 @@ func (client *ApiClient) TeamUpdate(id string, payload TeamUpdatePayload) (Team, } var result Team + err := client.http.Put("/teams/"+id, payload, &result) if err != nil { return Team{}, err } + return result, nil } @@ -74,11 +83,14 @@ func (client *ApiClient) GetTeams(params map[string]string) ([]Team, error) { if err != nil { return nil, err } + var result []Team + err = client.http.Get("/teams/organizations/"+organizationId, params, &result) if err != nil { return nil, err } + return result, err } diff --git a/client/team_role_assignment_test.go b/client/team_role_assignment_test.go index 5b3033e0..fdd317b3 100644 --- a/client/team_role_assignment_test.go +++ b/client/team_role_assignment_test.go @@ -151,30 +151,48 @@ var _ = Describe("TeamRoleAssignment", func() { }) Describe("Delete Project Assignment", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/roles/assignments/teams", map[string]string{"projectId": dummyProjectId, "teamId": dummyTeamId}).Times(1) - apiClient.TeamRoleAssignmentDelete(&TeamRoleAssignmentDeletePayload{TeamId: dummyTeamId, ProjectId: dummyProjectId}) + err = apiClient.TeamRoleAssignmentDelete(&TeamRoleAssignmentDeletePayload{TeamId: dummyTeamId, ProjectId: dummyProjectId}) }) It("Should send Delete request with correct params", func() {}) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) Describe("Delete Environment Assignment", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/roles/assignments/teams", map[string]string{"environmentId": dummyEnvironmentId, "teamId": dummyTeamId}).Times(1) - apiClient.TeamRoleAssignmentDelete(&TeamRoleAssignmentDeletePayload{TeamId: dummyTeamId, EnvironmentId: dummyEnvironmentId}) + err = apiClient.TeamRoleAssignmentDelete(&TeamRoleAssignmentDeletePayload{TeamId: dummyTeamId, EnvironmentId: dummyEnvironmentId}) }) It("Should send Delete request with correct params", func() {}) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) Describe("Delete Organization Assignment", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/roles/assignments/teams", map[string]string{"organizationId": dummyOrganizationId, "teamId": dummyTeamId}).Times(1) - apiClient.TeamRoleAssignmentDelete(&TeamRoleAssignmentDeletePayload{TeamId: dummyTeamId, OrganizationId: dummyOrganizationId}) + err = apiClient.TeamRoleAssignmentDelete(&TeamRoleAssignmentDeletePayload{TeamId: dummyTeamId, OrganizationId: dummyOrganizationId}) }) It("Should send Delete request with correct params", func() {}) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) }) diff --git a/client/team_test.go b/client/team_test.go index 7e6f0dae..7890a9be 100644 --- a/client/team_test.go +++ b/client/team_test.go @@ -42,7 +42,7 @@ var _ = Describe("Teams Client", func() { mockTeams := []Team{mockTeam} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/teams/organizations/"+organizationId, nil, gomock.Any()). Do(func(path string, request interface{}, response *[]Team) { @@ -70,10 +70,10 @@ var _ = Describe("Teams Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() createTeamPayload := TeamCreatePayload{} - copier.Copy(&createTeamPayload, &mockTeam) + _ = copier.Copy(&createTeamPayload, &mockTeam) expectedCreateRequest := createTeamPayload expectedCreateRequest.OrganizationId = organizationId @@ -120,14 +120,20 @@ var _ = Describe("Teams Client", func() { }) Describe("TeamDelete", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/teams/"+mockTeam.Id, nil) - apiClient.TeamDelete(mockTeam.Id) + err = apiClient.TeamDelete(mockTeam.Id) }) It("Should send DELETE request with team id", func() { httpCall.Times(1) }) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) Describe("TeamUpdate", func() { diff --git a/client/template.go b/client/template.go index 0e025fe8..704d0202 100644 --- a/client/template.go +++ b/client/template.go @@ -130,9 +130,11 @@ func (payload *TemplateCreatePayload) Invalidate() error { if payload.Type != TERRAGRUNT && payload.TerragruntVersion != "" { return errors.New("can't define terragrunt version for non-terragrunt template") } + if payload.Type == TERRAGRUNT && payload.TerragruntVersion == "" { return errors.New("must supply terragrunt version") } + if payload.Type == OPENTOFU && payload.OpentofuVersion == "" { return errors.New("must supply opentofu version") } @@ -178,6 +180,7 @@ func (payload *TemplateCreatePayload) Invalidate() error { if payload.Type == "cloudformation" && payload.FileName == "" { return errors.New("file_name is required with cloudformation template type") } + if payload.Type != "cloudformation" && payload.FileName != "" { return fmt.Errorf("file_name cannot be set when template type is: %s", payload.Type) } @@ -212,22 +215,27 @@ func (client *ApiClient) TemplateCreate(payload TemplateCreatePayload) (Template if err != nil { return Template{}, err } + payload.OrganizationId = organizationId var result Template + err = client.http.Post("/blueprints", payload, &result) if err != nil { return Template{}, err } + return result, nil } func (client *ApiClient) Template(id string) (Template, error) { var result Template + err := client.http.Get("/blueprints/"+id, nil, &result) if err != nil { return Template{}, err } + return result, nil } @@ -240,13 +248,16 @@ func (client *ApiClient) TemplateUpdate(id string, payload TemplateCreatePayload if err != nil { return Template{}, err } + payload.OrganizationId = organizationId var result Template + err = client.http.Put("/blueprints/"+id, payload, &result) if err != nil { return Template{}, err } + return result, nil } @@ -255,11 +266,14 @@ func (client *ApiClient) Templates() ([]Template, error) { if err != nil { return nil, err } + var result []Template + err = client.http.Get("/blueprints", map[string]string{"organizationId": organizationId}, &result) if err != nil { return nil, err } + return result, err } @@ -268,10 +282,12 @@ func (client *ApiClient) AssignTemplateToProject(id string, payload TemplateAssi if payload.ProjectId == "" { return result, errors.New("must specify projectId on assignment to a template") } + err := client.http.Patch("/blueprints/"+id+"/projects", payload, &result) if err != nil { return result, err } + return result, nil } diff --git a/client/template_test.go b/client/template_test.go index a4b49d99..76a7156d 100644 --- a/client/template_test.go +++ b/client/template_test.go @@ -29,7 +29,7 @@ var _ = Describe("Templates Client", func() { } jsonPayload, _ := json.Marshal(payload) var parsedPayload map[string]interface{} - json.Unmarshal(jsonPayload, &parsedPayload) + _ = json.Unmarshal(jsonPayload, &parsedPayload) Expect(parsedPayload["githubInstallationId"]).To(expected) }, Entry("Has value", 123, BeEquivalentTo(123)), @@ -63,7 +63,7 @@ var _ = Describe("Templates Client", func() { mockTemplates := []Template{mockTemplate} BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() expectedPayload := map[string]string{"organizationId": organizationId} httpCall = mockHttpClient.EXPECT(). Get("/blueprints", expectedPayload, gomock.Any()). @@ -91,10 +91,10 @@ var _ = Describe("Templates Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() createTemplatePayload := TemplateCreatePayload{} - copier.Copy(&createTemplatePayload, &mockTemplate) + _ = copier.Copy(&createTemplatePayload, &mockTemplate) expectedCreateRequest := createTemplatePayload expectedCreateRequest.OrganizationId = organizationId @@ -143,10 +143,10 @@ var _ = Describe("Templates Client", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() updateTemplatePayload := TemplateCreatePayload{} - copier.Copy(&updateTemplatePayload, &mockTemplate) + _ = copier.Copy(&updateTemplatePayload, &mockTemplate) expectedUpdateRequest := updateTemplatePayload expectedUpdateRequest.OrganizationId = organizationId diff --git a/client/user_environment_assignment_test.go b/client/user_environment_assignment_test.go index dad96381..e74623aa 100644 --- a/client/user_environment_assignment_test.go +++ b/client/user_environment_assignment_test.go @@ -75,14 +75,20 @@ var _ = Describe("User Environment Assignment", func() { }) Describe("RemoveUserFromEnvironment", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/roles/assignments/users", map[string]string{"environmentId": environmentId, "userId": userId}) - apiClient.RemoveUserRoleFromEnvironment(environmentId, userId) + err = apiClient.RemoveUserRoleFromEnvironment(environmentId, userId) }) It("Should send DELETE request with assignment id", func() { httpCall.Times(1) }) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) Describe("UserEnvironmentAssignments", func() { diff --git a/client/user_project_assignment_test.go b/client/user_project_assignment_test.go index f0b7e1e4..417638fe 100644 --- a/client/user_project_assignment_test.go +++ b/client/user_project_assignment_test.go @@ -78,14 +78,20 @@ var _ = Describe("Agent Project Assignment", func() { }) Describe("RemoveUserFromProject", func() { + var err error + BeforeEach(func() { httpCall = mockHttpClient.EXPECT().Delete("/permissions/projects/"+projectId+"/users/"+expectedResponse.Id, nil) - apiClient.RemoveUserFromProject(projectId, expectedResponse.Id) + err = apiClient.RemoveUserFromProject(projectId, expectedResponse.Id) }) It("Should send DELETE request with assignment id", func() { httpCall.Times(1) }) + + It("Should not return an error", func() { + Expect(err).Should(BeNil()) + }) }) Describe("UserProjectAssignments", func() { diff --git a/client/user_test.go b/client/user_test.go index 77ffd0e3..a5131977 100644 --- a/client/user_test.go +++ b/client/user_test.go @@ -24,7 +24,7 @@ var _ = Describe("User Client", func() { Describe("Success", func() { BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/organizations/"+organizationId+"/users", gomock.Any(), gomock.Any()). @@ -42,7 +42,7 @@ var _ = Describe("User Client", func() { Describe("Failure", func() { It("On error from server return the error", func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() expectedErr := errors.New("some error") httpCall = mockHttpClient.EXPECT(). diff --git a/client/vcs_token_test.go b/client/vcs_token_test.go index 76a1ba3c..2cc2c809 100644 --- a/client/vcs_token_test.go +++ b/client/vcs_token_test.go @@ -20,7 +20,7 @@ var _ = Describe("VCSToken", func() { var err error BeforeEach(func() { - mockOrganizationIdCall(organizationId) + mockOrganizationIdCall() httpCall = mockHttpClient.EXPECT(). Get("/vcs-token/"+vcsType, map[string]string{ diff --git a/client/workflow_triggers.go b/client/workflow_triggers.go index 55cc2e93..f8f9d4af 100644 --- a/client/workflow_triggers.go +++ b/client/workflow_triggers.go @@ -14,6 +14,7 @@ type WorkflowTriggerEnvironments struct { func (client *ApiClient) WorkflowTrigger(environmentId string) ([]WorkflowTrigger, error) { var result []WorkflowTrigger + err := client.http.Get("/environments/"+environmentId+"/downstream", nil, &result) if err != nil { return []WorkflowTrigger{}, err @@ -29,6 +30,7 @@ func (client *ApiClient) WorkflowTriggerUpsert(environmentId string, request Wor if err != nil { return []WorkflowTrigger{}, err } + return result, nil } diff --git a/env0/data_api_key.go b/env0/data_api_key.go index bec38965..1afa0375 100644 --- a/env0/data_api_key.go +++ b/env0/data_api_key.go @@ -46,10 +46,6 @@ func dataApiKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{ if err != nil { return diag.Errorf("could not read api key: %v", err) } - - if apiKey == nil { - return diag.Errorf("could not read api key: id %v not found", id) - } } else { apiKey, err = getApiKeyByName(d.Get("name").(string), meta) if err != nil { diff --git a/env0/data_api_key_test.go b/env0/data_api_key_test.go index 79296ce9..cb861de3 100644 --- a/env0/data_api_key_test.go +++ b/env0/data_api_key_test.go @@ -1,7 +1,6 @@ package env0 import ( - "fmt" "regexp" "testing" @@ -100,7 +99,7 @@ func TestApiKeyDataSource(t *testing.T) { t.Run("Throw error when by id and no api key found with that id", func(t *testing.T) { runUnitTest(t, - getErrorTestCase(apiKeyFieldsById, fmt.Sprintf("id %s not found", apiKey.Id)), + getErrorTestCase(apiKeyFieldsById, "not found"), mockListApiKeysCall([]client.ApiKey{otherApiKey}), ) }) diff --git a/env0/data_environment_test.go b/env0/data_environment_test.go index ebb9ee3b..4fde05bf 100644 --- a/env0/data_environment_test.go +++ b/env0/data_environment_test.go @@ -119,6 +119,7 @@ func TestEnvironmentDataSource(t *testing.T) { mockListEnvironmentsCall := func(returnValue []client.Environment, tem *client.Template) func(mockFunc *client.MockApiClientInterface) { return func(mock *client.MockApiClientInterface) { mock.EXPECT().EnvironmentsByName(environment.Name).AnyTimes().Return(returnValue, nil) + if tem != nil { mock.EXPECT().Template(environment.LatestDeploymentLog.BlueprintId).AnyTimes().Return(*tem, nil) } diff --git a/env0/data_gpg_key.go b/env0/data_gpg_key.go index 3e8d6edf..740f0ff3 100644 --- a/env0/data_gpg_key.go +++ b/env0/data_gpg_key.go @@ -41,6 +41,7 @@ func dataGpgKey() *schema.Resource { func dataGpgKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var gpgKey *client.GpgKey + var err error id, ok := d.GetOk("id") diff --git a/env0/data_kubernetes_credentials.go b/env0/data_kubernetes_credentials.go index c72918c1..76d1079f 100644 --- a/env0/data_kubernetes_credentials.go +++ b/env0/data_kubernetes_credentials.go @@ -33,6 +33,7 @@ func dataKubernetesCredentials(credentialsType CloudType) *schema.Resource { func dataKuberentesCredentialsRead(credentialsType CloudType) func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var credentials client.Credentials + var err error id, ok := d.GetOk("id") diff --git a/env0/data_module.go b/env0/data_module.go index b4c2f374..0609bb63 100644 --- a/env0/data_module.go +++ b/env0/data_module.go @@ -71,6 +71,7 @@ func dataModule() *schema.Resource { func dataModuleRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var module *client.Module + var err error id, ok := d.GetOk("id") diff --git a/env0/data_notification.go b/env0/data_notification.go index e16bbc5e..253a3abf 100644 --- a/env0/data_notification.go +++ b/env0/data_notification.go @@ -46,6 +46,7 @@ func dataNotification() *schema.Resource { func dataNotificationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var notification *client.Notification + var err error id, ok := d.GetOk("id") diff --git a/env0/data_oidc_credentials.go b/env0/data_oidc_credentials.go index 240e5266..7a781b90 100644 --- a/env0/data_oidc_credentials.go +++ b/env0/data_oidc_credentials.go @@ -33,6 +33,7 @@ func dataOidcCredentials(credentialsType CloudType) *schema.Resource { func dataOidcCredentialRead(credentialsType CloudType) func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { return func(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var credentials client.Credentials + var err error id, ok := d.GetOk("id") diff --git a/env0/data_project.go b/env0/data_project.go index 99b72aac..64b23fa6 100644 --- a/env0/data_project.go +++ b/env0/data_project.go @@ -74,6 +74,7 @@ func dataProject() *schema.Resource { func dataProjectRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var err error + var project client.Project id, ok := d.GetOk("id") diff --git a/env0/data_project_policy.go b/env0/data_project_policy.go index 97898c8a..fc46c902 100644 --- a/env0/data_project_policy.go +++ b/env0/data_project_policy.go @@ -94,6 +94,7 @@ func dataPolicy() *schema.Resource { func dataPolicyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var err diag.Diagnostics + var policy client.Policy projectId, ok := d.GetOk("project_id") diff --git a/env0/data_project_test.go b/env0/data_project_test.go index 31cd9244..27012900 100644 --- a/env0/data_project_test.go +++ b/env0/data_project_test.go @@ -237,7 +237,6 @@ func TestProjectDataSource(t *testing.T) { }, ) }) - }) t.Run("By Name with Parent Id", func(t *testing.T) { diff --git a/env0/data_provider.go b/env0/data_provider.go index 8c83ec6c..0c05d683 100644 --- a/env0/data_provider.go +++ b/env0/data_provider.go @@ -36,6 +36,7 @@ func dataProvider() *schema.Resource { func dataProviderRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var provider *client.Provider + var err error id, ok := d.GetOk("id") diff --git a/env0/data_sshkey.go b/env0/data_sshkey.go index 31d3a33e..7c3cd8c5 100644 --- a/env0/data_sshkey.go +++ b/env0/data_sshkey.go @@ -36,6 +36,7 @@ func dataSshKey() *schema.Resource { func dataSshKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var sshKey *client.SshKey + var err error if name, ok := d.GetOk("name"); ok { @@ -45,10 +46,12 @@ func dataSshKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{ } } else { id := d.Get("id") + sshKey, err = getSshKeyById(id, meta) if err != nil { return diag.Errorf("could not read ssh key: %v", err) } + if sshKey == nil { return diag.Errorf("could not read ssh key: id %s not found", id) } @@ -71,6 +74,7 @@ func getSshKeyByName(name interface{}, meta interface{}) (*client.SshKey, error) } var sshKeysByName []client.SshKey + for _, candidate := range sshKeys { if candidate.Name == name { sshKeysByName = append(sshKeysByName, candidate) diff --git a/env0/data_sshkey_test.go b/env0/data_sshkey_test.go index 6e0dcdda..ff1af0b2 100644 --- a/env0/data_sshkey_test.go +++ b/env0/data_sshkey_test.go @@ -50,7 +50,9 @@ func testUnitSshKeyDataSource(t *testing.T, byKey string) { } sshKeyAsJson, _ := json.Marshal(sshKey) + var jsonData map[string]string + _ = json.Unmarshal(sshKeyAsJson, &jsonData) testCase := resource.TestCase{ diff --git a/env0/data_team.go b/env0/data_team.go index e575e174..a933d615 100644 --- a/env0/data_team.go +++ b/env0/data_team.go @@ -36,6 +36,7 @@ func dataTeam() *schema.Resource { func dataTeamRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var err diag.Diagnostics + var team client.Team id, ok := d.GetOk("id") diff --git a/env0/data_teams.go b/env0/data_teams.go index a1f4473b..21d6e822 100644 --- a/env0/data_teams.go +++ b/env0/data_teams.go @@ -28,6 +28,7 @@ func dataTeams() *schema.Resource { func dataTeamsRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { apiClient := meta.(client.ApiClientInterface) + teams, err := apiClient.Teams() if err != nil { return diag.Errorf("Could not get teams: %v", err) diff --git a/env0/data_template.go b/env0/data_template.go index 2818eff1..79d5c754 100644 --- a/env0/data_template.go +++ b/env0/data_template.go @@ -154,6 +154,7 @@ func dataTemplate() *schema.Resource { func dataTemplateRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { var template client.Template + var err diag.Diagnostics if name, ok := d.GetOk("name"); ok { @@ -176,12 +177,14 @@ func dataTemplateRead(ctx context.Context, d *schema.ResourceData, meta interfac func getTemplateByName(name interface{}, meta interface{}) (client.Template, diag.Diagnostics) { apiClient := meta.(client.ApiClientInterface) + templates, err := apiClient.Templates() if err != nil { return client.Template{}, diag.Errorf("Could not query templates: %v", err) } var templatesByName []client.Template + for _, candidate := range templates { if candidate.Name == name && !candidate.IsDeleted { templatesByName = append(templatesByName, candidate) @@ -201,6 +204,7 @@ func getTemplateByName(name interface{}, meta interface{}) (client.Template, dia func getTemplateById(id interface{}, meta interface{}) (client.Template, diag.Diagnostics) { apiClient := meta.(client.ApiClientInterface) + template, err := apiClient.Template(id.(string)) if err != nil { return client.Template{}, diag.Errorf("Could not query template: %v", err) diff --git a/env0/data_templates.go b/env0/data_templates.go index a6653d22..22d85b3a 100644 --- a/env0/data_templates.go +++ b/env0/data_templates.go @@ -28,6 +28,7 @@ func dataTemplates() *schema.Resource { func dataTemplatesRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { apiClient := meta.(client.ApiClientInterface) + templates, err := apiClient.Templates() if err != nil { return diag.Errorf("Could not get templates: %v", err) diff --git a/env0/data_user.go b/env0/data_user.go index cb104349..0169c268 100644 --- a/env0/data_user.go +++ b/env0/data_user.go @@ -29,12 +29,14 @@ func dataUser() *schema.Resource { func getUserByEmail(email string, meta interface{}) (*client.User, diag.Diagnostics) { apiClient := meta.(client.ApiClientInterface) + organizationUsers, err := apiClient.Users() if err != nil { return nil, diag.Errorf("Could not get users: %v", err) } var usersByEmail []client.User + for _, organizationUser := range organizationUsers { if organizationUser.User.Email == email { usersByEmail = append(usersByEmail, organizationUser.User) @@ -44,6 +46,7 @@ func getUserByEmail(email string, meta interface{}) (*client.User, diag.Diagnost if len(usersByEmail) > 1 { return nil, diag.Errorf("Found multiple users with the same email: %s", email) } + if len(usersByEmail) == 0 { return nil, diag.Errorf("Could not find a user with the email: %s", email) } @@ -53,6 +56,7 @@ func getUserByEmail(email string, meta interface{}) (*client.User, diag.Diagnost func dataUserRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { email := d.Get("email").(string) + user, err := getUserByEmail(email, meta) if err != nil { return err diff --git a/env0/data_variable_set_test.go b/env0/data_variable_set_test.go index 1b8afb42..593235a0 100644 --- a/env0/data_variable_set_test.go +++ b/env0/data_variable_set_test.go @@ -46,6 +46,7 @@ func TestVariableSetDataSource(t *testing.T) { if projectId != "" { fields["project_id"] = projectId } + return dataSourceConfigCreate(resourceType, resourceName, fields) } diff --git a/env0/errors.go b/env0/errors.go index 66e164e4..4854abc2 100644 --- a/env0/errors.go +++ b/env0/errors.go @@ -12,6 +12,7 @@ import ( ) var ErrNoChanges = errors.New("no changes") +var ErrNotFound = errors.New("not found") func driftDetected(err error) bool { var failedResponseError *http.FailedResponseError diff --git a/env0/provider.go b/env0/provider.go index 69e1b3f3..4c69efa1 100644 --- a/env0/provider.go +++ b/env0/provider.go @@ -187,16 +187,19 @@ func createRestyClient(ctx context.Context) *resty.Client { if r != nil { tflog.SubsystemInfo(subCtx, "env0_api_client", "Sending request", map[string]interface{}{"method": r.Method, "url": r.URL}) } + return nil }). OnAfterResponse(func(c *resty.Client, r *resty.Response) error { tflog.SubsystemInfo(subCtx, "env0_api_client", "Received response", map[string]interface{}{"method": r.Request.Method, "url": r.Request.URL, "status": r.Status()}) + return nil }). AddRetryCondition(func(r *resty.Response, err error) bool { if r == nil { // No response. Possibly a networking issue (E.g. DNS lookup failure). tflog.SubsystemWarn(subCtx, "env0_api_client", "No response, retrying request") + return true } @@ -204,11 +207,13 @@ func createRestyClient(ctx context.Context) *resty.Client { // Retry when there's a 5xx error. Otherwise do not retry. if r.StatusCode() >= 500 || (isIntegrationTest && r.StatusCode() == 404) { tflog.SubsystemWarn(subCtx, "env0_api_client", "Received a failed or not found response, retrying request", map[string]interface{}{"method": r.Request.Method, "url": r.Request.URL, "status code": r.StatusCode()}) + return true } if r.StatusCode() == 200 && isIntegrationTest && r.String() == "[]" { tflog.SubsystemWarn(subCtx, "env0_api_client", "Received an empty list , retrying request", map[string]interface{}{"method": r.Request.Method, "url": r.Request.URL}) + return true } diff --git a/env0/provider_test.go b/env0/provider_test.go index e2ccbbb2..fd458bef 100644 --- a/env0/provider_test.go +++ b/env0/provider_test.go @@ -37,6 +37,7 @@ func runUnitTest(t *testing.T, testCase resource.TestCase, mockFunc func(mockFun testPattern := os.Getenv("TEST_PATTERN") if testPattern != "" && !strings.Contains(t.Name(), testPattern) { t.SkipNow() + return } @@ -47,11 +48,13 @@ func runUnitTest(t *testing.T, testCase resource.TestCase, mockFunc func(mockFun mockFunc(apiClientMock) testCase.ProviderFactories = map[string]func() (*schema.Provider, error){ + //nolint:all // tests "env0": func() (*schema.Provider, error) { provider := Provider("")() provider.ConfigureContextFunc = func(ctx context.Context, d *schema.ResourceData) (interface{}, diag.Diagnostics) { return apiClientMock, nil } + return provider, nil }, } @@ -67,7 +70,9 @@ func TestProvider(t *testing.T) { func testExpectedProviderError(t *testing.T, diags diag.Diagnostics, expectedKey string) { expectedError := fmt.Sprintf("The argument \"%s\" is required, but no definition was found.", expectedKey) + var errorDetail string + for _, diag := range diags { if strings.Contains(diag.Detail, expectedError) { errorDetail = diag.Detail diff --git a/env0/resource_api_key.go b/env0/resource_api_key.go index 60393946..c709a6dd 100644 --- a/env0/resource_api_key.go +++ b/env0/resource_api_key.go @@ -2,6 +2,7 @@ package env0 import ( "context" + "errors" "fmt" "github.com/env0/terraform-provider-env0/client" @@ -85,13 +86,15 @@ func resourceApiKeyCreate(ctx context.Context, d *schema.ResourceData, meta inte func resourceApiKeyRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { apiKey, err := getApiKeyById(d.Id(), meta) if err != nil { + if errors.Is(err, ErrNotFound) { + tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) + d.SetId("") + + return nil + } + return diag.Errorf("could not get api key: %v", err) } - if apiKey == nil { - tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) - d.SetId("") - return nil - } apiKey.ApiKeySecret = "" // Don't override the api key secret currently in the state. @@ -126,7 +129,7 @@ func getApiKeyById(id string, meta interface{}) (*client.ApiKey, error) { } } - return nil, nil + return nil, ErrNotFound } func getApiKeyByName(name string, meta interface{}) (*client.ApiKey, error) { @@ -138,6 +141,7 @@ func getApiKeyByName(name string, meta interface{}) (*client.ApiKey, error) { } var foundApiKeys []client.ApiKey + for _, apiKey := range apiKeys { if apiKey.Name == name { foundApiKeys = append(foundApiKeys, apiKey) @@ -159,9 +163,11 @@ func getApiKey(ctx context.Context, id string, meta interface{}) (*client.ApiKey _, err := uuid.Parse(id) if err == nil { tflog.Info(ctx, "Resolving api key by id", map[string]interface{}{"id": id}) + return getApiKeyById(id, meta) } else { tflog.Info(ctx, "Resolving api key by name", map[string]interface{}{"name": id}) + return getApiKeyByName(id, meta) } } @@ -169,11 +175,12 @@ func getApiKey(ctx context.Context, id string, meta interface{}) (*client.ApiKey func resourceApiKeyImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { apiKey, err := getApiKey(ctx, d.Id(), meta) if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, fmt.Errorf("api key with id %v not found", d.Id()) + } + return nil, err } - if apiKey == nil { - return nil, fmt.Errorf("api key with id %v not found", d.Id()) - } if err := writeResourceData(apiKey, d); err != nil { return nil, fmt.Errorf("schema resource data serialization failed: %w", err) diff --git a/env0/resource_approval_policy.go b/env0/resource_approval_policy.go index 71f4e606..8b88fe88 100644 --- a/env0/resource_approval_policy.go +++ b/env0/resource_approval_policy.go @@ -53,6 +53,7 @@ func resourceApprovalPolicyRead(ctx context.Context, d *schema.ResourceData, met if approvalPolicy.IsDeleted && !d.IsNewResource() { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } diff --git a/env0/resource_approval_policy_assignment.go b/env0/resource_approval_policy_assignment.go index 01c75cf5..3e855121 100644 --- a/env0/resource_approval_policy_assignment.go +++ b/env0/resource_approval_policy_assignment.go @@ -85,6 +85,7 @@ func resourceApprovalPolicyAssignmentRead(ctx context.Context, d *schema.Resourc } found := false + for _, approvalPolicyByScope := range approvalPolicyByScopeArr { if approvalPolicyByScope.ApprovalPolicy.Id == assignment.BlueprintId { found = true @@ -96,6 +97,7 @@ func resourceApprovalPolicyAssignmentRead(ctx context.Context, d *schema.Resourc if !found { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } diff --git a/env0/resource_aws_credentials.go b/env0/resource_aws_credentials.go index 98bfbf43..21972455 100644 --- a/env0/resource_aws_credentials.go +++ b/env0/resource_aws_credentials.go @@ -63,6 +63,7 @@ func resourceAwsCredentials() *schema.Resource { func resourceAwsCredentialsCreate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { _, accessKeyExist := d.GetOk("access_key_id") _, arnExist := d.GetOk("arn") + if !accessKeyExist && !arnExist { // Due to "import" must be inforced here and not in the schema level. // This fields are only available during creation (will not be returned in read or import). diff --git a/env0/resource_cloud_credentials_project_assignment.go b/env0/resource_cloud_credentials_project_assignment.go index abc0c124..37245a15 100644 --- a/env0/resource_cloud_credentials_project_assignment.go +++ b/env0/resource_cloud_credentials_project_assignment.go @@ -71,9 +71,11 @@ func resourceCloudCredentialsProjectAssignmentRead(ctx context.Context, d *schem found = true } } + if !found && !d.IsNewResource() { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } @@ -90,9 +92,11 @@ func resourceCloudCredentialsProjectAssignmentDelete(ctx context.Context, d *sch apiClient := meta.(client.ApiClientInterface) credentialId, projectId := getCredentialIdAndProjectId(d) + err := apiClient.RemoveCloudCredentialsFromProject(projectId, credentialId) if err != nil { return diag.Errorf("could not delete cloud credentials from project: %v", err) } + return nil } diff --git a/env0/resource_configuration_variable.go b/env0/resource_configuration_variable.go index 63e51dc6..63a0ef52 100644 --- a/env0/resource_configuration_variable.go +++ b/env0/resource_configuration_variable.go @@ -126,20 +126,24 @@ func validateNilValue(isReadOnly bool, isRequired bool, value string) error { if isReadOnly && isRequired && value == "" { return errors.New("'value' cannot be empty when 'is_read_only' and 'is_required' are true ") } + return nil } func whichScope(d *schema.ResourceData) (client.Scope, string) { scope := client.ScopeGlobal scopeId := "" + if projectId, ok := d.GetOk("project_id"); ok { scope = client.ScopeProject scopeId = projectId.(string) } + if templateId, ok := d.GetOk("template_id"); ok { scope = client.ScopeTemplate scopeId = templateId.(string) } + if environmentId, ok := d.GetOk("environment_id"); ok { scope = client.ScopeEnvironment scopeId = environmentId.(string) @@ -150,6 +154,7 @@ func whichScope(d *schema.ResourceData) (client.Scope, string) { func getConfigurationVariableCreateParams(d *schema.ResourceData) (*client.ConfigurationVariableCreateParams, error) { scope, scopeId := whichScope(d) + params := client.ConfigurationVariableCreateParams{Scope: scope, ScopeId: scopeId} if err := readResourceData(¶ms, d); err != nil { return nil, fmt.Errorf("schema resource data deserialization failed: %w", err) @@ -187,7 +192,9 @@ func resourceConfigurationVariableCreate(ctx context.Context, d *schema.Resource func getEnum(d *schema.ResourceData, selectedValue string) ([]string, error) { var enumValues []interface{} + var actualEnumValues []string + if specified, ok := d.GetOk("enum"); ok { enumValues = specified.([]interface{}) valueExists := false @@ -203,10 +210,12 @@ func getEnum(d *schema.ResourceData, selectedValue string) ([]string, error) { valueExists = true } } + if !valueExists { return nil, fmt.Errorf("value - '%s' is not one of the enum options %v", selectedValue, actualEnumValues) } } + return actualEnumValues, nil } @@ -258,28 +267,33 @@ func resourceConfigurationVariableDelete(ctx context.Context, d *schema.Resource apiClient := meta.(client.ApiClientInterface) id := d.Id() + err := apiClient.ConfigurationVariableDelete(id) if err != nil { return diag.Errorf("could not delete configurationVariable: %v", err) } + return nil } func resourceConfigurationVariableImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { var configurationParams ConfigurationVariableParams + inputData := d.Id() // soft delete isn't part of the configuration variable, so we need to set it d.Set("soft_delete", false) err := json.Unmarshal([]byte(inputData), &configurationParams) + if err != nil { + return nil, err + } + // We need this conversion since getConfigurationVariable query by the scope and in our BE we use blueprint as the scope name instead of template if string(configurationParams.Scope) == "TEMPLATE" { configurationParams.Scope = "BLUEPRINT" } - if err != nil { - return nil, err - } + variable, getErr := getConfigurationVariable(configurationParams, meta) if getErr != nil { return nil, errors.New(getErr[0].Summary) diff --git a/env0/resource_configuration_variable_test.go b/env0/resource_configuration_variable_test.go index 063d7512..8b23a780 100644 --- a/env0/resource_configuration_variable_test.go +++ b/env0/resource_configuration_variable_test.go @@ -181,12 +181,14 @@ resource "{{.resourceType}}" "{{.projResourceName}}" { if err != nil { panic(err) } + var tpl bytes.Buffer err = tmpl.Execute(&tpl, data) if err != nil { panic(err) } + stepConfig := tpl.String() testStep := resource.TestStep{ diff --git a/env0/resource_cost_credentials.go b/env0/resource_cost_credentials.go index 6cd6e053..3f36d2cc 100644 --- a/env0/resource_cost_credentials.go +++ b/env0/resource_cost_credentials.go @@ -75,6 +75,7 @@ func resourceCostCredentials(providerName string) *schema.Resource { getPayload := func(d *schema.ResourceData) (client.CredentialCreatePayload, error) { var payload client.CredentialCreatePayload + var value interface{} switch providerName { @@ -168,8 +169,8 @@ func resourceCostCredentialsRead(ctx context.Context, d *schema.ResourceData, me if _, err := apiClient.CloudCredentials(d.Id()); err != nil { return ResourceGetFailure(ctx, "cost credentials", d, err) } - return nil + return nil } func resourceCostCredentialsDelete(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { diff --git a/env0/resource_cost_credentials_project_assignment.go b/env0/resource_cost_credentials_project_assignment.go index 44608fe6..41a06da5 100644 --- a/env0/resource_cost_credentials_project_assignment.go +++ b/env0/resource_cost_credentials_project_assignment.go @@ -35,6 +35,7 @@ func resourceCostCredentialsProjectAssignmentCreate(ctx context.Context, d *sche apiClient := meta.(client.ApiClientInterface) credentialId, projectId := getCredentialIdAndProjectId(d) + result, err := apiClient.AssignCostCredentialsToProject(projectId, credentialId) if err != nil { return diag.Errorf("could not assign cost credentials to project: %v", err) @@ -65,6 +66,7 @@ func resourceCostdCredentialsProjectAssignmentRead(ctx context.Context, d *schem if !found && !d.IsNewResource() { d.SetId("") + return nil } @@ -77,9 +79,11 @@ func resourceCostCredentialsProjectAssignmentDelete(ctx context.Context, d *sche apiClient := meta.(client.ApiClientInterface) credentialId, projectId := getCredentialIdAndProjectId(d) + err := apiClient.RemoveCostCredentialsFromProject(projectId, credentialId) if err != nil { return diag.Errorf("could not delete cost credentials from project: %v", err) } + return nil } diff --git a/env0/resource_custom_flow.go b/env0/resource_custom_flow.go index 25212946..a5378d5d 100644 --- a/env0/resource_custom_flow.go +++ b/env0/resource_custom_flow.go @@ -104,9 +104,11 @@ func getCustomFlowByName(name string, meta interface{}) (*client.CustomFlow, err func getCustomFlow(ctx context.Context, id string, meta interface{}) (*client.CustomFlow, error) { if _, err := uuid.Parse(id); err == nil { tflog.Info(ctx, "Resolving custom flow by id", map[string]interface{}{"id": id}) + return meta.(client.ApiClientInterface).CustomFlow(id) } else { tflog.Info(ctx, "Resolving custom flow by name", map[string]interface{}{"name": id}) + return getCustomFlowByName(id, meta) } } @@ -118,7 +120,7 @@ func resourceCustomFlowImport(ctx context.Context, d *schema.ResourceData, meta } if err := writeResourceData(customFlow, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_custom_flow_assignment.go b/env0/resource_custom_flow_assignment.go index 4c9492a6..5256a96e 100644 --- a/env0/resource_custom_flow_assignment.go +++ b/env0/resource_custom_flow_assignment.go @@ -20,7 +20,9 @@ func getCustomFlowAssignmentFromId(d *schema.ResourceData) (client.CustomFlowAss split := strings.Split(id, "|") var assignment client.CustomFlowAssignment + assignment.ScopeId = split[0] + if len(split) > 1 { assignment.BlueprintId = split[1] } else { @@ -94,9 +96,11 @@ func resourceCustomFlowAssignmentRead(ctx context.Context, d *schema.ResourceDat } found := false + for _, assignment := range assignments { if assignment.BlueprintId == assignmentFromId.BlueprintId { found = true + break } } @@ -104,6 +108,7 @@ func resourceCustomFlowAssignmentRead(ctx context.Context, d *schema.ResourceDat if !found && !d.IsNewResource() { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } diff --git a/env0/resource_custom_flow_assignment_test.go b/env0/resource_custom_flow_assignment_test.go index 4134b01d..c17212c3 100644 --- a/env0/resource_custom_flow_assignment_test.go +++ b/env0/resource_custom_flow_assignment_test.go @@ -115,5 +115,4 @@ func TestUnitResourceCustomFlowAssignmentResource(t *testing.T) { ) }) }) - } diff --git a/env0/resource_custom_role.go b/env0/resource_custom_role.go index dc766c20..16404a78 100644 --- a/env0/resource_custom_role.go +++ b/env0/resource_custom_role.go @@ -160,6 +160,7 @@ func getCustomRoleByName(name string, meta interface{}) (*client.Role, error) { } var foundRoles []client.Role + for _, role := range roles { if role.Name == name { foundRoles = append(foundRoles, role) @@ -181,24 +182,28 @@ func getCustomRole(ctx context.Context, id string, meta interface{}) (*client.Ro _, err := uuid.Parse(id) if err == nil { tflog.Info(ctx, "Resolving custom role by id", map[string]interface{}{"id": id}) + return getCustomRoleById(id, meta) } else { tflog.Info(ctx, "Resolving custom role by name", map[string]interface{}{"name": id}) + return getCustomRoleByName(id, meta) } } func resourceCustomRoleImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { role, err := getCustomRole(ctx, d.Id(), meta) + if err != nil { return nil, err } + if role == nil { return nil, fmt.Errorf("custom role with id %v not found", d.Id()) } if err := writeResourceData(role, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_drift_detection.go b/env0/resource_drift_detection.go index a83fa9ae..0f6648eb 100644 --- a/env0/resource_drift_detection.go +++ b/env0/resource_drift_detection.go @@ -49,6 +49,7 @@ func resourceEnvironmentDriftRead(ctx context.Context, d *schema.ResourceData, m if !drift.Enabled { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } diff --git a/env0/resource_drift_detection_test.go b/env0/resource_drift_detection_test.go index 3a18666b..8e5f9853 100644 --- a/env0/resource_drift_detection_test.go +++ b/env0/resource_drift_detection_test.go @@ -15,6 +15,7 @@ func TestUnitEnvironmentDriftDetectionResource(t *testing.T) { accessor := resourceAccessor(resourceType, resourceName) drift := client.EnvironmentSchedulingExpression{Cron: "2 * * * *", Enabled: true} updateDrift := client.EnvironmentSchedulingExpression{Cron: "2 2 * * *", Enabled: true} + t.Run("Success", func(t *testing.T) { testCase := resource.TestCase{ Steps: []resource.TestStep{ diff --git a/env0/resource_environment.go b/env0/resource_environment.go index 41af1dc8..60511e9a 100644 --- a/env0/resource_environment.go +++ b/env0/resource_environment.go @@ -65,6 +65,7 @@ func getSubEnvironments(d *schema.ResourceData) ([]SubEnvironment, error) { func isTemplateless(d *schema.ResourceData) bool { _, ok := d.GetOk("without_template_settings.0") + return ok } @@ -91,6 +92,7 @@ func resourceEnvironment() *schema.Resource { if value != client.ENVIRONMENT && value != client.TERRAFORM { errs = append(errs, fmt.Errorf("%q can be either \"environment\" or \"terraform\", got: %q", key, value)) } + return }, }, @@ -240,6 +242,7 @@ func resourceEnvironment() *schema.Resource { if !matched || err != nil { errs = append(errs, fmt.Errorf("%q must be of iso format (for example: \"2021-12-13T10:00:00Z\"), got: %q", key, ttl)) } + return }, }, @@ -394,7 +397,7 @@ func setEnvironmentSchema(ctx context.Context, d *schema.ResourceData, environme return fmt.Errorf("schema resource data serialization failed: %w", err) } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if _, exists := d.GetOkExists("vcs_pr_comments_enabled"); !exists { // VcsPrCommentsEnabled may have been "forced" to be 'true', ignore any drifts if not explicitly configured in the environment resource. } else { @@ -472,6 +475,7 @@ func setEnvironmentSchema(ctx context.Context, d *schema.ResourceData, environme for _, newv := range variableSetsIds { found := false + for _, sortedv := range sortedVariablesSet { if newv == sortedv { found = true @@ -562,6 +566,7 @@ func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta } var environment client.Environment + var err error if !isTemplateless(d) { @@ -599,6 +604,7 @@ func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta d.SetId(environment.Id) d.Set("deployment_id", environment.LatestDeploymentLogId) + if environment.AutoDeployOnPathChangesOnly != nil { d.Set("auto_deploy_on_path_changes_only", *environment.AutoDeployOnPathChangesOnly) } @@ -622,6 +628,7 @@ func getEnvironmentVariableSetIdsFromApi(d *schema.ResourceData, apiClient clien } var environmentVariableSetIds []string + for _, variableSet := range environmentVariableSets { if variableSet.AssignmentScope == client.ENVIRONMENT { environmentVariableSetIds = append(environmentVariableSetIds, variableSet.Id) @@ -661,6 +668,7 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i if isTemplateless(d) { // environment with no template. templateId := d.Get("without_template_settings.0.id").(string) + template, err := apiClient.Template(templateId) if err != nil { return diag.Errorf("could not get template: %v", err) @@ -736,7 +744,7 @@ func shouldUpdate(d *schema.ResourceData) bool { return true } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("vcs_pr_comments_enabled"); exists { // if this field is set to 'false' will return that there is change each time. // this is because the terraform SDK is unable to detecred changes between 'unset' and 'false' (sdk limitation). @@ -806,6 +814,7 @@ func deploy(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Di for i, subEnvironment := range subEnvironments { configuration := d.Get(fmt.Sprintf("sub_environment_configuration.%d.configuration", i)).([]interface{}) configurationChanges := getConfigurationVariablesFromSchema(configuration) + configurationChanges, err = getUpdateConfigurationVariables(configurationChanges, subEnvironment.Id, client.ScopeEnvironment, apiClient) if err != nil { return diag.FromErr(err) @@ -845,16 +854,19 @@ func update(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Di if err != nil { return diag.Errorf("could not update environment: %v", err) } + return nil } func updateTTL(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Diagnostics { ttl := d.Get("ttl").(string) payload := getTTl(ttl) + _, err := apiClient.EnvironmentUpdateTTL(d.Id(), payload) if err != nil { return diag.Errorf("could not update the environment's ttl: %v", err) } + return nil } @@ -893,8 +905,10 @@ func resourceEnvironmentDelete(ctx context.Context, d *schema.ResourceData, meta if err != nil { if frerr, ok := err.(*http.FailedResponseError); ok && frerr.BadRequest() { tflog.Warn(ctx, "Could not delete environment. Already deleted?", map[string]interface{}{"id": d.Id(), "error": frerr.Error()}) + return nil } + return diag.Errorf("could not delete environment: %v", err) } @@ -925,20 +939,24 @@ func waitForEnvironmentDestroy(ctx context.Context, apiClient client.ApiClientIn deployment, err := apiClient.EnvironmentDeploymentLog(deploymentId) if err != nil { results <- fmt.Errorf("failed to get environment deployment '%s': %w", deploymentId, err) + return } if slices.Contains([]string{"TIMEOUT", "FAILURE", "CANCELLED", "INTERNAL_FAILURE", "ABORTING", "ABORTED", "SKIPPED", "NEVER_DEPLOYED"}, deployment.Status) { results <- fmt.Errorf("failed to wait for environment destroy to complete, deployment status is: %s", deployment.Status) + return } if deployment.Status == "SUCCESS" { results <- nil + return } tflog.Info(ctx, "current 'destroy' deployment status", map[string]interface{}{"deploymentId": deploymentId, "status": deployment.Status}) + if deployment.Status == "WAITING_FOR_USER" { tflog.Warn(ctx, "waiting for user approval (Env0 UI) to proceed with 'destroy' deployment") } @@ -946,6 +964,7 @@ func waitForEnvironmentDestroy(ctx context.Context, apiClient client.ApiClientIn select { case <-timer.C: results <- fmt.Errorf("timeout! last 'destroy' deployment status was '%s'", deployment.Status) + return case <-ticker.C: continue @@ -963,34 +982,34 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac return client.EnvironmentCreate{}, diag.Errorf("schema resource data deserialization failed: %v", err) } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("vcs_pr_comments_enabled"); exists { payload.VcsPrCommentsEnabled = boolPtr(val.(bool)) } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("deploy_on_push"); exists { payload.ContinuousDeployment = boolPtr(val.(bool)) } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("run_plan_on_pull_requests"); exists { payload.PullRequestPlanDeployments = boolPtr(val.(bool)) } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("prevent_auto_deploy"); exists { payload.PreventAutoDeploy = boolPtr(val.(bool)) } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("auto_deploy_on_path_changes_only"); exists { payload.AutoDeployOnPathChangesOnly = boolPtr(val.(bool)) } _, isWorkflow := d.GetOk("sub_environment_configuration") - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("approve_plan_automatically"); exists { payload.RequiresApproval = boolPtr(!val.(bool)) @@ -1004,7 +1023,7 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac payload.RequiresApproval = boolPtr(false) } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("is_remote_backend"); exists { payload.IsRemoteBackend = boolPtr(val.(bool)) } @@ -1082,6 +1101,7 @@ func assertEnvironment(d *schema.ResourceData) diag.Diagnostics { if !continuousDeployment && !pullRequestPlanDeployments { return diag.Errorf("run_plan_on_pull_requests or deploy_on_push must be enabled for auto_deploy_by_custom_glob") } + if !autoDeployOnPathChangesOnly { return diag.Errorf("cannot set auto_deploy_by_custom_glob when auto_deploy_on_path_changes_only is disabled") } @@ -1090,6 +1110,7 @@ func assertEnvironment(d *schema.ResourceData) diag.Diagnostics { isRemoteApplyEnabled := d.Get("is_remote_apply_enabled").(bool) isRemotedBackend := d.Get("is_remote_backend").(bool) approvePlanAutomatically := d.Get("approve_plan_automatically").(bool) + if isRemoteApplyEnabled && (!isRemotedBackend || !approvePlanAutomatically) { return diag.Errorf("cannot set is_remote_apply_enabled when approve_plan_automatically or is_remote_backend are disabled") } @@ -1105,7 +1126,7 @@ func getUpdatePayload(d *schema.ResourceData) (client.EnvironmentUpdate, diag.Di } // Because the terraform SDK is unable to detecred changes between 'unset' and 'false' (sdk limitation): always set a value here (even if there's no change). - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("vcs_pr_comments_enabled"); exists { payload.VcsPrCommentsEnabled = boolPtr(val.(bool)) } @@ -1148,12 +1169,14 @@ func getUpdatePayload(d *schema.ResourceData) (client.EnvironmentUpdate, diag.Di func getEnvironmentConfigurationSetChanges(d *schema.ResourceData, apiClient client.ApiClientInterface) (*client.ConfigurationSetChanges, error) { variableSetsFromSchema := getEnvironmentVariableSetIdsFromSchema(d) + variableSetFromApi, err := getEnvironmentVariableSetIdsFromApi(d, apiClient) if err != nil { return nil, err } var assignVariableSets []string + var unassignVariableSets []string for _, sv := range variableSetsFromSchema { @@ -1200,6 +1223,7 @@ func getEnvironmentConfigurationSetChanges(d *schema.ResourceData, apiClient cli func getDeployPayload(d *schema.ResourceData, apiClient client.ApiClientInterface, isRedeploy bool) (client.DeployRequest, error) { payload := client.DeployRequest{} + var err error if isTemplateless(d) { @@ -1246,7 +1270,7 @@ func getDeployPayload(d *schema.ResourceData, apiClient client.ApiClientInterfac } } - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if val, exists := d.GetOkExists("approve_plan_automatically"); exists { payload.UserRequiresApproval = boolPtr(!val.(bool)) } @@ -1261,6 +1285,7 @@ func getTTl(date string) client.TTL { Value: date, } } + return client.TTL{ Type: client.TTlTypeInfinite, Value: "", @@ -1287,11 +1312,13 @@ func deleteUnusedConfigurationVariables(configurationChanges client.Configuratio configurationChanges = append(configurationChanges, existVariable) } } + return configurationChanges } func linkToExistConfigurationVariables(configurationChanges client.ConfigurationChanges, existVariables client.ConfigurationChanges) client.ConfigurationChanges { updateConfigurationChanges := client.ConfigurationChanges{} + for _, change := range configurationChanges { if isExist, existVariable := isVariableExist(existVariables, change); isExist { change.Id = existVariable.Id @@ -1299,6 +1326,7 @@ func linkToExistConfigurationVariables(configurationChanges client.Configuration updateConfigurationChanges = append(updateConfigurationChanges, change) } + return updateConfigurationChanges } @@ -1308,6 +1336,7 @@ func isVariableExist(variables client.ConfigurationChanges, search client.Config return true, variable } } + return false, client.ConfigurationVariable{} } @@ -1319,12 +1348,14 @@ func typeEqual(variable client.ConfigurationVariable, search client.Configuratio func getEnvironmentByName(meta interface{}, name string, projectId string, excludeArchived bool) (client.Environment, diag.Diagnostics) { apiClient := meta.(client.ApiClientInterface) + environmentsByName, err := apiClient.EnvironmentsByName(name) if err != nil { return client.Environment{}, diag.Errorf("Could not get Environment: %v", err) } filteredEnvironments := []client.Environment{} + for _, candidate := range environmentsByName { if excludeArchived && candidate.IsArchived != nil && *candidate.IsArchived { continue @@ -1350,15 +1381,18 @@ func getEnvironmentByName(meta interface{}, name string, projectId string, exclu func getEnvironmentById(environmentId string, meta interface{}) (client.Environment, diag.Diagnostics) { apiClient := meta.(client.ApiClientInterface) + environment, err := apiClient.Environment(environmentId) if err != nil { return client.Environment{}, diag.Errorf("Could not find environment: %v", err) } + return environment, nil } func resourceEnvironmentImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { id := d.Id() + var getErr diag.Diagnostics var environment client.Environment @@ -1427,6 +1461,7 @@ func resourceEnvironmentImport(ctx context.Context, d *schema.ResourceData, meta } d.Set("is_inactive", false) // default is false. + if environment.IsArchived != nil { d.Set("is_inactive", *environment.IsArchived) } diff --git a/env0/resource_environment_discovery_configuration.go b/env0/resource_environment_discovery_configuration.go index 32d6bfbf..ae753071 100644 --- a/env0/resource_environment_discovery_configuration.go +++ b/env0/resource_environment_discovery_configuration.go @@ -270,7 +270,7 @@ func resourceEnvironmentDiscoveryConfigurationDelete(ctx context.Context, d *sch func setResourceEnvironmentDiscoveryConfiguration(d *schema.ResourceData, getPayload *client.EnvironmentDiscoveryPayload) error { if err := writeResourceData(getPayload, d); err != nil { - return fmt.Errorf("schema resource data serialization failed: %v", err) + return fmt.Errorf("schema resource data serialization failed: %w", err) } discoveryWriteSshKeyHelper(getPayload, d) diff --git a/env0/resource_environment_output_configuration_variable.go b/env0/resource_environment_output_configuration_variable.go index 6d91c3b1..05a5574a 100644 --- a/env0/resource_environment_output_configuration_variable.go +++ b/env0/resource_environment_output_configuration_variable.go @@ -134,7 +134,7 @@ func deserializeEnvironmentOutputConfigurationVariableValue(valueStr string) (*E func getEnvironmentOutputConfigurationVariableParamsFromVariable(d *schema.ResourceData, variable *client.ConfigurationVariable) (*EnvironmentOutputConfigurationVariableParams, error) { var params EnvironmentOutputConfigurationVariableParams if err := readResourceData(¶ms, d); err != nil { - return nil, fmt.Errorf("schema resource data deserialization failed: %v", err) + return nil, fmt.Errorf("schema resource data deserialization failed: %w", err) } params.Name = variable.Name @@ -153,9 +153,9 @@ func getEnvironmentOutputConfigurationVariableParamsFromVariable(d *schema.Resou } if variable.Type == nil || *variable.Type == client.ConfigurationVariableTypeEnvironment { - params.Type = "environment" + params.Type = client.ENVIRONMENT } else { - params.Type = "terraform" + params.Type = client.TERRAFORM } params.ScopeId = variable.ScopeId @@ -182,7 +182,7 @@ func getEnvironmentOutputConfigurationVariableParamsFromVariable(d *schema.Resou func getEnvironmentOutputCreateParams(d *schema.ResourceData) (*client.ConfigurationVariableCreateParams, error) { var params EnvironmentOutputConfigurationVariableParams if err := readResourceData(¶ms, d); err != nil { - return nil, fmt.Errorf("schema resource data deserialization failed: %v", err) + return nil, fmt.Errorf("schema resource data deserialization failed: %w", err) } if params.Scope != string(client.ScopeProject) && params.IsReadOnly { @@ -195,7 +195,7 @@ func getEnvironmentOutputCreateParams(d *schema.ResourceData) (*client.Configura } variableType := client.ConfigurationVariableTypeEnvironment - if params.Type == "terraform" { + if params.Type == client.TERRAFORM { variableType = client.ConfigurationVariableTypeTerraform } @@ -274,14 +274,14 @@ func resourceEnvironmentOutputConfigurationVariableImport(ctx context.Context, d inputData := d.Id() err := json.Unmarshal([]byte(inputData), &configurationParams) + if err != nil { + return nil, err + } // We need this conversion since getConfigurationVariable query by the scope and in our BE we use blueprint as the scope name instead of template if string(configurationParams.Scope) == "TEMPLATE" { configurationParams.Scope = "BLUEPRINT" } - if err != nil { - return nil, err - } variable, getErr := getConfigurationVariable(configurationParams, meta) if getErr != nil { diff --git a/env0/resource_environment_scheduling_test.go b/env0/resource_environment_scheduling_test.go index 7739061a..f47acdb3 100644 --- a/env0/resource_environment_scheduling_test.go +++ b/env0/resource_environment_scheduling_test.go @@ -60,7 +60,6 @@ func TestUnitEnvironmentSchedulingResource(t *testing.T) { } runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) - }) } @@ -77,6 +76,5 @@ func TestUnitEnvironmentSchedulingResource(t *testing.T) { } runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {}) - }) } diff --git a/env0/resource_environment_test.go b/env0/resource_environment_test.go index c691e9d0..7a71a1e0 100644 --- a/env0/resource_environment_test.go +++ b/env0/resource_environment_test.go @@ -844,6 +844,7 @@ func TestUnitEnvironmentResource(t *testing.T) { deploymentWithStatus := func(status string) *client.DeploymentLog { newDeployment := deploymentLog newDeployment.Status = status + return &newDeployment } @@ -1324,6 +1325,7 @@ func TestUnitEnvironmentResource(t *testing.T) { } formatVariables := func(variables []client.ConfigurationVariable) string { format := "" + for _, variable := range variables { schemaFormat := "" @@ -1342,12 +1344,13 @@ func TestUnitEnvironmentResource(t *testing.T) { `, variable.Schema.Type, variable.Schema.Format) } - } + varType := "environment" if *variable.Type == client.ConfigurationVariableTypeTerraform { varType = "terraform" } + format += fmt.Sprintf(`configuration{ name = "%s" value = "%s" @@ -1459,6 +1462,7 @@ func TestUnitEnvironmentResource(t *testing.T) { BlueprintRevision: environment.LatestDeploymentLog.BlueprintRevision, }, ConfigurationChanges: &client.ConfigurationChanges{configurationVariables}, }).Times(1).Return(environment, nil) + configurationVariables.Id = "generated-id-from-server" varTrue := true @@ -1469,6 +1473,7 @@ func TestUnitEnvironmentResource(t *testing.T) { mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, updatedEnvironment.Id).Times(3).Return(client.ConfigurationChanges{configurationVariables}, nil), // read after create -> on update mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, updatedEnvironment.Id).Times(1).Return(redeployConfigurationVariables, nil), // read after create -> on update -> read after update ) + redeployConfigurationVariables[0].Scope = client.ScopeDeployment redeployConfigurationVariables[0].IsSensitive = &isSensitive redeployConfigurationVariables[0].IsReadOnly = boolPtr(false) @@ -1983,7 +1988,6 @@ func TestUnitEnvironmentResource(t *testing.T) { mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1) }) }) - } testTTL := func() { @@ -2237,7 +2241,6 @@ func TestUnitEnvironmentResource(t *testing.T) { mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1) }) }) - } testForceDestroy := func() { @@ -2304,7 +2307,6 @@ func TestUnitEnvironmentResource(t *testing.T) { mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", updatedEnvironment.Id).Times(5).Return(nil, nil) mock.EXPECT().Environment(gomock.Any()).Times(5).Return(environment, nil) mock.EXPECT().EnvironmentDestroy(gomock.Any()).Times(1) - }) }) } @@ -2390,7 +2392,6 @@ func TestUnitEnvironmentResource(t *testing.T) { ProjectIds: []string{"no-match"}, }, nil) }) - }) t.Run("fail when trying to create an inactive environment", func(t *testing.T) { @@ -2439,7 +2440,6 @@ func TestUnitEnvironmentResource(t *testing.T) { K8sNamespace: environment.K8sNamespace, }).Times(1).Return(client.Environment{}, errors.New("error")) }) - }) t.Run("Failure in update", func(t *testing.T) { @@ -2485,7 +2485,6 @@ func TestUnitEnvironmentResource(t *testing.T) { mock.EXPECT().Environment(gomock.Any()).Times(2).Return(environment, nil) // 1 after create, 1 before update mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1) }) - }) t.Run("Failure in deploy", func(t *testing.T) { @@ -2533,7 +2532,6 @@ func TestUnitEnvironmentResource(t *testing.T) { mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(2).Return(client.ConfigurationChanges{}, nil) mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", environment.Id).Times(3).Return(nil, nil) }) - }) t.Run("Failure in read", func(t *testing.T) { @@ -2566,7 +2564,6 @@ func TestUnitEnvironmentResource(t *testing.T) { mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1) mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(0).Return(client.ConfigurationChanges{}, nil) }) - }) } @@ -2924,7 +2921,6 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) { mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1), ) }) - }) t.Run("Import By Id", func(t *testing.T) { diff --git a/env0/resource_gcp_credentials_test.go b/env0/resource_gcp_credentials_test.go index bd572729..8702e12b 100644 --- a/env0/resource_gcp_credentials_test.go +++ b/env0/resource_gcp_credentials_test.go @@ -130,7 +130,6 @@ func TestUnitGcpCredentialsResource(t *testing.T) { }) t.Run("validate missing arguments", func(t *testing.T) { - missingArgumentsTestCases := []resource.TestCase{ missingArgumentTestCase(resourceType, resourceName, map[string]interface{}{ "name": "update", @@ -139,13 +138,13 @@ func TestUnitGcpCredentialsResource(t *testing.T) { "service_account_key": "update", }, "name"), } + for _, testCase := range missingArgumentsTestCases { tc := testCase t.Run("validate missing arguments", func(t *testing.T) { runUnitTest(t, tc, func(mock *client.MockApiClientInterface) {}) }) - } }) diff --git a/env0/resource_git_token.go b/env0/resource_git_token.go index cb76d418..ac92cfcd 100644 --- a/env0/resource_git_token.go +++ b/env0/resource_git_token.go @@ -89,6 +89,7 @@ func getGitTokenByName(name string, meta interface{}) (*client.GitToken, error) } var foundGitTokens []client.GitToken + for _, gitToken := range gitTokens { if gitToken.Name == name { foundGitTokens = append(foundGitTokens, gitToken) @@ -110,9 +111,11 @@ func getGitToken(ctx context.Context, id string, meta interface{}) (*client.GitT _, err := uuid.Parse(id) if err == nil { tflog.Info(ctx, "Resolving git token by id", map[string]interface{}{"id": id}) + return meta.(client.ApiClientInterface).GitToken(id) } else { tflog.Info(ctx, "Resolving git token by name", map[string]interface{}{"name": id}) + return getGitTokenByName(id, meta) } } @@ -124,7 +127,7 @@ func resourceGitTokenImport(ctx context.Context, d *schema.ResourceData, meta in } if err := writeResourceData(gitToken, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_git_token_test.go b/env0/resource_git_token_test.go index 773e24d6..cf0ce695 100644 --- a/env0/resource_git_token_test.go +++ b/env0/resource_git_token_test.go @@ -156,5 +156,4 @@ func TestUnitGitTokenResource(t *testing.T) { mock.EXPECT().GitTokenDelete(gitToken.Id).Times(1) }) }) - } diff --git a/env0/resource_gpg_key.go b/env0/resource_gpg_key.go index ba511777..b2291ec1 100644 --- a/env0/resource_gpg_key.go +++ b/env0/resource_gpg_key.go @@ -110,6 +110,7 @@ func getGpgKeyByName(name string, meta interface{}) (*client.GpgKey, error) { } var foundGpgKeys []client.GpgKey + for _, gpgKey := range gpgKeys { if gpgKey.Name == name { foundGpgKeys = append(foundGpgKeys, gpgKey) @@ -131,9 +132,11 @@ func getGpgKey(ctx context.Context, idOrName string, meta interface{}) (*client. _, err := uuid.Parse(idOrName) if err == nil { tflog.Info(ctx, "Resolving gpg key by id", map[string]interface{}{"id": idOrName}) + return getGpgKeyById(idOrName, meta) } else { tflog.Info(ctx, "Resolving gpg key by name", map[string]interface{}{"name": idOrName}) + return getGpgKeyByName(idOrName, meta) } } @@ -145,7 +148,7 @@ func resourceGpgKeyImport(ctx context.Context, d *schema.ResourceData, meta inte } if err := writeResourceData(gpgKey, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_module.go b/env0/resource_module.go index 6a0adae2..fc29a91e 100644 --- a/env0/resource_module.go +++ b/env0/resource_module.go @@ -198,6 +198,7 @@ func getModuleByName(name string, meta interface{}) (*client.Module, error) { } var foundModules []client.Module + for _, module := range modules { if module.ModuleName == name { foundModules = append(foundModules, module) @@ -219,9 +220,11 @@ func getModule(ctx context.Context, id string, meta interface{}) (*client.Module _, err := uuid.Parse(id) if err == nil { tflog.Info(ctx, "Resolving module by id", map[string]interface{}{"id": id}) + return meta.(client.ApiClientInterface).Module(id) } else { tflog.Info(ctx, "Resolving module by name", map[string]interface{}{"name": id}) + return getModuleByName(id, meta) } } @@ -233,7 +236,7 @@ func resourceModuleImport(ctx context.Context, d *schema.ResourceData, meta inte } if err := writeResourceData(module, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_module_test.go b/env0/resource_module_test.go index dfb479d2..e76ee393 100644 --- a/env0/resource_module_test.go +++ b/env0/resource_module_test.go @@ -493,5 +493,4 @@ func TestUnitModuleResource(t *testing.T) { mock.EXPECT().ModuleDelete(module.Id).Times(1) }) }) - } diff --git a/env0/resource_notification.go b/env0/resource_notification.go index 0cfe94ef..2cd075e3 100644 --- a/env0/resource_notification.go +++ b/env0/resource_notification.go @@ -2,6 +2,7 @@ package env0 import ( "context" + "errors" "fmt" "github.com/env0/terraform-provider-env0/client" @@ -37,6 +38,7 @@ func resourceNotification() *schema.Resource { if notificationType != client.NotificationTypeSlack && notificationType != client.NotificationTypeTeams && notificationType != client.NotificationTypeEmail && notificationType != client.NotificationTypeWebhook { return diag.Errorf("Invalid notification type") } + return nil }, }, @@ -70,7 +72,7 @@ func getNotificationById(id string, meta interface{}) (*client.Notification, err } } - return nil, nil + return nil, ErrNotFound } func getNotificationByName(name string, meta interface{}) (*client.Notification, error) { @@ -82,6 +84,7 @@ func getNotificationByName(name string, meta interface{}) (*client.Notification, } var foundNotifications []client.Notification + for _, notification := range notifications { if notification.Name == name { foundNotifications = append(foundNotifications, notification) @@ -120,13 +123,17 @@ func resourceNotificationCreate(ctx context.Context, d *schema.ResourceData, met func resourceNotificationRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { notification, err := getNotificationById(d.Id(), meta) if err != nil { + if errors.Is(err, ErrNotFound) { + if notification == nil { + tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) + d.SetId("") + + return nil + } + } + return diag.Errorf("could not get notification: %v", err) } - if notification == nil { - tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) - d.SetId("") - return nil - } if err := writeResourceData(notification, d); err != nil { return diag.Errorf("schema resource data serialization failed: %v", err) @@ -146,7 +153,9 @@ func resourceNotificationUpdate(ctx context.Context, d *schema.ResourceData, met if d.HasChanges("webhook_secret") { webhookSecret := d.Get("webhook_secret").(string) + var strPtr *string + if webhookSecret == "" { // webhook secret was removed - pass 'null pointer' (will pass 'null' in json). // see https://docs.env0.com/reference/notifications-update-notification-endpoint @@ -178,9 +187,11 @@ func getNotification(ctx context.Context, id string, meta interface{}) (*client. _, err := uuid.Parse(id) if err == nil { tflog.Info(ctx, "Resolving notifcation by id", map[string]interface{}{"id": id}) + return getNotificationById(id, meta) } else { tflog.Info(ctx, "Resolving notifcation by name", map[string]interface{}{"name ": id}) + return getNotificationByName(id, meta) } } @@ -188,14 +199,15 @@ func getNotification(ctx context.Context, id string, meta interface{}) (*client. func resourceNotificationImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { notification, err := getNotification(ctx, d.Id(), meta) if err != nil { + if errors.Is(err, ErrNotFound) { + return nil, fmt.Errorf("notification with id %v not found", d.Id()) + } + return nil, err } - if notification == nil { - return nil, fmt.Errorf("notification with id %v not found", d.Id()) - } if err := writeResourceData(notification, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_notification_project_assignment.go b/env0/resource_notification_project_assignment.go index e2bded16..15cef1c1 100644 --- a/env0/resource_notification_project_assignment.go +++ b/env0/resource_notification_project_assignment.go @@ -2,6 +2,7 @@ package env0 import ( "context" + "errors" "fmt" "strings" @@ -100,20 +101,22 @@ func getNotificationProjectAssignment(d *schema.ResourceData, meta interface{}) } } - return nil, nil + return nil, ErrNotFound } func resourceNotificationProjectAssignmentRead(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics { assignment, err := getNotificationProjectAssignment(d, meta) if err != nil { + if errors.Is(err, ErrNotFound) { + // Notification endpoint not found. + tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) + d.SetId("") + + return nil + } + return ResourceGetFailure(ctx, "notification project assignment", d, err) } - if assignment == nil { - // Notification endpoint not found. - tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) - d.SetId("") - return nil - } if err := writeResourceData(assignment, d); err != nil { return diag.Errorf("schema resource data serialization failed: %v", err) diff --git a/env0/resource_notification_test.go b/env0/resource_notification_test.go index f03bc3e5..1ff316c6 100644 --- a/env0/resource_notification_test.go +++ b/env0/resource_notification_test.go @@ -52,6 +52,7 @@ func TestUnitNotificationResource(t *testing.T) { } webhookSecret := "my-little-secret" + var nullString *string t.Run("Success", func(t *testing.T) { diff --git a/env0/resource_organization_policy.go b/env0/resource_organization_policy.go index 688917cb..ccf907e8 100644 --- a/env0/resource_organization_policy.go +++ b/env0/resource_organization_policy.go @@ -74,12 +74,12 @@ func resourceOrganizationPolicyRead(ctx context.Context, d *schema.ResourceData, func validateTtl(defaultTtl *string, maxTtl *string) error { defaultDuration, err := ttlToDuration(defaultTtl) if err != nil { - return fmt.Errorf("invalid default ttl: %v", err) + return fmt.Errorf("invalid default ttl: %w", err) } maxDuration, err := ttlToDuration(maxTtl) if err != nil { - return fmt.Errorf("invalid max ttl: %v", err) + return fmt.Errorf("invalid max ttl: %w", err) } if maxDuration < defaultDuration { diff --git a/env0/resource_project.go b/env0/resource_project.go index b7d9db0d..4158f661 100644 --- a/env0/resource_project.go +++ b/env0/resource_project.go @@ -109,6 +109,7 @@ func resourceProjectUpdate(ctx context.Context, d *schema.ResourceData, meta int apiClient := meta.(client.ApiClientInterface) id := d.Id() + var payload client.ProjectUpdatePayload if d.HasChange("parent_project_id") { @@ -177,6 +178,7 @@ func resourceProjectDelete(ctx context.Context, d *schema.ResourceData, meta int select { case <-timer.C: done <- true + return case <-ticker.C: err := resourceProjectAssertCanDelete(d, meta) @@ -190,6 +192,7 @@ func resourceProjectDelete(ctx context.Context, d *schema.ResourceData, meta int } done <- true + return } } @@ -205,6 +208,7 @@ func resourceProjectDelete(ctx context.Context, d *schema.ResourceData, meta int if err := apiClient.ProjectDelete(id); err != nil { return diag.Errorf("could not delete project: %v", err) } + return nil } @@ -216,6 +220,7 @@ func resourceProjectImport(ctx context.Context, d *schema.ResourceData, meta int if err == nil { tflog.Info(ctx, "Resolving project by id", map[string]interface{}{"id": id}) + if project, err = getProjectById(id, meta); err != nil { return nil, err } diff --git a/env0/resource_project_policy.go b/env0/resource_project_policy.go index a9b38287..a27eac6f 100644 --- a/env0/resource_project_policy.go +++ b/env0/resource_project_policy.go @@ -10,6 +10,9 @@ import ( "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" ) +const INFINITE = "Infinite" +const INHERIT = "inherit" + func resourceProjectPolicy() *schema.Resource { return &schema.Resource{ CreateContext: resourceProjectPolicyCreate, @@ -89,14 +92,14 @@ func resourceProjectPolicy() *schema.Resource { Type: schema.TypeString, Description: "the maximum environment time-to-live allowed on deploy time. Format is - (Examples: 12-h, 3-d, 1-w, 1-M). Default value is 'inherit' which inherits the organization policy. must be equal or longer than default_ttl", Optional: true, - Default: "inherit", + Default: INHERIT, ValidateDiagFunc: ValidateTtl, }, "default_ttl": { Type: schema.TypeString, Description: "the default environment time-to-live allowed on deploy time. Format is - (Examples: 12-h, 3-d, 1-w, 1-M). Default value is 'inherit' which inherits the organization policy. must be equal or shorter than max_ttl", Optional: true, - Default: "inherit", + Default: INHERIT, ValidateDiagFunc: ValidateTtl, }, "force_remote_backend": { @@ -167,7 +170,7 @@ func resourceProjectPolicyUpdate(ctx context.Context, d *schema.ResourceData, me } // Validate if one is "inherit", the other must be too. - if (payload.MaxTtl == "inherit" || payload.DefaultTtl == "inherit") && payload.MaxTtl != payload.DefaultTtl { + if (payload.MaxTtl == INHERIT || payload.DefaultTtl == INHERIT) && payload.MaxTtl != payload.DefaultTtl { return diag.Errorf("max_ttl and default_ttl must both inherit organization settings or override them") } @@ -175,11 +178,11 @@ func resourceProjectPolicyUpdate(ctx context.Context, d *schema.ResourceData, me return diag.FromErr(err) } - if payload.DefaultTtl == "Infinite" { + if payload.DefaultTtl == INFINITE { payload.DefaultTtl = "" } - if payload.MaxTtl == "Infinite" { + if payload.MaxTtl == INFINITE { payload.MaxTtl = "" } @@ -210,6 +213,7 @@ func resourceProjectPolicyDelete(ctx context.Context, d *schema.ResourceData, me } d.SetId(projectId) + if err := writeResourceData(&policy, d); err != nil { return diag.Errorf("schema resource data serialization failed: %v", err) } @@ -226,8 +230,9 @@ func resourceProjectPolicyImport(ctx context.Context, d *schema.ResourceData, me } d.SetId(projectId) + if err := writeResourceData(&policy, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_project_policy_test.go b/env0/resource_project_policy_test.go index b874a81d..872b95a5 100644 --- a/env0/resource_project_policy_test.go +++ b/env0/resource_project_policy_test.go @@ -247,7 +247,6 @@ func TestUnitProjectPolicyResource(t *testing.T) { DisableDestroyEnvironments: resetPolicy.DisableDestroyEnvironments, SkipRedundantDeployments: resetPolicy.SkipRedundantDeployments, }).Times(1).Return(resetPolicy, nil) - }) }) diff --git a/env0/resource_provider.go b/env0/resource_provider.go index 3ca53e3b..45294d4e 100644 --- a/env0/resource_provider.go +++ b/env0/resource_provider.go @@ -104,6 +104,7 @@ func getProviderByName(name string, meta interface{}) (*client.Provider, error) } var foundProviders []client.Provider + for _, provider := range providers { if provider.Type == name { foundProviders = append(foundProviders, provider) @@ -125,9 +126,11 @@ func getProvider(ctx context.Context, idOrName string, meta interface{}) (*clien _, err := uuid.Parse(idOrName) if err == nil { tflog.Info(ctx, "Resolving provider by id", map[string]interface{}{"id": idOrName}) + return meta.(client.ApiClientInterface).Provider(idOrName) } else { tflog.Info(ctx, "Resolving provider by name", map[string]interface{}{"name": idOrName}) + return getProviderByName(idOrName, meta) } } @@ -139,7 +142,7 @@ func resourceProviderImport(ctx context.Context, d *schema.ResourceData, meta in } if err := writeResourceData(provider, d); err != nil { - return nil, fmt.Errorf("schema resource data serialization failed: %v", err) + return nil, fmt.Errorf("schema resource data serialization failed: %w", err) } return []*schema.ResourceData{d}, nil diff --git a/env0/resource_sshkey.go b/env0/resource_sshkey.go index ab8a1426..c8cdef5e 100644 --- a/env0/resource_sshkey.go +++ b/env0/resource_sshkey.go @@ -74,10 +74,12 @@ func resourceSshKeyRead(ctx context.Context, d *schema.ResourceData, meta interf if err != nil { return diag.Errorf("could not get ssh key: %v", err) } + if sshKey == nil { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") } + return nil } @@ -85,26 +87,34 @@ func resourceSshKeyDelete(ctx context.Context, d *schema.ResourceData, meta inte apiClient := meta.(client.ApiClientInterface) id := d.Id() + err := apiClient.SshKeyDelete(id) if err != nil { return diag.Errorf("could not delete ssh key: %v", err) } + return nil } func resourceSshKeyImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { id := d.Id() + var getErr error + _, uuidErr := uuid.Parse(id) + if uuidErr == nil { tflog.Info(ctx, "Resolving SSH key by id", map[string]interface{}{"id": id}) _, getErr = getSshKeyById(id, meta) } else { tflog.Info(ctx, "Resolving SSH key by name", map[string]interface{}{"name": id}) + var sshKey *client.SshKey + sshKey, getErr = getSshKeyByName(id, meta) d.SetId(sshKey.Id) } + if getErr != nil { return nil, getErr } else { diff --git a/env0/resource_team.go b/env0/resource_team.go index 354c7f40..5a672d59 100644 --- a/env0/resource_team.go +++ b/env0/resource_team.go @@ -90,22 +90,28 @@ func resourceTeamDelete(ctx context.Context, d *schema.ResourceData, meta interf if err != nil { return diag.Errorf("could not delete team: %v", err) } + return nil } func resourceTeamImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { id := d.Id() + var getErr diag.Diagnostics + _, uuidErr := uuid.Parse(id) if uuidErr == nil { tflog.Info(ctx, "Resolving team by id", map[string]interface{}{"id": id}) _, getErr = getTeamById(id, meta) } else { tflog.Info(ctx, "Resolving team by name", map[string]interface{}{"name": id}) + var team client.Team + team, getErr = getTeamByName(id, meta) d.SetId(team.Id) } + if getErr != nil { return nil, errors.New(getErr[0].Summary) } else { @@ -115,12 +121,14 @@ func resourceTeamImport(ctx context.Context, d *schema.ResourceData, meta interf func getTeamByName(name string, meta interface{}) (client.Team, diag.Diagnostics) { apiClient := meta.(client.ApiClientInterface) + teams, err := apiClient.TeamsByName(name) if err != nil { return client.Team{}, diag.Errorf("Could not get teams: %v", err) } var teamsByName []client.Team + for _, candidate := range teams { if candidate.Name == name { teamsByName = append(teamsByName, candidate) @@ -140,9 +148,11 @@ func getTeamByName(name string, meta interface{}) (client.Team, diag.Diagnostics func getTeamById(id interface{}, meta interface{}) (client.Team, diag.Diagnostics) { apiClient := meta.(client.ApiClientInterface) + team, err := apiClient.Team(id.(string)) if err != nil { return client.Team{}, diag.Errorf("Could not get team: %v", err) } + return team, nil } diff --git a/env0/resource_team_organization_assignment.go b/env0/resource_team_organization_assignment.go index 5089be7c..fc42ae03 100644 --- a/env0/resource_team_organization_assignment.go +++ b/env0/resource_team_organization_assignment.go @@ -47,6 +47,7 @@ func resourceTeamOrganizationAssignmentCreateOrUpdate(ctx context.Context, d *sc if err := readResourceData(&payload, d); err != nil { return diag.Errorf("schema resource data deserialization failed: %v", err) } + payload.OrganizationId = organizationId assignment, err := apiClient.TeamRoleAssignmentCreateOrUpdate(&payload) diff --git a/env0/resource_team_project_assignment.go b/env0/resource_team_project_assignment.go index 1a19db8f..783b292b 100644 --- a/env0/resource_team_project_assignment.go +++ b/env0/resource_team_project_assignment.go @@ -63,6 +63,7 @@ func resourceTeamProjectAssignmentCreateOrUpdate(ctx context.Context, d *schema. if !ok { role = d.Get("custom_role_id") } + payload.Role = role.(string) assignment, err := apiClient.TeamRoleAssignmentCreateOrUpdate(&payload) diff --git a/env0/resource_team_test.go b/env0/resource_team_test.go index 767bbe1c..7faf42ed 100644 --- a/env0/resource_team_test.go +++ b/env0/resource_team_test.go @@ -94,7 +94,6 @@ func TestUnitTeamResource(t *testing.T) { Description: team.Description, }).Times(1).Return(client.Team{}, errors.New("error")) }) - }) t.Run("Failure in update", func(t *testing.T) { @@ -128,7 +127,6 @@ func TestUnitTeamResource(t *testing.T) { mock.EXPECT().Team(gomock.Any()).Times(2).Return(team, nil) // 1 after create, 1 before update mock.EXPECT().TeamDelete(team.Id).Times(1) }) - }) t.Run("Failure in read", func(t *testing.T) { diff --git a/env0/resource_template.go b/env0/resource_template.go index 8cae780d..e60e9aa3 100644 --- a/env0/resource_template.go +++ b/env0/resource_template.go @@ -44,9 +44,11 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema { for _, attr := range allVCSAttributes { var found bool + for _, str := range strs { if str == attr { found = true + break } } @@ -296,6 +298,7 @@ func resourceTemplateCreate(ctx context.Context, d *schema.ResourceData, meta in if problem != nil { return problem } + template, err := apiClient.TemplateCreate(request) if err != nil { return diag.Errorf("could not create template: %v", err) @@ -317,6 +320,7 @@ func resourceTemplateRead(ctx context.Context, d *schema.ResourceData, meta inte if template.IsDeleted && !d.IsNewResource() { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } @@ -334,6 +338,7 @@ func resourceTemplateUpdate(ctx context.Context, d *schema.ResourceData, meta in if problem != nil { return problem } + _, err := apiClient.TemplateUpdate(d.Id(), request) if err != nil { return diag.Errorf("could not update template: %v", err) @@ -346,26 +351,34 @@ func resourceTemplateDelete(ctx context.Context, d *schema.ResourceData, meta in apiClient := meta.(client.ApiClientInterface) id := d.Id() + err := apiClient.TemplateDelete(id) if err != nil { return diag.Errorf("could not delete template: %v", err) } + return nil } func resourceTemplateImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { id := d.Id() + var getErr diag.Diagnostics + _, uuidErr := uuid.Parse(id) + if uuidErr == nil { tflog.Info(ctx, "Resolving template by id", map[string]interface{}{"id": id}) _, getErr = getTemplateById(id, meta) } else { tflog.Info(ctx, "Resolving template by name", map[string]interface{}{"name": id}) + var template client.Template + template, getErr = getTemplateByName(id, meta) d.SetId(template.Id) } + if getErr != nil { return nil, errors.New(getErr[0].Summary) } else { @@ -457,7 +470,7 @@ func templateRead(prefix string, template client.Template, d *schema.ResourceDat } if err := writeResourceDataEx(prefix, &template, d); err != nil { - return fmt.Errorf("schema resource data serialization failed: %v", err) + return fmt.Errorf("schema resource data serialization failed: %w", err) } // https://github.com/env0/terraform-provider-env0/issues/699 - backend removes the "/". diff --git a/env0/resource_template_project_assignment.go b/env0/resource_template_project_assignment.go index 85528466..883519aa 100644 --- a/env0/resource_template_project_assignment.go +++ b/env0/resource_template_project_assignment.go @@ -46,12 +46,15 @@ func resourceTemplateProjectAssignmentCreate(ctx context.Context, d *schema.Reso templateId := d.Get("template_id").(string) projectId := d.Get("project_id").(string) request := templateProjectAssignmentPayloadFromParameters(d) + result, err := apiClient.AssignTemplateToProject(templateId, request) if err != nil { return diag.Errorf("could not assign template to project: %v", err) } + resourceId := result.Id + "|" + projectId d.SetId(resourceId) + return nil } @@ -68,19 +71,24 @@ func resourceTemplateProjectAssignmentRead(ctx context.Context, d *schema.Resour if template.IsDeleted { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } var assignProjectId = d.Get("project_id").(string) + isProjectIdInTemplate := false + for _, projectId := range template.ProjectIds { if assignProjectId == projectId { isProjectIdInTemplate = true } } + if !isProjectIdInTemplate { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } @@ -92,9 +100,11 @@ func resourceTemplateProjectAssignmentDelete(ctx context.Context, d *schema.Reso templateId := d.Get("template_id").(string) projectId := d.Get("project_id").(string) + err := apiClient.RemoveTemplateFromProject(templateId, projectId) if err != nil { return diag.Errorf("could not delete template from project: %v", err) } + return nil } diff --git a/env0/resource_template_project_assignment_test.go b/env0/resource_template_project_assignment_test.go index 14c72c15..4d5b30ab 100644 --- a/env0/resource_template_project_assignment_test.go +++ b/env0/resource_template_project_assignment_test.go @@ -11,7 +11,6 @@ import ( ) func TestUnitTemplateProjectAssignmentResource(t *testing.T) { - resourceType := "env0_template_project_assignment" resourceName := "test" @@ -109,7 +108,6 @@ func TestUnitTemplateProjectAssignmentResource(t *testing.T) { Return(updateReturnValues, nil), ) }) - }) t.Run("throw error when missing values", func(t *testing.T) { @@ -126,7 +124,6 @@ func TestUnitTemplateProjectAssignmentResource(t *testing.T) { }) t.Run("detect drift", func(t *testing.T) { - runUnitTest(t, testCaseforCreate, func(mock *client.MockApiClientInterface) { mock.EXPECT().AssignTemplateToProject(resourceTemplateAssignment["template_id"].(string), payLoad). Times(1).Return(returnValues, nil) diff --git a/env0/resource_template_test.go b/env0/resource_template_test.go index 28441bd5..7afaa198 100644 --- a/env0/resource_template_test.go +++ b/env0/resource_template_test.go @@ -13,11 +13,15 @@ import ( func TestUnitTemplateResource(t *testing.T) { const resourceType = "env0_template" + const resourceName = "test" + const defaultVersion = "0.15.1" + const defaultType = "terraform" var resourceFullName = resourceAccessor(resourceType, resourceName) + gleeTemplate := client.Template{ Id: "id0", Name: "template0", @@ -495,75 +499,97 @@ func TestUnitTemplateResource(t *testing.T) { if template.Type != "" { templateAsDictionary["type"] = template.Type } + if template.Description != "" { templateAsDictionary["description"] = template.Description } + if template.Revision != "" { templateAsDictionary["revision"] = template.Revision } + if template.Path != "" { templateAsDictionary["path"] = template.Path } + if template.Retry != (client.TemplateRetry{}) && template.Retry.OnDeploy != nil { templateAsDictionary["retries_on_deploy"] = template.Retry.OnDeploy.Times if template.Retry.OnDeploy.ErrorRegex != "" { templateAsDictionary["retry_on_deploy_only_when_matches_regex"] = template.Retry.OnDeploy.ErrorRegex } } + if template.Retry != (client.TemplateRetry{}) && template.Retry.OnDestroy != nil { templateAsDictionary["retries_on_destroy"] = template.Retry.OnDestroy.Times if template.Retry.OnDestroy.ErrorRegex != "" { templateAsDictionary["retry_on_destroy_only_when_matches_regex"] = template.Retry.OnDestroy.ErrorRegex } } + if template.TerraformVersion != "" { templateAsDictionary["terraform_version"] = template.TerraformVersion } + if template.OpentofuVersion != "" { templateAsDictionary["opentofu_version"] = template.OpentofuVersion } + if template.TokenId != "" { templateAsDictionary["token_id"] = template.TokenId } + if template.TokenName != "" { templateAsDictionary["token_name"] = template.TokenName } + if template.GithubInstallationId != 0 { templateAsDictionary["github_installation_id"] = template.GithubInstallationId } + if template.IsGitlabEnterprise != false { templateAsDictionary["is_gitlab_enterprise"] = template.IsGitlabEnterprise } + if template.BitbucketClientKey != "" { templateAsDictionary["bitbucket_client_key"] = template.BitbucketClientKey } + if template.IsGithubEnterprise != false { templateAsDictionary["is_github_enterprise"] = template.IsGithubEnterprise } + if template.IsBitbucketServer != false { templateAsDictionary["is_bitbucket_server"] = template.IsBitbucketServer } + if template.FileName != "" { templateAsDictionary["file_name"] = template.FileName } + if template.TerragruntVersion != "" { templateAsDictionary["terragrunt_version"] = template.TerragruntVersion } + if template.IsTerragruntRunAll { templateAsDictionary["is_terragrunt_run_all"] = true } + if template.IsAzureDevOps { templateAsDictionary["is_azure_devops"] = true } + if template.IsHelmRepository { templateAsDictionary["is_helm_repository"] = true } + if template.HelmChartName != "" { templateAsDictionary["helm_chart_name"] = template.HelmChartName } + if template.IsGitlab { templateAsDictionary["is_gitlab"] = true } + if template.AnsibleVersion != "" { templateAsDictionary["ansible_version"] = template.AnsibleVersion } @@ -666,6 +692,7 @@ func TestUnitTemplateResource(t *testing.T) { {"Opentofu", opentofuTemplate, opentofuUpdatedTemplate}, {"Ansible", ansibleTemplate, ansibleUpdatedTemplate}, } + for _, templateUseCase := range templateUseCases { t.Run("Full "+templateUseCase.vcs+" template (without SSH keys)", func(t *testing.T) { templateCreatePayload := client.TemplateCreatePayload{ @@ -942,6 +969,7 @@ func TestUnitTemplateResource(t *testing.T) { } var testCases []resource.TestCase + for attribute, amounts := range testMatrix { for _, amount := range amounts { testCases = append(testCases, resource.TestCase{ @@ -1004,6 +1032,7 @@ func TestUnitTemplateResource(t *testing.T) { {"GitLab", "Bitbucket", map[string]interface{}{"name": "test", "repository": "env0/test", "token_id": "2", "bitbucket_client_key": "3"}, "\"bitbucket_client_key\": conflicts with token_id"}, {"GitLab EE", "GitHub EE", map[string]interface{}{"name": "test", "repository": "env0/test", "is_gitlab_enterprise": "true", "is_github_enterprise": "true"}, "\"is_github_enterprise\": conflicts with is_gitlab_enterprise"}, } + for _, mixUseCase := range mixedUsecases { t.Run("Mixed "+mixUseCase.firstVcs+" and "+mixUseCase.secondVcs+" template", func(t *testing.T) { var testCases []resource.TestCase @@ -1094,7 +1123,6 @@ func TestUnitTemplateResource(t *testing.T) { } runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) { - mock.EXPECT().TemplateCreate(client.TemplateCreatePayload{ Name: template.Name, Repository: template.Repository, diff --git a/env0/resource_user_organization_assignment.go b/env0/resource_user_organization_assignment.go index 74a048d9..da01875a 100644 --- a/env0/resource_user_organization_assignment.go +++ b/env0/resource_user_organization_assignment.go @@ -62,6 +62,7 @@ func resourceUserOrganizationAssignmentRead(ctx context.Context, d *schema.Resou for i := range users { if users[i].User.UserId == userId { user = &users[i] + break } } @@ -69,6 +70,7 @@ func resourceUserOrganizationAssignmentRead(ctx context.Context, d *schema.Resou if user == nil { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } diff --git a/env0/resource_user_organization_assignment_test.go b/env0/resource_user_organization_assignment_test.go index cf71b681..49892919 100644 --- a/env0/resource_user_organization_assignment_test.go +++ b/env0/resource_user_organization_assignment_test.go @@ -235,5 +235,4 @@ func TestUnitUserOrganizationAssignmentResource(t *testing.T) { ) }) }) - } diff --git a/env0/resource_user_project_assignment.go b/env0/resource_user_project_assignment.go index 6f6fb23f..f8fa76b8 100644 --- a/env0/resource_user_project_assignment.go +++ b/env0/resource_user_project_assignment.go @@ -57,6 +57,7 @@ func resourceUserProjectAssignmentCreate(ctx context.Context, d *schema.Resource if !ok { role = d.Get("custom_role_id") } + newAssignment.Role = role.(string) projectId := d.Get("project_id").(string) @@ -119,6 +120,7 @@ func resourceUserProjectAssignmentUpdate(ctx context.Context, d *schema.Resource if !ok { role = d.Get("custom_role_id") } + payload.Role = role.(string) apiClient := meta.(client.ApiClientInterface) diff --git a/env0/resource_user_team_assignment.go b/env0/resource_user_team_assignment.go index b2013f57..fdab3b2a 100644 --- a/env0/resource_user_team_assignment.go +++ b/env0/resource_user_team_assignment.go @@ -34,10 +34,11 @@ func (a *UserTeamAssignment) GetId() string { func GetUserTeamAssignmentFromId(id string) (*UserTeamAssignment, error) { // lastSplit is used to avoid issues where the user_id has underscores in it. - splitUserTeam := lastSplit(id, "_") + splitUserTeam := lastUnderscoreSplit(id) if len(splitUserTeam) != 2 { return nil, fmt.Errorf("the id %v is invalid must be _", id) } + return &UserTeamAssignment{ UserId: splitUserTeam[0], TeamId: splitUserTeam[1], @@ -125,9 +126,11 @@ func resourceUserTeamAssignmentRead(ctx context.Context, d *schema.ResourceData, } found := false + for _, user := range team.Users { if user.UserId == assignment.UserId { found = true + break } } @@ -135,6 +138,7 @@ func resourceUserTeamAssignmentRead(ctx context.Context, d *schema.ResourceData, if !found { tflog.Warn(ctx, "Drift Detected: Terraform will remove id from state", map[string]interface{}{"id": d.Id()}) d.SetId("") + return nil } @@ -185,7 +189,7 @@ func resourceUserTeamAssignmentDelete(ctx context.Context, d *schema.ResourceDat func resourceUserTeamAssignmentImport(ctx context.Context, d *schema.ResourceData, meta interface{}) ([]*schema.ResourceData, error) { assignment, err := GetUserTeamAssignmentFromId(d.Id()) if err != nil { - return nil, fmt.Errorf("%v", err) + return nil, fmt.Errorf("%w", err) } apiClient := meta.(client.ApiClientInterface) @@ -198,13 +202,16 @@ func resourceUserTeamAssignmentImport(ctx context.Context, d *schema.ResourceDat if frerr, ok := err.(*http.FailedResponseError); ok && frerr.NotFound() { return nil, fmt.Errorf("team %v not found", assignment.TeamId) } + return nil, err } found := false + for _, user := range team.Users { if user.UserId == assignment.UserId { found = true + break } } diff --git a/env0/resource_variable_set.go b/env0/resource_variable_set.go index b3aa889d..8d4311d3 100644 --- a/env0/resource_variable_set.go +++ b/env0/resource_variable_set.go @@ -105,6 +105,7 @@ func getVariableFromSchema(d map[string]interface{}) (*client.ConfigurationVaria if !ok { isSensitive = false } + res.IsSensitive = boolPtr(isSensitive) variableType := d["type"].(string) @@ -126,6 +127,7 @@ func getVariableFromSchema(d map[string]interface{}) (*client.ConfigurationVaria if len(value) == 0 { return nil, fmt.Errorf("free text variable '%s' must have a value", res.Name) } + res.Schema = &client.ConfigurationVariableSchema{ Type: "string", } @@ -133,6 +135,7 @@ func getVariableFromSchema(d map[string]interface{}) (*client.ConfigurationVaria if len(value) == 0 { return nil, fmt.Errorf("hcl variable '%s' must have a value", res.Name) } + res.Schema = &client.ConfigurationVariableSchema{ Format: "HCL", } @@ -140,9 +143,11 @@ func getVariableFromSchema(d map[string]interface{}) (*client.ConfigurationVaria if len(value) == 0 { return nil, fmt.Errorf("json variable '%s' must have a value", res.Name) } + res.Schema = &client.ConfigurationVariableSchema{ Format: "JSON", } + // validate JSON. var js json.RawMessage if err := json.Unmarshal([]byte(value), &js); err != nil { @@ -193,25 +198,28 @@ func getSchemaFromVariables(variables []client.ConfigurationVariable) (interface ivariable["is_sensitive"] = true } - if variable.Schema.Type == "string" { + switch { + case variable.Schema.Type == "string": if len(variable.Schema.Enum) > 0 { ivariable["format"] = "dropdown" ivalues := make([]interface{}, 0) + for _, value := range variable.Schema.Enum { ivalues = append(ivalues, value) } + ivariable["dropdown_values"] = ivalues } else { ivariable["format"] = "text" ivariable["value"] = variable.Value } - } else if variable.Schema.Format == "HCL" { + case variable.Schema.Format == "HCL": ivariable["format"] = "hcl" ivariable["value"] = variable.Value - } else if variable.Schema.Format == "JSON" { + case variable.Schema.Format == "JSON": ivariable["format"] = "json" ivariable["value"] = variable.Value - } else { + default: return nil, fmt.Errorf("unhandled variable use-case: %s", variable.Name) } } @@ -232,7 +240,9 @@ func getVariablesFromSchema(d *schema.ResourceData, organizationId string) ([]cl if err != nil { return nil, err } + variable.OrganizationId = organizationId + res = append(res, *variable) } @@ -324,6 +334,7 @@ func mergeVariables(schema []client.ConfigurationVariable, api []client.Configur for _, svariable := range schema { if svariable.Name == avariable.Name && *svariable.Type == *avariable.Type { found = true + break } } @@ -364,9 +375,7 @@ func resourceVariableSetRead(ctx context.Context, d *schema.ResourceData, meta i mergedVariables := mergeVariables(variablesFromSchema, variablesFromApi) // for "READ" the source of truth is the variables from the API - existing + deleted. - variables := append(mergedVariables.currentVariables, mergedVariables.deletedVariables...) - - ivariables, err := getSchemaFromVariables(variables) + ivariables, err := getSchemaFromVariables(append(mergedVariables.currentVariables, mergedVariables.deletedVariables...)) if err != nil { return diag.Errorf("failed to get schema from variables: %v", err) } diff --git a/env0/resource_variable_set_assignment.go b/env0/resource_variable_set_assignment.go index 97901f33..fec22cab 100644 --- a/env0/resource_variable_set_assignment.go +++ b/env0/resource_variable_set_assignment.go @@ -99,6 +99,7 @@ func resourceVariableSetAssignmentUpdate(ctx context.Context, d *schema.Resource for _, schemaSetId := range assignmentSchema.SetIds { if apiSetId == schemaSetId { found = true + break } } @@ -116,6 +117,7 @@ func resourceVariableSetAssignmentUpdate(ctx context.Context, d *schema.Resource apiSetId := apiConfigurationSet.Id if schemaSetId == apiSetId { found = true + break } } @@ -181,6 +183,7 @@ func resourceVariableSetAssignmentRead(ctx context.Context, d *schema.ResourceDa if schemaSetId == apiSetId { newSchemaSetIds = append(newSchemaSetIds, schemaSetId) + break } } @@ -198,6 +201,7 @@ func resourceVariableSetAssignmentRead(ctx context.Context, d *schema.ResourceDa for _, schemaSetId := range assignmentSchema.SetIds { if schemaSetId == apiSetId { found = true + break } } diff --git a/env0/resource_workflow_trigger_test.go b/env0/resource_workflow_trigger_test.go index 63852087..6062c329 100644 --- a/env0/resource_workflow_trigger_test.go +++ b/env0/resource_workflow_trigger_test.go @@ -19,7 +19,6 @@ func TestUnitWorkflowTriggerResource(t *testing.T) { otherTriggerId := "other_trigger_environment_id" t.Run("Success", func(t *testing.T) { - testCase := resource.TestCase{ Steps: []resource.TestStep{ { @@ -73,7 +72,6 @@ func TestUnitWorkflowTriggerResource(t *testing.T) { }).Times(1).Return(nil), ) }) - }) t.Run("Failure in Get Triggers", func(t *testing.T) { diff --git a/env0/resource_workflow_triggers.go b/env0/resource_workflow_triggers.go index d0c035d2..0b20da1a 100644 --- a/env0/resource_workflow_triggers.go +++ b/env0/resource_workflow_triggers.go @@ -67,9 +67,11 @@ func resourceWorkflowTriggersCreateOrUpdate(ctx context.Context, d *schema.Resou for _, rawId := range rawDownstreamIds { requestDownstreamIds = append(requestDownstreamIds, rawId.(string)) } + request := client.WorkflowTriggerUpsertPayload{ DownstreamEnvironmentIds: requestDownstreamIds, } + triggers, err := apiClient.WorkflowTriggerUpsert(environmentId, request) if err != nil { return diag.Errorf("could not Create or Update workflow triggers: %v", err) @@ -82,6 +84,7 @@ func resourceWorkflowTriggersCreateOrUpdate(ctx context.Context, d *schema.Resou d.SetId(environmentId) d.Set("downstream_environment_ids", downstreamIds) + return nil } diff --git a/env0/resource_workflow_triggers_test.go b/env0/resource_workflow_triggers_test.go index d27510a9..82726145 100644 --- a/env0/resource_workflow_triggers_test.go +++ b/env0/resource_workflow_triggers_test.go @@ -26,8 +26,8 @@ func TestUnitWorkflowTriggersResource(t *testing.T) { "environment_id": environmentId, "downstream_environment_ids": []string{trigger.Id}, }) - t.Run("Success", func(t *testing.T) { + t.Run("Success", func(t *testing.T) { testCase := resource.TestCase{ Steps: []resource.TestStep{ { @@ -67,7 +67,6 @@ func TestUnitWorkflowTriggersResource(t *testing.T) { mock.EXPECT().WorkflowTrigger(environmentId).Times(2).Return([]client.WorkflowTrigger{trigger}, nil), // 1 after createHCL, 1 before update mock.EXPECT().WorkflowTrigger(environmentId).Times(1).Return([]client.WorkflowTrigger{otherTrigger}, nil), // 1 after update ) - }) }) @@ -86,7 +85,6 @@ func TestUnitWorkflowTriggersResource(t *testing.T) { DownstreamEnvironmentIds: []string{trigger.Id}, }).Times(1).Return([]client.WorkflowTrigger{}, errors.New("error")) }) - }) t.Run("Failure in read", func(t *testing.T) { @@ -109,6 +107,5 @@ func TestUnitWorkflowTriggersResource(t *testing.T) { mock.EXPECT().WorkflowTrigger(environmentId).Return([]client.WorkflowTrigger{}, errors.New("error")) }) - }) } diff --git a/env0/test_helpers.go b/env0/test_helpers.go index a6505fd7..1fff4d9c 100644 --- a/env0/test_helpers.go +++ b/env0/test_helpers.go @@ -28,6 +28,7 @@ func hclAccessor(source TFSource, resourceType string, resourceName string) stri if source == DataSource { return fmt.Sprintf("%s.%s.%s", source, resourceType, resourceName) } + return fmt.Sprintf("%s.%s", resourceType, resourceName) } @@ -91,6 +92,7 @@ func missingArgumentTestCase(resourceType string, resourceName string, errorReso }, }, } + return testCaseFormMissingValidInputError } @@ -103,5 +105,6 @@ func missingArgumentTestCaseForCostCred(resourceType string, resourceName string }, }, } + return testCaseFormMissingValidInputError } diff --git a/env0/utils.go b/env0/utils.go index dcde55c4..40b9f5b5 100644 --- a/env0/utils.go +++ b/env0/utils.go @@ -30,6 +30,7 @@ type ResourceDataSliceStructValueWriter interface { func toSnakeCase(str string) string { snake := matchFirstCap.ReplaceAllString(str, "${1}_${2}") snake = matchAllCap.ReplaceAllString(snake, "${1}_${2}") + return strings.ToLower(snake) } @@ -51,6 +52,7 @@ func stringInSlice(str string, strs []string) bool { return true } } + return false } @@ -74,7 +76,7 @@ func reasourceDataGetValue(fieldName string, omitEmpty bool, d *schema.ResourceD _, okBool := dval.(bool) if okString || okBool || okInt { - //lint:ignore SA1019 reason: https://github.com/hashicorp/terraform-plugin-sdk/issues/817 + //nolint:staticcheck // https://github.com/hashicorp/terraform-plugin-sdk/issues/817 if _, exists := d.GetOkExists(fieldName); !exists { return nil } @@ -210,6 +212,7 @@ func readResourceDataSliceEx(field reflect.Value, resources []interface{}) error if err := readResourceDataSliceStructHelper(val, resource); err != nil { return err } + val = val.Elem() default: return fmt.Errorf("internal error - unhandled slice element kind %v", elemType.Kind()) @@ -315,6 +318,7 @@ func writeResourceData(i interface{}, d *schema.ResourceData) error { if parsedField.omitEmpty && field.String() == "" { continue } + if err := d.Set(fieldName, field.String()); err != nil { return err } @@ -322,6 +326,7 @@ func writeResourceData(i interface{}, d *schema.ResourceData) error { if parsedField.omitEmpty && field.Int() == 0 { continue } + if err := d.Set(fieldName, field.Int()); err != nil { return err } @@ -356,6 +361,7 @@ func getInterfaceSliceValues(i interface{}) []interface{} { func writeResourceDataGetSliceValues(i interface{}, name string, d *schema.ResourceData) ([]interface{}, error) { ivalues := getInterfaceSliceValues(i) + var values []interface{} // Iterate over the slice of values and build a slice of terraform values. @@ -385,6 +391,7 @@ func writeResourceDataGetSliceValues(i interface{}, name string, d *schema.Resou if err != nil { return nil, err } + values = append(values, value) default: return nil, fmt.Errorf("internal error - unhandled slice kind %v", valType.Kind()) @@ -464,6 +471,7 @@ func writeResourceDataEx(prefix string, i interface{}, d *schema.ResourceData) e if prefix == "" { return writeResourceData(i, d) } + return writeResourceDataSlice([]interface{}{i}, prefix, d) } @@ -478,6 +486,7 @@ func ttlToDuration(ttl *string) (time.Duration, error) { } numberStr := match[1] + number, err := strconv.Atoi(numberStr) if err != nil { return 0, fmt.Errorf("invalid TTL format %s - not a number: %w", *ttl, err) @@ -499,8 +508,8 @@ func ttlToDuration(ttl *string) (time.Duration, error) { return time.ParseDuration(fmt.Sprintf("%dh", hours)) } -func lastSplit(s string, sep string) []string { - lastIndex := strings.LastIndex(s, sep) +func lastUnderscoreSplit(s string) []string { + lastIndex := strings.LastIndex(s, "_") if lastIndex == -1 { return []string{s} } diff --git a/env0/utils_test.go b/env0/utils_test.go index 21f93d05..64dc4c90 100644 --- a/env0/utils_test.go +++ b/env0/utils_test.go @@ -464,9 +464,9 @@ func TestTTLToDuration(t *testing.T) { } func TestLastSplit(t *testing.T) { - assert.Equal(t, []string{"a", "b"}, lastSplit("a_b", "_")) - assert.Equal(t, []string{"a__", "b"}, lastSplit("a___b", "_")) - assert.Equal(t, []string{"a_", ""}, lastSplit("a__", "_")) + assert.Equal(t, []string{"a", "b"}, lastUnderscoreSplit("a_b")) + assert.Equal(t, []string{"a__", "b"}, lastUnderscoreSplit("a___b")) + assert.Equal(t, []string{"a_", ""}, lastUnderscoreSplit("a__")) - assert.Equal(t, []string{"abc"}, lastSplit("abc", "_")) + assert.Equal(t, []string{"abc"}, lastUnderscoreSplit("abc")) } diff --git a/env0/validators.go b/env0/validators.go index 276048ab..a509f5a1 100644 --- a/env0/validators.go +++ b/env0/validators.go @@ -17,6 +17,7 @@ func ValidateConfigurationPropertySchema(val interface{}, key string) (warns []s if value != string(client.HCL) && value != string(client.Text) && value != string(client.JSON) { errs = append(errs, fmt.Errorf("%q can be either \"HCL\", \"JSON\" or empty, got: %q", key, value)) } + return } @@ -62,6 +63,7 @@ func NewRegexValidator(r string) schema.SchemaValidateDiagFunc { if !cr.MatchString(i.(string)) { return diag.Errorf("must match pattern %v", r) } + return nil } } diff --git a/main.go b/main.go index 86c75e6e..0b69baef 100644 --- a/main.go +++ b/main.go @@ -17,6 +17,7 @@ var ( func main() { var debugMode bool + flag.BoolVar(&debugMode, "debug", false, "set to true to run the provider with support for debuggers like delve") flag.Parse() diff --git a/tests/harness.go b/tests/harness.go index 7d8c6eda..de4ee790 100644 --- a/tests/harness.go +++ b/tests/harness.go @@ -23,9 +23,13 @@ func main() { printTerraformVersion() makeSureRunningFromProjectRoot() + testNames := testNamesFromCommandLineArguments() + log.Println(len(testNames), " tests to run") + buildFakeTerraformRegistry() + destroyMode := os.Getenv("DESTROY_MODE") var wg sync.WaitGroup @@ -51,10 +55,12 @@ func main() { } func compileProvider() error { cmd := exec.Command("go", "build") + _, err := cmd.CombinedOutput() if err != nil { return err } + return nil } func runTest(testName string, destroy bool) (bool, error) { @@ -66,16 +72,20 @@ func runTest(testName string, destroy bool) (bool, error) { "terraform.tfstate.backup", "terraform.rc", } + for _, oneToDelete := range toDelete { os.RemoveAll(path.Join(testDir, oneToDelete)) } log.Println("Running test ", testName) + _, err := terraformCommand(testName, "init") if err != nil { return false, err } - terraformCommand(testName, "fmt") + + _, _ = terraformCommand(testName, "fmt") + if destroy { defer terraformDestory(testName) } @@ -84,41 +94,53 @@ func runTest(testName string, destroy bool) (bool, error) { if err != nil { return false, err } + _, err = terraformCommand(testName, "apply", "-auto-approve", "-var", "second_run=1") if err != nil { return false, err } + expectedOutputs, err := readExpectedOutputs(testName) if err != nil { return false, err } + outputsBytes, err := terraformCommand(testName, "output", "-json") if err != nil { return false, err } + outputs, err := bytesOfJsonToStringMap(outputsBytes) if err != nil { return false, err } + for key, expectedValue := range expectedOutputs { value, ok := outputs[key] if !ok { log.Println("Error: Expected terraform output ", key, " but no such output was created") + return false, nil } + if value != expectedValue { log.Printf("Error: Expected output of '%s' to be '%s' but found '%s'\n", key, expectedValue, value) + return false, nil } + log.Printf("Verified expected '%s'='%s' in %s", key, value, testName) } + if destroy { _, err = terraformCommand(testName, "destroy", "-auto-approve", "-var", "second_run=0") if err != nil { return false, err } } + log.Println("Successfully finished running test ", testName) + return true, nil } @@ -126,22 +148,28 @@ func readExpectedOutputs(testName string) (map[string]string, error) { expectedBytes, err := os.ReadFile(path.Join(TESTS_FOLDER, testName, "expected_outputs.json")) if err != nil { log.Println("Test folder for ", testName, " does not contain expected_outputs.json", err) + return nil, err } + return bytesOfJsonToStringMap(expectedBytes) } func bytesOfJsonToStringMap(data []byte) (map[string]string, error) { var stringMapUncasted map[string]interface{} + err := json.Unmarshal(data, &stringMapUncasted) if err != nil { log.Println("Unable to parse json:", err) log.Println("** JSON Input **") - log.Println(string(data[:])) + log.Println(string(data)) log.Println("******") + return nil, err } + result := map[string]string{} + for key, valueUncasted := range stringMapUncasted { switch value := valueUncasted.(type) { case string: @@ -150,14 +178,20 @@ func bytesOfJsonToStringMap(data []byte) (map[string]string, error) { result[key] = value["value"].(string) } } + return result, nil } func terraformDestory(testName string) { log.Println("Running destroy to clean up in", testName) + destroy := exec.Command("terraform", "destroy", "-auto-approve", "-var", "second_run=0") destroy.Dir = TESTS_FOLDER + "/" + testName - destroy.CombinedOutput() + + if err := destroy.Run(); err != nil { + log.Println("WARNING: error running terraform destroy") + } + log.Println("Done running terraform destroy in", testName) } @@ -167,26 +201,34 @@ func terraformCommand(testName string, arg ...string) ([]byte, error) { cmd.Env = os.Environ() cmd.Env = append(cmd.Env, "INTEGRATION_TESTS=1") cmd.Env = append(cmd.Env, "TF_LOG_PROVIDER=info") + var output, errOutput bytes.Buffer + cmd.Stderr = bufio.NewWriter(&errOutput) cmd.Stdout = bufio.NewWriter(&output) + log.Println("Running terraform ", arg, " in ", testName) + err := cmd.Run() log.Println(errOutput.String()) + if err != nil { log.Println("error running terraform ", arg, " in ", testName, " error: ", err) } else { log.Println("Completed successfully terraform", arg, "in", testName) } + return output.Bytes(), err } func printTerraformVersion() { versionString, err := exec.Command("terraform", "version").Output() + if err != nil { log.Fatalln("Unable to invoke terraform. Perhaps it's not in PATH?", err) } + log.Println("Terraform version: ", string(versionString)) } @@ -198,11 +240,13 @@ func makeSureRunningFromProjectRoot() { func testNamesFromCommandLineArguments() []string { testNames := []string{} + if len(os.Args) > 1 { for _, testName := range os.Args[1:] { if strings.HasPrefix(testName, TESTS_FOLDER+"/") { testName = testName[len(TESTS_FOLDER+"/"):] } + testName = strings.TrimSuffix(testName, "/") testNames = append(testNames, testName) } @@ -211,26 +255,31 @@ func testNamesFromCommandLineArguments() []string { if err != nil { log.Fatalln("Unable to list 'tests' folder", err) } + for _, file := range allFilesUnderTests { if strings.HasPrefix(file.Name(), "0") { testNames = append(testNames, file.Name()) } } } + return testNames } func buildFakeTerraformRegistry() { architecture := runtime.GOOS + "_" + runtime.GOARCH registry_dir := "tests/fake_registry/terraform-registry.env0.com/env0/env0/6.6.6/" + architecture + err := os.MkdirAll(registry_dir, 0755) if err != nil { log.Fatalln("Unable to create registry folder ", registry_dir, " error: ", err) } + data, err := os.ReadFile("terraform-provider-env0") if err != nil { log.Fatalln("Unable to read provider binary: did you build it?", err) } + err = os.WriteFile(registry_dir+"/terraform-provider-env0", data, 0755) if err != nil { log.Fatalln("Unable to write: ", err) @@ -240,6 +289,7 @@ func buildFakeTerraformRegistry() { if err != nil { log.Fatalln("Unable to get current working dir", err) } + terraformRc := fmt.Sprintf(` provider_installation { filesystem_mirror { @@ -250,6 +300,7 @@ provider_installation { exclude = ["terraform-registry.env0.com/*/*"] } }`, cwd) + err = os.WriteFile("tests/terraform.rc", []byte(terraformRc), 0644) if err != nil { log.Fatalln("Unable to write: ", err)