#atmos (2022-07)

2022-07-01

Release notes from atmos avatar
Release notes from atmos
06:54:33 PM

v1.4.23 what Fix atmos terraform clean command Add CLI docs for atmos terraform clean command why atmos terraform clean command, when deleting the folder that TF_DATA_DIR points to, was using the relative path and not the component’s folder. Use absolute path atmos terraform clean is not a standard terraform command, so we need to describe it separately in the docs

Release v1.4.23 · cloudposse/atmosattachment image

what Fix atmos terraform clean command Add CLI docs for atmos terraform clean command why atmos terraform clean command, when deleting the folder that TF_DATA_DIR points to, was using the relati…

Release notes from atmos avatar
Release notes from atmos
07:14:36 PM

v1.4.23 what Fix atmos terraform clean command Add CLI docs for atmos terraform clean command why atmos terraform clean command, when deleting the folder that TF_DATA_DIR points to, was using the relative path and not the component’s folder. Use absolute path atmos terraform clean is not a standard terraform command, so we need to describe it separately in the docs What’s Changed Add TF_CLI_ARGS_console env var to shell subcommand by <a class=”user-mention notranslate” data-hovercard-type=”user”…

2022-07-05

Release notes from atmos avatar
Release notes from atmos
04:34:37 PM

v1.4.24 what Refactor stacks to the latest pattern Refactor tests Update everything to the latest versions Update component processor why Refactor stacks in the examples to use orgs->tenants->accounts->regions + catalog and mixins - this is our latest pattern of stacks configuration Refactor tests to reflect the new structure of the stacks folder Use the latest versions of atmos, geodesic and terraform in the examples Small fixes in the component processor

Release v1.4.24 · cloudposse/atmosattachment image

what Refactor stacks to the latest pattern Refactor tests Update everything to the latest versions Update component processor why Refactor stacks in the examples to use orgs->tenants->accounts->…

Release notes from atmos avatar
Release notes from atmos
04:54:37 PM

v1.4.24 what Refactor stacks to the latest pattern Refactor tests Update everything to the latest versions Update component processor why Refactor stacks in the examples to use orgs->tenants->accounts->regions + catalog and mixins - this is our latest pattern of stacks configuration Refactor tests to reflect the new structure of the stacks folder Use the latest versions of atmos, geodesic and terraform in the examples Small fixes in the component processor

2022-07-07

Joe Niland avatar
Joe Niland

Kind of new to atmos and I have a question about the backend config file generation. We’ve noticed that each time we run atmos terraform … the backend.tf.json file is generated with a different order of the keys within the s3 element. From what I can tell it’s due to go iterating over struct keys using a random order.

I’ve created a rough test that shows this. If you run it multiple times you can see the ‘actual’ output change the ordering of the keys.

I’ve started to look at a fix but I thought I’ll ask here first as it may be something you guys can do quickly; or you can tell me a better way to deal with it.

what

• adding a (rough) test that demonstrates randomly changing terraform.backend.s3 key order in backend config file generation

why

• We’ve noticed that backend.tf.json constantly changes the order of the keys within the s3 element. From what I can tell it’s due to go iterating over struct keys using a random order.

references

• N/A

test output

❯ make testacc TEST=github.com/cloudposse/atmos/pkg/utils
go get
go test github.com/cloudposse/atmos/pkg/utils -v  -timeout 2m
=== RUN   TestBackendConfig
    json_utils_test.go:65: 
                Error Trace:    /Users/joe/git-proj/cloudposse/atmos/pkg/utils/json_utils_test.go:65
                Error:          Not equal: 
                                expected: "{\"terraform\":{\"backend\":{\"s3\":{\"encrypt\":true,\"key\":\"terraform.tfstate\",\"region\":\"us-east-1\",\"role_arn\":null,\"workspace_key_prefix\":\"app\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"sts-gbl-tfstate-backend\",\"dynamodb_table\":\"sts-gbl-tfstate-backend-lock\"}}}}"
                                actual  : "{\"terraform\":{\"backend\":{\"s3\":{\"dynamodb_table\":\"cp-ue2-root-tfstate-lock\",\"profile\":\"cp-gb2-root-tfstate\",\"role_arn\":null,\"workspace_key_prefix\":\"test-test-component\",\"region\":\"us-east-2\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"cp-ue2-root-tfstate\",\"encrypt\":true,\"key\":\"terraform.tfstate\"}}}}"
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -{"terraform":{"backend":{"s3":{"encrypt":true,"key":"terraform.tfstate","region":"us-east-1","role_arn":null,"workspace_key_prefix":"app","acl":"bucket-owner-full-control","bucket":"sts-gbl-tfstate-backend","dynamodb_table":"sts-gbl-tfstate-backend-lock"}}}}
                                +{"terraform":{"backend":{"s3":{"dynamodb_table":"cp-ue2-root-tfstate-lock","profile":"cp-gb2-root-tfstate","role_arn":null,"workspace_key_prefix":"test-test-component","region":"us-east-2","acl":"bucket-owner-full-control","bucket":"cp-ue2-root-tfstate","encrypt":true,"key":"terraform.tfstate"}}}}
                Test:           TestBackendConfig
--- FAIL: TestBackendConfig (0.06s)
FAIL
FAIL    github.com/cloudposse/atmos/pkg/utils   0.404s
FAIL
make: *** [testacc] Error 1

