Skip to content

Commit

Permalink
Merge branch 'main' into fix-agent-project-assignment-#914
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerHeber authored Aug 16, 2024
2 parents 692597b + 6e3153f commit 6e39397
Show file tree
Hide file tree
Showing 41 changed files with 416 additions and 213 deletions.
31 changes: 31 additions & 0 deletions .golangci.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
linters:
enable:
- errname
- errorlint
- gocheckcompilerdirectives
- gochecknoglobals
- gochecknoinits
- goconst
- gocritic
- misspell
- nilerr
- nilnil
- nlreturn
- perfsprint
- prealloc
- predeclared
- reassign
- sloglint
- spancheck
- testifylint
- unparam
- unused
- usestdlibvars
- wsl

linters-settings:
errorlint:
asserts: false
errcheck:
exclude-functions:
- (*github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema.ResourceData).Set
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,8 @@ resource "env0_template" "example" {
### Run local version of the provider

- Build - `./build.sh`
- Create the plugins folder - `mkdir -p ~/.terraform.d/plugins/terraform.env0.com/local/env0/6.6.6/darwin_amd64` (for M1 processor, replace `amd64` with `arm64`)
- Copy the built binary - `cp ./terraform-provider-env0 ~/.terraform.d/plugins/terraform.env0.com/local/env0/6.6.6/darwin_amd64` (Replace `darwin` with `linux` on Linux)
- Create the plugins folder - `mkdir -p ~/.terraform.d/plugins/terraform.env0.com/local/env0/6.6.6/darwin_arm64` (for Intel processor, replace `arm64` with `amd64`)
- Copy the built binary - `cp ./terraform-provider-env0 ~/.terraform.d/plugins/terraform.env0.com/local/env0/6.6.6/darwin_arm64` (Replace `darwin` with `linux` on Linux)
- Require the local provider in your `main.tf` -

```
Expand Down
2 changes: 1 addition & 1 deletion client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type ApiClientInterface interface {
TeamCreate(payload TeamCreatePayload) (Team, error)
TeamUpdate(id string, payload TeamUpdatePayload) (Team, error)
TeamDelete(id string) error
Environments() ([]Environment, error)
EnvironmentsByName(name string) ([]Environment, error)
ProjectEnvironments(projectId string) ([]Environment, error)
Environment(id string) (Environment, error)
EnvironmentCreate(payload EnvironmentCreate) (Environment, error)
Expand Down
12 changes: 6 additions & 6 deletions client/api_client_mock.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions client/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ package client
import (
"encoding/json"
"fmt"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

Expand Down Expand Up @@ -213,14 +212,15 @@ func (Environment) getEndpoint() string {
return "/environments"
}

func (client *ApiClient) Environments() ([]Environment, error) {
func (client *ApiClient) EnvironmentsByName(name string) ([]Environment, error) {
organizationId, err := client.OrganizationId()
if err != nil {
return nil, err
}

return getAll(client, map[string]string{
"organizationId": organizationId,
"name": name,
})
}

Expand Down
22 changes: 17 additions & 5 deletions client/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,17 @@ var _ = Describe("Environment Client", func() {
BeforeEach(func() {
mockOrganizationIdCall(organizationId)
httpCall = mockHttpClient.EXPECT().
Get("/environments", gomock.Any(), gomock.Any()).
Get("/environments", map[string]string{
"limit": "100",
"offset": "0",
"organizationId": organizationId,
"name": mockEnvironment.Name,
}, gomock.Any()).
Do(func(path string, request interface{}, response *[]Environment) {
*response = mockEnvironments
})

environments, err = apiClient.Environments()
environments, err = apiClient.EnvironmentsByName(mockEnvironment.Name)
})

It("Should send GET request", func() {
Expand Down Expand Up @@ -75,6 +80,7 @@ var _ = Describe("Environment Client", func() {
"offset": "0",
"limit": "100",
"organizationId": organizationId,
"name": mockEnvironment.Name,
}, gomock.Any()).
Do(func(path string, request interface{}, response *[]Environment) {
*response = environmentsP1
Expand All @@ -85,12 +91,13 @@ var _ = Describe("Environment Client", func() {
"offset": "100",
"limit": "100",
"organizationId": organizationId,
"name": mockEnvironment.Name,
}, gomock.Any()).
Do(func(path string, request interface{}, response *[]Environment) {
*response = environmentsP2
}).Times(1)

environments, err = apiClient.Environments()
environments, err = apiClient.EnvironmentsByName(mockEnvironment.Name)
})

It("Should return the environments", func() {
Expand Down Expand Up @@ -143,10 +150,15 @@ var _ = Describe("Environment Client", func() {
expectedErr := errors.New("some error")
mockOrganizationIdCall(organizationId)
httpCall = mockHttpClient.EXPECT().
Get("/environments", gomock.Any(), gomock.Any()).
Get("/environments", map[string]string{
"limit": "100",
"offset": "0",
"organizationId": organizationId,
"name": mockEnvironment.Name,
}, gomock.Any()).
Return(expectedErr)

_, err = apiClient.Environments()
_, err = apiClient.EnvironmentsByName(mockEnvironment.Name)
Expect(expectedErr).Should(Equal(err))
})
})
Expand Down
64 changes: 46 additions & 18 deletions client/template.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package client

//templates are actually called "blueprints" in some parts of the API, this layer
//attempts to abstract this detail away - all the users of api client should
//only use "template", no mention of blueprint
// templates are actually called "blueprints" in some parts of the API, this layer
// attempts to abstract this detail away - all the users of api client should
// only use "template", no mention of blueprint

import (
"errors"
Expand All @@ -13,6 +13,9 @@ import (
"github.com/Masterminds/semver/v3"
)

const TERRAGRUNT = "terragrunt"
const OPENTOFU = "opentofu"

type TemplateRetryOn struct {
Times int `json:"times,omitempty"`
ErrorRegex string `json:"errorRegex"`
Expand Down Expand Up @@ -61,10 +64,11 @@ type Template struct {
IsAzureDevOps bool `json:"isAzureDevOps" tfschema:"is_azure_devops"`
IsHelmRepository bool `json:"isHelmRepository"`
HelmChartName string `json:"helmChartName" tfschema:",omitempty"`
IsGitLab bool `json:"isGitLab" tfschema:"is_gitlab"`
IsGitlab bool `json:"isGitLab"`
TerragruntTfBinary string `json:"terragruntTfBinary" tfschema:",omitempty"`
TokenName string `json:"tokenName" tfschema:",omitempty"`
GitlabProjectId int `json:"gitlabProjectId" tfschema:",omitempty"`
AnsibleVersion string `json:"ansibleVersion" tfschema:",omitempty"`
}

type TemplateCreatePayload struct {
Expand All @@ -75,7 +79,7 @@ type TemplateCreatePayload struct {
Name string `json:"name"`
Repository string `json:"repository"`
Path string `json:"path,omitempty"`
IsGitLab bool `json:"isGitLab"`
IsGitlab bool `json:"isGitLab"`
TokenName string `json:"tokenName,omitempty"`
TokenId string `json:"tokenId,omitempty"`
GithubInstallationId int `json:"githubInstallationId,omitempty"`
Expand All @@ -95,6 +99,7 @@ type TemplateCreatePayload struct {
IsHelmRepository bool `json:"isHelmRepository"`
HelmChartName string `json:"helmChartName,omitempty"`
TerragruntTfBinary string `json:"terragruntTfBinary,omitempty"`
AnsibleVersion string `json:"ansibleVersion,omitempty"`
}

type TemplateAssignmentToProjectPayload struct {
Expand Down Expand Up @@ -122,32 +127,51 @@ func (payload *TemplateCreatePayload) Invalidate() error {
return errors.New("must not specify organizationId")
}

if payload.Type != "terragrunt" && payload.TerragruntVersion != "" {
if payload.Type != TERRAGRUNT && payload.TerragruntVersion != "" {
return errors.New("can't define terragrunt version for non-terragrunt template")
}
if payload.Type == "terragrunt" && payload.TerragruntVersion == "" {
if payload.Type == TERRAGRUNT && payload.TerragruntVersion == "" {
return errors.New("must supply terragrunt version")
}
if payload.Type == "opentofu" && payload.OpentofuVersion == "" {
if payload.Type == OPENTOFU && payload.OpentofuVersion == "" {
return errors.New("must supply opentofu version")
}

if payload.TerragruntTfBinary != "" && payload.Type != "terragrunt" {
if payload.TerragruntTfBinary != "" && payload.Type != TERRAGRUNT {
return fmt.Errorf("terragrunt_tf_binary should only be used when the template type is 'terragrunt', but type is '%s'", payload.Type)
}

if payload.IsTerragruntRunAll {
if payload.Type != "terragrunt" {
if payload.Type != TERRAGRUNT {
return errors.New(`can't set is_terragrunt_run_all to "true" for non-terragrunt template`)
}

