Implement `atmos toolchain` to install 3rd party tools
Describe the Feature
Atmos should automatically add the packages base-path as a priority search PATH when executing any commands.
Expected Behavior
Packages are automatically installed and available to atmos components, custom commands, and workflows.
When running atmos terraform plan and it depends on [email protected], if not installed it should automatically install it (if it's configured).
When running a custom command, that needs tflint, it should be able to automatically install it.
When running a workflow that needs tfsec, it should automatically install it.
Available Commands:
add Add or update a tool and version in .tool-versions
aliases List configured tool aliases
clean Remove all installed tools by deleting the .tools directory
completion Generate the autocompletion script for the specified shell
exec Exec a specific version of a tool (replaces current process)
get Show all versions configured for a tool, sorted in semver order
help Help about any command
info Display tool configuration from registry
install Install a CLI binary from the registry
list List configured tools and their installation status
path Emit the complete PATH environment variable for configured tool versions
remove Remove a tool or a specific version from .tool-versions
run Run a specific version of a tool
set Set a specific version for a tool in .tool-versions
uninstall Uninstall a CLI binary from the registry
which Display the path to an executable
Flags:
-h, --help help for toolchain
--log-level string Set log level (debug, info, warn, error)
--tool-versions string Path to tool-versions file (default ".tool-versions")
--tools-config string Path to tools configuration file (default "tools.yaml")
--tools-dir string Directory to store installed tools (default ".tools")
Use Case
- Install versions of opentofu or terraform
- Install helmfile
- Install terraform-docs, tflint, etc.
- Support multiple concurrent versions
- Install any other binaries needed by workflows, custom commands, etc
- Atmos should act as a wrapper for atmos, to install and exec any version of atmos using the
usekeyword
Describe Ideal Solution
Atmos Commands
# Install all packages
atmos toolchain install
# Update packages
atmos toolchain update
Atmos Configuration
# atmos.yaml
# Automatically install (if not installed) and use this version of atmos, and exec (replacing PID).
# See `exec` example in `toolchain-experiment`
# THis replaces the current process, it's NOT A SUBSHELL.
# This ensures everyone uses the right version based on the configuration.
use 1.183.1
# Define packages required for a specific type of component
# In this example, we're refering to the "opentofu" alias and
# specifying the version of 1.10.3
components:
terraform:
command: [email protected]
helmfile:
command: [email protected]
The use keyword will install atmos at version 1.183.1, then call syscall.Exec to replace the current process with the atmos at the correct version, if the current version is not already 1.183.1.
Aliases
Aliases map a short name like opentofu to a registry configuration of opentofu/opentofu or terraform to something like hashicorp/terraform
# toolchain subcommand configuration
toolchain:
# Tool name aliases for .tool-versions compatibility
# Maps common tool names to their registry owner/repo paths
aliases:
terraform: hashicorp/terraform
opentofu: opentofu/opentofu
helm: helm/helm
kubectl: kubernetes-sigs/kubectl
kustomize: kubernetes-sigs/kustomize
kind: kubernetes-sigs/kind
krew: kubernetes-sigs/krew
github-comment: suzuki-shunsuke/github-comment
tflint: terraform-linters/tflint
tfsec: aquasecurity/tfsec
checkov: bridgecrewio/checkov
terragrunt: gruntwork-io/terragrunt
packer: hashicorp/packer
vault: hashicorp/vault
consul: hashicorp/consul
nomad: hashicorp/nomad
waypoint: hashicorp/waypoint
boundary: hashicorp/boundary
helmfile: helmfile/helmfile
atmos: cloudposse/atmos
Tool Versions
We should use the simple .tool-versions convention popularized by asdf
tool1 1.2.3 4.5.7
tool2 2.3.4
tool3 5.6.7
The first semver is the default version, when nothing is specified
This file is consulted when calling install/uninstall/add/remove.
We should use aliases in this file to refer to toolchain tools. This is so we can change the mapping in the future.
Aqua Registry Configuration
We're going to add partial Aqua support for registries and expand the implementation over time. To start, we want to support the essential packages we depend on.
Local Configuration
We also want a local toolchain configuration, when not depending on the Aqua registry.
# toolchain subcommand configuration
toolchain:
# this is relative to the repo root
install-dir: .tools/bin
# Local tools configuration
# This file is consulted first before the Aqua registry
# Allows local overrides and custom tool definitions
tools:
# Atmos configuration supporting both raw and gzipped binaries
cloudposse/atmos:
type: github_release
repo_owner: cloudposse
repo_name: atmos
binary_name: atmos
version_constraints:
- constraint: ">= 1.0.0"
asset: atmos_{{trimV .Version}}_{{.OS}}_{{.Arch}}
format: raw
- constraint: ">= 1.0.0"
asset: atmos_{{trimV .Version}}_{{.OS}}_{{.Arch}}.gz
format: gzip
# Override a registry tool with local settings
hashicorp/terraform:
type: http
url: https://releases.hashicorp.com/terraform/{{trimV .Version}}/terraform_{{trimV .Version}}_{{.OS}}_{{.Arch}}.zip
format: zip
binary_name: terraform
# TFLint configuration
terraform-linters/tflint:
type: github_release
repo_owner: terraform-linters
repo_name: tflint
binary_name: tflint
# Custom tool not in registry
my-custom-tool:
type: github_release
repo_owner: myorg
repo_name: my-custom-tool
asset: my-custom-tool_{{trimV .Version}}_{{.OS}}_{{.Arch}}.tar.gz
format: tar.gz
binary_name: my-custom-tool
# Override OpenTofu to use a specific version constraint
opentofu/opentofu:
type: github_release
repo_owner: opentofu
repo_name: opentofu
binary_name: tofu
version_constraints:
- constraint: ">= 1.10.0"
asset: tofu_{{trimV .Version}}_{{.OS}}_{{.Arch}}.tar.gz
format: tar.gz
- constraint: "< 1.10.0"
asset: tofu_{{trimV .Version}}_{{.OS}}_{{.Arch}}.zip
format: zip
helm/helm:
type: http
repo_owner: helm
repo_name: helm
url: https://get.helm.sh/helm-v{{.Version}}-{{.OS}}-{{.Arch}}.tar.gz
format: tar.gz
binary_name: helm
Stack Configuration
# stacks/_defaults.yaml
components:
terraform:
vpc:
command: hashicorp/[email protected]
When the command is evaluated, it's first checked against the toolchain, and if it's not installed, it will be automatically installed.
The PATH for exec will be updated to use the directory containing terraform for version 1.11.4
Workflows
workflows:
tflint:
description: "Run tflint on a component"
needs:
- [email protected]
- [email protected]
steps:
- name: tflint
type: shell
command: |
cd terraform/components
tflint --recursive
Custom Commands
# Custom CLI commands
commands:
- name: tfsec
description: Run tfsec against a specified Terraform component
arguments:
- name: component
description: Name of the component to scan
needs:
- [email protected]
steps:
# Navigate to the Terraform component directory
- cd components/terraform/{{ .Arguments.component }}
# Run tfsec scan
- tfsec .
Alternatives Considered
Using aqua directly. However, the aims of the Aqua Project are different than our aims and Aqua does not expose an SDK or stable interfaces for other CLIs to use Aqua.
There are several options:
- Use a version manager like aqua
- Run a version manager like aqua internally
- Call a version manager like aqua as a Go Module
- Develop the feature from scratch
The option 1 is simplest and easiest. We don't need to develop anything. We only need to encourage users to use a version manager. But the drawback is that users need to install tools themselves. By enabling atmos to manage tools, users don't need to install tools themselves.
The option 2 is not bad.
I think we should consider this option first.
I'm the author of aqua, which is a CLI version manager, so I use aqua here.
atmos can generate configuration files of aqua such as aqua.yaml, registry.yaml, aqua-checksums.json, and aqua-policy.yaml internally.
And atmos can execute aqua install internally to install tools.
By adding $(aqua root-dir)/bin to $PATH internally, users don't need to be aware of aqua.
aqua also provides a update command.
aqua provides Renovate config presets. We can utilize them or develop similar presets for Atmos.
This is an advanced topic, but one of problems is checksum verification in CI. Checksum verification is a feature of aqua. This feature is very important for security. aqua recommends managing aqua-checksums.json by Git, otherwise checksums can be tampered. If atmos hides aqua and doesn't manage aqua-checksums.json by Git, the checksum verification doesn't work enough. But I'm aware that managing aqua-checksums.json by Git is a bit bothersome because we need to update checksums when we update tools. Especially, when we update tools by Renovate, we also need to update checksums. aqua provides a GitHub Action to update checksums automatically, but this requires a GitHub App.
The option 3 is good, but I guess most version managers are not Go Modules but CLI.
aqua is written in Go, and it doesn't use Go's internal packages, which means all packages are public.
But aqua doesn't intend that other tools depend on aqua's packages.
API isn't third party friendly, and API can be changed drastically, so I don't recommend using aqua as a library.
I don't recommend the option 4. We should utilize existing tools rather than re-inventing the wheel. We need to maintain something like aqua's registries ourselves. aqua provides various features. If other options isn't acceptable, we need to consider this option.
What do you think?
@suzuki-shunsuke
- Use a version manager like aqua
My main concern is this: it defeats the purpose of the desired functionality, which is to avoid the need to manually install additional toolsâincluding the tooling required to do so (besides Atmos itself). The goal is for everything to "just work" out of the box. I feel so strongly about this that Iâd rather hold off until we find a solution that aligns with this principle.
- Run a version manager like aqua internally
Same response as (1).
- Call a version manager like aqua as a Go Module
Thank you for clarifying this point!
This was definitely my preferred approach. I was hoping this approach could work, as I noticed that the packages were not strictly internal.
However, I appreciate your warning about the unstable interface and its potential for changes without notice. This raises concerns and gives me pause. Let's see if there's a middle ground (see option 5).
- Develop the feature from scratch
I would prefer to rule this out as well.
Iâd like to propose two additional options based on your feedback.
Option 5: Call Aqua (Preferred)
We have an upcoming PR that introduces support for editorconfig.
- #896
Whatâs notable about this implementation is that while many editorconfig packages are internal, we were able to call their public methods, achieving a near-native integration within Atmos.
What if we did something similar with Aqua?
In this model, if Aqua could expose a few stable APIs that provide command-line-like functionality from within Go, Atmos could call those directly. This way there's not a lot of work to maintain in Aqua, while providing other tooling the ability to leverage aqua. While this may sacrifice full flexibility, it offers the advantage of maintaining a native user experience within Atmos.
As part of this if we could programmatically pass a string containing aqua configuration, we can then work very well with parts of aqua. We could generate that configuration inside of atmos, so the package manafement can feel more integrated with things like stacks, workflows and custom commands.
Option 6: Go-based Aqua Bootstrapper (Less Preferred)
This is a riff on your #2.
If Aqua provided a method to bootstrap itself programmatically via Go, Atmos could invoke that as part of its process. This approach would enable similar functionality, but with a few limitations. Specifically, weâd be constrained to using Aquaâs configuration files without the ability to embed or extend those configurations directly within Atmos.
This limitation concerns me because weâre introducing radical improvements to Atmos configuration handling in an upcoming PR that adds support for remote configurations, local overrides, imports, and more. All of that provides a consistent configuration interface for atmos, including the underlying tooling. Iâd like those improvements to be universally applicable across Atmos functionality.
- #808
Sorry for late reply. About the option 5,
- We need to add several public API to aqua.
- If we make atmos packages available to users directly, we need to develop something like aqua-proxy.
We can't use aqua-proxy as aqua-proxy executes
aqua execand aqua can't use atmos configuration files. Please see How does Lazy Install work? too. We need to develop a tool like atmos-proxy and execute a command likeatmos exec. - I considered sharing the install directory with aqua, but it may cause trouble because aqua library atmos uses might not be compatible with aqua users use. aqua has API to change the install directory, so atmos should change it.
- Note that aqua uses logrus as logger. We can customize the setting of logrus, but probably the log format is different from the log format of atmos.
- About checksum verification, I'm wondering how atmos supports it. This feature is important for security, so I'd like to support this somehow. But if atmos creates a file like aqua-checksums.json, this has trade-off of user experience because users need to manage the file.
- About Renovate, we need to develop Renovate Config Preset like aqua-renovate-config. I guess this is not so difficult.
This limitation concerns me because weâre introducing radical improvements to Atmos configuration handling in an upcoming PR that adds support for remote configurations, local overrides, imports, and more. All of that provides a consistent configuration interface for atmos, including the underlying tooling. Iâd like those improvements to be universally applicable across Atmos functionality.
Probably I don't understand features remote configurations, local overrides, imports, and more completely,
but one idea is that Atmos generates aqua configuration based on those features and executes aqua.
This is like kustomize.
So for a general purpose package manager, aqua is exceptional. For example, I like how we can define packages that install cleanly on a local workstation as well as in CI. The proxy installer is a brilliant feature, almost like lazy loading but for binaries. Never seen that before. I like the fact that only the binaries used are the ones installed, and that they still come from the vetted sources in the aqua configuration.
What we are trying to accomplish with this, as a core capability of atmos, is so that atmos can bootstrap everything required to get off the ground. It could mean also bootstrapping aqua itself.
When we started writing Atmos, for example, we were keen to use vendir. We in fact started with it, but felt overall it was overkill for the problem we were aiming to solve. We ended up using go-getter to implement something simpler and good enough (but not for @hans-d who went for Vendir anyways!)
I feel that this maybe the case here, that if the public APIs of aqua are not yet stable for embedding, and its capabilities are so rich, it may be overkill for the scope of this feature for our purposes.
Maybe all we need is a much simpler subset of functionality? Within atmos, we have a controlled environment. Things like the Lazy Install would be easier implemented in other ways, since each command can declare its dependencies in the schema.
The goal for this feature is to make running as command in atmos automatically install the versions of the tools it needs, without any other configuration of the host system.
This includes
- helmfile commands, that can install helm and helmfile
- terraform commands that can install any OpenTofu or terraform binary
- custom commands that can install any command needed for that command to work
- workflows like custom commands can declare their dependencies which get installed
For this reason, want to stay focused on finding a way to accomplish this for a near native feel in atmos. @suzuki-shunsuke if you have any more creative ideas for this simpler use-case, I am open for it!
If we make atmos packages available to users directly, we need to develop something like aqua-proxy.
I'd like to clarify this point.
custom commands that can install any command needed for that command to work
For instance, when Atmos installs tflint, do you expect that users can execute tflint command directly?
If so, we need to add tflint command in $PATH.
Or do you expect users can execute tflint via Atmos subcommands?
If so, we don't need to add tflint command in $PATH. This means we don't need something like aqua-proxy.
Hmm. Please give me some time to consider this more deeply. I want to utilize aqua echosystem while integrating it with Atmos well.
For instance, when Atmos installs tflint, do you expect that users can execute tflint command directly?
Probably you would expect this.
For instance, when Atmos installs tflint, do you expect that users can execute
tflintcommand directly?
Tools like aqua, tenv, asdf, and many others follow a similar pattern. We're not trying to replace them, nor do Atmos users have to define their dependencies within Atmos if they prefer other solutions.
But the design goal of Atmos isnât to be just another cog in an existing toolâs wheelâitâs to be the wheel itself. The goal is to establish a consistent automation environment, backed by configuration.
The Unix philosophy teaches us to "do one thing well," but the reality is that there are too many tools (too many cogs) that do one thing wellâand no unified way to tie them togetherâthe wheel.
Thatâs where Atmos comes in.
If your project depends on tflint (a cog), that's great. Install it with Atmos, then integrate it into your projectâs configuration in a consistent, documented manner. No guesswork, no scattered toolsâjust a streamlined, repeatable workflow.
Developers donât need to manually configure their shell, install 20 different tools by hand, or wrangle disparate dependencies. Instead, they install Atmos, and it all just works. Your team defines how the tools are used, and Atmos acts as the interfaceâmaking it feel as if Atmos itself implements everything.
Only, it doesnât reinvent the wheelâitâs built on the shoulders of giants.
For instance, when Atmos installs tflint, do you expect that users can execute tflint command directly?
Probably you would expect this.
Actually, no. While we could optimize for that later, our real focus is on using the command within Atmos.
The goal of Atmos is that no one should need to write a shell script just to call Atmosâif they do, we've failed. No one should need to rely on Makefiles, Taskfiles, or other task runner toolsâif they do, we've failed.
Atmos itself should be the interface that seamlessly integrates tools and workflows, providing a superior DX, eliminating the need for ad-hoc scripting or additional tooling just to glue things together.
To enable users to execute installed tools in workflows, there are several options.
- Introduce proxy like aqua-proxy for changing tool versions dynamically
- Change environment variable $PATH for changing tool versions dynamically
- Install tools in the directory specific to each project
About point 2, this approach is entirely different from aqua, making it difficult to reuse aqua's resources. Iâd like to avoid this.
Regarding point 3, it's simple and not necessarily a bad approach. However, we would need to install tools for each project and reinstall them every time tool versions change. This results in wasted time and storage.
How about this?
- Develop atmos-proxy like aqua-proxy https://github.com/suzuki-shunsuke/atmos-proxy
- Add the command
atmos exec(Oratmos packages exec) -
atmos packages installreads atmos.yaml and stack configurations and install tools into$ATMOS_PKG_DIR. The path is configurable. It creates links to atmos-proxy in$ATMOS_PKG_DIR/bin. The mechanism is same with aqua - atmos sub commands adds
$ATMOS_PKG_DIR/binto$PATHbefore executing commands - When commands managed by atmos are executed, atmos-proxy executes
atmos exec -- <command> [<arg> ...].atmos execreads configuration files and installs commands before executing them
I've created atmos-proxy based on aqua-proxy. https://github.com/suzuki-shunsuke/atmos-proxy
And I'm working on implementing aqua packages install command.
I'm considering a Go package wrapping aqua.
atmos - aquaw (wrapper) -> aqua
The wrapper abstracts aqua. I'm not sure if it works well, but this abstraction layer may be good for both atmos and aqua.
Develop atmos-proxy like aqua-proxy https://github.com/suzuki-shunsuke/atmos-proxy
- Not clear how the proxy would handle multiple concurrent versions of some tool installed (e.g. opentofu, helm, helmfile, etc)
Add the command atmos exec (Or atmos packages exec)
- This makes sense to me as a feature I hadn't considered
atmos packages installreadsatmos.yamland stack configurations and install tools into$ATMOS_PKG_DIR.
- And supports multiple concurrent versions of tools
The path is configurable. It creates links to
atmos-proxyin$ATMOS_PKG_DIR/bin.
- My understanding is links don't work consistently across OS-boundaries
- Links on Windows require administrative permissions
- Windows is a growing demographic of Atmos users, especially in the enterprise
When commands managed by atmos are executed,
atmos-proxyexecutesatmos exec -- <command> [<arg> ...].
- How do you envision the version selection working?
@suzuki-shunsuke how would atmos-proxy get installed? I could understand if atmos could "cell-divide" so to say, but we don't want additional requirements to use atmos.
Not clear how the proxy would handle multiple concurrent versions of some tool installed (e.g. opentofu, helm, helmfile, etc)
https://aquaproj.github.io/docs/guides/command-alias
aqua supports command aliases for this.
e.g.
packages:
- name: hashicorp/[email protected]
- name: hashicorp/terraform
version: v0.13.7
command_aliases:
- command: terraform
alias: terraform-013
# no_link: true
Links on Windows require administrative permissions
aqua uses hard links on Windows.
https://aquaproj.github.io/docs/reference/lazy-install#on-windows
How do you envision the version selection working?
Atmos can define packages in several locations, so we need to define the precedence order.
how would atmos-proxy get installed? I could understand if atmos could "cell-divide" so to say, but we don't want additional requirements to use atmos.
This is same with aqua-proxy. atmos automatically installs atmos-proxy, so uses don't need to install them themselves.
Links on Windows require administrative permissions aqua uses hard links on Windows. https://aquaproj.github.io/docs/reference/lazy-install#on-windows
Without Developer Mode, my understanding is still that Admin is required.
Not clear how the proxy would handle multiple concurrent versions of some tool installed (e.g. opentofu, helm, helmfile, etc) https://aquaproj.github.io/docs/guides/command-alias aqua supports command aliases for this.
What I don't like about this is it requires changing the script to change the alias, rather than changing the version, of the commands. On the other hand, within a single script execution, if both versions are required, using the alias approach is good. But optimizing for the marginal case, doesn't seem ideal for me.
atmos automatically installs atmos-proxy, so uses don't need to install them themselves.
That works
@suzuki-shunsuke I neglected to respond to this comment.
This is a good one, because it highlights the strategic differences of what we're optimizing for in atmos vs aqua. As you know, it's hard to optimize for multiple things at the same time.
Aqua for good reason is optimizing for system-wide usage, installing packages so that the user running the commands directly in their terminal get the best experience.
Atmos is optimizing for running the commands are dependencies of workflows, subcommands, and components; they should install automatically, and ideally before starting any commands. We should have a visual cue on the screen (e.g. with a spinner), when dependencies are installed. Like with aqua, we want to lazy-install the commands, but we have the privilege of knowing what commands a workflow, component or subcommand depend on. Therefore, the proxy approach is suboptimal. From a UX perspective, we would prefer the installation to happen prior to running the workflows. For example, if there's any problem installing dependencies, we would rather know before we're halfway through the workflow. We want to ensure commands just work, and simplify upgrades.
To enable users to execute installed tools in workflows, there are several options.
- Introduce proxy like
aqua-proxyfor changing tool versions dynamically
Overall, I like this as a shim, automatically installed by atmos, as needed â enabling direct execution of the commands installed; however, it's secondary to first-class support within subcommands, workflows and components. It's a "bonus", rather than the primary interface.
- Change environment variable
$PATHfor changing tool versions dynamically
This is IMO the preferred route, because it allows the workflows, commands, and components to operate consistently, merely calling the intended command (e.g. helm or terraform, ecspresso, etc) without leveraging aliases. I see aliases as a good workaround for when within a single execution shell, both are needed; but this is an edge case.
- Install tools in the directory specific to each project
About point 2, this approach is entirely different from aqua, making it difficult to reuse aqua's resources. Iâd like to avoid this.
I'm more concerned with the experience inside of one project working well, less concerned with the duplication of binaries (conservation of storage).
If we think about terraform providers, they are downloaded in each project. A provider cache directory can be specified with terraform; we could do something similar down the road. Alternatively, so long as binaries are stored in programmatically deterministic locations, multiple projects could update the same target directory and reuse binaries. Paths would need to take into account the source.
Regarding point 3, it's simple and not necessarily a bad approach. However, we would need to install tools for each project and reinstall them every time tool versions change. This results in wasted time and storage.
Just to reiterate, main priority is to get it working within a project. If we have a strategy for how that could work across projects, that can be implemented later.
Without Developer Mode, my understanding is still that Admin is required.
Oh, really? I'll take a look.
we would prefer the installation to happen prior to running the workflows
I see. Atmos workflows define required packages and their versions, so we can achieve this. Even if lazy install is disabled, proxy still has meaning to change versions dynamically.
On the other hand, within a single script execution, if both versions are required, using the alias approach is good.
Yes. Command alias is a solution for this. If we need a single version in a context, aliases aren't necessary. proxy can select a version from multiple versions according to the priority. This is same with aqua.
One more idea, symlinks can be an optional optimization, if it causes problems.
Approach 2
This is the detail of the option 3 I mentioned before.
Install tools in the directory specific to each project
Directory structure:
PROJECT_ROOT_DIR/
atmos.yaml
.atmos/
versions.json # package versions atmos copied to bin directory
bin/
<command>
...
...
~/.local/share/atmos/ (ATMOS_PKG_DIR)
pkgs/ # Install tools (same with aqua)
atmos sub commands such as atmos packages install do the following things:
- Install packages in ATMOS_ROOT_DIR
- Copy commands to
PROJECT_ROOT_DIR/.atmos/bin - Sets package versions to
PROJECT_ROOT_DIR/.atmos/versions.json - Add PROJECT_ROOT_DIR/.atmos/bin to the environment variable $PATH
If commands don't exist in PROJECT_ROOT_DIR/.atmos/bin or versions.json are different from versions defined in atmos configuration files, atmos installs packages in ATMOS_ROOT_DIR and copy commands to PROJECT_ROOT_DIR/.atmos/bin.
Why is ATMOS_PKG_DIR required?
- Cache
- aqua's API restriction. aqua provides install API but it installs tools in a given directory, and we can't change the directory structure. We want to avoid re-inventing wheel, so we want to utilize this API
We can remove files from ATMOS_PKG_DIR after coping commands to PROJECT_ROOT_DIR/.atmos/bin if we want. This saves storage, but cache doesn't work.
Pros.
- This approach doesn't need proxy. It's simple and we can avoid issues related to proxy.
- This approach doesn't need links. We can avoid links specific issues (Especially on Windows).
Cons.
- This approach needs to install commands in project directories. Some people don't like it.
- Single binaries work well, but some tools don't work. For instance, tfenv doesn't work
Based on this, I'm concerned we haven't captured the requirement that within a project, multiple tool versions must be supported. Different versions of terraform components can require different versions of terraform/opentofu.
PROJECT_ROOT_DIR/
atmos.yaml
.atmos/
versions.json # package versions atmos copied to bin directory
bin/
<command>
...
At a minimum, this would need to be:
atmos.yaml
.atmos/
versions.json # package versions atmos copied to bin directory
bin/
<command = terraform>
<version = 1.5.7>
<executable = terraform>
...
Regarding ATMOS_PKG_DIR, I don't yet have a technical opinion on it.
[!WARNING]
â Draft Proposal (still working on it)
I will remove this đ when proposal finished.
General Requirements
Must:
- Install packages on-demand when workflows, subcommands or components need them
- Installation happens before beginning execution of the workflow, subcommands, or components; we want to fail-fast
- Support global tool versions that are used as defaults
- Support a command to clean unused versions of tools
Nice to have:
- Packages can define other packages they need (e.g. helmfile needs helm)
- Ability to run commands conveniently outside of atmos
- Support
.tool-versionsas an alternative way to specify default versions for a project - Compatibility with aqua
- Some way to update the package versions automatically (how to initialize versions? or update to "latest")
- Lockfile with versions installed
- Signature validation (when tools support it)
1ď¸âŁ Global Tool Manifest (atmos.yaml)
Defines all available versions and sets a default version per tool.
Example: atmos.yaml
packages:
base-path: ./packages
bin-path: ./packages/bin
# Feature flag for symlink-based management
use-symlinks: false
registry:
# defines where to find packages, with support for multype types of registries
# Initially, we would support aqua
- name: aqua-registry
type: aqua
# The !include is an atmos function
settings: !include suzuki-aqua-registry.yaml
# defines packages to install
install:
terraform:
default: "1.5.7" # Default version
registry: aqua-registry
versions:
- "1.3.7"
- "1.5.7"
helm:
default: "3.10.0"
registry: aqua-registry
versions:
- "3.10.0"
- "3.12.1"
helmfile:
default: "0.144.0"
registry: aqua-registry
needs:
helm: 3.1.4
versions:
- "0.144.0"
- "0.145.0"
Requirements:
- Explicit versions only (no SemVer ranges, at this time).
- The default version is used if no overrides are present.
2ď¸âŁ Per-Stack and Per-Component Version Overrides
Each stack/component can specify which version to use. Only packages configured in the registry can be installed (otherwise, there's no way to find them)
Example: stacks/dev.yaml
components:
terraform:
vpc:
needs:
opentofu: "1.7.0"
eks:
needs:
terraform: "1.5.7"
helm: "3.12.1"
Requirements:
- Overrides atmos.yaml defaults per stack/component.
- Only explicit versions allowed.
3ď¸âŁ Support .tool-versions (Project-Level Default)
Follows standard asdf format and only allows explicit versions.
Example: .tool-versions
opentofu 1.7.0
helm 3.12.1
Requirements:
- Overrides
atmos.yamldefault versions. - File is only valid at the repository root (
ATMOS_BASE_DIR)
4ď¸âŁ Atmos Commands for Managing Versions
Install All Tools
Installs all tools and versions from atmos.yaml and what is defined in stacks, subcommands, and workflows.
atmos packages install
Update Installed Versions
Updates tools to match atmos.yaml definitions.
atmos packages update
```
### Execute Commands with a Specific Tool Version
Runs Terraform 1.5.7, ignoring other defaults. If `terraform` not installed, it will error.
$ atmos exec --use [email protected] terraform version Terraform v1.5.7 on darwin_arm64
If it's not installed, it would error:
$ atmos exec --use [email protected] terraform version
Package is not installed. Please use the --install flag or run atmos packages install.
Or if it's not configured:
$ atmos exec --use [email protected] terraform version Package is not configured. Please configure it first.
Runs Terraform 1.5.7, ignoring other defaults. If `terraform` not installed, it will install it. A spinner conveys the installation process.
atmos exec --install --use [email protected] terraform version ⣝ Installing [email protected] Terraform v1.5.7 on darwin_arm64
## 5ď¸âŁ Directory Structure
### đ **Default**: Duplicated Binaries (use-symlinks: false)
Binaries are copied to `packages/bin/` (`packages.base-path` in the `atmos.yaml`) for the default version.
```yaml
PROJECT_ROOT/
atmos.yaml
packages/
bin/
terraform # Default Terraform version (copied)
helm # Default Helm version (copied)
terraform/
1.3.7/
terraform
1.5.7/
terraform
helm/
3.10.0/
helm
3.12.1/
helm
đ Alternative: Symlink-Based Management (use-symlinks: true) for Defaults
Symlinks are created inside packages/bin/ instead of duplicating binaries.
PROJECT_ROOT/
packages/
bin/
terraform -> ../terraform/1.5.7/terraform # Symlink to default version
helm -> ../helm/3.10.0/helm # Symlink to default version
terraform/
1.3.7/
terraform
1.5.7/
terraform
helm/
3.10.0/
helm
3.12.1/
helm
6ď¸âŁ Dynamic $PATH Management
Since no wrapper scripts are used, $PATH dynamically ensures correct versions.
# Output the PATH for default tool versions
atmos path
# Set up the path for a particular stack and vpc
# Really only useful for debugging
atmos path --stack ue2-dev --component vpc
A command that outputs the correct $PATH based on:
- Explicitly requested versions (atmos exec --use)
- Per-stack/component versions (stacks/*.yaml)
- Project-wide .tool-versions (if present)
- Global default (atmos.yaml)
Example Usage:
Emit Correct $PATH for Active Tool Versions
Set the PATH to use the default tool versions for the project. This is not a primary use-case for Atmos.
$ export PATH=$(atmos path):$PATH
$ terraform version
Terraform v1.5.7
on darwin_arm64
Example Output
$ atmos path
/your/project/packages/bin:/your/project/packages/terraform/1.5.7:/your/project/packages/helm/3.12.1:/usr/local/bin:/usr/bin
- Ensures Terraform 1.5.7 and Helm 3.12.1 are used first.
- No need to modify scripts.
The Atmos Proxy provides a way to use binaries managed by Atmos outside of Atmos itself. This allows developers and CI/CD environments to run terraform, helm, helmfile, and other tools without manually configuring $PATH or invoking atmos exec.
Use Case
-
Allows users to run managed tools directly (
terraform,helm, etc.) without needingatmos execorexport PATH=$(atmos path). - Ensures consistency across CLI usage inside and outside Atmos.
- Reduces friction when using Atmos-managed tools in scripts, CI/CD, or interactive terminals.
7ď¸âŁ How Packages Works with Atmos Workflows
Atmos workflows should automatically install and configure the correct tool versions without requiring atmos exec --use. Instead, workflows can declare required tool versions directly within the packages.install block, ensuring that the necessary tools are available before execution.
Example: Defining an Atmos Workflow with Automatic Package Installation
workflows:
deploy-infra:
description: "Deploy Terraform infrastructure"
needs:
terraform: 1.5.7
helm: 3.1.2
steps:
- command: terraform init
- command: terraform plan
- command: terraform apply -auto-approve
8ď¸âŁ How It Works with Atmos Custom Commands
# atmos.yaml
commands:
plan:
description: "Run Terraform plan"
needs:
terraform: 1.5.7
steps:
- command: terraform init
- command: terraform plan
9ď¸âŁ The Atmos Proxy (Optional)
The Atmos Proxy is inspired by the aqua proxy. It's not the primary use-case of packages for Atmos, but is available for users to use packages installed by atmos conveniently in their shell or shell-scripts.
How the Atmos Proxy Works
When the proxy is enabled:
- The proxy command (e.g.
atmos-proxyis in the user'sPATH) - The proxy calls
atmos execwhich automatically installs proxy binaries (e.g. lazy loads) in a designated path (e.g.,~/.atmos/bin/where where everpackages.base_pathis set to), when not installed, otherwise calls the binary - Any command (e.g.,
terraform) is intercepted by the proxy. - The proxy only handles default versions of tools for the project
- The proxy forwards the command to the correct binary managed by Atmos, calling
atmos exec.
Enabling the Atmos Proxy
To enable the proxy, users can define it in atmos.yaml:
packages:
proxy:
enabled: true
Then, to install the thin wrapper, would run:
atmos packages --install-proxy
Final Summary
â
Explicit versions onlyâno SemVer ranges.
â
atmos.yaml defines available and default tool versions.
â
Per-stack/component/workflow/subcommand settings override defaults.
â
.tool-versions provides an industry-standard alternative.
â
Symlink support is optional (packages.use-symlinks: true | false).
â
No wrapper scriptsârelies purely on $PATH resolution
â
atmos path dynamically sets the correct environment (for scripts that call atmos)