Skip to main content

Atmos

atmos is both a command-line tool and Golang module for provisioning, managing and orchestrating workflows across various toolchains including terraform and helmfile.

The atmos tool is part of the SweetOps toolchain and was built to make DevOps and Cloud automation easier across multiple tools. It has direct support for automating Terraform, Helmfile. By utilizing Stacks, atmos enable you to effortlessly manage your Terraform and Helmfile Components from your local machine, in your CI/CD pipelines, or using spacelift.

Problem

A modern infrastructure depends on lots of various tools like terraform, packer, helmfile, helm, kubectl, docker, etc. All these tools have varying degrees of configuration support, but most are not optimized for defining DRY configurations across dozens or hundreds of environments. Moreover, the configuration format is very different between the tools, but usually, boils down to some kind of key-value configuration in either JSON or YAML. This lack of configuration consistency poses a problem when we want to make it easy to declaratively define the settings that end-users should care about.

Solution

We defined a “universal” configuration format that works for all the tools we use. When using terarform, helmfile, etc we design our components as re-usable building blocks that accept simple declarative parameters and offload all business logic to the tools themselves.

We designed this configuration schema in YAML and added convenient and robust deep-merging strategies that allow configurations to extend to other configurations. As part of this, we support OOP concepts of mixins, inheritance, and multiple inheritances - but all applied to the configuration. We support YAML anchors to clean up complex blocks of configuration, folder structures, environment variables, and all kinds of tool-specific settings.

Alternatives

There are a number of alternative tools to atmos, that accomplish some aspect of it.

ToolDescriptionWebsite
terragrunthttps://github.com/gruntwork-io/terragrunt
astrohttps://github.com/uber/astro
terraspacehttps://github.com/boltops-tools/terraspace
leverageThe Leverage CLI intended to orchestrate Leverage Reference Architecture for AWShttps://github.com/binbashar/leverage
optaThe next generation of Infrastructure-as-Code. Work with high-level constructs instead of getting lost in low-level cloud configurationhttps://github.com/run-x/optahttps://docs.opta.dev/
pterradactylPterradactyl is a library developed to abstract Terraform configuration from the Terraform environment setup.https://github.com/nike-inc/pterradactyl
terramateTerramate is a tool for managing multiple terraform stackshttps://github.com/mineiros-io/terramate
make (honorable mention)Many companies (including cloudposse) start by leveraging make with Makefile and targets to call terraform. This is a tried and true way, but at the scale we help our customer operate didn’t work. We know, because we tried it for ~3 years and suffocated under the weight of environment variables and stink of complexity only a mother could love.https://www.gnu.org/software/make/

What atmos is not:

  • An alternative to chef, puppet, or ansible. Instead, atmos is the type of tool that would call these tools.

  • An alternative to CI or CD systems. If anything, those systems will call atmos.

Design Considerations

  • Keep it strictly declarative (no concept of iterators or interpolations)

  • Offload all imperative design to the underlying tools

  • Do not write a programming language in YAML (e.g. CloudFormation) or JSON (e.g. terraform or JSONNET, KSONNET)

  • Do not use any esoteric expressions (e.g. JSONNET)

  • Keep it Simple Stupid (KISS)

  • Ensure compatibility with multiple tools, not just terraform

  • Define all configuration in files and not based on filesystem conventions.

Usage

atmos help (actually, we still need to implement this 😵 after porting to golang)

info

IMPORTANT

Atmos underwent a complete rewrite from an esoteric task runner framework called variant2 into native Golang as of version 1.0. The documentation is not updated everywhere. The interface is identical/backward compatible (and enhanced), but some references to variant2 are inaccurate. You can assume this documentation is for the latest version of atmos.

Subcommands are positional arguments passed to the atmos command.

Subcommand: version

Show the current version

Subcommand: describe

Show the deep-merged configuration for stacks and components.

Subcommand: terraform

  • Supports all built-in Terraform Subcommands (we essentially pass them through to the terraform command)

  • deploy is equivalent to atmos terraform apply -auto-approve

  • generate backend is used to generate the static backend.tf.json file that should be committed to VCS

  • generate varfile (deprecated command: write varfile) — This command generates a varfile for a terraform component: atmos terraform generate varfile <component> -s <stack> -f <file>

  • clean deletes any orphaned varfiles or planfiles