c, _ := semver.NewConstraint(">= 0.28.1")

v, err := semver.NewVersion(payload.TerragruntVersion)
if err != nil {
return fmt.Errorf("invalid semver version %s: %s", payload.TerragruntVersion, err.Error())
return fmt.Errorf("invalid semver version %s: %w", payload.TerragruntVersion, err)
}

if !c.Check(v) {
return fmt.Errorf(`can't set is_terragrunt_run_all to "true" for terragrunt versions lower than 0.28.1`)
return errors.New("can't set is_terragrunt_run_all to 'true' for terragrunt versions lower than 0.28.1")
}
}

if payload.Type == "ansible" && payload.AnsibleVersion != "latest" {
if payload.AnsibleVersion == "" {
return errors.New("'ansible_version' is required")
}

c, _ := semver.NewConstraint(">= 3.0.0")

v, err := semver.NewVersion(payload.AnsibleVersion)
if err != nil {
return fmt.Errorf("invalid ansible version '%s': %w", payload.AnsibleVersion, err)
}

if !c.Check(v) {
return errors.New("supported ansible versions are 3.0.0 and above")
}
}

Expand All @@ -172,11 +196,11 @@ func (payload *TemplateCreatePayload) Invalidate() error {
}
}

if payload.Type != "terragrunt" && payload.Type != "terraform" {
if payload.Type != TERRAGRUNT && payload.Type != "terraform" {
payload.TerraformVersion = ""
}

if payload.Type != "opentofu" && payload.TerragruntTfBinary != "opentofu" {
if payload.Type != OPENTOFU && payload.TerragruntTfBinary != OPENTOFU {
payload.OpentofuVersion = ""
}

Expand All @@ -186,7 +210,7 @@ func (payload *TemplateCreatePayload) Invalidate() error {
func (client *ApiClient) TemplateCreate(payload TemplateCreatePayload) (Template, error) {
organizationId, err := client.OrganizationId()
if err != nil {
return Template{}, nil
return Template{}, err
}
payload.OrganizationId = organizationId

Expand Down Expand Up @@ -262,18 +286,22 @@ func (client *ApiClient) VariablesFromRepository(payload *VariablesFromRepositor
}

params := map[string]string{}

for key, value := range paramsInterface {
if key == "githubInstallationId" {
switch key {
case "githubInstallationId":
params[key] = strconv.Itoa(int(value.(float64)))
} else if key == "sshKeyIds" {
case "sshKeyIds":
sshkeys := []string{}

if value != nil {
for _, sshkey := range value.([]interface{}) {
sshkeys = append(sshkeys, "\""+sshkey.(string)+"\"")
}
}

params[key] = "[" + strings.Join(sshkeys, ",") + "]"
} else {
default:
params[key] = value.(string)
}
}
Expand Down
20 changes: 12 additions & 8 deletions client/template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,13 +126,15 @@ var _ = Describe("Templates Client", func() {
})

Describe("TemplateDelete", func() {
var err error

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Delete("/blueprints/"+mockTemplate.Id, nil)
apiClient.TemplateDelete(mockTemplate.Id)
httpCall = mockHttpClient.EXPECT().Delete("/blueprints/"+mockTemplate.Id, nil).Times(1)
err = apiClient.TemplateDelete(mockTemplate.Id)
})

It("Should send DELETE request with template id", func() {
httpCall.Times(1)
It("should not return an error", func() {
Expect(err).To(BeNil())
})
})

Expand Down Expand Up @@ -209,14 +211,16 @@ var _ = Describe("Templates Client", func() {
})

Describe("remove template from project", func() {
var err error

projectId := "project-id"
BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Delete("/blueprints/"+mockTemplate.Id+"/projects/"+projectId, nil)
apiClient.RemoveTemplateFromProject(mockTemplate.Id, projectId)
httpCall = mockHttpClient.EXPECT().Delete("/blueprints/"+mockTemplate.Id+"/projects/"+projectId, nil).Times(1)
err = apiClient.RemoveTemplateFromProject(mockTemplate.Id, projectId)
})

It("Should send DELETE request with template id and project id", func() {
httpCall.Times(1)
It("should not return an error", func() {
Expect(err).To(BeNil())
})
})

Expand Down
2 changes: 1 addition & 1 deletion docs/data-sources/template.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ data "env0_template" "example" {
- `terragrunt_version` (String) terragrunt version to use
- `token_id` (String) The token id used for private git repos or for integration with GitLab
- `token_name` (String) the token name used for integration with GitLab
- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu)
- `type` (String) the template type

<a id="nestedatt--ssh_keys"></a>
### Nested Schema for `ssh_keys`
Expand Down
4 changes: 3 additions & 1 deletion docs/resources/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,7 @@ Required:

Optional:

- `ansible_version` (String) the ansible version to use (required when the template type is 'ansible'). Supported versions are 3.0.0 and above
- `bitbucket_client_key` (String) the bitbucket client key used for integration
- `description` (String) description for the template
- `file_name` (String) the cloudformation file name. Required if the template type is cloudformation
Expand All @@ -173,6 +174,7 @@ Optional:
- `is_azure_devops` (Boolean) true if this template integrates with azure dev ops
- `is_bitbucket_server` (Boolean) true if this template uses bitbucket server repository
- `is_github_enterprise` (Boolean) true if this template uses github enterprise repository
- `is_gitlab` (Boolean) set to 'true' if the repository is Gitlab
- `is_gitlab_enterprise` (Boolean) true if this template uses gitlab enterprise repository
- `is_helm_repository` (Boolean) true if this template integrates with a helm repository
- `is_terragrunt_run_all` (Boolean) true if this template should execute run-all commands on multiple modules (check https://terragrunt.gruntwork.io/docs/features/execute-terraform-commands-on-multiple-modules-at-once/#the-run-all-command for additional details). Can only be true with "terragrunt" template type and terragrunt version 0.28.1 and above
Expand All @@ -189,7 +191,7 @@ Optional:
- `terragrunt_version` (String) the Terragrunt version to use (example: 0.36.5)
- `token_id` (String) the git token id to be used
- `token_name` (String) token name for Gitlab
- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu)
- `type` (String) template type (allowed values: terraform, terragrunt, pulumi, k8s, workflow, cloudformation, helm, opentofu, ansible)

Read-Only:

Expand Down
Loading

0 comments on commit 6e39397

Please sign in to comment.