Custom Flows

Hook into env0 deployment cycle using custom flows

You can create custom flows for a template, that allows you to run whatever you want (bash, python, gcloud, Ansible, CloudFormation, etc.), whenever you want in the deployment process (before or after Terraform init/plan/apply, and even destroy/error).

For specific custom flow per template:
Create a file named env0.yml under the template folder. env0 will use this file when is defined.

For sharing custom flow for all templates:
Create a file named env0.yml under the root folder. env0 will use this file when it is defined and the template folder doesn't define this file.

For sharing custom flow for all environments under the project:
Create a custom flow file in any repository and configure it as a project policy.

We support the suffixes env0.yml and env0.yaml in the same way.

๐Ÿ“˜

Working Directory for env0.yml

env0 will run custom flow commands with a working directory based on where the file was taken.
Based on that you can reference your commands to other scripts/commands by using "./script.sh"

Below is an example of a custom flow which creates a more "dynamic" Terraform file, based on an environment variable value, and uses Python and Jinja before the Terraform Init (the full example of this template git repo (including the env0.yml file) is here):

version: 1

deploy:
  steps:
    terraformInit:
      before:
        - pip install --user j2cli
        - 'echo Generating "stage.auto.tfvars" for "$STAGE"'
        - j2 stage.auto.tfvars.j2 > stage.auto.tfvars
        - 'echo -e "Generated \"stage.auto.tfvars\": \n$(cat stage.auto.tfvars)"'
        - 'echo Generating "elb.tf" for "$STAGE"'
        - j2 elb.tf.j2 > elb.tf
        - 'echo -e "Generated \"elb.tf\": \n$(cat elb.tf)"'

You can include the following possible hooks in an env0.yml file.

๐Ÿ“˜

OpenTofu backwards-compatibility

Terraform's custom flow format is interchangeable with OpenTofu's format

Hook Stages

version:
  type: number
shell:
  type: string

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    terraformInit:
      before:
        - type: string
      after:
        - type: string

    terraformPlan:
      before:
        - type: string
      after:
        - type: string

    terraformApply:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

    terraformOutput:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    terraformInit:
      before:
        - type: string
      after:
        - type: string

    terraformPlan:
      before:
        - type: string
      after:
        - type: string

    terraformDestroy:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell:
  type: string

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    opentofuInit:
      before:
        - type: string
      after:
        - type: string

    opentofuPlan:
      before:
        - type: string
      after:
        - type: string

    opentofuApply:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

    opentofuOutput:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    opentofuInit:
      before:
        - type: string
      after:
        - type: string

    opentofuPlan:
      before:
        - type: string
      after:
        - type: string

    opentofuDestroy:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell:
  type: string

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    pulumiPreview:
      before:
        - type: string
      after:
        - type: string

    pulumiUp:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

    pulumiOutput:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    pulumiPreview:
      before:
        - type: string
      after:
        - type: string

    pulumiDestroy:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell:
  type: string

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    cfCreateChangeSet:
      before:
        - type: string
      after:
        - type: string

    cfDeploy:
      before:
        - type: string
      after:
        - type: string

    cfOutput:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    cfListStackResources:
      before:
        - type: string
      after:
        - type: string

    cfDeleteStack:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell:
  type: string

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string
    
    k8sDiff:
      before:
        - type: string
      after:
        - type: string

    k8sApply:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    k8sDiff:
      before:
        - type: string
      after:
        - type: string

    k8sDelete:
      before:
        - type: string
      after:
        - type: string
version:
  type: number
shell:
  type: string

deploy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string
    
    helmDiff:
      before:
        - type: string
      after:
        - type: string

    helmUpgrade:
      before:
        - type: string
      after:
        - type: string

    storeState:
      before:
        - type: string
      after:
        - type: string

destroy:
  onCompletion:
    - type: string
  onSuccess:
    - type: string
  onFailure:
    - type: string

  steps:
    setupVariables:
      after:
        - type: string

    helmDiff:
      before:
        - type: string
      after:
        - type: string

    helmUninstall:
      before:
        - type: string
      after:
        - type: string

Handling errors

If any command in your custom flow returns a non-zero exit code, the env0 deployment will stop on a 'Failed' status. You can leverage this to perform validation - for example - asserting a certain environment variable was supplied.
In such a case, env0 will display any output from stderr as the deployment error. If no output is found in stderr, it will be shown as an "Unknown Error".

๐Ÿ“˜

Error Message Handling

Use the following notation 1>&2 to redirect stdout messages to stderr. This allows env0 to display the message in its UI. (see I/O Redirection)

Here is an example -

version: 1
deploy:
  steps:
    setupVariables:
      after:
        - if [ -z "$MY_VAR" ]; then echo "MY_VAR must be supplied" 1>&2 && exit 1; fi

Exposed env0 System Environment Variables

env0 exposes the following environment variables for you to use:

