Skip to content

Commit

Permalink
Feat: environment resource does not support new templateless creation (
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerHeber authored Aug 18, 2022
1 parent 49ca99c commit 07dc174
Show file tree
Hide file tree
Showing 7 changed files with 210 additions and 19 deletions.
1 change: 1 addition & 0 deletions client/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ type Environment struct {
IsArchived bool `json:"isArchived"`
TerragruntWorkingDirectory string `json:"terragruntWorkingDirectory,omitempty"`
VcsCommandsAlias string `json:"vcsCommandsAlias"`
BlueprintId string `json:"blueprintId" tfschema:"-"`
}

type EnvironmentCreate struct {
Expand Down
4 changes: 2 additions & 2 deletions env0/data_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,8 +166,8 @@ func dataTemplateRead(ctx context.Context, d *schema.ResourceData, meta interfac
return diag.Errorf("schema resource data serialization failed: %v", err)
}

templateReadRetryOnHelper(d, "deploy", template.Retry.OnDeploy)
templateReadRetryOnHelper(d, "destroy", template.Retry.OnDestroy)
templateReadRetryOnHelper("", d, "deploy", template.Retry.OnDeploy)
templateReadRetryOnHelper("", d, "destroy", template.Retry.OnDestroy)

return nil
}
Expand Down
54 changes: 48 additions & 6 deletions env0/resource_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -339,36 +339,60 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i

setEnvironmentSchema(d, environment, environmentConfigurationVariables)

if d.Get("template_id").(string) == "" {
// envrionment with no template.
template, err := apiClient.Template(environment.BlueprintId)
if err != nil {
return diag.Errorf("could not get template: %v", err)
}
if err := templateRead("without_template_settings", template, d); err != nil {
return diag.Errorf("schema resource data serialization failed: %v", err)
}
}

return nil
}

func resourceEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