❯ make testacc TEST=github.com/cloudposse/atmos/pkg/utils
go get
go test github.com/cloudposse/atmos/pkg/utils -v  -timeout 2m
=== RUN   TestBackendConfig
    json_utils_test.go:65: 
                Error Trace:    /Users/joe/git-proj/cloudposse/atmos/pkg/utils/json_utils_test.go:65
                Error:          Not equal: 
                                expected: "{\"terraform\":{\"backend\":{\"s3\":{\"encrypt\":true,\"key\":\"terraform.tfstate\",\"region\":\"us-east-1\",\"role_arn\":null,\"workspace_key_prefix\":\"app\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"sts-gbl-tfstate-backend\",\"dynamodb_table\":\"sts-gbl-tfstate-backend-lock\"}}}}"
                                actual  : "{\"terraform\":{\"backend\":{\"s3\":{\"workspace_key_prefix\":\"test-test-component\",\"encrypt\":true,\"role_arn\":null,\"dynamodb_table\":\"cp-ue2-root-tfstate-lock\",\"key\":\"terraform.tfstate\",\"profile\":\"cp-gb2-root-tfstate\",\"region\":\"us-east-2\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"cp-ue2-root-tfstate\"}}}}"
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -{"terraform":{"backend":{"s3":{"encrypt":true,"key":"terraform.tfstate","region":"us-east-1","role_arn":null,"workspace_key_prefix":"app","acl":"bucket-owner-full-control","bucket":"sts-gbl-tfstate-backend","dynamodb_table":"sts-gbl-tfstate-backend-lock"}}}}
                                +{"terraform":{"backend":{"s3":{"workspace_key_prefix":"test-test-component","encrypt":true,"role_arn":null,"dynamodb_table":"cp-ue2-root-tfstate-lock","key":"terraform.tfstate","profile":"cp-gb2-root-tfstate","region":"us-east-2","acl":"bucket-owner-full-control","bucket":"cp-ue2-root-tfstate"}}}}
                Test:           TestBackendConfig
--- FAIL: TestBackendConfig (0.04s)
FAIL
FAIL    github.com/cloudposse/atmos/pkg/utils   0.241s
FAIL
make: *** [testacc] Error 1

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

interesting. we don’t commit the generated backend anymore so never even noticed this.

if the backend.tf.json is not committed, then does the order of the keys even matter ?

what

• adding a (rough) test that demonstrates randomly changing terraform.backend.s3 key order in backend config file generation

why

• We’ve noticed that backend.tf.json constantly changes the order of the keys within the s3 element. From what I can tell it’s due to go iterating over struct keys using a random order.

references

• N/A

test output

❯ make testacc TEST=github.com/cloudposse/atmos/pkg/utils
go get
go test github.com/cloudposse/atmos/pkg/utils -v  -timeout 2m
=== RUN   TestBackendConfig
    json_utils_test.go:65: 
                Error Trace:    /Users/joe/git-proj/cloudposse/atmos/pkg/utils/json_utils_test.go:65
                Error:          Not equal: 
                                expected: "{\"terraform\":{\"backend\":{\"s3\":{\"encrypt\":true,\"key\":\"terraform.tfstate\",\"region\":\"us-east-1\",\"role_arn\":null,\"workspace_key_prefix\":\"app\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"sts-gbl-tfstate-backend\",\"dynamodb_table\":\"sts-gbl-tfstate-backend-lock\"}}}}"
                                actual  : "{\"terraform\":{\"backend\":{\"s3\":{\"dynamodb_table\":\"cp-ue2-root-tfstate-lock\",\"profile\":\"cp-gb2-root-tfstate\",\"role_arn\":null,\"workspace_key_prefix\":\"test-test-component\",\"region\":\"us-east-2\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"cp-ue2-root-tfstate\",\"encrypt\":true,\"key\":\"terraform.tfstate\"}}}}"
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -{"terraform":{"backend":{"s3":{"encrypt":true,"key":"terraform.tfstate","region":"us-east-1","role_arn":null,"workspace_key_prefix":"app","acl":"bucket-owner-full-control","bucket":"sts-gbl-tfstate-backend","dynamodb_table":"sts-gbl-tfstate-backend-lock"}}}}
                                +{"terraform":{"backend":{"s3":{"dynamodb_table":"cp-ue2-root-tfstate-lock","profile":"cp-gb2-root-tfstate","role_arn":null,"workspace_key_prefix":"test-test-component","region":"us-east-2","acl":"bucket-owner-full-control","bucket":"cp-ue2-root-tfstate","encrypt":true,"key":"terraform.tfstate"}}}}
                Test:           TestBackendConfig
--- FAIL: TestBackendConfig (0.06s)
FAIL
FAIL    github.com/cloudposse/atmos/pkg/utils   0.404s
FAIL
make: *** [testacc] Error 1