Subcommand: helmfile

  • Supports all helmfile subcommands

  • describe

  • generate varfile — This command generates a varfile for a helmfile component: atmos helmfile generate varfile <component> -s <stack> -f <file>

Subcommand: workflow

This subcommand is temporarily unavailable as a result of a major refactor from variant2 to golang. We will reintroduce the subcommand and it has not been officially deprecated.

https://github.com/cloudposse/atmos

Latest Releases

https://github.com/cloudposse/atmos/releases

Open Issues

https://github.com/cloudposse/atmos/issues

Examples

Provision Terraform Component

To provision a Terraform component using the atmos CLI, run the following commands in the geodesic container shell:

atmos terraform plan eks --stack=ue2-dev
atmos terraform apply eks --stack=ue2-dev

Where:

  • eks is the Terraform component to provision (from the components/terraform folder) that is defined in the stack. If the component is not defined in the stack, it will error.

  • --stack=ue2-dev is the stack to provision the component into (or in other words, where to read the configuration)

info

You can pass any argument supported by terraform and it will be passed through to the system call to terraform. e.g. We can pass the -destroy flag to terraform plan by running atmos terraform plan -destroy --stack=uw2-dev

Short versions of the command-line arguments can also be used:

atmos terraform plan eks -s ue2-dev
atmos terraform apply eks -s ue2-dev

To execute plan and apply in one step, use terrafrom deploy command:

atmos terraform deploy eks -s ue2-dev

Provision Terraform Component with Planfile

You can use a terraform planfile (previously generated with atmos terraform plan) in atmos terraform apply/deploy commands by running the following:

atmos terraform plan test/test-component-override -s tenant1/ue2/dev
atmos terraform apply test/test-component-override -s tenant1-ue2-dev --from-plan
atmos terraform deploy test/test-component-override -s tenant1-ue2-dev --from-plan

Provision Helmfile Component

To provision a helmfile component using the atmos CLI, run the following commands in the container shell:

atmos helmfile diff nginx-ingress --stack=ue2-dev
atmos helmfile apply nginx-ingress --stack=ue2-dev

Where:

  • nginx-ingress is the helmfile component to provision (from the components/helmfile folder)

  • --stack=ue2-dev is the stack to provision the component into

Short versions of the command-line arguments can be used:

atmos helmfile diff nginx-ingress -s ue2-dev
atmos helmfile apply nginx-ingress -s ue2-dev

To execute diff and apply in one step, use helmfile deploy command:

atmos helmfile deploy nginx-ingress -s ue2-dev

View Deep-merged CLI Configs

Use atmos describe config command to show the effective CLI configuration. Use --format of json or yaml to alter the output to structured data.

The deep-merge processes files from these locations:

  • system dir (/usr/local/etc/atmos/atmos.yaml on Linux, %LOCALAPPDATA%/atmos/atmos.yaml on Windows)

  • home dir (~/.atmos/atmos.yaml)

  • atmos.yaml in the current directory

Here are some more examples:

atmos describe config -help
atmos describe config

atmos describe config --format=json
atmos describe config --format json
atmos describe config -f=json
atmos describe config -f json

atmos describe config --format=yaml
atmos describe config --format yaml
atmos describe config -f=yaml
atmos describe config -f yaml

Example Commands

atmos version
atmos describe config

# Describe components and stacks
atmos describe component <component> -s <stack>
atmos describe component <component> --stack <stack>

# Generate
atmos terraform generate backend <component> -s <stack>
atmos terraform write varfile <component> -s <stack> # this command will be changed to `terraform generate varfile`
atmos terraform write varfile <component> -s <stack> -f ./varfile.json # supports output file

# Terraform
# (almost) all native Terraform commands supported
# https://www.terraform.io/docs/cli/commands/index.html
atmos terraform plan <component> -s <stack>
atmos terraform apply <component> -s <stack> -auto-approve
atmos terraform apply <component> -s <stack> --from-plan
atmos terraform deploy <component> -s <stack>
atmos terraform deploy <component> -s <stack> --from-plan
atmos terraform deploy <component> -s <stack> -deploy-run-init=true
atmos terraform workspace <component> -s <stack>
atmos terraform validate <component> -s <stack>
atmos terraform output <component> -s <stack>
atmos terraform graph <component> -s <stack>
atmos terraform show <component> -s <stack>
atmos terraform clean <component> -s <stack>