if shouldUpdate(d) {
err := update(d, apiClient)
if err != nil {
if err := update(d, apiClient); err != nil {
return err
}
}

if shouldUpdateTTL(d) {
err := updateTTL(d, apiClient)
if err != nil {

if err := updateTTL(d, apiClient); err != nil {
return err
}
}

if shouldUpdateTemplate(d) {
if err := updateTemplate(d, apiClient); err != nil {
return err
}
}

if shouldDeploy(d) {
err := deploy(d, apiClient)
if err != nil {
if err := deploy(d, apiClient); err != nil {
return err
}
}

return nil
}

func shouldUpdateTemplate(d *schema.ResourceData) bool {
if d.Get("template_id") != "" {
// Using an environment with a template.
return false
}

return d.HasChange("without_template_settings.0")
}

func shouldDeploy(d *schema.ResourceData) bool {
return d.HasChanges("revision", "configuration")
}
Expand All @@ -381,6 +405,24 @@ func shouldUpdateTTL(d *schema.ResourceData) bool {
return d.HasChange("ttl")
}

func updateTemplate(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Diagnostics {
payload, problem := templateCreatePayloadFromParameters("without_template_settings.0", d)
if problem != nil {
return problem
}

environment, err := apiClient.Environment(d.Id())
if err != nil {
return diag.Errorf("could not get environment: %v", err)
}

if _, err := apiClient.TemplateUpdate(environment.BlueprintId, payload); err != nil {
return diag.Errorf("could not update template: %v", err)
}

return nil
}

func deploy(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Diagnostics {
deployPayload := getDeployPayload(d, apiClient, true)
deployResponse, err := apiClient.EnvironmentDeploy(d.Id(), deployPayload)
Expand Down
104 changes: 104 additions & 0 deletions env0/resource_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -997,6 +997,7 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
WorkspaceName: "workspace-name",
TerragruntWorkingDirectory: "/terragrunt/directory/",
VcsCommandsAlias: "alias",
BlueprintId: "id-template-0",
}

template := client.Template{
Expand All @@ -1021,6 +1022,28 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
TerraformVersion: "0.12.24",
}

updatedTemplate := client.Template{
Id: "id-template-0",
Name: "single-use-template-for-" + environment.Name,
Description: "description1",
Repository: "env0/repo1",
Path: "path/zero1",
Revision: "branch-zero1",
Retry: client.TemplateRetry{
OnDeploy: &client.TemplateRetryOn{
Times: 3,
ErrorRegex: "RetryMeForDeploy.*",
},
OnDestroy: &client.TemplateRetryOn{
Times: 3,
ErrorRegex: "RetryMeForDestroy.*",
},
},
Type: "terraform",
GithubInstallationId: 2,
TerraformVersion: "0.12.25",
}

environmentCreatePayload := client.EnvironmentCreate{
Name: environment.Name,
ProjectId: environment.ProjectId,
Expand Down Expand Up @@ -1055,6 +1078,27 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
OrganizationId: template.OrganizationId,
}

templateUpdatePayload := client.TemplateCreatePayload{
Repository: updatedTemplate.Repository,
Description: updatedTemplate.Description,
GithubInstallationId: updatedTemplate.GithubInstallationId,
IsGitlabEnterprise: updatedTemplate.IsGitlabEnterprise,
IsGitLab: updatedTemplate.TokenId != "",
TokenId: updatedTemplate.TokenId,
Path: updatedTemplate.Path,
Revision: updatedTemplate.Revision,
Type: client.TemplateTypeTerraform,
Retry: updatedTemplate.Retry,
TerraformVersion: updatedTemplate.TerraformVersion,
BitbucketClientKey: updatedTemplate.BitbucketClientKey,
IsGithubEnterprise: updatedTemplate.IsGithubEnterprise,
IsBitbucketServer: updatedTemplate.IsBitbucketServer,
FileName: updatedTemplate.FileName,
TerragruntVersion: updatedTemplate.TerragruntVersion,
IsTerragruntRunAll: updatedTemplate.IsTerragruntRunAll,
OrganizationId: updatedTemplate.OrganizationId,
}

createPayload := client.EnvironmentCreateWithoutTemplate{
EnvironmentCreate: environmentCreatePayload,
TemplateCreate: templateCreatePayload,
Expand Down Expand Up @@ -1106,6 +1150,7 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
t.Run("Success in create", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
// Create the environment and template
{
Config: createEnvironmentResourceConfig(environment, template),
Check: resource.ComposeAggregateTestCheckFunc(
Expand All @@ -1127,13 +1172,72 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retry_on_destroy_only_when_matches_regex", template.Retry.OnDestroy.ErrorRegex),
),
},
// Update the template.
{
Config: createEnvironmentResourceConfig(environment, updatedTemplate),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "name", environment.Name),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
resource.TestCheckNoResourceAttr(accessor, "template_id"),
resource.TestCheckResourceAttr(accessor, "workspace", environment.WorkspaceName),
resource.TestCheckResourceAttr(accessor, "terragrunt_working_directory", environment.TerragruntWorkingDirectory),
resource.TestCheckResourceAttr(accessor, "vcs_commands_alias", environment.VcsCommandsAlias),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.repository", updatedTemplate.Repository),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.terraform_version", updatedTemplate.TerraformVersion),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.type", updatedTemplate.Type),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.path", updatedTemplate.Path),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.revision", updatedTemplate.Revision),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retries_on_deploy", strconv.Itoa(updatedTemplate.Retry.OnDeploy.Times)),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retry_on_deploy_only_when_matches_regex", updatedTemplate.Retry.OnDeploy.ErrorRegex),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retries_on_destroy", strconv.Itoa(updatedTemplate.Retry.OnDestroy.Times)),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retry_on_destroy_only_when_matches_regex", updatedTemplate.Retry.OnDestroy.ErrorRegex),
),
},
// No need to update template
{
Config: createEnvironmentResourceConfig(environment, updatedTemplate),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "name", environment.Name),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
resource.TestCheckNoResourceAttr(accessor, "template_id"),
resource.TestCheckResourceAttr(accessor, "workspace", environment.WorkspaceName),
resource.TestCheckResourceAttr(accessor, "terragrunt_working_directory", environment.TerragruntWorkingDirectory),
resource.TestCheckResourceAttr(accessor, "vcs_commands_alias", environment.VcsCommandsAlias),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.repository", updatedTemplate.Repository),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.terraform_version", updatedTemplate.TerraformVersion),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.type", updatedTemplate.Type),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.path", updatedTemplate.Path),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.revision", updatedTemplate.Revision),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retries_on_deploy", strconv.Itoa(updatedTemplate.Retry.OnDeploy.Times)),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retry_on_deploy_only_when_matches_regex", updatedTemplate.Retry.OnDeploy.ErrorRegex),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retries_on_destroy", strconv.Itoa(updatedTemplate.Retry.OnDestroy.Times)),
resource.TestCheckResourceAttr(accessor, "without_template_settings.0.retry_on_destroy_only_when_matches_regex", updatedTemplate.Retry.OnDestroy.ErrorRegex),
),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {
// Step1
mock.EXPECT().EnvironmentCreateWithoutTemplate(createPayload).Times(1).Return(environment, nil)
mock.EXPECT().Environment(environment.Id).Times(1).Return(environment, nil)
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil)
mock.EXPECT().Template(environment.BlueprintId).Times(1).Return(template, nil)