❯ make testacc TEST=github.com/cloudposse/atmos/pkg/utils
go get
go test github.com/cloudposse/atmos/pkg/utils -v  -timeout 2m
=== RUN   TestBackendConfig
    json_utils_test.go:65: 
                Error Trace:    /Users/joe/git-proj/cloudposse/atmos/pkg/utils/json_utils_test.go:65
                Error:          Not equal: 
                                expected: "{\"terraform\":{\"backend\":{\"s3\":{\"encrypt\":true,\"key\":\"terraform.tfstate\",\"region\":\"us-east-1\",\"role_arn\":null,\"workspace_key_prefix\":\"app\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"sts-gbl-tfstate-backend\",\"dynamodb_table\":\"sts-gbl-tfstate-backend-lock\"}}}}"
                                actual  : "{\"terraform\":{\"backend\":{\"s3\":{\"workspace_key_prefix\":\"test-test-component\",\"encrypt\":true,\"role_arn\":null,\"dynamodb_table\":\"cp-ue2-root-tfstate-lock\",\"key\":\"terraform.tfstate\",\"profile\":\"cp-gb2-root-tfstate\",\"region\":\"us-east-2\",\"acl\":\"bucket-owner-full-control\",\"bucket\":\"cp-ue2-root-tfstate\"}}}}"
                            
                                Diff:
                                --- Expected
                                +++ Actual
                                @@ -1 +1 @@
                                -{"terraform":{"backend":{"s3":{"encrypt":true,"key":"terraform.tfstate","region":"us-east-1","role_arn":null,"workspace_key_prefix":"app","acl":"bucket-owner-full-control","bucket":"sts-gbl-tfstate-backend","dynamodb_table":"sts-gbl-tfstate-backend-lock"}}}}
                                +{"terraform":{"backend":{"s3":{"workspace_key_prefix":"test-test-component","encrypt":true,"role_arn":null,"dynamodb_table":"cp-ue2-root-tfstate-lock","key":"terraform.tfstate","profile":"cp-gb2-root-tfstate","region":"us-east-2","acl":"bucket-owner-full-control","bucket":"cp-ue2-root-tfstate"}}}}
                Test:           TestBackendConfig
--- FAIL: TestBackendConfig (0.04s)
FAIL
FAIL    github.com/cloudposse/atmos/pkg/utils   0.241s
FAIL
make: *** [testacc] Error 1

Joe Niland avatar
Joe Niland

Nope it wouldn’t matter in that case

Joe Niland avatar
Joe Niland

In this particular project I’ve started working with an existing code base and these files are committed.

Joe Niland avatar
Joe Niland

Still learning the full extent of atmos but I guess if this file is generated based on the auto_generate_backend_file being true it really shouldn’t need to be in git

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

The encoding/json library apparently offers no way to decode data into a interface{} without scrambling the order of the keys in objects. Generally this isn’t a correctness issue (per the spec the order isn’t important), but it is super annoying to humans who may look at JSON data before and after it’s been processed by a go program.

The most popular yaml library solves the problem like this: https://godoc.org/gopkg.in/yaml.v2#MapSlice

1
RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

someone has created a specific golib for it https://github.com/ake-persson/mapslice-json

ake-persson/mapslice-json

Go MapSlice for ordered marshal/ unmarshal of maps in JSON

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

and here’s one example without the above lib to dump deterministic json https://go.dev/play/p/yZ5DxZLIMXC

Joe Niland avatar
Joe Niland

Yep I’ve looked at those when starting to try to fix it. It’d be a fair assumption to gitignore this file if atmos is generating it.

I don’t know if it’s worth fixing or not.

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

if you want to run terraform without atmos, then you would need to commit the file i suppose

Joe Niland avatar
Joe Niland

good point

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

you could also hard code the backend and setup atmos to not generate the backend

Joe Niland avatar
Joe Niland

yeah true - in this case it never changes and is really just for convenience

Joe Niland avatar
Joe Niland

Thanks, we will discuss further

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

all options

  1. we make backend generation deterministic in atmos
  2. ignore backend.tf.json from infra repo and allow atmos to do its thing (provided no one needs to run terraform without atmos)
  3. hard code the backend for all terraform modules and disable atmos from generating the backend
1
RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

no problem, happy to help

1
RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

cc: @Andriy Knysh (Cloud Posse)

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

It’s Golang issue, it iterates a collection in random order, nothing can be done about it, at least not with something simple

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

If you have the backend files already, disable generation in atmos

Joe Niland avatar
Joe Niland

Thanks - that seems like the best option right now

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

Although we can sort the keys before writing to a file, that would be a simple solution :)

Joe Niland avatar
Joe Niland

I wasn’t sure if the iteration within json.Marshal is doing it

Joe Niland avatar
Joe Niland

like will it respect order if you presort the value being passed in?

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

Yes, Marshal does it

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

So it’s not easy to fix

Joe Niland avatar
Joe Niland

Ah cool - I think I saw people suggesting making an alternative Marshal function. Can’t seem to find the URL right now though.

Joe Niland avatar
Joe Niland

Well maybe one for the backlog but probably it won’t affect many people anyway

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

I added a summary comment to your PR so we can discuss it further in the future if the above workarounds don’t work for you

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

i knew about the issue for a long time, did not get to fix it since it really dud not affect anything but was just annoying

Joe Niland avatar
Joe Niland

@RB (Ronak) (Cloud Posse) thanks for that. That’s really helpful.

@Andriy Knysh (Cloud Posse) ah that makes sense there’s a lot I don’t know about go so this was a surprise!

Joe Niland avatar
Joe Niland

and yes it is an annoying “feature”

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

everything with golang and json is annoying

