Skip to content

Commit

Permalink
Feat: add support for 'move environment' backend
Browse files Browse the repository at this point in the history
  • Loading branch information
TomerHeber committed Aug 22, 2024
1 parent 4de999d commit 1a1e776
Show file tree
Hide file tree
Showing 7 changed files with 176 additions and 22 deletions.
1 change: 1 addition & 0 deletions client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ type ApiClientInterface interface {
EnvironmentUpdate(id string, payload EnvironmentUpdate) (Environment, error)
EnvironmentDeploy(id string, payload DeployRequest) (EnvironmentDeployResponse, error)
EnvironmentUpdateTTL(id string, payload TTL) (Environment, error)
EnvironmentMove(id string, projectId string) error
EnvironmentScheduling(environmentId string) (EnvironmentScheduling, error)
EnvironmentSchedulingUpdate(environmentId string, payload EnvironmentScheduling) (EnvironmentScheduling, error)
EnvironmentSchedulingDelete(environmentId string) error
Expand Down
14 changes: 14 additions & 0 deletions client/api_client_mock.go

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

53 changes: 39 additions & 14 deletions client/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,30 +3,34 @@ package client
import (
"encoding/json"
"fmt"

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

type ConfigurationVariableType int

func (c *ConfigurationVariableType) ReadResourceData(fieldName string, d *schema.ResourceData) error {
val := d.Get(fieldName).(string)
intVal, ok := VariableTypes[val]
if !ok {
return fmt.Errorf("unknown configuration variable type %s", val)

intVal, err := GetConfigurationVariableType(val)
if err != nil {
return err
}

*c = intVal

return nil
}

func (c *ConfigurationVariableType) WriteResourceData(fieldName string, d *schema.ResourceData) error {
val := *c
valStr := ""
if val == 0 {
var valStr string

switch val := *c; val {
case 0:
valStr = "environment"
} else if val == 1 {
case 1:
valStr = "terraform"
} else {
default:
return fmt.Errorf("unknown configuration variable type %d", val)
}

Expand All @@ -40,11 +44,6 @@ const (

type ConfigurationChanges []ConfigurationVariable

var VariableTypes = map[string]ConfigurationVariableType{
"terraform": ConfigurationVariableTypeTerraform,
"environment": ConfigurationVariableTypeEnvironment,
}

type TTL struct {
Type TTLType `json:"type"`
Value string `json:"value,omitempty"`
Expand Down Expand Up @@ -159,6 +158,21 @@ type EnvironmentCreateWithoutTemplate struct {
TemplateCreate TemplateCreatePayload
}

type EnvironmentMoveRequest struct {
ProjectId string `json:"projectId"`
}

func GetConfigurationVariableType(variableType string) (ConfigurationVariableType, error) {
switch variableType {
case "terraform":
return ConfigurationVariableTypeTerraform, nil
case "environment":
return ConfigurationVariableTypeEnvironment, nil
default:
return 0, fmt.Errorf("unknown configuration variable type %s", variableType)
}
}

// The custom marshalJSON is used to return a flat JSON.
func (create EnvironmentCreateWithoutTemplate) MarshalJSON() ([]byte, error) {
// 1. Marshal to JSON both structs.
Expand All @@ -173,9 +187,11 @@ func (create EnvironmentCreateWithoutTemplate) MarshalJSON() ([]byte, error) {

// 2. Unmarshal both JSON byte arrays to two maps.
var ecm, tcm map[string]interface{}

if err := json.Unmarshal(ecb, &ecm); err != nil {
return nil, err
}

if err := json.Unmarshal(tcb, &tcm); err != nil {
return nil, err
}
Expand Down Expand Up @@ -254,8 +270,9 @@ func (client *ApiClient) EnvironmentCreateWithoutTemplate(payload EnvironmentCre

organizationId, err := client.OrganizationId()
if err != nil {
return result, nil
return result, err
}

payload.TemplateCreate.OrganizationId = organizationId

if err := client.http.Post("/environments/without-template", payload, &result); err != nil {
Expand Down Expand Up @@ -311,3 +328,11 @@ func (client *ApiClient) EnvironmentDeploy(id string, payload DeployRequest) (En
}
return result, nil
}

func (client *ApiClient) EnvironmentMove(id string, projectId string) error {
payload := &EnvironmentMoveRequest{
ProjectId: projectId,
}

return client.http.Post("/environments/"+id+"/move", payload, nil)
}
30 changes: 26 additions & 4 deletions client/environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -279,13 +279,15 @@ var _ = Describe("Environment Client", func() {
})

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

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Post("/environments/"+mockEnvironment.Id+"/destroy", nil, gomock.Any())
apiClient.EnvironmentDestroy(mockEnvironment.Id)
httpCall = mockHttpClient.EXPECT().Post("/environments/"+mockEnvironment.Id+"/destroy", nil, gomock.Any()).Times(1)
_, err = apiClient.EnvironmentDestroy(mockEnvironment.Id)
})

It("Should send a destroy request", func() {
httpCall.Times(1)
It("Should not return error", func() {
Expect(err).To(BeNil())
})
})

Expand Down Expand Up @@ -416,6 +418,26 @@ var _ = Describe("Environment Client", func() {
})
})
})

Describe("Environment Move", func() {
var err error

environmentId := "envid"
projectId := "projid"

request := EnvironmentMoveRequest{
ProjectId: projectId,
}

BeforeEach(func() {
httpCall = mockHttpClient.EXPECT().Post("/environments/"+environmentId+"/move", &request, nil).Times(1)
err = apiClient.EnvironmentMove(environmentId, projectId)
})

It("Should not return an error", func() {
Expect(err).To(BeNil())
})
})
})

func TestMarshalEnvironmentCreateWithoutTemplate(t *testing.T) {
Expand Down
10 changes: 8 additions & 2 deletions env0/resource_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,6 @@ func resourceEnvironment() *schema.Resource {
Type: schema.TypeString,
Description: "project id of the environment",
Required: true,
ForceNew: true,
},
"template_id": {
Type: schema.TypeString,
Expand Down Expand Up @@ -718,6 +717,13 @@ func resourceEnvironmentRead(ctx context.Context, d *schema.ResourceData, meta i
func resourceEnvironmentUpdate(ctx context.Context, d *schema.ResourceData, meta interface{}) diag.Diagnostics {
apiClient := meta.(client.ApiClientInterface)

if d.HasChange("project_id") {
newProjectId := d.Get("project_id").(string)
if err := apiClient.EnvironmentMove(d.Id(), newProjectId); err != nil {
return diag.Errorf("failed to move environment to project id '%s': %s", newProjectId, err)
}
}

if shouldUpdate(d) {
if err := update(d, apiClient); err != nil {
return err
Expand Down Expand Up @@ -1274,7 +1280,7 @@ func typeEqual(variable client.ConfigurationVariable, search client.Configuratio
}

func getConfigurationVariableFromSchema(variable map[string]interface{}) client.ConfigurationVariable {
varType := client.VariableTypes[variable["type"].(string)]
varType, _ := client.GetConfigurationVariableType(variable["type"].(string))

configurationVariable := client.ConfigurationVariable{
Name: variable["name"].(string),
Expand Down
76 changes: 76 additions & 0 deletions env0/resource_environment_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,82 @@ func TestUnitEnvironmentResource(t *testing.T) {
})
})

t.Run("move environment", func(t *testing.T) {
newProjectId := "new-project-id"

environment := client.Environment{
Id: uuid.New().String(),
Name: "name",
ProjectId: "project-id",
LatestDeploymentLog: client.DeploymentLog{
BlueprintId: templateId,
},
}

environmentCreate := client.EnvironmentCreate{
Name: environment.Name,
ProjectId: environment.ProjectId,
DeployRequest: &client.DeployRequest{
BlueprintId: environment.LatestDeploymentLog.BlueprintId,
},
}

movedEnvironment := environment
movedEnvironment.ProjectId = newProjectId

testCase := resource.TestCase{
Steps: []resource.TestStep{
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": environment.Name,
"project_id": environment.ProjectId,
"template_id": templateId,
"force_destroy": true,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "project_id", environment.ProjectId),
),
},
{
Config: resourceConfigCreate(resourceType, resourceName, map[string]interface{}{
"name": movedEnvironment.Name,
"project_id": movedEnvironment.ProjectId,
"template_id": templateId,
"force_destroy": true,
}),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(accessor, "id", environment.Id),
resource.TestCheckResourceAttr(accessor, "project_id", movedEnvironment.ProjectId),
),
},
},
}

runUnitTest(t, testCase, func(mock *client.MockApiClientInterface) {
gomock.InOrder(
mock.EXPECT().Template(environment.LatestDeploymentLog.BlueprintId).Times(1).Return(template, nil),
mock.EXPECT().EnvironmentCreate(environmentCreate).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().ConfigurationSetsAssignments("ENVIRONMENT", environment.Id).Times(1).Return(nil, 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().ConfigurationSetsAssignments("ENVIRONMENT", environment.Id).Times(1).Return(nil, nil),

mock.EXPECT().EnvironmentMove(environment.Id, newProjectId).Return(nil),

mock.EXPECT().Environment(movedEnvironment.Id).Times(1).Return(movedEnvironment, nil),
mock.EXPECT().ConfigurationVariablesByScope(client.ScopeEnvironment, movedEnvironment.Id).Times(1).Return(client.ConfigurationChanges{}, nil),
mock.EXPECT().ConfigurationSetsAssignments("ENVIRONMENT", movedEnvironment.Id).Times(1).Return(nil, nil),

mock.EXPECT().EnvironmentDestroy(movedEnvironment.Id).Times(1),
)
})
})

t.Run("vcs pr comments enabled", func(t *testing.T) {
testCase := resource.TestCase{
Steps: []resource.TestStep{
Expand Down
14 changes: 12 additions & 2 deletions tests/integration/012_environment/main.tf
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ resource "env0_project" "test_project" {
force_destroy = true
}

resource "env0_project" "test_project2" {
name = "Test-Project2-for-environment-${random_string.random.result}"
force_destroy = true
}

data "env0_template" "github_template_for_environment" {
name = "Github Integrated Template"
}
Expand All @@ -29,6 +34,11 @@ resource "env0_template_project_assignment" "assignment" {
project_id = env0_project.test_project.id
}

resource "env0_template_project_assignment" "assignment2" {
template_id = env0_template.template.id
project_id = env0_project.test_project2.id
}

resource "env0_environment" "auto_glob_envrironment" {
depends_on = [env0_template_project_assignment.assignment]
name = "environment-auto-glob-${random_string.random.result}"
Expand All @@ -41,11 +51,11 @@ resource "env0_environment" "auto_glob_envrironment" {
force_destroy = true
}

resource "env0_environment" "example" {
resource "env0_environment" "environment" {
depends_on = [env0_template_project_assignment.assignment]
force_destroy = true
name = "environment-${random_string.random.result}"
project_id = env0_project.test_project.id
project_id = var.second_run ? env0_project.test_project2.id : env0_project.test_project.id
template_id = env0_template.template.id
configuration {
name = "environment configuration variable"
Expand Down

0 comments on commit 1a1e776

Please sign in to comment.