Terraform

Introduction

Terraform is an infrastructure as code (IaC) tool that allows you to build, change, and version infrastructure safely and efficiently. This includes both low-level components like compute instances, storage, and networking, as well as high-level components like DNS entries and SaaS features.

Easily provision elements in the cloud

The core functionality of Bunnyshell is to allow users to easily create environments where they can test their applications. However, if you want to create any elements in the cloud, you will need to use Terraform. It is independent from our Kubernetes integration and it lets you define cloud resources in configuration files that you can version, share and reuse.


Preparing your Terraform Module directory

Module structure

Below you will find a typical file structure for a Terraform module:

.
├── LICENSE
├── README.md
├── main.tf
├── variables.tf
├── outputs.tf

None of these files are required, or have any special meaning to Terraform when it uses your module. According to Terraform documentation, you can create a module with a single .tf file, or use any other file structure you like.

Each of the files listed above serves a purpose:

  • LICENSE contains the license under which the module is distributed. The LICENSE file shows the terms under which a shared Terraform module has been made available. Terraform does not use this file.
  • README.md contains the documentation for your module, in markdown format. Although Terraform does not use it, services like Terraform Registry and GitHub will display the contents of this file.
  • main.tf contains the main set of configuration for your module. You can also create other configuration files and organize them in a way which suits your needs.
  • variables.tf contains the variable definitions for your module. When your module is used by others, the variables are configured as arguments in the module block. Any variables that are not given a default value will become required arguments. Variables with default values can also be provided as module arguments, overriding the default value.
  • outputs.tf will contain the output definitions for your module. Module outputs are made available to the configuration using the module, so they are often used to pass information about the parts of your infrastructure defined by the module to other parts of your configuration.

The following files should not be distributed as part of your module:

  • terraform.tfstate and terraform.tfstate.backup: These files contain your Terraform state, and are how Terraform keeps track of the relationship between your configuration and the infrastructure provisioned by it.
  • .terraform: This directory contains the modules and plugins used to provision your infrastructure. These files are specific to a specific instance of Terraform when provisioning infrastructure, not the configuration of the infrastructure defined in .tf files.
  • *.tfvars: Since module input variables are set via arguments to the module block in your configuration, you don't need to distribute any .tfvars files with your module, unless you are also using it as a standalone Terraform configuration.

If you are tracking changes to your module in a version control system, such as git, you will want to configure your version control system to ignore these files.

Warning: Secret information, such as passwords or access keys, will become public if those files are committed to a public version control system such as GitHub.

More information is included on the official Terraform website.


Integration with application deployment

With our Terraform integration, an output value can be mapped to an environment variable from an application deployed in Kubernetes, or mapped as an input for other Terraform modules.

For example, let's assume a database in a cloud service has a random IP assigned to it. Bunnyshell can extract that IP in an output and then map that output to a variable from the web application component. When the environment is deployed, the Terraform module is applied and when the app is deployed, Bunnyshell injects the database IP in the variable from the web application, therefore linking the two.

The mechanism through which this happens is exportVariables.


Examples

To make it easier to understand how Bunnyshell integrates with Terraform, we included an example for how an AWS S3 bucket might be created using a simple Terraform module. You can find the full bunnyshell.yaml on GitHub.

This example is also available in one of Bunnyshell's Templates.

    -
        kind: Terraform
        name: s3-bucket
        gitRepo: 'https://github.com/bunnyshell/demo-books.git'
        gitBranch: terraform
        gitApplicationPath: /backend/_terraform
        runnerImage: 'hashicorp/terraform:1.5.1'
        deploy:
            - 'cd backend/_terraform'
            - '/bns/helpers/terraform/get_managed_backend > zz_backend_override.tf'
            - 'terraform init -input=false -no-color'
            - 'terraform apply -var "bucket_name=demo-{{ env.unique }}" -input=false -auto-approve -no-color'
            - 'BNS_TF_STATE_LIST=`terraform show -json`'
            - 'BUCKET_NAME=`terraform output --raw s3_bucket_name`'
            - 'BUCKET_REGION=`terraform output --raw s3_bucket_region`'
        destroy:
            - 'cd backend/_terraform'
            - '/bns/helpers/terraform/get_managed_backend > zz_backend_override.tf'
            - 'terraform init -input=false -no-color'
            - 'terraform destroy -var "bucket_name=demo-{{ env.unique }}" -input=false -auto-approve -no-color'
        start:
            - '# you can trigger an external flow or alter resources consumed by resources when an Environment is started in Bunnyshell'
        stop:
            - '# you can trigger an external flow or alter resources consumed by resources when an Environment is stopped in Bunnyshell'            
        exportVariables:
            - BUCKET_NAME
            - BUCKET_REGION
        environment:
            AWS_ACCESS_KEY_ID: your-key
            AWS_REGION: your-region
            AWS_SECRET_ACCESS_KEY: your-secret