Joe Niland avatar
Joe Niland

lol that doesn’t motivate me to learn golang better.. will stick with python :D

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

python + json =

1
Joe Niland avatar
Joe Niland

go get is really cool though

1
RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

googling a little bit more and i see this library https://github.com/tidwall/gjson

tidwall/gjson

Get JSON values quickly - JSON parser for Go

1
RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

10.6k stars

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)
@pretty:{"sortKeys":true} 
Joe Niland avatar
Joe Niland

Looks great @RB (Ronak) (Cloud Posse)

Joe Niland avatar
Joe Niland

So true @Andriy Knysh (Cloud Posse)

2022-07-08

2022-07-12

Michael Dizon avatar
Michael Dizon

running into this error trying to deploy the eks module (https://github.com/cloudposse/terraform-aws-eks-cluster).

Get "<https://REDACTED.us-east-1.eks.amazonaws.com/api/v1/namespaces/kube-system/configmaps/aws-auth>": getting credentials: exec plugin is configured to use API version client.authentication.k8s.io/v1beta1, plugin returned version client.authentication.k8s.io/v1alpha1

my cluster version is 1.22

cloudposse/terraform-aws-eks-cluster

Terraform module for provisioning an EKS cluster

Michael Dizon avatar
Michael Dizon

do i need to update the aws cli version in geodesic / atmos?

2022-07-13

Michael Dizon avatar
Michael Dizon

:point_up: updated the aws cli to 2.7.14 resolved the error

party_parrot3
azec avatar

@Andriy Knysh (Cloud Posse), I know this is not about the atmos strictly, but I have been able to build Docker image with atmos based on geodesic and run it. I don’t have problem with AWS session, but I have a problem with Terraform needing to authenticate against private GitLab Repo to clone underlying TF module and I need to think how to feed GitLab Personal Access Token safely to all git operations done by that container. I am targeting public Spacelift workers 1st, than if that works great I will try private EC2 workers.

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

for private workers, we do it in user-data

%{ if github_netrc_enabled }
  export SPACELIFT_WORKER_EXTRA_MOUNTS=/root/.netrc:/conf/.netrc

  export GITHUB_TOKEN=$(aws ssm get-parameters --region=${region} --name ${github_netrc_ssm_path_token} --with-decryption --query "Parameters[0].Value" --output text)
  export GITHUB_USER=$(aws ssm get-parameters --region=${region} --name ${github_netrc_ssm_path_user} --with-decryption --query "Parameters[0].Value" --output text)

  echo "Creating .netrc" | tee -a /var/log/spacelift/info.log
  NETRC="/root/.netrc"
  printf "machine github.com\n" > "$NETRC"
  printf "login %s\n" "$GITHUB_USER" >> "$NETRC"
  printf "password %s\n" "$GITHUB_TOKEN" >> "$NETRC"
  echo "Created .netrc" | tee -a /var/log/spacelift/info.log
%{ endif }
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

same can be done for public workers (not in user-data, but in Spacelift hooks), just the IAM role that you give the workers needs the permissions to read from SSM (also you need to consider security implications of that if any)

azec avatar

Curious if people keep these in Spacelift contexts & environment typically …

2022-07-17

Release notes from atmos avatar
Release notes from atmos
02:54:37 PM

v1.4.25 what Update component deps calculation Add tests to check this why When a component defines inherited YAML base components in metadata.inherits and the stack imported the YAML file(s) where the inherited YAML components are defined, add the imported YAML files to the deps labels Before this fix, only the base terraform component was used in the deps calculation. All imported YAML files where the base YAML components were defined were not included in the deps labels (and the YAML files were…

Release v1.4.25 · cloudposse/atmosattachment image

what Update component deps calculation Add tests to check this why When a component defines inherited YAML base components in metadata.inherits and the stack imported the YAML file(s) where the …

Joe Niland avatar
Joe Niland

We’re building out a new project using atmos and I have hit the following error a few times:

Executing command:
/usr/bin/terraform plan -var-file root-account-map.terraform.tfvars.json -out root-account-map.planfile
Acquiring state lock. This may take a few moments...
╷
│ Error: stack name pattern must be provided in 'stacks.name_pattern' CLI config or 'ATMOS_STACKS_NAME_PATTERN' ENV variable
│ 
│   with module.accounts.data.utils_component_config.config,
│   on .terraform/modules/accounts/modules/remote-state/main.tf line 1, in data "utils_component_config" "config":
│    1: data "utils_component_config" "config" {
│ 
╵
Releasing state lock. This may take a few moments...
exit status 1

The first time, it was within tfstate-backend, and was resolved by not setting the access_roles variable. We’re in the middle of the cold start phase with this project.

The one above is happening with account-map and I’m not yet sure why!

I know I haven’t provided enough info but if I could get some help with debugging the cause, that would be much appreciated!

Here is the output of atmos describe config:

{
  "base_path": "",
  "Components": {
    "Terraform": {
      "base_path": "components",
      "apply_auto_approve": false,
      "deploy_run_init": true,
      "init_run_reconfigure": true,
      "auto_generate_backend_file": true
    },
    "Helmfile": {
      "base_path": "components/helmfile",
      "kubeconfig_path": "/dev/shm",
      "helm_aws_profile_pattern": "{namespace}-{tenant}-gbl-{stage}-helm",
      "cluster_name_pattern": "{namespace}-{tenant}-{environment}-{stage}-eks-cluster"
    }
  },
  "Stacks": {
    "base_path": "stacks",
    "included_paths": [
      "org/**/*"
    ],
    "excluded_paths": [
      "catalog/**/*",
      "**/_defaults.yaml"
    ],
    "name_pattern": "{stage}"
  },
  "Workflows": {
    "base_path": "workflows"
  },
  "Logs": {
    "verbose": true,
    "colors": true
  },
  "Commands": null,
  "Initialized": true
}

/cc: @Matt Gowie

Erik Osterman (Cloud Posse) avatar
Erik Osterman (Cloud Posse)


Error: stack name pattern must be provided in ‘stacks.name_pattern’ CLI config or ‘ATMOS_STACKS_NAME_PATTERN’ ENV variable
can you share what the:

stacks:
  name_pattern:
    ...

is in your atmos.yaml?

Joe Niland avatar
Joe Niland

That would be useful

It’s just:

  name_pattern: "{stage}"
Joe Niland avatar
Joe Niland

We did try alternate pattterns with dashes in case there’s an assumption somewhere. We also changed descriptor_formats to match the above.

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

remote-state is processed by the utils provider, which uses atmos code. When TF executes the provider, it executes it from the component (terraform) folder (e.g. components/vpc)

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

so the provider does not see atmos.yaml in the repo root (although atmos CLI does since you execute atmos commands from the repo’s root)

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

there are a few patterns to fix it:

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)
  1. Place atmos.yaml into /usr/local/etc/atmos/atmos.yaml https://github.com/cloudposse/atmos/blob/master/examples/complete/rootfs/usr/local/etc/atmos/atmos.yaml - atmos code knows this location, so all processes can see it and find the CLI config - that’s what we do, and it works both locally and in Docker container
# 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)
# <https://en.wikipedia.org/wiki/Glob_(programming)>

# Base path for components, stacks and workflows configurations.
# Can also be set using `ATMOS_BASE_PATH` ENV var, or `--base-path` command-line argument.
# Supports both absolute and relative paths.
# If not provided or is an empty string, `components.terraform.base_path`, `components.helmfile.base_path`, `stacks.base_path` and `workflows.base_path`
# are independent settings (supporting both absolute and relative paths).
# If `base_path` is provided, `components.terraform.base_path`, `components.helmfile.base_path`, `stacks.base_path` and `workflows.base_path`
# are considered paths relative to `base_path`.
base_path: ""

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: "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_INIT_RUN_RECONFIGURE` ENV var, or `--init-run-reconfigure` command-line argument
    init_run_reconfigure: 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
  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: "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"

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: "stacks"
  # Can also be set using `ATMOS_STACKS_INCLUDED_PATHS` ENV var (comma-separated values string)
  included_paths:
    - "orgs/**/*"
  # Can also be set using `ATMOS_STACKS_EXCLUDED_PATHS` ENV var (comma-separated values string)
  excluded_paths:
    - "**/_defaults.yaml"
  # Can also be set using `ATMOS_STACKS_NAME_PATTERN` ENV var
  name_pattern: "{tenant}-{environment}-{stage}"

workflows:
  # Can also be set using `ATMOS_WORKFLOWS_BASE_PATH` ENV var, or `--workflows-dir` command-line arguments
  # Supports both absolute and relative paths
  base_path: "stacks/workflows"

logs:
  verbose: false
  colors: true

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)
  1. Use ATMOS_CLI_CONFIG_PATH ENV var to point atmos and utils provider to the location of atmos.yaml https://github.com/cloudposse/atmos/pull/168

what

• Add ATMOS_CLI_CONFIG_PATH ENV var • Detect more YAML stack misconfigurations • Add functionality to define atmos custom CLI commands

why

ATMOS_CLI_CONFIG_PATH ENV var allows specifying the location of atmos.yaml CLI config file. This is useful for CI/CD environments (e.g. Spacelift) where an infrastructure repository gets loaded into a custom path and atmos.yaml is not in the locations where atmos expects to find it (no need to copy atmos.yaml into /usr/local/etc/atmos/atmos.yaml) • Detect more YAML stack misconfigurations, e.g. when the same tenant/environment/stage is defined in more than one top-level YAML stack config file (directly or via imports).

For example, if the same `var.tenant = tenant1` is specified for `tenant1-ue2-dev` and `tenant2-ue2-dev` stacks, the  
command `atmos describe component test/test-component-override -s tenant1-ue2-dev` will throw this error
    Searching for stack config where the component 'test/test-component-override' is defined
    Found config for the component 'test/test-component-override' for the stack 'tenant1-ue2-dev' in the file 'tenant1/ue2/dev'
    Found config for the component 'test/test-component-override' for the stack 'tenant1-ue2-dev' in the file 'tenant2/ue2/dev'
    
    Found duplicate config for the component 'test/test-component-override' for the stack 'tenant1-ue2-dev' in the files: tenant1/ue2/dev, tenant2/ue2/dev.
    Check that all context variables in the stack name pattern '{tenant}-{environment}-{stage}' are correctly defined in the files and not duplicated.
    Check that imports are valid.
    
    

• Allow extending atmos with custom commands. Custom commands can be defined in atmos.yaml CLI config file. Custom commands support subcommands at any level (e.g. atmos my-command subcommand1 suncommand2 argument1 argument2 flag1 flag2)

# Custom CLI commands
commands:
  - name: tf
    description: Execute terraform commands
    # subcommands
    commands:
      - name: plan
        description: This command plans terraform components
        arguments:
          - name: component
            description: Name of the component
        flags:
          - name: stack
            shorthand: s
            description: Name of the stack
            required: true
        env:
          - key: ENV_VAR_1
            value: ENV_VAR_1_value
          - key: ENV_VAR_2
            # `valueCommand` is an external command to execute to get the value for the ENV var
            # Either 'value' or 'valueCommand' can be specified for the ENV var, but not both
            valueCommand: echo ENV_VAR_2_value
        # steps support Go templates
        steps:
          - atmos terraform plan {{ .Arguments.component }} -s {{ .Flags.stack }}
  - 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
  - name: play
    description: This command plays games
    steps:
      - echo Playing...
    # subcommands
    commands:
      - name: hello
        description: This command says Hello world
        steps:
          - echo Saying Hello world...
          - echo Hello world
      - name: ping
        description: This command plays ping-pong
        steps:
          - echo Playing ping-pong...
          - echo pong

Custom commands support Go templates and ENV vars in commands steps, and Go templates in ENV vars values, as well as allow specifying an external executable to be called to get the value for an ENV var.

They are automatically added to atmos help:

Available Commands:
  aws         Execute 'aws' commands
  completion  Generate the autocompletion script for the specified shell
  describe    Execute 'describe' commands
  helmfile    Execute 'helmfile' commands
  help        Help about any command
  play        This command plays games
  terraform   Execute 'terraform' commands
  tf          Execute terraform commands
  validate    Execute 'validate' commands
  vendor      Execute 'vendor' commands
  version     Print the CLI version
  workflow    Execute a workflow

Custom commands test

atmos play ping

Executing command:
/bin/echo Playing ping-pong...
Playing ping-pong...

Executing command:
/bin/echo pong
pong

atmos play hello

Executing command:
/bin/echo Saying Hello world...
Saying Hello world...

Executing command:
/bin/echo Hello world
Hello world

atmos terraform provision test/test-component-override -s tenant1-ue2-dev

Using ENV vars:
ATMOS_COMPONENT=test/test-component-override
ATMOS_STACK=tenant1-ue2-dev

Executing command:
/usr/local/bin/atmos terraform plan test/test-component-override -s tenant1-ue2-dev

....

Executing command:
/usr/local/bin/atmos terraform apply test/test-component-override -s tenant1-ue2-dev

atmos tf plan test/test-component-override -s tenant1-ue2-dev

# This command gets the value for the ENV var by calling an external executable
Executing command:
/bin/echo ENV_VAR_2_value

Executing command:
/usr/local/bin/atmos terraform plan test/test-component-override -s tenant1-ue2-dev

references

• YAML interface inspired by ahoy-cli and choria-io’s appbuilder. See discussion thread.

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)
  1. Put atmos.yaml into the terraform component folder (e.g. components/vpc/atmos.yaml) so the provider will see it. It’s duplication/multiplication of CLI config and not recommended at all
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

