Skip to content

Commit

Permalink
Feat: support new ansible template type (#930)
Browse files Browse the repository at this point in the history
* Feat: support new ansible template type

* fix test

* added 'latest' as a possible version
  • Loading branch information
TomerHeber authored Aug 11, 2024
1 parent 6cfa4b5 commit 6ceee88
Show file tree
Hide file tree
Showing 8 changed files with 221 additions and 59 deletions.
60 changes: 44 additions & 16 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 @@ -65,6 +68,7 @@ type Template struct {
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 Down Expand Up @@ -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
4 changes: 1 addition & 3 deletions env0/data_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ package env0

import (
"context"
"fmt"
"strings"

"github.com/env0/terraform-provider-env0/client"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
Expand Down Expand Up @@ -49,7 +47,7 @@ func dataTemplate() *schema.Resource {
},
"type": {
Type: schema.TypeString,
Description: fmt.Sprintf("template type (allowed values: %s)", strings.Join(allowedTemplateTypes, ", ")),
Description: "the template type",
Computed: true,
},
"project_ids": {
Expand Down
1 change: 1 addition & 0 deletions env0/data_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ func TestUnitTemplateData(t *testing.T) {
t.Run("Template By Name", func(t *testing.T) {
deletedTemplate := template
deletedTemplate.IsDeleted = true

runUnitTest(t,
getValidTestCase(map[string]interface{}{"name": template.Name}),
func(mock *client.MockApiClientInterface) {
Expand Down
37 changes: 21 additions & 16 deletions env0/resource_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,6 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

var allowedTemplateTypes = []string{
"terraform",
"terragrunt",
"pulumi",
"k8s",
"workflow",
"cloudformation",
"helm",
"opentofu",
}

func getTemplateSchema(prefix string) map[string]*schema.Schema {
var allVCSAttributes = []string{
"token_id",
Expand All @@ -39,6 +28,18 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema {
"path",
}

var allowedTemplateTypes = []string{
"terraform",
"terragrunt",
"pulumi",
"k8s",
"workflow",
"cloudformation",
"helm",
"opentofu",
"ansible",
}

allVCSAttributesBut := func(strs ...string) []string {
butAttrs := []string{}

Expand Down Expand Up @@ -255,6 +256,13 @@ func getTemplateSchema(prefix string) map[string]*schema.Schema {
Optional: true,
Default: false,
},
"ansible_version": {
Type: schema.TypeString,
Description: "the ansible version to use (required when the template type is 'ansible'). Supported versions are 3.0.0 and above",
Optional: true,
ValidateDiagFunc: NewRegexValidator(`^(?:[0-9]{1,2}\.[0-9]{1,2}\.[0-9]{1,2})|latest|$`),
Default: "",
},
}

if prefix == "" {
Expand Down Expand Up @@ -403,11 +411,8 @@ func templateCreatePayloadFromParameters(prefix string, d *schema.ResourceData)
// If the user has set a value - use it.
if terragruntTfBinary := d.Get(terragruntTfBinaryKey).(string); terragruntTfBinary != "" {
payload.TerragruntTfBinary = terragruntTfBinary
} else {
// No value was set - if it's a new template resource of type 'terragrunt' - default to 'opentofu'
if templateType.(string) == "terragrunt" && isNew {
payload.TerragruntTfBinary = "opentofu"
}
} else if templateType.(string) == "terragrunt" && isNew {
payload.TerragruntTfBinary = "opentofu"
}
}

Expand Down
Loading

0 comments on commit 6ceee88

Please sign in to comment.