Variable NameValue Description
ENV0_ENVIRONMENT_IDThe deployed Environment ID
ENV0_PROJECT_IDThe Project ID of the deployed Environment
ENV0_PROJECT_NAMEThe Project Name of the deployed Environment
ENV0_DEPLOYMENT_LOG_IDThe deployment ID
ENV0_DEPLOYMENT_TYPEThe deployment type.
One of deploy / destroy / prPlan
ENV0_DEPLOYMENT_REVISIONThe revision

- Available only when deployment revision defined
ENV0_WORKSPACE_NAMEThe Terraform Workspace name used in the Environment
ENV0_ROOT_DIRThe root repository path
ENV0_ORGANIZATION_IDYour env0 organization ID
ENV0_TEMPLATE_IDThe deployed Template ID
ENV0_TEMPLATE_PATHThe deployed template path to its terraform configuration within the VCS repo
ENV0_TEMPLATE_REPOSITORYThe repository of the template
ENV0_TEMPLATE_REVISIONThe revision that the code of the repository will be cloned from. May be missing if no revision is set for the template
ENV0_TEMPLATE_DIRThe deployed Template path to its terraform configuration during the deployment
ENV0_TEMPLATE_NAMEThe deployed Template name
ENV0_ENVIRONMENT_NAMEThe deployed Environment name
ENV0_ENVIRONMENT_CREATOR_NAMEThe name of the environment creator
ENV0_ENVIRONMENT_CREATOR_EMAILThe email of the environment creator
ENV0_DEPLOYER_NAMEThe name of the deployer
ENV0_DEPLOYER_EMAILThe email of the deployer
ENV0_REVIEWER_NAMEThe name of the reviewer

- Available only after approve was clicked.
ENV0_REVIEWER_EMAILThe email of the reviewer

- Available only after approve was clicked.
ENV0_PR_AUTHORGitHub- the username PR author
GitLab- the username commit author

- Available only in PR plan
ENV0_PR_SOURCE_BRANCHThe source branch of the PR

- Available only in PR plan
ENV0_PR_TARGET_BRANCHThe target branch of the PR

- Available only in PR plan
ENV0_COMMIT_HASHThe commit hash of deployed Environment

- Available only in PR plan
ENV0_OIDC_TOKENThe OIDC Token - read more here on how to enable it and use it
ENV0_VCS_ACCESS_TOKENWhen using a native VCS integration, this will represent the access token we use to clone the repository
ENV0_TF_PLAN_JSONThe file path to a JSON representation of a Terraform Plan file (output of terraform show -json)
ENV0_CLI_ARGS_PLANThe additional CLI arguments when running a remote plan with options

See this sample for using these variables in our public templates repo.

๐Ÿ“˜

Note

You can use exposed env0 variables in the UI using the ${...} notation:

The Deployment Container

Custom flows allow you to run bash commands. The deployment image contains selected tools that are installed by default (cloud utils, os utils, package managers), read more about the tools which are pre-installed.

Exporting your own environment variables

In order to export your own Environment Variable to use in any downstream custom flow you should write a shell command in the format: echo KEY=VALUE >> $ENV0_ENV.
In later steps of the same deployment, you will be able to use $KEY to access the stored value.

๐Ÿ“˜

Note

For env0.yml files that use schema version 2 when using multiple commands within a given step e.g. "terraform:plan-before", all commands are executed inside the same shell. Exported environment variables as such are only available for steps that will come after the step. For using the values within the same shell using the usual export key=value still works.

Forcing Manual Approvals

Whenever ENV0_REQUIRES_APPROVAL=true env0 will force a deployment to be approved regardless of the "Auto Approval" setting in the environment. For example, instead of failing a deployment based on some policies, we can use this field to help ensure that someone can still review and proceed with the deployment.

Usage:
- echo ENV0_REQUIRES_APPROVAL=true >> $ENV0_ENV

FAQ

Q. How can I use Custom Flows to add new Terraform Variables?
The recommended approach to adding terraform variables is to use variable definitions files, specifically the _.auto.tfvars or _.auto.tfvars.json files to pass additional variables to your Terraform code. Note: Please be aware of the Terraform Variable Definition Precedence order as defined here: https://www.terraform.io/docs/language/values/variables.html#variable-definition-precedence.
Another way is to create new Environment Variables inside the custom flow in the form TF_VAR_key=value. To do that please follow Exporting your own environment variables.

Q. I noticed that env0 runs Init again after an approval, how can I only run my custom flow once per deployment?
You can take advantage of the ENV0_REVIEWER_NAME field to see if the plan has already been approved.
e.g.

deploy:
  steps:
    terraformInit:
      before: # run ./myScript.sh only on pre-approval
        - if [[ -z $ENV0_REVIEWER_NAME ]]; then ./myScript.sh; fi  

Q. Does env0 SaaS runners use static IP addresses? I would like to whitelist env0's runners to access my network.
Yes, env0 uses those IP addresses for all the outbound requests.

๐Ÿ“˜

GitHub API limits

When downloading resources from GitHub (e.g. tflint) make sure you use an authenticated call (e.g. curl -u USER:TOKEN). Otherwise, you will likely run into API limits, as unauthenticated calls are pooled with other SaaS users, with a much lower API limit. See GitHub Rate Limiting.

๐Ÿ“

Additional Content