# Helmfile
# All native helmfile commands supported including [global options]
# https://github.com/roboll/helmfile#cli-reference
atmos helmfile diff <component> -s <stack>
atmos helmfile apply <component> -s <stack>

# Helmfile with [global options]
atmos helmfile diff <component> -s <stack> --global-options "--no-color --namespace=test"
atmos helmfile diff <component> -s <stack> --global-options="--no-color --namespace test"

Workflows

danger

IMPORTANT This is in atmos 0.x and while this functionality has not been deprecated, it also has not been ported over to atmos 1.x yet.

Workflows are a way of combining multiple commands into one executable unit of work, kind of like a basic task-runner.

In the CLI, workflows can be defined using two different methods:

In the first case, we define workflows in the configuration file for the stack (which we specify on the command line). To execute the workflows from workflows in dev/us-east-2.yaml, run the following commands:

  atmos workflow deploy-all -s ue2-dev

Note that workflows defined in the stack config files can be executed only for the particular stack (environment and stage). It's not possible to provision resources for multiple stacks this way.

In the second case (defining workflows in a separate file), a single workflow can be created to provision resources into different stacks. The stacks for the workflow steps can be specified in the workflow config.

For example, to run terraform plan and helmfile diff on all terraform and helmfile components in the example, execute the following command:

  atmos workflow plan-all -f workflows

where the command-line option -f (--file for long version) instructs the atmos CLI to look for the plan-all workflow in the file workflows.

As we can see, in multi-environment workflows, each workflow job specifies the stack it's operating on:

workflows:
plan-all:
description: Run 'terraform plan' and 'helmfile diff' on all components for all stacks
steps:
- job: terraform plan vpc
stack: ue2-dev
- job: terraform plan eks
stack: ue2-dev
- job: helmfile diff nginx-ingress
stack: ue2-dev
- job: terraform plan vpc
stack: ue2-staging
- job: terraform plan eks
stack: ue2-staging

You can also define a workflow in a separate file without specifying the stack in the workflow's job config. In this case, the stack needs to be provided on the command line.

For example, to run the deploy-all workflow from the workflows file for the ue2-dev stack, execute the following command:

  atmos workflow deploy-all -f workflows -s ue2-dev
info

For an example of what this looks like within Geodesic see the section on “Filesystem Layout”

Our general recommended filesystem layout looks like this. It can be customized using the CLI Configuration file.

# Your infratructure repository
infrastructure/

│ # Centralized components configuration
├── stacks/
│ │ └── catalog/
│ │
│ └── $stack.yaml

│ # Components are broken down by tool
├── components/
│ │
│ ├── terraform/ # root modules in here
│ │ ├── vpc/
│ │ ├── eks/
│ │ ├── rds/
│ │ ├── iam/
│ │ ├── dns/
│ │ └── sso/
│ │
│ └── helmfile/ # helmfiles are organized by chart
│ ├── cert-manager/helmfile.yaml
│ └── external-dns/helmfile.yaml

│ # Makefile for building the CLI
├── Makefile

│ # Docker image for shipping the CLI and all dependencies
└── Dockerfile (optional)

CLI Configuration

Atmos supports a CLI configuration to define configure the behavior working with stacks and components.

In Geodesic we typically put this in /usr/local/etc/atmos/atmos.yaml (e.g. in rootfs/... in the infrastructure repository). Note this file uses the stack config format for consistency, but we do not consider it a stack configuration.

The CLI config is loaded from the following locations (from lowest to highest priority):

  • system dir (/usr/local/etc/atmos on Linux, %LOCALAPPDATA%/atmos on Windows)

  • home dir (~/.atmos)

  • current directory (./)

  • ENV vars

  • Command-line arguments

It supports POSIX-style Globs for file names/paths (double-star ** is supported)

Environment Variables

Most YAML settings can be defined also as environment variables. This is helpful while doing local development. For example, setting ATMOS_STACKS_BASE_PATH to a path in /localhost to your local development folder, will enable you to rapidly iterate.