📘

AWS Credentials

In order for the module to be able to create the S3 bucket, it needs AWS access with correct privileges. You can find the needed IAM permissions here.

The environment variables needed are:
AWS_ACCESS_KEY_ID: your-key
AWS_REGION: your-region
AWS_SECRET_ACCESS_KEY: your-secret

They can either be specified at Component level, in the environment attribute, or inherited from the environment level, environmentVariables top-level attribute.

Let's understand what the module does, by analyzing each of its attributes.


Component attributes

gitRepo

The URL of the Git repository where the Terraform module is located. The repository will be cloned in the workdir of the runner image.

gitBranch

The branch where the Terraform module is located. This attribute is relative to the gitRepo.

gitApplicationPath

The path to the application folder, relative to the branch. When provided, it will watch for changes only within this folder to trigger Auto-updates (if enabled) or to display that a new version of your application is available.

:warning: The repository will be cloned entirely, not just the specified gitApplicationPath.

runnerImage

This is where you specify the image where Bunnyshell runs the commands given by the user. It can be an image built specifically for the runner, or any other ready-made image.

You can build an image using a [DockerImage](components-docker-image) component and use it as your runner.


Commands

In the Deploy, Start, Stop and Delete properties you can add any type of commands.

📘

Note

Steps for the Deploy property are mandatory. The other properties can be left blank.

📘

Note

Deploy and Start in Terraform, Helm, KubernetesManifest and GenericComponents always run in the order in which components are defined in bunnyshell.yaml. This is important because we can inject it in the component’s environment variables and use it later in a separate component.

Stop and Destroy run simultaneously.


Storing the state

To be able to keep a track of create resources, the Terraform state must be persisted. Bunnyshell provides you with a backend to store the state in - credentials are scoped at the Organization level, meaning all Environments from all Projects within an Organization will have access to the same AWS S3 bucket.

You can use the Bunnyshell-provided backend or you can use your own.

- '/bns/helpers/terraform/get_managed_backend > zz_backend_override.tf'

Displaying Components' resources in the UI

For Bunnyshell to be able to discover the resources this Terraform module creates and read its output values, we need the Terraform state to be captured in an environment variable called BNS_TF_STATE_LIST. This is the last line of the deploy scripts in the example.

- 'BNS_TF_STATE_LIST=`terraform show -json`'

Value Interpolation for Docker Images

If you are using Bunnyshell to build your images using Docker Image, so you can deploy them with Terraform outside Kubernetes, you can use value interpolation as exemplified below. NAME represents the component name of your Docker Image Component. Bunnyshell will replace it with the built image.

  • {{ components.NAME.image }}: contains full image name, including the tag.
    Example: nginx:latest
  • {{ components.dev_test_image.imageName }}: contains only the image name.
    Example: nginx
  • {{ components.dev_test_image.imageTag }}: contains only the image tag.
    Example: latest

Injecting Bunnyshell Variables in your resources

If you need to inject Bunnyshell environment variables in your resources, please refer to the Value Interpolation page.


exportVariables

Using this property you can export variables from Terraform modules to other components inside the environment. They are captured only during the deploy and can be used after they reach the next interpolation context in the same deploy workflow, or in subsequent start/stop/destroy workflows.

📘

Note

These are not environment variables, but variables exported from your deployment script.

Example: In the section presented above, under deploy, we defined the variables BUCKET_NAME and BUCKET_REGION.

deploy:
    - 'cd backend/_terraform'
    - '/bns/helpers/terraform/get_managed_backend > zz_backend_override.tf'
    - 'terraform init -input=false -no-color'
    - 'terraform apply -var "bucket_name=demo-{{ env.unique }}" -input=false -auto-approve -no-color'
    - 'BNS_TF_STATE_LIST=`terraform show -json`'
    - 'BUCKET_NAME=`terraform output --raw s3_bucket_name`'
    - 'BUCKET_REGION=`terraform output --raw s3_bucket_region`'

Normally, these variables would only be available within the runner. But in our case, we are going to list them under exportedVariables, so they can be used in other components as well.

exportVariables:
    - BUCKET_NAME
    - BUCKET_REGION

This way, after deployment Bunnyshell will store the variable (encrypted) in a database. The variable will be available for you to use in another component, if need be. You can use the exported variables in interpolation contexts using key with the format {{components.NAME.exported.VAR_NAME}}:

{{components.s3-bucket.exported.BUCKET_NAME}}
{{components.s3-bucket.exported.BUCKET_REGION}}

Terraform Components also have Environment Variables. Environment Variables are injected in runnerImage as Environment Variables. Usually, they are used in passing variables to Terraform modules

All Component, Environment and Project variables are inherited.


Resources & state

Resources are fully managed by Terraform using the state file, store in the backend of your choice.