// Step2
mock.EXPECT().Environment(environment.Id).Times(2).Return(environment, nil)
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(2).Return(client.ConfigurationChanges{}, nil)
mock.EXPECT().Template(environment.BlueprintId).Times(1).Return(template, nil)
mock.EXPECT().Environment(environment.Id).Times(1).Return(environment, nil)
mock.EXPECT().TemplateUpdate(environment.BlueprintId, templateUpdatePayload).Times(1).Return(updatedTemplate, nil)
mock.EXPECT().Template(environment.BlueprintId).Times(1).Return(updatedTemplate, nil)

// Step3
mock.EXPECT().Environment(environment.Id).Times(2).Return(environment, nil)
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(2).Return(client.ConfigurationChanges{}, nil)
mock.EXPECT().Template(environment.BlueprintId).Times(2).Return(updatedTemplate, nil)
mock.EXPECT().EnvironmentDestroy(environment.Id).Times(1)
})
})
Expand Down
34 changes: 23 additions & 11 deletions env0/resource_template.go
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ func resourceTemplateRead(ctx context.Context, d *schema.ResourceData, meta inte
return nil
}

if err := templateRead(template, d); err != nil {
if err := templateRead("", template, d); err != nil {
return diag.Errorf("%v", err)
}

Expand Down Expand Up @@ -354,24 +354,36 @@ func templateCreatePayloadFromParameters(prefix string, d *schema.ResourceData)
}

// Reads template and writes to the resource data.
func templateRead(template client.Template, d *schema.ResourceData) error {
if err := writeResourceData(&template, d); err != nil {
func templateRead(prefix string, template client.Template, d *schema.ResourceData) error {
if err := writeResourceDataEx(prefix, &template, d); err != nil {
return fmt.Errorf("schema resource data serialization failed: %v", err)
}

templateReadRetryOnHelper(d, "deploy", template.Retry.OnDeploy)
templateReadRetryOnHelper(d, "destroy", template.Retry.OnDestroy)
templateReadRetryOnHelper(prefix, d, "deploy", template.Retry.OnDeploy)
templateReadRetryOnHelper(prefix, d, "destroy", template.Retry.OnDestroy)

return nil
}

// Helpers function for templateRead.
func templateReadRetryOnHelper(d *schema.ResourceData, retryType string, retryOn *client.TemplateRetryOn) {
if retryOn != nil {
d.Set("retries_on_"+retryType, retryOn.Times)
d.Set("retry_on_"+retryType+"_only_when_matches_regex", retryOn.ErrorRegex)
func templateReadRetryOnHelper(prefix string, d *schema.ResourceData, retryType string, retryOn *client.TemplateRetryOn) {
if prefix != "" {
value := d.Get(prefix + ".0").(map[string]interface{})
if retryOn != nil {
value["retries_on_"+retryType] = retryOn.Times
value["retry_on_"+retryType+"_only_when_matches_regex"] = retryOn.ErrorRegex
} else {
value["retries_on_"+retryType] = 0
value["retry_on_"+retryType+"_only_when_matches_regex"] = ""
}
d.Set(prefix, []interface{}{value})
} else {
d.Set("retries_on_"+retryType, 0)
d.Set("retry_on_"+retryType+"_only_when_matches_regex", "")
if retryOn != nil {
d.Set("retries_on_"+retryType, retryOn.Times)
d.Set("retry_on_"+retryType+"_only_when_matches_regex", retryOn.ErrorRegex)
} else {
d.Set("retries_on_"+retryType, 0)
d.Set("retry_on_"+retryType+"_only_when_matches_regex", "")
}
}
}
7 changes: 7 additions & 0 deletions env0/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -404,3 +404,10 @@ func writeResourceDataSlice(i interface{}, name string, d *schema.ResourceData)

return nil
}

func writeResourceDataEx(prefix string, i interface{}, d *schema.ResourceData) error {
if prefix == "" {
return writeResourceData(i, d)
}
return writeResourceDataSlice([]interface{}{i}, prefix, d)
}
25 changes: 25 additions & 0 deletions tests/integration/012_environment/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,28 @@ output "revision" {
output "terragrunt_working_directory" {
value = env0_environment.terragrunt_environment.terragrunt_working_directory
}

data "env0_template" "github_template" {
name = "Github Integrated Template"
}

resource "env0_environment" "environment-without-template" {
force_destroy = true
name = "environment-without-template-${random_string.random.result}"
project_id = env0_project.test_project.id
approve_plan_automatically = true
revision = "master"
auto_deploy_on_path_changes_only = false

without_template_settings {
description = "Template description - GitHub"
type = "terraform"
repository = data.env0_template.github_template.repository
github_installation_id = data.env0_template.github_template.github_installation_id
path = var.second_run ? "second" : "misc/null-resource"
retries_on_deploy = 3
retry_on_deploy_only_when_matches_regex = "abc"
retries_on_destroy = 1
terraform_version = "0.15.1"
}
}

0 comments on commit 07dc174

Please sign in to comment.