VariableYAML PathDescription
ATMOS_COMPONENTS_TERRAFORM_BASE_PATHcomponents.terraform.base_path
ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVEcomponents.terraform.apply_auto_approve
ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INITcomponents.terraform.deploy_run_init
ATMOS_COMPONENTS_HELMFILE_BASE_PATHcomponents.helmfile.base_path
ATMOS_COMPONENTS_HELMFILE_KUBECONFIG_PATHcomponents.helmfile.aws_profile_pattern
ATMOS_COMPONENTS_HELMFILE_HELM_AWS_PROFILE_PATTERNcomponents.helmfile.helm_aws_profile_pattern
ATMOS_COMPONENTS_HELMFILE_CLUSTER_NAME_PATTERNcomponents.helmfile.cluster_name_pattern
ATMOS_STACKS_BASE_PATHstacks.base_path
ATMOS_STACKS_INCLUDED_PATHSstacks.included_paths
ATMOS_STACKS_EXCLUDED_PATHSstacks.excluded_paths
ATMOS_STACKS_NAME_PATTERNstacks.name_pattern
ATMOS_LOGS_VERBOSEFor more verbose output, set this environment variable to true to see the logs how the CLI finds the configs and performs merges.

Example atmos.yaml Configuration File

(see: https://github.com/cloudposse/atmos/blob/master/examples/complete/atmos.yaml#L30)


components:
# Settings for all terraform components
terraform:
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_BASE_PATH` ENV var, or `--terraform-dir` command-line argument
# Supports both absolute and relative paths
base_path: "/atmos_root/components/terraform"
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE` ENV var
apply_auto_approve: false
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT` ENV var, or `--deploy-run-init` command-line argument
deploy_run_init: true
# Can also be set using `ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE` ENV var, or `--auto-generate-backend-file` command-line argument
auto_generate_backend_file: false

# Settings for all helmfile components
helmfile:
# Can also be set using `ATMOS_COMPONENTS_HELMFILE_BASE_PATH` ENV var, or `--helmfile-dir` command-line argument
# Supports both absolute and relative paths
base_path: "/atmos_root/components/helmfile"
# Can also be set using `ATMOS_COMPONENTS_HELMFILE_KUBECONFIG_PATH` ENV var
kubeconfig_path: "/dev/shm"
# Can also be set using `ATMOS_COMPONENTS_HELMFILE_HELM_AWS_PROFILE_PATTERN` ENV var
helm_aws_profile_pattern: "{namespace}-{tenant}-gbl-{stage}-helm"
# Can also be set using `ATMOS_COMPONENTS_HELMFILE_CLUSTER_NAME_PATTERN` ENV var
cluster_name_pattern: "{namespace}-{tenant}-{environment}-{stage}-eks-cluster"

# Settings for all stacks
stacks:
# Can also be set using `ATMOS_STACKS_BASE_PATH` ENV var, or `--config-dir` and `--stacks-dir` command-line arguments
# Supports both absolute and relative paths
base_path: "/atmos_root/stacks"
# Can also be set using `ATMOS_STACKS_INCLUDED_PATHS` ENV var (comma-separated values string)
included_paths:
- "**/*"
# Can also be set using `ATMOS_STACKS_EXCLUDED_PATHS` ENV var (comma-separated values string)
excluded_paths:
- "globals/**/*"
- "catalog/**/*"
- "**/*globals*"
# Can also be set using `ATMOS_STACKS_NAME_PATTERN` ENV var
name_pattern: "{tenant}-{environment}-{stage}"

logs:
verbose: false
colors: true

Troubleshooting

info

For more verbose output, you can always set the environment variable ATMOS_LOGS_VERBOSE=true to see the logs how the CLI finds the configs and performs merges.

Error: stack name pattern must be provided in 'stacks.name_pattern' config or 'ATMOS_STACKS_NAME_PATTERN' ENV variable

This means that you are probably missing a section like this in your atmos.yml. See the instructions on CLI Configuration for more details.

stacks:
name_pattern: "{tenant}-{environment}-{stage}"

Error: The stack name pattern '{tenant}-{environment}-{stage}' specifies 'tenant, but the stack ue1-prod does not have a tenant defined`

This means that your name_pattern declares a tenant is required, but not specified. Either specify a tenant in your vars for the stack configuration, or remove the {tenant} from the name_pattern

stacks:
name_pattern: "{tenant}-{environment}-{stage}"

How-to Guides

Concepts