Skip to content

Commit

Permalink
Feat: add support for subenvironments (#579)
Browse files Browse the repository at this point in the history
* Feat: add support for subenvironments

* passing 'workflow' in type

* added integration tests

* minor fix
  • Loading branch information
TomerHeber committed Feb 5, 2023
1 parent 499c695 commit bb71636
Show file tree
Hide file tree
Showing 7 changed files with 181 additions and 31 deletions.
1 change: 1 addition & 0 deletions client/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ type EnvironmentCreate struct {
TerragruntWorkingDirectory string `json:"terragruntWorkingDirectory,omitempty"`
VcsCommandsAlias string `json:"vcsCommandsAlias"`
IsRemoteBackend *bool `json:"isRemoteBackend,omitempty" tfschema:"-"`
Type string `json:"type,omitempty"`
}

// When converted to JSON needs to be flattened. See custom MarshalJSON below.
Expand Down
5 changes: 4 additions & 1 deletion client/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -415,5 +415,8 @@ func TestMarshalEnvironmentCreateWithoutTemplate(t *testing.T) {
var environmentCreateFromJSON EnvironmentCreate

require.NoError(t, json.Unmarshal(b, &environmentCreateFromJSON))
require.Equal(t, environmentCreate, environmentCreateFromJSON)

environmentCreateWithType := environmentCreate
environmentCreateWithType.Type = templateCreate.Type
require.Equal(t, environmentCreateWithType, environmentCreateFromJSON)
}
9 changes: 1 addition & 8 deletions client/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,6 @@ type TemplateRetry struct {
OnDestroy *TemplateRetryOn `json:"onDestroy"`
}

type TemplateType string

const (
TemplateTypeTerraform TemplateType = "terraform"
TemplateTypeTerragrunt TemplateType = "terragrunt"
)

type TemplateSshKey struct {
Id string `json:"id"`
Name string `json:"name"`
Expand Down Expand Up @@ -70,7 +63,7 @@ type Template struct {
type TemplateCreatePayload struct {
Retry TemplateRetry `json:"retry"`
SshKeys []TemplateSshKey `json:"sshKeys,omitempty"`
Type TemplateType `json:"type"`
Type string `json:"type"`
Description string `json:"description"`
Name string `json:"name"`
Repository string `json:"repository"`
Expand Down
29 changes: 28 additions & 1 deletion env0/resource_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -599,7 +599,7 @@ func shouldUpdateTemplate(d *schema.ResourceData) bool {
}

func shouldDeploy(d *schema.ResourceData) bool {
return d.HasChanges("revision", "configuration")
return d.HasChanges("revision", "configuration", "sub_environment_configuration")
}

func shouldUpdate(d *schema.ResourceData) bool {
Expand Down Expand Up @@ -627,6 +627,31 @@ func updateTemplate(d *schema.ResourceData, apiClient client.ApiClientInterface)

func deploy(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Diagnostics {
deployPayload := getDeployPayload(d, apiClient, true)

subEnvironments, err := getSubEnvironments(d)
if err != nil {
return diag.Errorf("failed to extract subenvrionments from resourcedata: %v", err)
}

if len(subEnvironments) > 0 {
deployPayload.SubEnvironments = make(map[string]client.SubEnvironment)

for i, subEnvironment := range subEnvironments {
configuration := d.Get(fmt.Sprintf("sub_environment_configuration.%d.configuration", i)).([]interface{})
configurationChanges := getConfigurationVariablesFromSchema(configuration)
configurationChanges = getUpdateConfigurationVariables(configurationChanges, subEnvironment.Id, apiClient)

for i := range configurationChanges {
configurationChanges[i].Scope = client.ScopeEnvironment
}

deployPayload.SubEnvironments[subEnvironment.Alias] = client.SubEnvironment{
Revision: subEnvironment.Revision,
ConfigurationChanges: configurationChanges,
}
}
}

deployResponse, err := apiClient.EnvironmentDeploy(d.Id(), deployPayload)
if err != nil {
return diag.Errorf("failed deploying environment: %v", err)
Expand Down Expand Up @@ -729,6 +754,8 @@ func getCreatePayload(d *schema.ResourceData, apiClient client.ApiClientInterfac
}

if len(subEnvironments) > 0 {
payload.Type = "workflow"

deployPayload.SubEnvironments = make(map[string]client.SubEnvironment)

for _, subEnvironment := range subEnvironments {
Expand Down
114 changes: 96 additions & 18 deletions env0/resource_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1335,7 +1335,7 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
TokenId: template.TokenId,
Path: template.Path,
Revision: template.Revision,
Type: client.TemplateTypeTerraform,
Type: "terraform",
Retry: template.Retry,
TerraformVersion: template.TerraformVersion,
BitbucketClientKey: template.BitbucketClientKey,
Expand All @@ -1356,7 +1356,7 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
TokenId: updatedTemplate.TokenId,
Path: updatedTemplate.Path,
Revision: updatedTemplate.Revision,
Type: client.TemplateTypeTerraform,
Type: "terraform",
Retry: updatedTemplate.Retry,
TerraformVersion: updatedTemplate.TerraformVersion,
BitbucketClientKey: updatedTemplate.BitbucketClientKey,
Expand Down Expand Up @@ -1590,7 +1590,7 @@ func TestUnitEnvironmentWithoutTemplateResource(t *testing.T) {
})
}

func TestUnitEnvironmentWithoutSubEnvironment(t *testing.T) {
func TestUnitEnvironmentWithSubEnvironment(t *testing.T) {
resourceType := "env0_environment"
resourceName := "test"
accessor := resourceAccessor(resourceType, resourceName)
Expand All @@ -1599,7 +1599,7 @@ func TestUnitEnvironmentWithoutSubEnvironment(t *testing.T) {
EnvironmentId: "subenv1id",
}

subEnvrionment := SubEnvironment{
subEnvironment := SubEnvironment{
Alias: "alias1",
Revision: "revision1",
Configuration: client.ConfigurationChanges{
Expand All @@ -1618,7 +1618,21 @@ func TestUnitEnvironmentWithoutSubEnvironment(t *testing.T) {
},
}

subEnvrionmentWithId := subEnvrionment
updatedSubEnvironment := subEnvironment
updatedSubEnvironment.Configuration = append(updatedSubEnvironment.Configuration, client.ConfigurationVariable{
Name: "name2",
Value: "value2",
Scope: client.ScopeEnvironment,
IsSensitive: boolPtr(false),
IsReadOnly: boolPtr(false),
IsRequired: boolPtr(false),
Schema: &client.ConfigurationVariableSchema{
Type: "string",
},
Type: (*client.ConfigurationVariableType)(intPtr(0)),
})

subEnvrionmentWithId := subEnvironment
subEnvrionmentWithId.Id = workflowSubEnvironment.EnvironmentId

environment := client.Environment{
Expand All @@ -1630,7 +1644,7 @@ func TestUnitEnvironmentWithoutSubEnvironment(t *testing.T) {
LatestDeploymentLog: client.DeploymentLog{
WorkflowFile: &client.WorkflowFile{
Environments: map[string]client.WorkflowSubEnvironment{
subEnvrionment.Alias: workflowSubEnvironment,
subEnvironment.Alias: workflowSubEnvironment,
},
},
},
Expand All @@ -1642,22 +1656,33 @@ func TestUnitEnvironmentWithoutSubEnvironment(t *testing.T) {
DeployRequest: &client.DeployRequest{
BlueprintId: environment.BlueprintId,
SubEnvironments: map[string]client.SubEnvironment{
subEnvrionment.Alias: {
Revision: subEnvrionment.Revision,
ConfigurationChanges: subEnvrionment.Configuration,
subEnvironment.Alias: {
Revision: subEnvironment.Revision,
ConfigurationChanges: subEnvironment.Configuration,
},
},
},
Type: "workflow",
}

template := client.Template{
ProjectId: environment.ProjectId,
}

deployRequest := client.DeployRequest{
BlueprintId: environment.BlueprintId,
BlueprintRevision: environment.LatestDeploymentLog.BlueprintRevision,
SubEnvironments: map[string]client.SubEnvironment{
subEnvironment.Alias: {
Revision: subEnvironment.Revision,
ConfigurationChanges: updatedSubEnvironment.Configuration,
},
},
}

t.Run("Success in create", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
// Create the environment and template
{
Config: fmt.Sprintf(`
resource "%s" "%s" {
Expand All @@ -1678,21 +1703,66 @@ func TestUnitEnvironmentWithoutSubEnvironment(t *testing.T) {
environmentCreatePayload.Name,
environmentCreatePayload.ProjectId,
environment.BlueprintId,
subEnvrionment.Alias,
subEnvrionment.Revision,
subEnvrionment.Configuration[0].Name,
subEnvrionment.Configuration[0].Value,
subEnvironment.Alias,
subEnvironment.Revision,
subEnvironment.Configuration[0].Name,
subEnvironment.Configuration[0].Value,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "name", environment.Name),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
resource.TestCheckResourceAttr(accessor, "template_id", environment.BlueprintId),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.id", workflowSubEnvironment.EnvironmentId),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.alias", subEnvrionment.Alias),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.revision", subEnvrionment.Revision),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.0.name", subEnvrionment.Configuration[0].Name),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.0.value", subEnvrionment.Configuration[0].Value),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.alias", subEnvironment.Alias),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.revision", subEnvironment.Revision),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.0.name", subEnvironment.Configuration[0].Name),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.0.value", subEnvironment.Configuration[0].Value),
),
},
{
Config: fmt.Sprintf(`
resource "%s" "%s" {
name = "%s"
project_id = "%s"
template_id = "%s"
force_destroy = true
sub_environment_configuration {
alias = "%s"
revision = "%s"
configuration {
name = "%s"
value = "%s"
}
configuration {
name = "%s"
value = "%s"
}
}
}`,
resourceType, resourceName,
environmentCreatePayload.Name,
environmentCreatePayload.ProjectId,
environment.BlueprintId,
subEnvironment.Alias,
subEnvironment.Revision,
subEnvironment.Configuration[0].Name,
subEnvironment.Configuration[0].Value,
updatedSubEnvironment.Configuration[1].Name,
updatedSubEnvironment.Configuration[1].Value,
),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "name", environment.Name),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
resource.TestCheckResourceAttr(accessor, "template_id", environment.BlueprintId),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.id", workflowSubEnvironment.EnvironmentId),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.alias", subEnvironment.Alias),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.revision", subEnvironment.Revision),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.0.name", subEnvironment.Configuration[0].Name),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.0.value", subEnvironment.Configuration[0].Value),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.1.name", updatedSubEnvironment.Configuration[1].Name),
resource.TestCheckResourceAttr(accessor, "sub_environment_configuration.0.configuration.1.value", updatedSubEnvironment.Configuration[1].Value),
),
},
},
Expand All @@ -1704,6 +1774,14 @@ func TestUnitEnvironmentWithoutSubEnvironment(t *testing.T) {
mock.EXPECT().EnvironmentCreate(environmentCreatePayload).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().Environment(environment.Id).Times(1).Return(environment, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, environment.Id).Times(1).Return(client.ConfigurationChanges{}, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, subEnvrionmentWithId.Id).Times(1).Return(subEnvironment.Configuration, nil),
mock.EXPECT().EnvironmentDeploy(environment.Id, deployRequest).Times(1).Return(client.EnvironmentDeployResponse{
Id: environment.Id,
}, 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().EnvironmentDestroy(environment.Id).Times(1),
)
})
Expand Down
6 changes: 3 additions & 3 deletions env0/resource_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ func TestUnitTemplateResource(t *testing.T) {
const resourceType = "env0_template"
const resourceName = "test"
const defaultVersion = "0.15.1"
const defaultType = client.TemplateTypeTerraform
const defaultType = "terraform"

var resourceFullName = resourceAccessor(resourceType, resourceName)
gleeTemplate := client.Template{
Expand Down Expand Up @@ -526,7 +526,7 @@ func TestUnitTemplateResource(t *testing.T) {
TokenId: templateUseCase.template.TokenId,
Path: templateUseCase.template.Path,
Revision: templateUseCase.template.Revision,
Type: client.TemplateTypeTerraform,
Type: "terraform",
Retry: templateUseCase.template.Retry,
TerraformVersion: templateUseCase.template.TerraformVersion,
BitbucketClientKey: templateUseCase.template.BitbucketClientKey,
Expand All @@ -548,7 +548,7 @@ func TestUnitTemplateResource(t *testing.T) {
TokenId: templateUseCase.updatedTemplate.TokenId,
Path: templateUseCase.updatedTemplate.Path,
Revision: templateUseCase.updatedTemplate.Revision,
Type: client.TemplateType(templateUseCase.updatedTemplate.Type),
Type: templateUseCase.updatedTemplate.Type,
Retry: templateUseCase.updatedTemplate.Retry,
TerraformVersion: templateUseCase.updatedTemplate.TerraformVersion,
BitbucketClientKey: templateUseCase.updatedTemplate.BitbucketClientKey,
Expand Down
48 changes: 48 additions & 0 deletions tests/integration/012_environment/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,51 @@ resource "env0_environment" "environment-without-template" {
terraform_version = "0.15.1"
}
}

resource "env0_template" "workflow_template" {
name = "Template for workflow environment"
type = "workflow"
repository = "https://github.com/env0/templates"
path = "misc/workflow-environment-basic"
terraform_version = "1.1.5"
}

resource "env0_template_project_assignment" "assignment_workflow" {
template_id = env0_template.workflow_template.id
project_id = env0_project.test_project.id
}

resource "env0_environment" "workflow-environment" {
depends_on = [env0_template_project_assignment.assignment_workflow]
force_destroy = true
name = "environment-workflow-${random_string.random.result}"
project_id = env0_project.test_project.id
template_id = env0_template.workflow_template.id
approve_plan_automatically = true

sub_environment_configuration {
alias = "rootService1"
revision = "master"
configuration {
name = "sub_env1_var1"
value = "hello"
}
configuration {
name = "sub_env1_var2"
value = "world"
}
}

sub_environment_configuration {
alias = "rootService2"
revision = "master"
configuration {
name = "sub_env2_var1"
value = "hello"
}
configuration {
name = var.second_run ? "sub_env2_var3" : "sub_env2_var2"
value = var.second_run ? "world2" : "world"
}
}
}

0 comments on commit bb71636

Please sign in to comment.