Direktiv: building a machine on AWS using Terraform WITHOUT a Terraform environment? (part 1)
First question is how?
Direktiv is a serverless workflow engine, which *technically* means it spins up containers on-demand (using Kubernetes and Knative) and shuts them down in the sequence you’ve defined (workflow). So in the case of Terraform (or Ansible, shown in a different article) — we’re simply spinning up the latest version of the providers software as a container!
Second question is why?
- Terraform and Ansible are GREAT solutions for infrastructure provisioning and management, with a heap of plugins and extensions. Why would we try and reinvent the wheel — especially if you consider how widely both have been adopted. Direktiv uses the plugins, extension and playbooks already built for both products and extends it into the broader microservices workflow architecture we provide
- You get both environments without the effort of installing or maintaining them (or the resource cost of running them continuously). Just update the Docker build file with the latest (or version you’re comfortable with) if you don’t want to use the version we provide. This is the ephemeral nature of Direktiv workflows!
Btw, same applies for Ansible plugin and Docker build file.
You can also do this yourself! Install the Direktiv-all-in-one and play along!!
The Terraform Script
In this article we’re going to be looking at an implementation of a Terraform script. Firstly, we’re going to provision a virtual machine (Linux) to Amazon Web Services using the Terraform script below:
Note: examples for GCP and Azure here
The Terraform script above will deploy an Ubuntu machine to an AWS region. We will pass a couple of configuration parameters via the Direktiv workflow (region, secret and key).
The Direktiv Workflow
Now let’s have a look at the workflow YAML. We’re going to break this workflow down state-by-state.
Getting the Terraform script (Git)
Let’s use the Git plugin to get the Terraform script from GitHub. Let’s look at the workflow extract below:
So this is the breakdown of what’s happening. The Git plugin is cloning our Terraform example repository. The following argument to the git
command is very important:
$out/instance/terraform-git-var
- The plugin is writing the output of the git clone (a tar.gz file) to an internal Direktiv variable called
terraform-git-var
. - The
/instance/
component of the argument instructs Direktiv to only create this variable for the workflow instance at runtime (which means the value is only available for this specific workflow and only created at the time when it runs — more about variables here).
Effectively we now have the full Git repository in a variable as a binary tar.gz file!
Running Terraform script
Ok … here we go! Below is the snippet which calls Terraform:
Ok, so we have the full binary repository containing the Terraform script which we want to use (located in the /amazon
directory) in a variable called terraform-git-var
.
So to run the Terraform script we defined the function called tfrun
. tfrun
takes a couple of inputs:
execution-folder: terraform-git-var/amazon
: this is a location pointing to the Git repository (variable + the Git directory structure where themain.tf
file is located)action: jq(.action)
: the action Terraform needs to apply to the script, passed as a JSON input formatted as follow:
{
"action": "apply"
}
args-on-init: ["-backend-config=address=http://localhost:8001/terraform-amazon-instance
: we’re asking Terraform to pass the state output of the script back to the container (“plugin”) and store it in a variable calledterraform-amazon-instance
— to be used at a later stage or in different workflows (more on this later)variables
: these are variables used by the Terraformmain.tf
script. The most import part to this is that thestate-name
needs to match up to the previous value of theargs-on-init
value.
Now let’s look at the function
definition for tfrun
:
image
: instruct Direktiv to use thevorteil/terraform:v1
container as the plugin or extensionfiles
: this declaration is documented here. This feature of Direktiv instructs the function to take theterraform-git-var
variable (key: terraform-git-var
), defined as aninstance
scoped variable (scope: instance
) and unzip it as atar.gz
file type!
It’s VERY important to understand the importance of the above — it means that the file inside the variable become available as a mounted & shared drive to the container! It also allows the execution-folder
argument for the container to read the main.tf
file from the directory.
Cleaning up and notify
The final 2 states checks the action
which was sent to the Terraform script (either apply
or destroy
). If it is apply
a message is sent to Discord with the machine details.
Final workflow
Here is a video of the workflow in action:
And here is the final workflow:
As always — feedback and questions welcomed!
PS: someone asked to see the output of the workflow. Here is the “apply” action output:
[38;5;248m[10:31:48.257][0m Preparing workflow triggered by API.
[38;5;248m[10:31:48.356][0m Running state logic -- clone-terraform-examples:1 (action)
[38;5;248m[10:31:48.431][0m Sleeping until function 'git-command' returns.
[38;5;248m[10:31:49.991][0m running command 0 'clone https://github.com/vorteil/terraform-examples.git /mnt/shared/1uxr6qlijtrPRTCPEE9nwRJ9LyO/out/instance/terraform-git-var'
[38;5;248m[10:31:50.754][0m Function 'git-command' returned.
[38;5;248m[10:31:50.762][0m Transitioning to next state: deploy-aws (2).
[38;5;248m[10:31:50.769][0m Running state logic -- deploy-aws:2 (action)
[38;5;248m[10:31:50.769][0m {
"action": "apply",
"return": {
"cmd0": {
"cmd": "clone https://github.com/vorteil/terraform-examples.git /mnt/shared/1uxr6qlijtrPRTCPEE9nwRJ9LyO/out/instance/terraform-git-var",
"output": "/mnt/shared/1uxr6qlijtrPRTCPEE9nwRJ9LyO/out/instance/terraform-git-var"
}
}
}
[38;5;248m[10:31:50.769][0m Decrypting secrets.
[38;5;248m[10:31:50.782][0m Sleeping until function 'tfrun' returns.
[38;5;248m[10:31:53.533][0m adding to the global map to control action ids
[38;5;248m[10:31:53.628][0m Checking if tfstate service http backend is alive...
[38;5;248m[10:31:53.706][0m Wait till backend service is functional
[38;5;248m[10:31:53.776][0m Initializing terraform....
[38;5;248m[10:31:53.840][0m Reading in TFVars.json...
[38;5;248m[10:31:53.180][0m
[0m[1mInitializing the backend...[0m
[38;5;248m[10:31:53.186][0m Fetching tfstate variable...
[38;5;248m[10:31:53.190][0m [0m[32m
Successfully configured the backend "http"! Terraform will automatically
use this backend unless the backend configuration changes.[0m
2021/07/07 00:31:53 [DEBUG] GET http://localhost:8001/terraform-amazon-instance
[38;5;248m[10:31:53.214][0m
[0m[1mInitializing provider plugins...[0m
- Finding latest version of hashicorp/aws...
[38;5;248m[10:31:54.625][0m - Installing hashicorp/aws v3.48.0...
[38;5;248m[10:32:01.466][0m - Installed hashicorp/aws v3.48.0 (signed by HashiCorp)Terraform has created a lock file [1m.terraform.lock.hcl[0m to record the provider
selections it made above. Include this file in your version control repository
so that Terraform can guarantee to make the same selections by default when
you run "terraform init" in the future.[0m
[38;5;248m[10:32:01.474][0m
[0m[1m[32mTerraform has been successfully initialized![0m[32m[0m
[0m[32m
You may now begin working with Terraform. Try running "terraform plan" to see
any changes that are required for your infrastructure. All Terraform commands
should now work.If you ever set or change modules or backend configuration for Terraform,
rerun this command to reinitialize your working directory. If you forget, other
commands will detect it and remind you to do so if necessary.[0m
[38;5;248m[10:32:01.479][0m Executing 'apply' for terraform
[38;5;248m[10:32:02.152][0m 2021/07/07 00:32:02 [DEBUG] GET http://localhost:8001/terraform-amazon-instance
[38;5;248m[10:32:02.157][0m Fetching tfstate variable...
[38;5;248m[10:32:09.194][0m [0m[1maws_instance.web: Creating...[0m[0m
[38;5;248m[10:32:19.161][0m [0m[1maws_instance.web: Still creating... [10s elapsed][0m[0m
[38;5;248m[10:32:21.572][0m [0m[1maws_instance.web: Creation complete after 13s [id=i-0382710118e5f441d][0m
[38;5;248m[10:32:21.588][0m 2021/07/07 00:32:21 [DEBUG] POST http://localhost:8001/terraform-amazon-instance
[38;5;248m[10:32:21.592][0m Saving new tfstate variable...
[38;5;248m[10:32:21.617][0m [33m╷[0m[0m
[33m│[0m [0m[1m[33mWarning: [0m[0m[1mValue for undeclared variable[0m
[33m│[0m [0m
[33m│[0m [0m[0mThe root module does not declare a variable named "state-name" but a value
[33m│[0m [0mwas found in file "terraform.tfvars.json". If you meant to use this value,
[33m│[0m [0madd a "variable" block to the configuration.
[33m│[0m [0m
[33m│[0m [0mTo silence these warnings, use TF_VAR_... environment variables to provide
[33m│[0m [0mcertain "global" settings to all configurations in your organization. To
[33m│[0m [0mreduce the verbosity of these warnings, use the -compact-warnings option.
[33m╵[0m[0m
[0m[1m[32m
Apply complete! Resources: 1 added, 0 changed, 0 destroyed.
[0m[0m[1m[32m
Outputs:[0mip-address = "3.26.57.150"
[38;5;248m[10:32:21.632][0m Sending output back to direktiv...
[38;5;248m[10:32:22.320][0m Fetching tfstate variable...
[38;5;248m[10:32:22.359][0m Fetching tfstate variable...
[38;5;248m[10:32:22.385][0m Function 'tfrun' returned.
[38;5;248m[10:32:22.386][0m Transforming state data.
[38;5;248m[10:32:22.393][0m Transitioning to next state: check_apply_or_destroy (3).
[38;5;248m[10:32:22.399][0m Running state logic -- check_apply_or_destroy:3 (switch)
[38;5;248m[10:32:22.399][0m {
"action": "apply",
"amazon_ip": "3.26.57.150"
}
[38;5;248m[10:32:22.399][0m Switch condition 0 succeeded
[38;5;248m[10:32:22.403][0m Transitioning to next state: send_message (4).
[38;5;248m[10:32:22.410][0m Running state logic -- send_message:4 (action)
[38;5;248m[10:32:22.410][0m Decrypting secrets.
[38;5;248m[10:32:22.421][0m Sleeping until function 'discordmsg' returns.
[38;5;248m[10:32:24.578][0m Function 'discordmsg' returned.
[38;5;248m[10:32:24.585][0m Workflow completed.