@Joe Niland

Matt Gowie avatar
Matt Gowie

Good stuff @Andriy Knysh (Cloud Posse) — Thanks for the breakdown! I didn’t know it was possible that the Atmos CLI could find the config and the provider couldn’t. That is a bit confusing, but it’s a complicated system so I could see that being hard.

Two things:

  1. Would it make sense for the provider / atmos in general to search from the CWD of the component / root module (i.e. /localhost/workspace/project/components/vpc) to the system root directory (i.e. /) for the atmos.yaml file? As in, first it would check the vpc folder, then components, then project, and then recursively down the stack? I feel like that would cover this use-case and would be more intuitive.
  2. If #1 doesn’t make sense, I wonder if we can improve the logging or docs to call that out better? We’d be happy to do the work to contribute there… Joe and I will discuss what would’ve helped us debug that and submit a PR if something makes sense.
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

anything can be done in the code, but it should not add even more magic to the process

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

if we start searching in the current folder and then go up, how do we know where to stop and where is the root of the repo?

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

that’s why we put atmos.yaml into the location where both atmos and the utils provider can find it, or we can use ATMOS_CLI_CONFIG_PATH ENV var if your atmos.yaml is in a diff location

Joe Niland avatar
Joe Niland

@Andriy Knysh (Cloud Posse) thanks very much.

