Custom Commands
Atmos can be easily extended to support any number of custom CLI commands. Custom commands are exposed through the atmos CLI when you run atmos help. It's a great way to centralize the way operational tools are run in order to improve DX.
For example, one great way to use custom commands is to tie all the miscellaneous scripts into one consistent CLI interface. Then we can kiss those
ugly, inconsistent arguments to bash scripts goodbye! Just wire up the commands in atmos to call the script. Then developers can just run atmos help
and discover all available commands.
Simple Exampleβ
Here is an example to play around with to get started.
Adding the following to atmos.yaml will introduce a new hello command.
# Custom CLI commands
commands:
- name: hello
description: This command says Hello world
steps:
- "echo Hello world!"
We can run this example like this:
atmos hello
Positional Argumentsβ
Atmos also supports positional arguments. If a positional argument is required but not provided by the user, the command will failβunless you define a default in your config.
For the example, adding the following to atmos.yaml will introduce a new greet command that accepts one name argument,
but uses a default of "John Doe" if none is provided.
# subcommands
commands:
- name: greet
description: This command says hello to the provided name
arguments:
- name: name
description: Name to greet
required: true
default: John Doe
steps:
- "echo Hello {{ .Arguments.name }}!"
We can run this example like this:
atmos greet Alice
or defaulting to "John Doe"
atmos greet
Trailing Argumentsβ
Atmos supports trailing arguments after -- (a standalone double-dash). The -- itself is a delimiter that signals the end of Atmos-specific options. Anything after -- is passed directly to the underlying command without being interpreted by Atmos. The value of these trailing arguments is accessible in {{ .TrailingArgs }}.
For the example, adding the following to atmos.yaml will introduce a new echo command that accepts one name argument and also uses trailingArgs
- name: ansible run
description: "Runs an Ansible playbook, allowing extra arguments after --."
arguments:
- name: playbook
description: "The Ansible playbook to run"
default: site.yml
required: true
steps:
- "ansible-playbook {{ .Arguments.playbook }} {{ .TrailingArgs }}"
Output:
$ atmos ansible run -- --limit web
Running: ansible-playbook site.yml --limit web
PLAY [web] *********************************************************************
Passing Flagsβ
Passing flags works much like passing positional arguments, except for that they are passed using long or short flags.
Flags can be optional (this is configured by setting the required attribute to false).
# subcommands
commands:
- name: hello
description: This command says hello to the provided name
flags:
- name: name
shorthand: n
description: Name to greet
required: true
steps:
- "echo Hello {{ .Flags.name }}!"
We can run this example like this, using the long flag:
atmos hello --name world
Or, using the shorthand, we can just write:
atmos hello -n world
Advanced Examplesβ
Define a New Terraform Commandβ
# Custom CLI commands
commands:
- name: terraform
description: Execute 'terraform' commands
# subcommands
commands:
- name: provision
description: This command provisions terraform components
arguments:
- name: component
description: Name of the component
flags:
- name: stack
shorthand: s
description: Name of the stack
required: true
# ENV var values support Go templates
env:
- key: ATMOS_COMPONENT
value: "{{ .Arguments.component }}"
- key: ATMOS_STACK
value: "{{ .Flags.stack }}"
steps:
- atmos terraform plan $ATMOS_COMPONENT -s $ATMOS_STACK
- atmos terraform apply $ATMOS_COMPONENT -s $ATMOS_STACK
Override an Existing Terraform Commandβ
# Custom CLI commands
commands:
- name: terraform
description: Execute 'terraform' commands
# subcommands
commands:
- name: apply
description: This command executes 'terraform apply -auto-approve' on terraform components
arguments:
- name: component
description: Name of the component
flags:
- name: stack
shorthand: s
description: Name of the stack
required: true
steps:
- atmos terraform apply {{ .Arguments.component }} -s {{ .Flags.stack }} -auto-approve
Show Component Infoβ
# Custom CLI commands
commands:
- name: show
description: Execute 'show' commands
# subcommands
commands:
- name: component
description: Execute 'show component' command
arguments:
- name: component
description: Name of the component
flags:
- name: stack
shorthand: s
description: Name of the stack
required: true
# ENV var values support Go templates and have access to {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables
env:
- key: ATMOS_COMPONENT
value: "{{ .Arguments.component }}"
- key: ATMOS_STACK
value: "{{ .Flags.stack }}"
- key: ATMOS_TENANT
value: "{{ .ComponentConfig.vars.tenant }}"
- key: ATMOS_STAGE
value: "{{ .ComponentConfig.vars.stage }}"
- key: ATMOS_ENVIRONMENT
value: "{{ .ComponentConfig.vars.environment }}"
# If a custom command defines 'component_config' section with 'component' and 'stack', 'atmos' generates the config for the component in the stack
# and makes it available in {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables,
# exposing all the component sections (which are also shown by 'atmos describe component' command)
component_config:
component: "{{ .Arguments.component }}"
stack: "{{ .Flags.stack }}"
# Steps support using Go templates and can access all configuration settings (e.g. {{ .ComponentConfig.xxx.yyy.zzz }})
# Steps also have access to the ENV vars defined in the 'env' section of the 'command'
steps:
- 'echo Atmos component from argument: "{{ .Arguments.component }}"'
- 'echo ATMOS_COMPONENT: "$ATMOS_COMPONENT"'
- 'echo Atmos stack: "{{ .Flags.stack }}"'
- 'echo Terraform component: "{{ .ComponentConfig.component }}"'
- 'echo Backend S3 bucket: "{{ .ComponentConfig.backend.bucket }}"'
- 'echo Terraform workspace: "{{ .ComponentConfig.workspace }}"'
- 'echo Namespace: "{{ .ComponentConfig.vars.namespace }}"'
- 'echo Tenant: "{{ .ComponentConfig.vars.tenant }}"'
- 'echo Environment: "{{ .ComponentConfig.vars.environment }}"'
- 'echo Stage: "{{ .ComponentConfig.vars.stage }}"'
- 'echo Dependencies: "{{ .ComponentConfig.deps }}"'
Set EKS Clusterβ
# Custom CLI commands
commands:
- name: set-eks-cluster
description: |
Download 'kubeconfig' and set EKS cluster.
Example usage:
atmos set-eks-cluster eks/cluster -s plat-ue1-dev -r admin
atmos set-eks-cluster eks/cluster -s plat-uw2-prod --role reader
verbose: false # Set to `true` to see verbose outputs
arguments:
- name: component
description: Name of the component
flags:
- name: stack
shorthand: s
description: Name of the stack
required: true
- name: role
shorthand: r
description: IAM role to use
required: true
# If a custom command defines 'component_config' section with 'component' and 'stack',
# Atmos generates the config for the component in the stack
# and makes it available in {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables,
# exposing all the component sections (which are also shown by 'atmos describe component' command)
component_config:
component: "{{ .Arguments.component }}"
stack: "{{ .Flags.stack }}"
env:
- key: KUBECONFIG
value: /dev/shm/kubecfg.{{ .Flags.stack }}-{{ .Flags.role }}
steps:
- >
aws
--profile {{ .ComponentConfig.vars.namespace }}-{{ .ComponentConfig.vars.tenant }}-gbl-{{ .ComponentConfig.vars.stage }}-{{ .Flags.role }}
--region {{ .ComponentConfig.vars.region }}
eks update-kubeconfig
--name={{ .ComponentConfig.vars.namespace }}-{{ .Flags.stack }}-eks-cluster
--kubeconfig="${KUBECONFIG}"
> /dev/null
- chmod 600 ${KUBECONFIG}
- echo ${KUBECONFIG}
List Stacks and Componentsβ
# Custom CLI commands
commands:
- name: list
description: Execute 'atmos list' commands
# subcommands
commands:
- name: stacks
description: |
List all Atmos stacks.
steps:
- >
atmos describe stacks --process-templates=false --sections none | grep -e "^\S" | sed s/://g
- name: components
description: |
List all Atmos components in all stacks or in a single stack.
Example usage:
atmos list components
atmos list components -s tenant1-ue1-dev
atmos list components --stack tenant2-uw2-prod
flags:
- name: stack
shorthand: s
description: Name of the stack
required: false
steps:
- >
{{ if .Flags.stack }}
atmos describe stacks --stack {{ .Flags.stack }} --format json --sections none | jq ".[].components.terraform" | jq -s add | jq -r "keys[]"
{{ else }}
atmos describe stacks --format json --sections none | jq ".[].components.terraform" | jq -s add | jq -r "keys[]"
{{ end }}
Using Authentication with Custom Commandsβ
Custom commands can specify an identity field to authenticate before execution. This is useful when commands need to interact with cloud resources that require specific credentials or elevated permissions.
When an identity is specified, Atmos will:
- Authenticate using the specified identity (prompting for MFA if required)
- Write temporary credentials to a file
- Set environment variables pointing to the credential files (
AWS_SHARED_CREDENTIALS_FILE,AWS_CONFIG_FILE,AWS_PROFILE, etc.) - Execute all command steps with these environment variables
Example: Custom Command with Authenticationβ
commands:
- name: deploy-infra
description: Deploy infrastructure with superadmin privileges
identity: superadmin # Authenticate as superadmin before running
arguments:
- name: component
description: Component to deploy
required: true
flags:
- name: stack
shorthand: s
description: Stack to deploy to
required: true
steps:
- atmos terraform plan {{ .Arguments.component }} -s {{ .Flags.stack }}
- atmos terraform apply {{ .Arguments.component }} -s {{ .Flags.stack }} -auto-approve
To execute this command:
atmos deploy-infra vpc -s plat-ue2-prod
Atmos will:
- Authenticate as
superadmin(prompting for MFA if configured) - Set up environment variables pointing to temporary credential files
- Execute both
terraform planandterraform applywith those credentials
Example: Multi-Step Command with Authenticationβ
commands:
- name: audit-resources
description: Audit cloud resources with auditor credentials
identity: auditor
flags:
- name: region
shorthand: r
description: AWS region
required: true
steps:
- |
echo "Running audit in {{ .Flags.region }}..."
aws sts get-caller-identity
- |
aws ec2 describe-instances --region {{ .Flags.region }} \
--query 'Reservations[].Instances[].[InstanceId,State.Name,Tags[?Key==`Name`].Value|[0]]' \
--output table
- |
aws s3 ls --region {{ .Flags.region }}
All steps in this command will execute with the auditor identity credentials.
Authentication with Component Configβ
You can combine identity authentication with component configuration:
commands:
- name: provision-with-auth
description: Provision component with specific identity
identity: infrastructure-admin
arguments:
- name: component
description: Component to provision
flags:
- name: stack
shorthand: s
description: Stack name
required: true
component_config:
component: "{{ .Arguments.component }}"
stack: "{{ .Flags.stack }}"
env:
- key: COMPONENT_REGION
value: "{{ .ComponentConfig.vars.region }}"
steps:
- |
echo "Provisioning {{ .Arguments.component }} in {{ .ComponentConfig.vars.region }}"
echo "Using identity: infrastructure-admin"
aws sts get-caller-identity
- atmos terraform apply {{ .Arguments.component }} -s {{ .Flags.stack }}
Configure identities in your atmos.yaml under the auth section. See the Authentication documentation for configuration details.
Overriding Identity at Runtimeβ
You can override the identity specified in the command configuration using the --identity flag:
# Use the identity from command config
atmos deploy-infra vpc -s plat-ue2-prod
# Override with a different identity
atmos deploy-infra vpc -s plat-ue2-prod --identity developer
# Use no identity (skip authentication even if configured)
atmos deploy-infra vpc -s plat-ue2-prod --identity ""
The --identity flag is automatically added to all custom commands and takes precedence over the identity field in the command configuration.
The identity field applies to all steps in the custom command. If you need different identities for different operations, consider using separate custom commands or workflows with per-step identity configuration.