diff --git a/client/model.go b/client/model.go index d0504bc1..a56f384b 100644 --- a/client/model.go +++ b/client/model.go @@ -76,12 +76,12 @@ const ( type TemplateRetryOn struct { Times int `json:"times,omitempty"` - ErrorRegex string `json:"errorRegex,omitempty"` + ErrorRegex string `json:"errorRegex"` } type TemplateRetry struct { - OnDeploy TemplateRetryOn `json:"onDeploy,omitempty"` - OnDestroy TemplateRetryOn `json:"onDestroy,omitempty"` + OnDeploy *TemplateRetryOn `json:"onDeploy"` + OnDestroy *TemplateRetryOn `json:"onDestroy"` } type TemplateType string @@ -97,7 +97,7 @@ type TemplateSshKey struct { } type TemplateCreatePayload struct { - Retry TemplateRetry `json:"retry,omitempty"` + Retry TemplateRetry `json:"retry"` SshKeys []TemplateSshKey `json:"sshKeys,omitempty"` Type TemplateType `json:"type"` Description string `json:"description"` diff --git a/env0/data_configuration_variable_test.go b/env0/data_configuration_variable_test.go index 9d68d738..6b04eca5 100644 --- a/env0/data_configuration_variable_test.go +++ b/env0/data_configuration_variable_test.go @@ -42,11 +42,11 @@ func TestUnitConfigurationVariableData(t *testing.T) { Steps: []resource.TestStep{ { Config: dataSourceConfigCreate(resourceType, resourceName, map[string]interface{}{"id": configurationVariable.Id}), - Check: checkResources, + Check: checkResources, }, { Config: dataSourceConfigCreate(resourceType, resourceName, map[string]interface{}{"name": configurationVariable.Name}), - Check: checkResources, + Check: checkResources, }, }, }, @@ -62,7 +62,7 @@ func TestUnitConfigurationVariableData(t *testing.T) { Steps: []resource.TestStep{ { Config: dataSourceConfigCreate(resourceType, resourceName, map[string]interface{}{"id": configurationVariable.Id, "template_id": "template_id"}), - Check: checkResources, + Check: checkResources, }, }, }, diff --git a/env0/data_template.go b/env0/data_template.go index 62937001..372871e7 100644 --- a/env0/data_template.go +++ b/env0/data_template.go @@ -112,14 +112,14 @@ func dataTemplateRead(ctx context.Context, d *schema.ResourceData, meta interfac d.Set("type", template.Type) d.Set("project_ids", template.ProjectIds) d.Set("terraform_version", template.TerraformVersion) - if (template.Retry.OnDeploy != client.TemplateRetryOn{}) { + if template.Retry.OnDeploy != nil { d.Set("retries_on_deploy", template.Retry.OnDeploy.Times) d.Set("retry_on_deploy_only_when_matches_regex", template.Retry.OnDeploy.ErrorRegex) } else { d.Set("retries_on_deploy", 0) d.Set("retry_on_deploy_only_when_matches_regex", "") } - if (template.Retry.OnDestroy != client.TemplateRetryOn{}) { + if template.Retry.OnDestroy != nil { d.Set("retries_on_destroy", template.Retry.OnDestroy.Times) d.Set("retry_on_destroy_only_when_matches_regex", template.Retry.OnDestroy.ErrorRegex) } else { diff --git a/env0/data_template_test.go b/env0/data_template_test.go index dd14c5f1..ff8f30d9 100644 --- a/env0/data_template_test.go +++ b/env0/data_template_test.go @@ -21,8 +21,8 @@ func TestUnitTemplateData(t *testing.T) { ErrorRegex: "error retry on destroy", } templateRetry := client.TemplateRetry{ - OnDeploy: onDeploy, - OnDestroy: onDestroy, + OnDeploy: &onDeploy, + OnDestroy: &onDestroy, } template := client.Template{ diff --git a/env0/resource_template.go b/env0/resource_template.go index 13da736c..7f8cccab 100644 --- a/env0/resource_template.go +++ b/env0/resource_template.go @@ -5,12 +5,22 @@ import ( "errors" "github.com/env0/terraform-provider-env0/client" "github.com/google/uuid" + "github.com/hashicorp/go-cty/cty" "github.com/hashicorp/terraform-plugin-sdk/v2/diag" "github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema" "log" ) func resourceTemplate() *schema.Resource { + validateRetries := func(i interface{}, path cty.Path) diag.Diagnostics { + retries := i.(int) + if retries < 1 || retries > 3 { + return diag.Errorf("Retries amount must be between 1 and 3") + } + + return nil + } + return &schema.Resource{ CreateContext: resourceTemplateCreate, ReadContext: resourceTemplateRead, @@ -61,24 +71,28 @@ func resourceTemplate() *schema.Resource { }, }, "retries_on_deploy": { - Type: schema.TypeInt, - Description: "number of times to retry when deploying an environment based on this template", - Optional: true, + Type: schema.TypeInt, + Description: "number of times to retry when deploying an environment based on this template", + Optional: true, + ValidateDiagFunc: validateRetries, }, "retry_on_deploy_only_when_matches_regex": { - Type: schema.TypeString, - Description: "if specified, will only retry (on deploy) if error matches specified regex", - Optional: true, + Type: schema.TypeString, + Description: "if specified, will only retry (on deploy) if error matches specified regex", + Optional: true, + RequiredWith: []string{"retries_on_deploy"}, }, "retries_on_destroy": { - Type: schema.TypeInt, - Description: "number of times to retry when destroying an environment based on this template", - Optional: true, + Type: schema.TypeInt, + Description: "number of times to retry when destroying an environment based on this template", + Optional: true, + ValidateDiagFunc: validateRetries, }, "retry_on_destroy_only_when_matches_regex": { - Type: schema.TypeString, - Description: "if specified, will only retry (on destroy) if error matches specified regex", - Optional: true, + Type: schema.TypeString, + Description: "if specified, will only retry (on destroy) if error matches specified regex", + Optional: true, + RequiredWith: []string{"retries_on_destroy"}, }, "github_installation_id": { Type: schema.TypeInt, @@ -160,10 +174,10 @@ func templateCreatePayloadFromParameters(d *schema.ResourceData) (client.Templat if onDeploy != nil || onDestroy != nil { result.Retry = client.TemplateRetry{} if onDeploy != nil { - result.Retry.OnDeploy = *onDeploy + result.Retry.OnDeploy = onDeploy } if onDestroy != nil { - result.Retry.OnDestroy = *onDestroy + result.Retry.OnDestroy = onDestroy } } @@ -213,14 +227,14 @@ func resourceTemplateRead(ctx context.Context, d *schema.ResourceData, meta inte } d.Set("ssh_keys", rawSshKeys) - if (template.Retry.OnDeploy != client.TemplateRetryOn{}) { + if template.Retry.OnDeploy != nil { d.Set("retries_on_deploy", template.Retry.OnDeploy.Times) d.Set("retry_on_deploy_only_when_matches_regex", template.Retry.OnDeploy.ErrorRegex) } else { d.Set("retries_on_deploy", 0) d.Set("retry_on_deploy_only_when_matches_regex", "") } - if (template.Retry.OnDestroy != client.TemplateRetryOn{}) { + if template.Retry.OnDestroy != nil { d.Set("retries_on_destroy", template.Retry.OnDestroy.Times) d.Set("retry_on_destroy_only_when_matches_regex", template.Retry.OnDestroy.ErrorRegex) } else { diff --git a/env0/resource_template_test.go b/env0/resource_template_test.go index d1a9407a..91435f4d 100644 --- a/env0/resource_template_test.go +++ b/env0/resource_template_test.go @@ -10,7 +10,7 @@ import ( "testing" ) -func TestUnitTeplateResource(t *testing.T) { +func TestUnitTemplateResource(t *testing.T) { const resourceType = "env0_template" const resourceName = "test" const defaultVersion = "0.15.1" @@ -27,11 +27,11 @@ func TestUnitTeplateResource(t *testing.T) { Path: "path/zero", Revision: "branch-zero", Retry: client.TemplateRetry{ - OnDeploy: client.TemplateRetryOn{ + OnDeploy: &client.TemplateRetryOn{ Times: 2, ErrorRegex: "RetryMeForDeploy.*", }, - OnDestroy: client.TemplateRetryOn{ + OnDestroy: &client.TemplateRetryOn{ Times: 1, ErrorRegex: "RetryMeForDestroy.*", }, @@ -49,11 +49,11 @@ func TestUnitTeplateResource(t *testing.T) { Path: "path/zero/new", Revision: "branch-zero-new", Retry: client.TemplateRetry{ - OnDeploy: client.TemplateRetryOn{ + OnDeploy: &client.TemplateRetryOn{ Times: 1, ErrorRegex: "NewForDeploy.*", }, - OnDestroy: client.TemplateRetryOn{ + OnDestroy: &client.TemplateRetryOn{ Times: 2, ErrorRegex: "NewForDestroy.*", }, @@ -312,4 +312,49 @@ func TestUnitTeplateResource(t *testing.T) { mock.EXPECT().TemplateDelete(template.Id).Times(1).Return(nil) }) }) + + t.Run("Invalid retry times field", func(t *testing.T) { + testMatrix := map[string][]int{ + "retries_on_deploy": {-1, 0, 4, 5}, + "retries_on_destroy": {-1, 0, 4, 5}, + } + + var testCases []resource.TestCase + for attribute, amounts := range testMatrix { + for _, amount := range amounts { + testCases = append(testCases, resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{"name": "test", "repository": "env0/test", attribute: amount}), + ExpectError: regexp.MustCompile("Retries amount must be between 1 and 3"), + }, + }, + }) + } + } + + for _, testCase := range testCases { + runUnitTest(t, testCase, func(mockFunc *client.MockApiClientInterface) {}) + } + }) + + t.Run("Invalid retry regex field", func(t *testing.T) { + testMatrix := map[string]string{ + "retries_on_deploy": "retry_on_deploy_only_when_matches_regex", + "retries_on_destroy": "retry_on_destroy_only_when_matches_regex", + } + + var testCases []resource.TestCase + for timesAttribute, regexAttribute := range testMatrix { + testCases = append(testCases, resource.TestCase{ + Steps: []resource.TestStep{ + { + Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{"name": "test", "repository": "env0/test", regexAttribute: "bla"}), + ExpectError: regexp.MustCompile(fmt.Sprintf("`%s,%s` must be specified", timesAttribute, regexAttribute)), + }, + }, + }) + } + + }) }