Running a successful terraform plan
command pre-merge does not always mean that the terraform apply
command will also be successful post-merge. This statement is key to understanding the value of applying changes before merging.
For example, Amazon S3 Bucket uniqueness is validated on the AWS server-side. Due to this, users are unaware that a bucket name is not actually unique until it is created during deployment.
Users can ensure that their main branch is always reflective of error-free deployed infrastructure by applying changes before merging. If errors occur during deployment, users can correct those errors before the code changes land in the main branch.
Atlantis, in particular, has been a popular choice in the Terraform community for addressing this problem and has brought about a unique approach to applying changes from a pull request before they are merged. In this blog post, we will be discussing Atlantis and how you can implement your own Atlantis style workflow using Spacelift.
Once upon a time, there was Atlantis. No, we aren’t talking about the ancient mythologic civilization of Atlantis described by Plato around 360 B.C. Instead, we are talking about Atlantis, the self-hosted, open-source tool used for automating Terraform deployments.
Atlantis has become quite popular in the Terraform community, primarily due to its ease of use and purpose-built nature. Historically speaking, there haven’t been many alternatives to Atlantis in the Terraform world. In the early years of Terraform, users needed to manually build out their automation through scripts, using their own CI/CD tool of choice – typically something like Jenkins. Unsurprisingly, some users still follow this approach and manage their CI/CD tooling automation from scratch.
Atlantis has been a game-changer in helping users accelerate their ability to obtain automation around their Terraform code. By spinning up Atlantis and configuring it to their version control system, Atlantis users can quickly get automated deployments around their code. Atlantis has some quirks, such as maintaining the Atlantis project file, but overall, Atlantis is a great tool that solves a specific problem.
For some users, the features of Atlantis may satisfy their infrastructure automation needs, but for users with more comprehensive requirements, Atlantis may fall short.
Workflow Constraints
Out of the box, Atlantis is a tool that drives everything through pull requests (or merge requests, depending on which VCS you are using). Atlantis supports being able to customize what commands and arguments are to be run for a given Atlantis project and optionally pass in extra arguments to these commands.
However, the overarching deployment workflow used by Atlantis cannot be changed. Users of Atlantis must open a pull request for their code if they want to invoke Atlantis in any fashion, whether their goal is to simply run a terraform plan
or deploy their code with terraform apply
.
This reliance on pull requests brings about issues for users who want additional flexibility around their deployment workflows. For example, what if users wanted to trigger deployments when a new git tag is published? (rhetorical question, but this is just a single example of where this lack of flexibility begins).
Dependency Constraints
Dependency management when working with infrastructure as code is important, especially with cloud resources. Atlantis doesn’t have a native scalable means of managing your dependencies across your Terraform deployments. You can achieve this through native Terraform or Terragrunt by using things like depends_on
and dependencies
blocks.
Additionally, you could structure your Atlantis project file in such a way to maintain your dependencies as well, but doing this is hacky. There have been third-party tools to help with this issue (Atlantis project file generators), but users are forced to run these tools anytime they add new files or change their dependencies.
Later in this article, we will be describing an approach that users can utilize on Spacelift to manage their deployment dependencies.
With Spacelift, users can build flexible workflows through Spacelift Policies. The default behavior of Spacelift will deploy changes when they land into a Spacelift Stack’s tracked branch, but for those who prefer pre-merge apply – this behavior is customizable.
You can also configure Spacelift to deploy changes based on other conditions, perhaps a specific branch, or always allow changes to be applied pre-merge from any branch. Spacelift Policies allow you to make your deployment flow as flexible as possible.
You can even access date/time information and require approvals during certain days of the week or times of the day, etc. The possibilities are limitless!
In addition to workflow customization, you can also use Spacelift Policies to analyze your Terraform plan results. Plan Policies allow for the ability to prevent specific resources from being deployed or enforce resources to be configured in a certain way.
For example, perhaps you want to enforce tags are present on all resources, or specific tags always are specified on your resources.
How to Pre-Merge Apply
When using Spacelift, if you would like your pull request changes to be applied pre-merge, you simply need to visit your pull request in the Spacelift UI and click the promote button.
Select your pull request:
Click the promote button:
By clicking the promote button, you are “promoting” your proposed run, which did not run any apply commands, into a tracked run, which does run terraform apply
commands.
It’s that easy!
With Spacelift, dependency management is easy as well. One common pattern we see used is using a Spacelift Trigger policy to trigger dependencies. You simply define a policy that, in essence, says trigger all stacks that have a label “depends-on:$this-stack” after deployment completes.
Then, you simply attach this policy to all of your stacks, and from then on, whenever you need something to depend on something else, you can simply label it as such.
Here’s an example of a Trigger policy capable of doing this:
package spacelift
# This example trigger policy will cause every stack that declares dependency on
# the current one to get triggered the current one is successfully updated.
# You can read more about trigger policies here:
# https://docs.spacelift.io/concepts/policy/trigger-policy
trigger[stack.id] {
stack := input.stacks[_]
input.run.state == "FINISHED"
stack.labels[_] == concat("", ["depends-on:", input.stack.id])
}
# Learn more about sampling policy evaluations here:
# https://docs.spacelift.io/concepts/policy#sampling-policy-inputs
sample { true }
Let’s walk through a real-world example of using this policy.
First, we’ve created this policy and attached it to our stacks:
In this case, we have already attached the above policy to all of our stacks, so now we can simply define our dependencies using labels. We have built a simple dependency flow from development > staging > production in the example below.
You’ll notice that the staging stack contains a label that depends on the development stack, and the production stack has a label that depends on the staging stack. Having the policy attached to these stacks described previously will trigger our stacks in the appropriate environment flow order.
Note: Depending on your use case, you may also want to apply additional policies to the staging and production stacks to prevent them from being triggered directly and only allow them to be triggered from this flow.
Now that we have this setup, if we were to trigger our development stack, upon completion, the staging stack is triggered, and lastly, the production stack. Users can then visualize this deployment flow in the Spacelift Runs view. Pretty neat!
As you might have picked up by now, Spacelift is the most flexible management platform for infrastructure as code. Similar to Atlantis, Spacelift is purpose-built for infrastructure as code automation.
Besides simply supporting Terraform deployments, Spacelift also supports other tools and languages such as AWS CloudFormation, Pulumi, and Kubernetes – with support for even more tools coming soon. But there’s more to Spacelift than simply supporting these additional tools.
Spacelift Policies allow users to customize their Spacelift account using policy-as-code. User-defined policies can decide things such as:
- Who gets to log in to your Spacelift account and with what level of access
- Who gets to access individual Stacks and with what level of access
- Who can approve or reject a run, and how a run can be approved
- Which runs and tasks can be started
- What infrastructure changes can be applied
- How Git push events are interpreted
- Which task commands can be executed
- What happens when blocking runs terminate
In addition to policies, Spacelift also offers other unique features such as Resource and Run Observability, a Module Registry, Drift Detection, and more (check Documentation).
In this article, we described Atlantis and its pull-request-driven workflow. We then discussed how users could use the promote functionality to achieve this same pre-merge apply workflow using Spacelift. Furthermore, we talked about using Spacelift to manage your dependencies by combining a trigger policy with labels.
Atlantis is a great tool for Terraform automation, especially for those looking to run their own completely self-hosted CI/CD solution. Currently, Spacelift does not provide a self-hosted option but instead follows a hybrid approach allowing users to run their workers on their own infrastructure while using the Spacelift UI as a service. If you’d like to see Spacelift implement a completely self-hosted option, let us know in the comments of this article!
Thank you for reading. Feel free to comment and share on your social media platform if it is of any value to you. We wish you the best of luck in your infrastructure automation journey!
The most Flexible CI/CD Automation Tool
Spacelift is an alternative to using homegrown solutions on top of a generic CI. It helps overcome common state management issues and adds several must-have capabilities for infrastructure management.