Putting the file in /usr/local/etc/atmos/atmos.yaml has solved it locally.

On the Spacelift public runners we’ll set ATMOS_CLI_CONFIG_PATH

Joe Niland avatar
Joe Niland

I agree with @Matt Gowie re improved logging. Could it not log when atmos.yaml is not found in the expected location(s)?

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

on Spacelift public runners, we use .spacelift/config.yml

version: "1"

stack_defaults:

  before_init:
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-install-atmos"
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-configure-paths"
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-write-vars"
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-tf-workspace"

  before_plan:
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-configure-paths"

  before_apply:
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-configure-paths"

  environment:
    ATMOS_BASE_PATH: /mnt/workspace/source
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

in spacelift-install-atmos

#!/bin/bash

# Add -x for troubleshooting
set -ex -o pipefail

# This should match the Dockerfile
ATMOS_VERSION=1.4.25

# Using `registry.hub.docker.com/cloudposse/geodesic:latest-debian` as Spacelift runner image on public worker pool
apt-get update && apt-get install -y --allow-downgrades atmos="${ATMOS_VERSION}-*"

# If runner image is Alpine Linux
# apk add [email protected]~=${ATMOS_VERSION}

atmos version

# Copy the atmos CLI config file into the destination `/usr/local/etc/atmos` where all processes can see it
mkdir -p /usr/local/etc/atmos
cp /mnt/workspace/source/rootfs/usr/local/etc/atmos/atmos.yaml /usr/local/etc/atmos/atmos.yaml
cat /usr/local/etc/atmos/atmos.yaml

