Skip to content

Commit

Permalink
Chore: Add configuration for waiting for environment to finish deploy…
Browse files Browse the repository at this point in the history
…ment (#273)

* feat: allow env0_environment creation to be synchronous

This commit adds a new "wait" parameter to the env0_environment
resource. If this parameter is set to true, the resource waits for the
environment's latest deployment to hit a terminal state. This allows
multiple environments to be chained together with terraform
dependencies to deploy environments in a specific order. The parameter
defaults to false to preserve existing behaviour

* Update docs

* PR comments + add integration test

* Update docs

* Fix ACK vs FULLY_DEPLOYED

* Fetch specific deployment status, instead of latestDeploymentLog status

* Fix destroy test mock

* Add tests

* Fix create environment mock

* Fix configuration variable mock in tests

* Fix wait_for failure test

* Remove unnecessary mock on failure

* Go back to using revision, using name in data using id is flaky

* Fix wait_for docs

* Remove comments

* go fmt

* Add logs for deployment polling

Co-authored-by: Jack Stone <[email protected]>
Co-authored-by: update generated docs action <[email protected]>
  • Loading branch information
3 people authored Apr 5, 2022
1 parent e5a303b commit 2e045e5
Show file tree
Hide file tree
Showing 8 changed files with 1,143 additions and 748 deletions.
3 changes: 2 additions & 1 deletion client/api_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,13 +62,14 @@ type ApiClientInterface interface {
ProjectEnvironments(projectId string) ([]Environment, error)
Environment(id string) (Environment, error)
EnvironmentCreate(payload EnvironmentCreate) (Environment, error)
EnvironmentDestroy(id string) (Environment, error)
EnvironmentDestroy(id string) (EnvironmentDeployResponse, error)
EnvironmentUpdate(id string, payload EnvironmentUpdate) (Environment, error)
EnvironmentDeploy(id string, payload DeployRequest) (EnvironmentDeployResponse, error)
EnvironmentUpdateTTL(id string, payload TTL) (Environment, error)
EnvironmentScheduling(environmentId string) (EnvironmentScheduling, error)
EnvironmentSchedulingUpdate(environmentId string, payload EnvironmentScheduling) (EnvironmentScheduling, error)
EnvironmentSchedulingDelete(environmentId string) error
Deployment(id string) (DeploymentLog, error)
WorkflowTrigger(environmentId string) ([]WorkflowTrigger, error)
WorkflowTriggerUpsert(environmentId string, request WorkflowTriggerUpsertPayload) ([]WorkflowTrigger, error)
EnvironmentDriftDetection(environmentId string) (EnvironmentSchedulingExpression, error)
Expand Down
19 changes: 17 additions & 2 deletions client/api_client_mock.go

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

17 changes: 14 additions & 3 deletions client/environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ type DeploymentLog struct {
BlueprintId string `json:"blueprintId"`
BlueprintRepository string `json:"blueprintRepository"`
BlueprintRevision string `json:"blueprintRevision"`
Status string `json:"status"`
}

type Environment struct {
Expand Down Expand Up @@ -130,11 +131,11 @@ func (self *ApiClient) EnvironmentCreate(payload EnvironmentCreate) (Environment
return result, nil
}

func (self *ApiClient) EnvironmentDestroy(id string) (Environment, error) {
var result Environment
func (self *ApiClient) EnvironmentDestroy(id string) (EnvironmentDeployResponse, error) {
var result EnvironmentDeployResponse
err := self.http.Post("/environments/"+id+"/destroy", nil, &result)
if err != nil {
return Environment{}, err
return EnvironmentDeployResponse{}, err
}
return result, nil
}
Expand Down Expand Up @@ -168,3 +169,13 @@ func (self *ApiClient) EnvironmentDeploy(id string, payload DeployRequest) (Envi
}
return result, nil
}

func (self *ApiClient) Deployment(id string) (DeploymentLog, error) {
var result DeploymentLog
err := self.http.Get("/environments/deployments/"+id, nil, &result)

if err != nil {
return DeploymentLog{}, err
}
return result, nil
}
1 change: 1 addition & 0 deletions docs/resources/environment.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ resource "env0_environment" "example" {
- **run_plan_on_pull_requests** (Boolean) should run terraform plan on pull requests creations
- **terragrunt_working_directory** (String) The working directory path to be used by a Terragrunt template. If left empty '/' is used.
- **ttl** (String) the date the environment should be destroyed at (iso format). omitting this attribute will result in infinite ttl.
- **wait_for** (String) whether or not to wait for environment to fully deploy
- **workspace** (String) the terraform workspace of the environment

### Read-Only
Expand Down
68 changes: 67 additions & 1 deletion env0/resource_environment.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,16 @@ import (
"fmt"
"log"
"regexp"
"time"

"github.com/env0/terraform-provider-env0/client"
"github.com/google/uuid"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const deploymentStatusWaitPollInterval = 10 // In seconds

func resourceEnvironment() *schema.Resource {
return &schema.Resource{
CreateContext: resourceEnvironmentCreate,
Expand Down Expand Up @@ -109,6 +112,19 @@ func resourceEnvironment() *schema.Resource {
Description: "destroy safegurad",
Optional: true,
},
"wait_for": {
Type: schema.TypeString,
Description: "whether or not to wait for environment to fully deploy",
Optional: true,
Default: "ACK",
ValidateFunc: func(val interface{}, key string) (warns []string, errs []error) {
value := val.(string)
if value != "ACK" && value != "FULLY_DEPLOYED" {
errs = append(errs, fmt.Errorf("%q can be either \"ACK\", \"FULLY_DEPLOYED\" or empty, got: %q", key, value))
}
return
},
},
"terragrunt_working_directory": {
Type: schema.TypeString,
Description: "The working directory path to be used by a Terragrunt template. If left empty '/' is used.",
Expand Down Expand Up @@ -189,6 +205,7 @@ func setEnvironmentSchema(d *schema.ResourceData, environment client.Environment
safeSet(d, "auto_deploy_by_custom_glob", environment.AutoDeployByCustomGlob)
safeSet(d, "ttl", environment.LifespanEndAt)
safeSet(d, "terragrunt_working_directory", environment.TerragruntWorkingDirectory)

if environment.LatestDeploymentLog != (client.DeploymentLog{}) {
d.Set("template_id", environment.LatestDeploymentLog.BlueprintId)
d.Set("revision", environment.LatestDeploymentLog.BlueprintRevision)
Expand Down Expand Up @@ -260,6 +277,13 @@ func resourceEnvironmentCreate(ctx context.Context, d *schema.ResourceData, meta
d.Set("deployment_id", environment.LatestDeploymentLogId)
setEnvironmentSchema(d, environment, environmentConfigurationVariables)

if shouldWaitForDeployment(d) {
err := waitForDeployment(environment.LatestDeploymentLogId, apiClient)
if err != nil {
return diag.Errorf("failed deploying environment: %v", err)
}
}

return nil
}

Expand Down Expand Up @@ -326,6 +350,14 @@ func deploy(d *schema.ResourceData, apiClient client.ApiClientInterface) diag.Di
return diag.Errorf("failed deploying environment: %v", err)
}
d.Set("deployment_id", deployResponse.Id)

if shouldWaitForDeployment(d) {
err := waitForDeployment(deployResponse.Id, apiClient)
if err != nil {
return diag.Errorf("failed deploying environment: %v", err)
}
}

return nil
}

Expand Down Expand Up @@ -362,10 +394,17 @@ func resourceEnvironmentDelete(ctx context.Context, d *schema.ResourceData, meta

apiClient := meta.(client.ApiClientInterface)

_, err := apiClient.EnvironmentDestroy(d.Id())
destroyResponse, err := apiClient.EnvironmentDestroy(d.Id())
if err != nil {
return diag.Errorf("could not delete environment: %v", err)
}
if shouldWaitForDeployment(d) {
err := waitForDeployment(destroyResponse.Id, apiClient)
if err != nil {
return diag.Errorf("failed to delete environment: %v", err)
}
}

return nil
}

Expand Down Expand Up @@ -675,3 +714,30 @@ func resourceEnvironmentImport(ctx context.Context, d *schema.ResourceData, meta
return []*schema.ResourceData{d}, nil
}
}

func shouldWaitForDeployment(d *schema.ResourceData) bool {
return d.Get("wait_for").(string) == "FULLY_DEPLOYED"
}

func waitForDeployment(deploymentLogId string, apiClient client.ApiClientInterface) error {
log.Println("[INFO] Waiting for deployment to finish")
for {
deployment, err := apiClient.Deployment(deploymentLogId)
if err != nil {
return err
}
switch deployment.Status {
case "IN_PROGRESS",
"QUEUED",
"WAITING_FOR_USER":
log.Println("[INFO] Deployment not yet done deploying. Got status ", deployment.Status)
time.Sleep(deploymentStatusWaitPollInterval * time.Second)
case "SUCCESS",
"SKIPPED":
log.Println("[INFO] Deployment done deploying! Got status ", deployment.Status)
return nil
default:
return fmt.Errorf("environment deployment reached failure status: %v", deployment.Status)
}
}
}
Loading

0 comments on commit 2e045e5

Please sign in to comment.