# Remove -x for security
set -e +x
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

but setting ATMOS_CLI_CONFIG_PATH=/mnt/workspace/source/rootfs/usr/local/etc/atmos/atmos.yaml will work as well

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)
version: "1"

stack_defaults:

  before_init:
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-install-atmos"
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-configure-paths"
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-write-vars"
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-tf-workspace"

  before_plan:
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-configure-paths"

  before_apply:
    - "/mnt/workspace/source/rootfs/usr/local/bin/spacelift-configure-paths"

  environment:
    ATMOS_CLI_CONFIG_PATH: /mnt/workspace/source/rootfs/usr/local/etc/atmos/atmos.yaml  # instead of "cp /mnt/workspace/source/rootfs/usr/local/etc/atmos/atmos.yaml /usr/local/etc/atmos/atmos.yaml"
    ATMOS_BASE_PATH: /mnt/workspace/source
Joe Niland avatar
Joe Niland

@Andriy Knysh (Cloud Posse) sorry, I missed your latest reply.

Thanks again for that. We will try one of those approaches.

@Matt Gowie ^^

1

2022-07-18

azec avatar

I remember seeing example of multiple instances of same component in same stack possible with atmos, but having hard time finding that example now. Anyone knows how I can achieve this?

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)
components:
   terraform:
     rds-defaults:
       metadata:
         type: abstract # we don't want to allow deploying this since it's just a collection of default values; like abstract base class in OOP which can't be instantiated 
       vars:
         # default vars go here

     rds-1:
       metadata:
         component: rds # point to Terraform component in components/terraform/rds
         inherits:
           - rds-defaults # inherit the default values
       vars:
         # specific vars go here, also can override any default vars like in OOP base/derived classes

     rds-2:
       metadata:
         component: rds # point to Terraform component in components/terraform/rds
         inherits:
           - rds-defaults # inherit the default values
       vars:
         # specific vars go here, also can override any default vars like in OOP base/derived classes
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)
atmos terraform plan rds-1 -s xxx
atmos terraform plan rds-2 -s xxx

atmos terraform plan rds-defaults -s xxx   - will throw an error that an abstract component can't be provisioned
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

inherits: can inherit from many YAML configs (e.g. you can have many mixins with default value and combine them via inheritance). Also, inheritance can go to any level: A inherits B inherits C inherits D etc.

azec avatar

got it! that’s it, exactly what I needed…

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

rds-defaults: can be in a separate file (e.g. in catalog/rds/rds-default.yaml and then you import it into each stack

import:
  - catalog/rds/rds-defaults
azec avatar

Ok trying that now …

azec avatar

Worked great. Thanks again @Andriy Knysh (Cloud Posse)

1

2022-07-19

2022-07-22

Marcin Brański avatar
Marcin Brański

Guys. I’ve been trying to setup new project with atmos and after brew install atmos I’m getting

docker: Error response from daemon: pull access denied for cloudposse/atmos, repository does not exist or may require 'docker login': denied: requested access to the resource is denied.

I tried to find that docker on docker hub but it seems to be nonexistent

1
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

we install it like this

ATMOS_VERSION=1.4.25

apt-get update && apt-get install -y --allow-downgrades atmos="${ATMOS_VERSION}-*"

# If runner image is Alpine Linux
# apk add [email protected]~=${ATMOS_VERSION}
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

or like this

# Install atmos
ARG ATMOS_VERSION
RUN wget <https://github.com/cloudposse/atmos/releases/download/v${ATMOS_VERSION}/atmos_${ATMOS_VERSION}_linux_amd64> && \
    mv atmos_${ATMOS_VERSION}_linux_amd64 /usr/local/bin/atmos && \
    chmod +x /usr/local/bin/atmos && \
    atmos version
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

not sure why brew is not working, @RB (Ronak) (Cloud Posse) can you take a look?

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)
➜  ~ brew uninstall atmos
Uninstalling /usr/local/Cellar/atmos/1.4.22... (6 files, 17.2MB)
➜  ~ brew install atmos
==> Downloading <https://ghcr.io/v2/homebrew/core/atmos/manifests/1.4.25>
######################################################################## 100.0%
==> Downloading <https://ghcr.io/v2/homebrew/core/atmos/blobs/sha256:e6a872c1467d>
==> Downloading from <https://pkg-containers.githubusercontent.com/ghcr1/blobs/sh>
######################################################################## 100.0%
==> Pouring atmos--1.4.25.monterey.bottle.tar.gz
🍺  /usr/local/Cellar/atmos/1.4.25: 6 files, 17.2MB
==> Running `brew cleanup atmos`...
Disable this behaviour by setting HOMEBREW_NO_INSTALL_CLEANUP.
Hide these hints with HOMEBREW_NO_ENV_HINTS (see `man brew`).
➜  ~ atmos version
1.4.25
RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

works for me on intel osx 12.4

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

@Marcin Brański the issue youre seeing seems like a docker issue ? atmos doesn’t rely on docker so that’s an odd error

Marcin Brański avatar
Marcin Brański

Hmmm, let me check what was installed with brew

Marcin Brański avatar
Marcin Brański

:white_check_mark: I have uninstalled atmos , removed usr/local/bin/atmos file, reinstalled and now its correct binary.

This was the content of /usr/local/bin/atmos before I deleted it: https://gist.github.com/3h4x/883cf3c04d860ff6386b030d82be3d33 Some script wrapper to run geodesic

I worked with atmos like a year ago or so, so it was probably some outdated file that got stuck and brew didn’t replace it on installing atmos upgrade. Im not exactly sure.

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

if you have geodesic then you do not need to install atmos using brew

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

inside geodesic run apt update && apt install atmos -y

2022-07-28

Michael Dizon avatar
Michael Dizon

is there a way to create atmos commands without having to fork the atmos repo and building a custom image?

RB (Ronak) (Cloud Posse) avatar
RB (Ronak) (Cloud Posse)

You can add custom cli commands using the atmos.yaml

1
Michael Dizon avatar
Michael Dizon

omg i love atmos

1
Michael Dizon avatar
Michael Dizon

it would be cool if i were able to add one to the atmos workspace

Matt Gowie avatar
Matt Gowie

Hey folks — In the Component.yaml spec I believe there should be a way to specify a non-remote, local file URI, but I haven’t been able to find an example of that. Do you folks know what that looks like? I remember digging for it weeks ago and went all the way down to the go-getter code level and couldn’t figure it out in a first pass. Wanted to ask here before I went down that rabbit hole again.

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

Matt, we don’t have that example, but we use go-getter which supports it

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

I’ll get to the computer and try to find it if you don’t find it before

Matt Gowie avatar
Matt Gowie

I couldn’t find it in the go-getter repo or their tests. I’m just not sure what the YAML key is supposed to be or if it needs to be an absolute path or not. Go-getter’s docs are not the best.

Joe Niland avatar
Joe Niland

@Matt Gowie FWIW I can get it working with file:/// and an absolute path

1
Matt Gowie avatar
Matt Gowie

Ah awesome — nice find @Joe Niland

Joe Niland avatar
Joe Niland

But relative or some kind of convention where it searches from some location set by ENV would be good.. I think.

Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

If relative paths not supported by go-getter, we could probably add that support in atmos

Joe Niland avatar
Joe Niland

It seems to imply that it does but I can’t find a working example, or get it working myself. I always get the error “relative paths require a module with a pwd”

Matt Gowie avatar
Matt Gowie

Running into this again — Implementing relative paths would be really nice. I’ll open an issue.

Matt Gowie avatar
Matt Gowie

Describe the Feature

In a component.yaml file, we should be able to supply relative paths to mixins so that we can specify mixins as a local file instead of a remote file.

Use Case

The use-case is to use client specific mixin files. This enables mixin files to not need to be published to an open source repository and/or use token based GitHub URLs to fetch private repository files.

Describe Ideal Solution

Something like the following would be great:

apiVersion: atmos/v1
kind: ComponentVendorConfig
metadata:
  name: s3-bucket-vendor-config
  description: Source and mixins config for vendoring of 's3-bucket' component
spec:
  source:
    # ...

  mixins:
    - uri: file:///../../mixins/providers.mixin.tf
      filename: providers.mixin.tf

Alternatives Considered

N/A

Additional Context

• See thread in Slack about using file:/// and how it doesn’t work for relative paths: https://sweetops.slack.com/archives/C031919U8A0/p1659032722469009

1
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)
  # mixins override files from 'source' with the same 'filename' (e.g. 'context.tf' will override 'context.tf' from the 'source')
  # mixins are processed in the order they are declared in the list
  mixins:
    # <https://github.com/hashicorp/go-getter/issues/98>
    # Mixins 'uri' supports the following protocols: local files (absolute and relative paths), Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP
    # - uri: <https://raw.githubusercontent.com/cloudposse/terraform-null-label/0.25.0/exports/context.tf>
    # This mixin `uri` is relative to the current `vpc-flow-logs-bucket` folder
    - uri: ../../mixins/context.tf
      filename: context.tf
    - uri: <https://raw.githubusercontent.com/cloudposse/terraform-aws-components/{{.Version}}/modules/datadog-agent/introspection.mixin.tf>
      version: 0.196.1
      filename: introspection.mixin.tf
Andriy Knysh (Cloud Posse) avatar
Andriy Knysh (Cloud Posse)

@Matt Gowie you don’t need to use file://… to specify local files with absolute or relative path, just use uri: ../../mixins/context.tf or uri: <absolute_path>/mixins/context.tf

1
Matt Gowie avatar
Matt Gowie

Awesome — Thank you Andriy!

2022-07-29

    keyboard_arrow_up