diff --git a/examples/quick-start/Dockerfile b/examples/quick-start/Dockerfile index fd599c6e1..a7971074b 100644 --- a/examples/quick-start/Dockerfile +++ b/examples/quick-start/Dockerfile @@ -1,12 +1,12 @@ # Geodesic: https://github.com/cloudposse/geodesic/ -ARG GEODESIC_VERSION=2.9.2 +ARG GEODESIC_VERSION=2.9.4 ARG GEODESIC_OS=debian # Atmos # https://atmos.tools/ # https://github.com/cloudposse/atmos # https://github.com/cloudposse/atmos/releases -ARG ATMOS_VERSION=1.65.0 +ARG ATMOS_VERSION=1.66.0 # Terraform: https://github.com/hashicorp/terraform/releases ARG TF_VERSION=1.7.3 diff --git a/examples/quick-start/stacks/orgs/acme/_defaults.yaml b/examples/quick-start/stacks/orgs/acme/_defaults.yaml index 6c47b3c74..cc786942e 100644 --- a/examples/quick-start/stacks/orgs/acme/_defaults.yaml +++ b/examples/quick-start/stacks/orgs/acme/_defaults.yaml @@ -13,3 +13,15 @@ vars: # key: "terraform.tfstate" # region: "your-aws-region" # role_arn: "arn:aws:iam:::role/" + +terraform: + vars: + tags: + atmos_component: "{{ .atmos_component }}" + atmos_stack: "{{ .atmos_stack }}" + atmos_manifest: "{{ .atmos_stack_file }}" + terraform_workspace: "{{ .workspace }}" + # `provisioned_at` uses the Sprig functions + # https://masterminds.github.io/sprig/date.html + # https://pkg.go.dev/time#pkg-constants + provisioned_at: '{{ dateInZone "2006-01-02T15:04:05Z07:00" (now) "UTC" }}' diff --git a/examples/tests/atmos.yaml b/examples/tests/atmos.yaml index f2414c34f..35d80d0f6 100644 --- a/examples/tests/atmos.yaml +++ b/examples/tests/atmos.yaml @@ -54,8 +54,15 @@ stacks: # 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}" + # To define Atmos stack naming convention, use either `name_pattern` or `name_template`. + # `name_template` has higher priority (if `name_template` is specified, `name_pattern` will be ignored). + # `name_pattern` can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var + # name_pattern: "{tenant}-{environment}-{stage}" + # `name_template` is a Golang template. + # For the template tokens, and you can use any Atmos sections and attributes that the Atmos command + # `atmos describe component -s ` generates (refer to https://atmos.tools/cli/commands/describe/component). + # `name_template` can also be set using 'ATMOS_STACKS_NAME_TEMPLATE' ENV var + name_template: "{{.vars.tenant}}-{{.vars.environment}}-{{.vars.stage}}" workflows: # Can also be set using 'ATMOS_WORKFLOWS_BASE_PATH' ENV var, or '--workflows-dir' command-line arguments diff --git a/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml b/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml index b4ed9fa4c..678f41ac3 100644 --- a/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml +++ b/examples/tests/rootfs/usr/local/etc/atmos/atmos.yaml @@ -55,7 +55,9 @@ stacks: excluded_paths: - "**/_defaults.yaml" # Can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var - name_pattern: "{tenant}-{environment}-{stage}" + # name_pattern: "{tenant}-{environment}-{stage}" + # Can also be set using 'ATMOS_STACKS_NAME_TEMPLATE' ENV var + name_template: "{{.vars.tenant}}-{{.vars.environment}}-{{.vars.stage}}" workflows: # Can also be set using 'ATMOS_WORKFLOWS_BASE_PATH' ENV var, or '--workflows-dir' command-line arguments diff --git a/examples/tests/stacks/catalog/terraform/vpc.yaml b/examples/tests/stacks/catalog/terraform/vpc.yaml index d83eb09d4..dcc71d050 100644 --- a/examples/tests/stacks/catalog/terraform/vpc.yaml +++ b/examples/tests/stacks/catalog/terraform/vpc.yaml @@ -42,9 +42,5 @@ components: map_public_ip_on_launch: true subnet_type_tag_key: "type" providers: - context: - values: - component: vpc - atmos_component: infra/vpc aws: - assume_role: "test_role" + assume_role: "vpc_role" diff --git a/examples/tests/stacks/mixins/region/global-region.yaml b/examples/tests/stacks/mixins/region/global-region.yaml index 0834c73db..384b4c06b 100644 --- a/examples/tests/stacks/mixins/region/global-region.yaml +++ b/examples/tests/stacks/mixins/region/global-region.yaml @@ -1,9 +1,3 @@ vars: region: us-east-2 environment: gbl - -terraform: - providers: - context: - values: - environment: gbl diff --git a/examples/tests/stacks/mixins/region/us-east-1.yaml b/examples/tests/stacks/mixins/region/us-east-1.yaml index 258333293..0676d2e24 100644 --- a/examples/tests/stacks/mixins/region/us-east-1.yaml +++ b/examples/tests/stacks/mixins/region/us-east-1.yaml @@ -12,9 +12,3 @@ components: - us-east-1a - us-east-1b - us-east-1c - -terraform: - providers: - context: - values: - environment: ue1 diff --git a/examples/tests/stacks/mixins/region/us-east-2.yaml b/examples/tests/stacks/mixins/region/us-east-2.yaml index a6870161e..b073d02b5 100644 --- a/examples/tests/stacks/mixins/region/us-east-2.yaml +++ b/examples/tests/stacks/mixins/region/us-east-2.yaml @@ -12,9 +12,3 @@ components: - us-east-2a - us-east-2b - us-east-2c - -terraform: - providers: - context: - values: - environment: ue2 diff --git a/examples/tests/stacks/mixins/region/us-west-1.yaml b/examples/tests/stacks/mixins/region/us-west-1.yaml index ea62990b0..8f2c156c9 100644 --- a/examples/tests/stacks/mixins/region/us-west-1.yaml +++ b/examples/tests/stacks/mixins/region/us-west-1.yaml @@ -1,9 +1,3 @@ vars: region: us-west-1 environment: uw1 - -terraform: - providers: - context: - values: - environment: uw1 diff --git a/examples/tests/stacks/mixins/region/us-west-2.yaml b/examples/tests/stacks/mixins/region/us-west-2.yaml index ee2a454e2..c8fa64019 100644 --- a/examples/tests/stacks/mixins/region/us-west-2.yaml +++ b/examples/tests/stacks/mixins/region/us-west-2.yaml @@ -1,9 +1,3 @@ vars: region: us-west-2 environment: uw2 - -terraform: - providers: - context: - values: - environment: uw2 diff --git a/examples/tests/stacks/mixins/stage/dev.yaml b/examples/tests/stacks/mixins/stage/dev.yaml index 4dc1c700f..2b0a1fb5a 100644 --- a/examples/tests/stacks/mixins/stage/dev.yaml +++ b/examples/tests/stacks/mixins/stage/dev.yaml @@ -4,9 +4,3 @@ vars: settings: config: is_prod: false - -terraform: - providers: - context: - values: - stage: dev diff --git a/examples/tests/stacks/mixins/stage/prod.yaml b/examples/tests/stacks/mixins/stage/prod.yaml index d221b51f5..9739ed2a0 100644 --- a/examples/tests/stacks/mixins/stage/prod.yaml +++ b/examples/tests/stacks/mixins/stage/prod.yaml @@ -4,9 +4,3 @@ vars: settings: config: is_prod: true - -terraform: - providers: - context: - values: - stage: prod diff --git a/examples/tests/stacks/mixins/stage/staging.yaml b/examples/tests/stacks/mixins/stage/staging.yaml index e6f9caf6f..1927d27f1 100644 --- a/examples/tests/stacks/mixins/stage/staging.yaml +++ b/examples/tests/stacks/mixins/stage/staging.yaml @@ -4,9 +4,3 @@ vars: settings: config: is_prod: false - -terraform: - providers: - context: - values: - stage: staging diff --git a/examples/tests/stacks/mixins/stage/test1.yaml b/examples/tests/stacks/mixins/stage/test1.yaml index 88065b034..d2b212059 100644 --- a/examples/tests/stacks/mixins/stage/test1.yaml +++ b/examples/tests/stacks/mixins/stage/test1.yaml @@ -4,9 +4,3 @@ vars: settings: config: is_prod: false - -terraform: - providers: - context: - values: - stage: test1 diff --git a/examples/tests/stacks/orgs/cp/_defaults.yaml b/examples/tests/stacks/orgs/cp/_defaults.yaml index dd44a7383..f3d1a70fa 100644 --- a/examples/tests/stacks/orgs/cp/_defaults.yaml +++ b/examples/tests/stacks/orgs/cp/_defaults.yaml @@ -2,7 +2,15 @@ vars: namespace: cp terraform: - vars: {} + vars: + tags: + atmos_component: "{{ .atmos_component }}" + atmos_stack: "{{ .atmos_stack }}" + atmos_manifest: "{{ .atmos_stack_file }}" + spacelift_stack: "{{ .spacelift_stack }}" + atlantis_project: "{{ .atlantis_project }}" + region: "{{ .vars.region }}" + terraform_workspace: "{{ .workspace }}" backend_type: s3 # s3, remote, vault, static, azurerm, etc. backend: @@ -49,14 +57,19 @@ terraform: min_length: 2 max_length: 20 values: - namespace: cp + namespace: "{{ .vars.namespace }}" + tenant: "{{ .vars.tenant }}" + environment: "{{ .vars.environment }}" + stage: "{{ .vars.stage }}" + atmos_component: "{{ .atmos_component }}" + atmos_stack: "{{ .atmos_stack }}" helmfile: - vars: {} + vars: { } components: - terraform: {} - helmfile: {} + terraform: { } + helmfile: { } settings: spacelift: diff --git a/examples/tests/stacks/orgs/cp/tenant1/_defaults.yaml b/examples/tests/stacks/orgs/cp/tenant1/_defaults.yaml index 1392f4ac5..756d40e6a 100644 --- a/examples/tests/stacks/orgs/cp/tenant1/_defaults.yaml +++ b/examples/tests/stacks/orgs/cp/tenant1/_defaults.yaml @@ -14,9 +14,3 @@ settings: tenant: tenant1 environment: ue2 stage: prod - -terraform: - providers: - context: - values: - tenant: tenant1 diff --git a/examples/tests/stacks/orgs/cp/tenant2/_defaults.yaml b/examples/tests/stacks/orgs/cp/tenant2/_defaults.yaml index 16d89d73c..d6120a08a 100644 --- a/examples/tests/stacks/orgs/cp/tenant2/_defaults.yaml +++ b/examples/tests/stacks/orgs/cp/tenant2/_defaults.yaml @@ -14,9 +14,3 @@ settings: tenant: tenant2 environment: ue2 stage: prod - -terraform: - providers: - context: - values: - tenant: tenant2 diff --git a/go.mod b/go.mod index f38389dcd..2fa8658eb 100644 --- a/go.mod +++ b/go.mod @@ -9,7 +9,7 @@ require ( github.com/bmatcuk/doublestar/v4 v4.6.1 github.com/charmbracelet/bubbles v0.18.0 github.com/charmbracelet/bubbletea v0.25.0 - github.com/charmbracelet/lipgloss v0.9.1 + github.com/charmbracelet/lipgloss v0.10.0 github.com/elewis787/boa v0.1.2 github.com/fatih/color v1.16.0 github.com/go-git/go-git/v5 v5.11.0 @@ -27,7 +27,7 @@ require ( github.com/lrstanley/bubblezone v0.0.0-20240125042004-b7bafc493195 github.com/mitchellh/go-homedir v1.1.0 github.com/mitchellh/mapstructure v1.5.0 - github.com/open-policy-agent/opa v0.62.0 + github.com/open-policy-agent/opa v0.62.1 github.com/otiai10/copy v1.14.0 github.com/pkg/errors v0.9.1 github.com/samber/lo v1.39.0 @@ -132,7 +132,7 @@ require ( github.com/prometheus/common v0.48.0 // indirect github.com/prometheus/procfs v0.12.0 // indirect github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 // indirect - github.com/rivo/uniseg v0.4.6 // indirect + github.com/rivo/uniseg v0.4.7 // indirect github.com/sagikazarmark/locafero v0.4.0 // indirect github.com/sagikazarmark/slog-shim v0.1.0 // indirect github.com/sahilm/fuzzy v0.1.1-0.20230530133925-c48e322e2a8f // indirect @@ -160,14 +160,14 @@ require ( go.opentelemetry.io/otel/trace v1.21.0 // indirect go.uber.org/atomic v1.9.0 // indirect go.uber.org/multierr v1.9.0 // indirect - golang.org/x/crypto v0.19.0 // indirect + golang.org/x/crypto v0.21.0 // indirect golang.org/x/exp v0.0.0-20230905200255-921286631fa9 // indirect golang.org/x/mod v0.14.0 // indirect - golang.org/x/net v0.21.0 // indirect + golang.org/x/net v0.22.0 // indirect golang.org/x/oauth2 v0.16.0 // indirect golang.org/x/sync v0.6.0 // indirect - golang.org/x/sys v0.17.0 // indirect - golang.org/x/term v0.17.0 // indirect + golang.org/x/sys v0.18.0 // indirect + golang.org/x/term v0.18.0 // indirect golang.org/x/text v0.14.0 // indirect golang.org/x/time v0.5.0 // indirect golang.org/x/tools v0.17.0 // indirect diff --git a/go.sum b/go.sum index 23c80aa9e..1cdad2cc9 100644 --- a/go.sum +++ b/go.sum @@ -254,8 +254,8 @@ github.com/charmbracelet/bubbles v0.18.0 h1:PYv1A036luoBGroX6VWjQIE9Syf2Wby2oOl/ github.com/charmbracelet/bubbles v0.18.0/go.mod h1:08qhZhtIwzgrtBjAcJnij1t1H0ZRjwHyGsy6AL11PSw= github.com/charmbracelet/bubbletea v0.25.0 h1:bAfwk7jRz7FKFl9RzlIULPkStffg5k6pNt5dywy4TcM= github.com/charmbracelet/bubbletea v0.25.0/go.mod h1:EN3QDR1T5ZdWmdfDzYcqOCAps45+QIJbLOBxmVNWNNg= -github.com/charmbracelet/lipgloss v0.9.1 h1:PNyd3jvaJbg4jRHKWXnCj1akQm4rh8dbEzN1p/u1KWg= -github.com/charmbracelet/lipgloss v0.9.1/go.mod h1:1mPmG4cxScwUQALAAnacHaigiiHB9Pmr+v1VEawJl6I= +github.com/charmbracelet/lipgloss v0.10.0 h1:KWeXFSexGcfahHX+54URiZGkBFazf70JNMtwg/AFW3s= +github.com/charmbracelet/lipgloss v0.10.0/go.mod h1:Wig9DSfvANsxqkRsqj6x87irdy123SR4dOXlKa91ciE= github.com/cheggaaa/pb v1.0.27/go.mod h1:pQciLPpbU0oxA0h+VJYYLxO+XeDQb5pZijXscXHm81s= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -312,8 +312,6 @@ github.com/docker/cli v24.0.0+incompatible h1:0+1VshNwBQzQAx9lOl+OYCTCEAD8fKs/qe github.com/docker/cli v24.0.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= github.com/docker/distribution v2.8.2+incompatible h1:T3de5rq0dB1j30rp0sA2rER+m322EBzniBPB6ZIzuh8= github.com/docker/distribution v2.8.2+incompatible/go.mod h1:J2gT2udsDAN96Uj4KfcMRqY0/ypR+oyYUYmja8H+y+w= -github.com/docker/docker v24.0.0+incompatible h1:z4bf8HvONXX9Tde5lGBMQ7yCJgNahmJumdrStZAbeY4= -github.com/docker/docker v24.0.0+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker v25.0.3+incompatible h1:D5fy/lYmY7bvZa0XTZ5/UJPljor41F+vdyJG5luQLfQ= github.com/docker/docker v25.0.3+incompatible/go.mod h1:eEKB0N0r5NX/I1kEveEz05bcu8tLC/8azJZsviup8Sk= github.com/docker/docker-credential-helpers v0.7.0 h1:xtCHsjxogADNZcdv1pKUHXryefjlVRqWqIhk/uXJp0A= @@ -608,8 +606,8 @@ github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= -github.com/open-policy-agent/opa v0.62.0 h1:8NAWkrg3tnMBi+pYqL7pEi7h6QmbMmVf/5IyjJS/A2s= -github.com/open-policy-agent/opa v0.62.0/go.mod h1:FD8D++1j1m74Qam2iUnKlfPDeoxWTXANaRUVu8W/tmA= +github.com/open-policy-agent/opa v0.62.1 h1:UcxBQ0fe6NEjkYc775j4PWoUFFhx4f6yXKIKSTAuTVk= +github.com/open-policy-agent/opa v0.62.1/go.mod h1:YqiSIIuvKwyomtnnXkJvy0E3KtVKbavjPJ/hNMuOmeM= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= github.com/opencontainers/image-spec v1.1.0-rc6 h1:XDqvyKsJEbRtATzkgItUqBA7QHk58yxX1Ov9HERHNqU= @@ -640,8 +638,8 @@ github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0 h1:MkV+77GLUNo github.com/rcrowley/go-metrics v0.0.0-20200313005456-10cdbea86bc0/go.mod h1:bCqnVzQkZxMG4s8nGwiZ5l3QUCyqpo9Y+/ZMZ9VjZe4= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.6 h1:Sovz9sDSwbOz9tgUy8JpT+KgCkPYJEN/oYzlJiYTNLg= -github.com/rivo/uniseg v0.4.6/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= +github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= +github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= @@ -765,8 +763,8 @@ golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0 golang.org/x/crypto v0.3.0/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4= golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU= -golang.org/x/crypto v0.19.0 h1:ENy+Az/9Y1vSrlrvBSyna3PITt4tiZLf7sgCjZBX7Wo= -golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= +golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA= +golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= @@ -859,8 +857,8 @@ golang.org/x/net v0.1.0/go.mod h1:Cx3nUiGt4eDBEyega/BKRp+/AlGL8hYe7U9odMt2Cco= golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc= -golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4= -golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc= +golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -978,8 +976,8 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.17.0 h1:25cE3gD+tdBA7lp7QfhuV+rJiE9YXTcS3VG1SqssI/Y= -golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4= +golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -987,8 +985,8 @@ golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U= -golang.org/x/term v0.17.0 h1:mkTF7LCd6WGJNL3K1Ad7kwxNfYAW6a8a8QqtMblp/4U= -golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= +golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8= +golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= diff --git a/internal/exec/atlantis_generate_repo_config.go b/internal/exec/atlantis_generate_repo_config.go index f9d0c6416..5e4ed771e 100644 --- a/internal/exec/atlantis_generate_repo_config.go +++ b/internal/exec/atlantis_generate_repo_config.go @@ -2,7 +2,6 @@ package exec import ( "fmt" - cfg "github.com/cloudposse/atmos/pkg/config" "path" "path/filepath" "reflect" @@ -13,6 +12,7 @@ import ( "github.com/samber/lo" "github.com/spf13/cobra" + cfg "github.com/cloudposse/atmos/pkg/config" c "github.com/cloudposse/atmos/pkg/convert" "github.com/cloudposse/atmos/pkg/schema" u "github.com/cloudposse/atmos/pkg/utils" @@ -314,23 +314,32 @@ func ExecuteAtlantisGenerateRepoConfig( context := cfg.GetContextFromVars(varsSection) context.Component = strings.Replace(componentName, "/", "-", -1) context.ComponentPath = terraformComponentPath - contextPrefix, err := cfg.GetContextPrefix(stackConfigFileName, context, cliConfig.Stacks.NamePattern, stackConfigFileName) + contextPrefix, err := cfg.GetContextPrefix(stackConfigFileName, context, GetStackNamePattern(cliConfig), stackConfigFileName) if err != nil { return err } + configAndStacksInfo := schema.ConfigAndStacksInfo{ + ComponentFromArg: componentName, + Stack: stackConfigFileName, + ComponentMetadataSection: metadataSection, + ComponentSettingsSection: settingsSection, + ComponentVarsSection: varsSection, + Context: context, + ComponentSection: map[string]any{ + cfg.VarsSectionName: varsSection, + cfg.SettingsSectionName: settingsSection, + cfg.MetadataSectionName: metadataSection, + }, + } + // Calculate terraform workspace // Base component is required to calculate terraform workspace for derived components if terraformComponent != componentName { context.BaseComponent = terraformComponent } - workspace, err := BuildTerraformWorkspace( - stackConfigFileName, - cliConfig.Stacks.NamePattern, - metadataSection, - context, - ) + workspace, err := BuildTerraformWorkspace(cliConfig, configAndStacksInfo) if err != nil { return err } @@ -360,7 +369,7 @@ func ExecuteAtlantisGenerateRepoConfig( WhenModified: whenModified, } - atlantisProjectName := BuildAtlantisProjectName(context, projectTemplate.Name) + atlantisProjectName := cfg.ReplaceContextTokens(context, projectTemplate.Name) atlantisProject := schema.AtlantisProjectConfig{ Name: atlantisProjectName, diff --git a/internal/exec/atlantis_utils.go b/internal/exec/atlantis_utils.go index bdd0bdde7..e384194ff 100644 --- a/internal/exec/atlantis_utils.go +++ b/internal/exec/atlantis_utils.go @@ -10,23 +10,16 @@ import ( "github.com/cloudposse/atmos/pkg/schema" ) -// BuildAtlantisProjectName builds an Atlantis project name from the provided context and project name pattern -func BuildAtlantisProjectName(context schema.Context, projectNameTemplate string) string { - return cfg.ReplaceContextTokens(context, projectNameTemplate) -} - // BuildAtlantisProjectNameFromComponentConfig builds an Atlantis project name from the component config func BuildAtlantisProjectNameFromComponentConfig( cliConfig schema.CliConfiguration, - componentName string, - componentSettingsSection map[any]any, - componentVarsSection map[any]any, + configAndStacksInfo schema.ConfigAndStacksInfo, ) (string, error) { var atlantisProjectTemplate schema.AtlantisProjectConfig var atlantisProjectName string - if atlantisSettingsSection, ok := componentSettingsSection["atlantis"].(map[any]any); ok { + if atlantisSettingsSection, ok := configAndStacksInfo.ComponentSettingsSection["atlantis"].(map[any]any); ok { // 'settings.atlantis.project_template' has higher priority than 'settings.atlantis.project_template_name' if atlantisSettingsProjectTemplate, ok := atlantisSettingsSection["project_template"].(map[any]any); ok { err := mapstructure.Decode(atlantisSettingsProjectTemplate, &atlantisProjectTemplate) @@ -39,12 +32,11 @@ func BuildAtlantisProjectNameFromComponentConfig( } } - context := cfg.GetContextFromVars(componentVarsSection) - context.Component = strings.Replace(componentName, "/", "-", -1) - // If Atlantis project template is defined and has a name, replace tokens in the name and add the Atlantis project to the output if !reflect.ValueOf(atlantisProjectTemplate).IsZero() && atlantisProjectTemplate.Name != "" { - atlantisProjectName = BuildAtlantisProjectName(context, atlantisProjectTemplate.Name) + context := cfg.GetContextFromVars(configAndStacksInfo.ComponentVarsSection) + context.Component = strings.Replace(configAndStacksInfo.ComponentFromArg, "/", "-", -1) + atlantisProjectName = cfg.ReplaceContextTokens(context, atlantisProjectTemplate.Name) } } diff --git a/internal/exec/aws_eks_update_kubeconfig.go b/internal/exec/aws_eks_update_kubeconfig.go index 2ef1929fd..b10a01bd8 100644 --- a/internal/exec/aws_eks_update_kubeconfig.go +++ b/internal/exec/aws_eks_update_kubeconfig.go @@ -133,7 +133,7 @@ func ExecuteAwsEksUpdateKubeconfig(kubeconfigContext schema.AwsEksUpdateKubeconf return err } - if len(cliConfig.Stacks.NamePattern) < 1 { + if len(GetStackNamePattern(cliConfig)) < 1 { return errors.New("stack name pattern must be provided in 'stacks.name_pattern' CLI config or 'ATMOS_STACKS_NAME_PATTERN' ENV variable") } @@ -142,7 +142,7 @@ func ExecuteAwsEksUpdateKubeconfig(kubeconfigContext schema.AwsEksUpdateKubeconf kubeconfigContext.Tenant, kubeconfigContext.Environment, kubeconfigContext.Stage, - cliConfig.Stacks.NamePattern, + GetStackNamePattern(cliConfig), ) if err != nil { return err diff --git a/internal/exec/describe_affected_utils.go b/internal/exec/describe_affected_utils.go index 461df1fd0..046729989 100644 --- a/internal/exec/describe_affected_utils.go +++ b/internal/exec/describe_affected_utils.go @@ -857,37 +857,33 @@ func appendToAffected( settingsSection = i.(map[any]any) } - // Affected Spacelift stack - spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig( - cliConfig, - componentName, - stackName, - settingsSection, - varSection, - ) + configAndStacksInfo := schema.ConfigAndStacksInfo{ + ComponentFromArg: componentName, + Stack: stackName, + ComponentVarsSection: varSection, + ComponentSettingsSection: settingsSection, + ComponentSection: map[string]any{ + cfg.VarsSectionName: varSection, + cfg.SettingsSectionName: settingsSection, + }, + } + // Affected Spacelift stack + spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig(cliConfig, configAndStacksInfo) if err != nil { return nil, err } - affected.SpaceliftStack = spaceliftStackName // Affected Atlantis project - atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig( - cliConfig, - componentName, - settingsSection, - varSection, - ) - + atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig(cliConfig, configAndStacksInfo) if err != nil { return nil, err } - affected.AtlantisProject = atlantisProjectName if includeSpaceliftAdminStacks { - affectedList, err = addAffectedSpaceliftAdminStack(cliConfig, affectedList, settingsSection, stacks, stackName, componentName) + affectedList, err = addAffectedSpaceliftAdminStack(cliConfig, affectedList, settingsSection, stacks, stackName, componentName, configAndStacksInfo) if err != nil { return nil, err } @@ -1088,6 +1084,7 @@ func addAffectedSpaceliftAdminStack( stacks map[string]any, currentStackName string, currentComponentName string, + configAndStacksInfo schema.ConfigAndStacksInfo, ) ([]schema.Affected, error) { // Convert the `settings` section to the `Settings` structure @@ -1122,16 +1119,25 @@ func addAffectedSpaceliftAdminStack( return affectedList, nil } - adminStackContextPrefix, err := cfg.GetContextPrefix(currentStackName, adminStackContext, cliConfig.Stacks.NamePattern, currentStackName) - if err != nil { - return nil, err + var adminStackContextPrefix string + + if cliConfig.Stacks.NameTemplate != "" { + adminStackContextPrefix, err = u.ProcessTmpl("spacelift-admin-stack-name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false) + if err != nil { + return nil, err + } + } else { + adminStackContextPrefix, err = cfg.GetContextPrefix(currentStackName, adminStackContext, GetStackNamePattern(cliConfig), currentStackName) + if err != nil { + return nil, err + } } var componentVarsSection map[any]any var componentSettingsSection map[any]any var componentSettingsSpaceliftSection map[any]any - // Find the Spacelift adin stack that manages the current stack + // Find the Spacelift admin stack that manages the current stack for stackName, stackSection := range stacks { if stackSectionMap, ok := stackSection.(map[string]any); ok { if componentsSection, ok := stackSectionMap["components"].(map[string]any); ok { @@ -1149,9 +1155,18 @@ func addAffectedSpaceliftAdminStack( return nil, err } - contextPrefix, err := cfg.GetContextPrefix(stackName, context, cliConfig.Stacks.NamePattern, stackName) - if err != nil { - return nil, err + var contextPrefix string + + if cliConfig.Stacks.NameTemplate != "" { + contextPrefix, err = u.ProcessTmpl("spacelift-stack-name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false) + if err != nil { + return nil, err + } + } else { + contextPrefix, err = cfg.GetContextPrefix(stackName, context, GetStackNamePattern(cliConfig), stackName) + if err != nil { + return nil, err + } } if adminStackContext.Component == componentName && adminStackContextPrefix == contextPrefix { diff --git a/internal/exec/describe_dependents.go b/internal/exec/describe_dependents.go index 565f83dad..eeb0851d0 100644 --- a/internal/exec/describe_dependents.go +++ b/internal/exec/describe_dependents.go @@ -232,32 +232,28 @@ func ExecuteDescribeDependents( if stackComponentType == "terraform" { // Spacelift stack - spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig( - cliConfig, - stackComponentName, - stackName, - stackComponentSettingsSection, - stackComponentVarsSection, - ) + configAndStacksInfo := schema.ConfigAndStacksInfo{ + ComponentFromArg: stackComponentName, + Stack: stackName, + ComponentVarsSection: stackComponentVarsSection, + ComponentSettingsSection: stackComponentSettingsSection, + ComponentSection: map[string]any{ + cfg.VarsSectionName: stackComponentVarsSection, + cfg.SettingsSectionName: stackComponentSettingsSection, + }, + } + spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig(cliConfig, configAndStacksInfo) if err != nil { return nil, err } - dependent.SpaceliftStack = spaceliftStackName // Atlantis project - atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig( - cliConfig, - stackComponentName, - stackComponentSettingsSection, - stackComponentVarsSection, - ) - + atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig(cliConfig, configAndStacksInfo) if err != nil { return nil, err } - dependent.AtlantisProject = atlantisProjectName } diff --git a/internal/exec/describe_stacks.go b/internal/exec/describe_stacks.go index f28fcc7ca..066f1d004 100644 --- a/internal/exec/describe_stacks.go +++ b/internal/exec/describe_stacks.go @@ -2,6 +2,7 @@ package exec import ( "fmt" + c "github.com/cloudposse/atmos/pkg/convert" "strings" "github.com/spf13/cobra" @@ -109,6 +110,12 @@ func ExecuteDescribeStacks( finalStacksMap := make(map[string]any) var varsSection map[any]any var metadataSection map[any]any + var settingsSection map[any]any + var envSection map[any]any + var providersSection map[any]any + var overridesSection map[any]any + var backendSection map[any]any + var backendTypeSection string var stackName string context := schema.Context{} @@ -136,20 +143,84 @@ func ExecuteDescribeStacks( return nil, err } - // Component vars - if varsSection, ok = componentSection["vars"].(map[any]any); ok { + if varsSection, ok = componentSection[cfg.VarsSectionName].(map[any]any); !ok { + varsSection = map[any]any{} + } + + if metadataSection, ok = componentSection[cfg.MetadataSectionName].(map[any]any); !ok { + metadataSection = map[any]any{} + } + + if settingsSection, ok = componentSection[cfg.SettingsSectionName].(map[any]any); !ok { + settingsSection = map[any]any{} + } + + if envSection, ok = componentSection[cfg.EnvSectionName].(map[any]any); !ok { + envSection = map[any]any{} + } + + if providersSection, ok = componentSection[cfg.ProvidersSectionName].(map[any]any); !ok { + providersSection = map[any]any{} + } + + if overridesSection, ok = componentSection[cfg.OverridesSectionName].(map[any]any); !ok { + overridesSection = map[any]any{} + } + + if backendSection, ok = componentSection[cfg.BackendSectionName].(map[any]any); !ok { + backendSection = map[any]any{} + } + + if backendTypeSection, ok = componentSection[cfg.BackendTypeSectionName].(string); !ok { + backendTypeSection = "" + } + + configAndStacksInfo := schema.ConfigAndStacksInfo{ + ComponentFromArg: componentName, + Stack: stackName, + ComponentMetadataSection: metadataSection, + ComponentVarsSection: varsSection, + ComponentSettingsSection: settingsSection, + ComponentEnvSection: envSection, + ComponentProvidersSection: providersSection, + ComponentOverridesSection: overridesSection, + ComponentBackendSection: backendSection, + ComponentBackendType: backendTypeSection, + ComponentSection: map[string]any{ + cfg.VarsSectionName: varsSection, + cfg.MetadataSectionName: metadataSection, + cfg.SettingsSectionName: settingsSection, + cfg.EnvSectionName: envSection, + cfg.ProvidersSectionName: providersSection, + cfg.OverridesSectionName: overridesSection, + cfg.BackendSectionName: backendSection, + cfg.BackendTypeSectionName: backendTypeSection, + }, + } + + configAndStacksInfo.ComponentSection["atmos_component"] = componentName + configAndStacksInfo.ComponentSection["atmos_stack"] = stackName + configAndStacksInfo.ComponentSection["atmos_stack_file"] = stackFileName + + if comp, ok := configAndStacksInfo.ComponentSection["component"].(string); !ok || comp == "" { + configAndStacksInfo.ComponentSection["component"] = componentName + } + + // Stack name + if cliConfig.Stacks.NameTemplate != "" { + stackName, err = u.ProcessTmpl("describe-stacks-name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false) + if err != nil { + return nil, err + } + } else { context = cfg.GetContextFromVars(varsSection) - stackName, err = cfg.GetContextPrefix(stackFileName, context, cliConfig.Stacks.NamePattern, stackFileName) + configAndStacksInfo.Context = context + stackName, err = cfg.GetContextPrefix(stackFileName, context, GetStackNamePattern(cliConfig), stackFileName) if err != nil { return nil, err } } - // Component metadata - if metadataSection, ok = componentSection["metadata"].(map[any]any); !ok { - metadataSection = map[any]any{} - } - if filterByStack != "" && filterByStack != stackFileName && filterByStack != stackName { continue } @@ -173,36 +244,45 @@ func ExecuteDescribeStacks( finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["terraform"].(map[string]any)[componentName] = make(map[string]any) } + // Terraform workspace + workspace, err := BuildTerraformWorkspace(cliConfig, configAndStacksInfo) + if err != nil { + return nil, err + } + componentSection["workspace"] = workspace + configAndStacksInfo.ComponentSection["workspace"] = workspace + + // Atmos component, stack, and stack manifest file + componentSection["atmos_component"] = componentName + componentSection["atmos_stack"] = stackName + componentSection["atmos_stack_file"] = stackFileName + + // Process `Go` templates + componentSectionStr, err := u.ConvertToYAML(componentSection) + if err != nil { + return nil, err + } + + componentSectionProcessed, err := u.ProcessTmpl("describe-stacks-all-sections", componentSectionStr, configAndStacksInfo.ComponentSection, true) + if err != nil { + return nil, err + } + + componentSectionConverted, err := c.YAMLToMapOfInterfaces(componentSectionProcessed) + if err != nil { + return nil, err + } + + componentSection = c.MapsOfInterfacesToMapsOfStrings(componentSectionConverted) + if err != nil { + return nil, err + } + + // Add sections for sectionName, section := range componentSection { if len(sections) == 0 || u.SliceContainsString(sections, sectionName) { finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["terraform"].(map[string]any)[componentName].(map[string]any)[sectionName] = section } - - // Terraform workspace - if len(sections) == 0 || u.SliceContainsString(sections, "workspace") { - workspace, err := BuildTerraformWorkspace( - stackName, - cliConfig.Stacks.NamePattern, - metadataSection, - context, - ) - if err != nil { - return nil, err - } - - finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["terraform"].(map[string]any)[componentName].(map[string]any)["workspace"] = workspace - } - - // Atmos component, stack, and stack manifest file - if len(sections) == 0 || u.SliceContainsString(sections, "atmos_component") { - finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["terraform"].(map[string]any)[componentName].(map[string]any)["atmos_component"] = componentName - } - if len(sections) == 0 || u.SliceContainsString(sections, "atmos_stack") { - finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["terraform"].(map[string]any)[componentName].(map[string]any)["atmos_stack"] = stackName - } - if len(sections) == 0 || u.SliceContainsString(sections, "atmos_stack_file") { - finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["terraform"].(map[string]any)[componentName].(map[string]any)["atmos_stack_file"] = stackFileName - } } } } @@ -227,10 +307,79 @@ func ExecuteDescribeStacks( return nil, err } - // Component vars - if varsSection, ok = componentSection["vars"].(map[any]any); ok { - context := cfg.GetContextFromVars(varsSection) - stackName, err = cfg.GetContextPrefix(stackFileName, context, cliConfig.Stacks.NamePattern, stackFileName) + if varsSection, ok = componentSection[cfg.VarsSectionName].(map[any]any); !ok { + varsSection = map[any]any{} + } + + if metadataSection, ok = componentSection[cfg.MetadataSectionName].(map[any]any); !ok { + metadataSection = map[any]any{} + } + + if settingsSection, ok = componentSection[cfg.SettingsSectionName].(map[any]any); !ok { + settingsSection = map[any]any{} + } + + if envSection, ok = componentSection[cfg.EnvSectionName].(map[any]any); !ok { + envSection = map[any]any{} + } + + if providersSection, ok = componentSection[cfg.ProvidersSectionName].(map[any]any); !ok { + providersSection = map[any]any{} + } + + if overridesSection, ok = componentSection[cfg.OverridesSectionName].(map[any]any); !ok { + overridesSection = map[any]any{} + } + + if backendSection, ok = componentSection[cfg.BackendSectionName].(map[any]any); !ok { + backendSection = map[any]any{} + } + + if backendTypeSection, ok = componentSection[cfg.BackendTypeSectionName].(string); !ok { + backendTypeSection = "" + } + + configAndStacksInfo := schema.ConfigAndStacksInfo{ + ComponentFromArg: componentName, + Stack: stackName, + ComponentMetadataSection: metadataSection, + ComponentVarsSection: varsSection, + ComponentSettingsSection: settingsSection, + ComponentEnvSection: envSection, + ComponentProvidersSection: providersSection, + ComponentOverridesSection: overridesSection, + ComponentBackendSection: backendSection, + ComponentBackendType: backendTypeSection, + ComponentSection: map[string]any{ + cfg.VarsSectionName: varsSection, + cfg.MetadataSectionName: metadataSection, + cfg.SettingsSectionName: settingsSection, + cfg.EnvSectionName: envSection, + cfg.ProvidersSectionName: providersSection, + cfg.OverridesSectionName: overridesSection, + cfg.BackendSectionName: backendSection, + cfg.BackendTypeSectionName: backendTypeSection, + }, + } + + configAndStacksInfo.ComponentSection["atmos_component"] = componentName + configAndStacksInfo.ComponentSection["atmos_stack"] = stackName + configAndStacksInfo.ComponentSection["atmos_stack_file"] = stackFileName + + if comp, ok := configAndStacksInfo.ComponentSection["component"].(string); !ok || comp == "" { + configAndStacksInfo.ComponentSection["component"] = componentName + } + + // Stack name + if cliConfig.Stacks.NameTemplate != "" { + stackName, err = u.ProcessTmpl("describe-stacks-name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false) + if err != nil { + return nil, err + } + } else { + context = cfg.GetContextFromVars(varsSection) + configAndStacksInfo.Context = context + stackName, err = cfg.GetContextPrefix(stackFileName, context, GetStackNamePattern(cliConfig), stackFileName) if err != nil { return nil, err } @@ -259,20 +408,36 @@ func ExecuteDescribeStacks( finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["helmfile"].(map[string]any)[componentName] = make(map[string]any) } + // Atmos component, stack, and stack manifest file + componentSection["atmos_component"] = componentName + componentSection["atmos_stack"] = stackName + componentSection["atmos_stack_file"] = stackFileName + + // Process `Go` templates + componentSectionStr, err := u.ConvertToYAML(componentSection) + if err != nil { + return nil, err + } + + componentSectionProcessed, err := u.ProcessTmpl("describe-stacks-all-sections", componentSectionStr, configAndStacksInfo.ComponentSection, true) + if err != nil { + return nil, err + } + + componentSectionConverted, err := c.YAMLToMapOfInterfaces(componentSectionProcessed) + if err != nil { + return nil, err + } + + componentSection = c.MapsOfInterfacesToMapsOfStrings(componentSectionConverted) + if err != nil { + return nil, err + } + + // Add sections for sectionName, section := range componentSection { if len(sections) == 0 || u.SliceContainsString(sections, sectionName) { finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["helmfile"].(map[string]any)[componentName].(map[string]any)[sectionName] = section - - // Atmos component, stack, and stack manifest file - if len(sections) == 0 || u.SliceContainsString(sections, "atmos_component") { - finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["helmfile"].(map[string]any)[componentName].(map[string]any)["atmos_component"] = componentName - } - if len(sections) == 0 || u.SliceContainsString(sections, "atmos_stack") { - finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["helmfile"].(map[string]any)[componentName].(map[string]any)["atmos_stack"] = stackName - } - if len(sections) == 0 || u.SliceContainsString(sections, "atmos_stack_file") { - finalStacksMap[stackName].(map[string]any)["components"].(map[string]any)["helmfile"].(map[string]any)[componentName].(map[string]any)["atmos_stack_file"] = stackFileName - } } } } diff --git a/internal/exec/spacelift_utils.go b/internal/exec/spacelift_utils.go index 12e90eabf..6598ed834 100644 --- a/internal/exec/spacelift_utils.go +++ b/internal/exec/spacelift_utils.go @@ -6,17 +6,18 @@ import ( cfg "github.com/cloudposse/atmos/pkg/config" "github.com/cloudposse/atmos/pkg/schema" + u "github.com/cloudposse/atmos/pkg/utils" ) // BuildSpaceliftStackName builds a Spacelift stack name from the provided context and stack name pattern -func BuildSpaceliftStackName(spaceliftSettings map[any]any, context schema.Context, contextPrefix string) (string, string) { +func BuildSpaceliftStackName(spaceliftSettings map[any]any, context schema.Context, contextPrefix string) (string, string, error) { if spaceliftStackNamePattern, ok := spaceliftSettings["stack_name_pattern"].(string); ok { - return cfg.ReplaceContextTokens(context, spaceliftStackNamePattern), spaceliftStackNamePattern + return cfg.ReplaceContextTokens(context, spaceliftStackNamePattern), spaceliftStackNamePattern, nil } else if spaceliftStackName, ok := spaceliftSettings["stack_name"].(string); ok { - return spaceliftStackName, contextPrefix + return spaceliftStackName, contextPrefix, nil } else { defaultSpaceliftStackNamePattern := fmt.Sprintf("%s-%s", contextPrefix, context.Component) - return strings.Replace(defaultSpaceliftStackNamePattern, "/", "-", -1), contextPrefix + return strings.Replace(defaultSpaceliftStackNamePattern, "/", "-", -1), contextPrefix, nil } } @@ -66,7 +67,12 @@ func BuildSpaceliftStackNames(stacks map[string]any, stackNamePattern string) ([ } context.Component = component - spaceliftStackName, _ := BuildSpaceliftStackName(spaceliftSettings, context, contextPrefix) + + spaceliftStackName, _, err := BuildSpaceliftStackName(spaceliftSettings, context, contextPrefix) + if err != nil { + return nil, err + } + allStackNames = append(allStackNames, strings.Replace(spaceliftStackName, "/", "-", -1)) } } @@ -79,30 +85,39 @@ func BuildSpaceliftStackNames(stacks map[string]any, stackNamePattern string) ([ // BuildSpaceliftStackNameFromComponentConfig builds Spacelift stack name from the component config func BuildSpaceliftStackNameFromComponentConfig( cliConfig schema.CliConfiguration, - componentName string, - stackName string, - componentSettingsSection map[any]any, - componentVarsSection map[any]any, + configAndStacksInfo schema.ConfigAndStacksInfo, ) (string, error) { var spaceliftStackName string var spaceliftSettingsSection map[any]any + var contextPrefix string + var err error - if i, ok2 := componentSettingsSection["spacelift"]; ok2 { + if i, ok2 := configAndStacksInfo.ComponentSettingsSection["spacelift"]; ok2 { spaceliftSettingsSection = i.(map[any]any) } - context := cfg.GetContextFromVars(componentVarsSection) - context.Component = strings.Replace(componentName, "/", "-", -1) - // Spacelift stack if spaceliftWorkspaceEnabled, ok := spaceliftSettingsSection["workspace_enabled"].(bool); ok && spaceliftWorkspaceEnabled { - contextPrefix, err := cfg.GetContextPrefix(stackName, context, cliConfig.Stacks.NamePattern, stackName) + context := cfg.GetContextFromVars(configAndStacksInfo.ComponentVarsSection) + context.Component = strings.Replace(configAndStacksInfo.ComponentFromArg, "/", "-", -1) + + if cliConfig.Stacks.NameTemplate != "" { + contextPrefix, err = u.ProcessTmpl("name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false) + if err != nil { + return "", err + } + } else { + contextPrefix, err = cfg.GetContextPrefix(configAndStacksInfo.Stack, context, GetStackNamePattern(cliConfig), configAndStacksInfo.Stack) + if err != nil { + return "", err + } + } + + spaceliftStackName, _, err = BuildSpaceliftStackName(spaceliftSettingsSection, context, contextPrefix) if err != nil { return "", err } - - spaceliftStackName, _ = BuildSpaceliftStackName(spaceliftSettingsSection, context, contextPrefix) } return spaceliftStackName, nil diff --git a/internal/exec/stack_utils.go b/internal/exec/stack_utils.go index c2ed31dec..8b557e3f8 100644 --- a/internal/exec/stack_utils.go +++ b/internal/exec/stack_utils.go @@ -11,37 +11,44 @@ import ( ) // BuildTerraformWorkspace builds Terraform workspace -func BuildTerraformWorkspace( - stack string, - stackNamePattern string, - componentMetadata map[any]any, - context schema.Context, -) (string, error) { - +func BuildTerraformWorkspace(cliConfig schema.CliConfiguration, configAndStacksInfo schema.ConfigAndStacksInfo) (string, error) { var contextPrefix string var err error + var tmpl string - if stackNamePattern != "" { - contextPrefix, err = cfg.GetContextPrefix(stack, context, stackNamePattern, stack) + if cliConfig.Stacks.NameTemplate != "" { + tmpl, err = u.ProcessTmpl("terraform-workspace-stacks-name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false) + if err != nil { + return "", err + } + contextPrefix = tmpl + } else if cliConfig.Stacks.NamePattern != "" { + contextPrefix, err = cfg.GetContextPrefix(configAndStacksInfo.Stack, configAndStacksInfo.Context, cliConfig.Stacks.NamePattern, configAndStacksInfo.Stack) if err != nil { return "", err } } else { - contextPrefix = strings.Replace(stack, "/", "-", -1) + contextPrefix = strings.Replace(configAndStacksInfo.Stack, "/", "-", -1) } var workspace string + componentMetadata := configAndStacksInfo.ComponentMetadataSection - if terraformWorkspacePattern, terraformWorkspacePatternExist := componentMetadata["terraform_workspace_pattern"].(string); terraformWorkspacePatternExist { - // Terraform workspace can be overridden per component in YAML config `metadata.terraform_workspace_pattern` - workspace = cfg.ReplaceContextTokens(context, terraformWorkspacePattern) + // Terraform workspace can be overridden per component using `metadata.terraform_workspace_pattern` or `metadata.terraform_workspace_template` or `metadata.terraform_workspace` + if terraformWorkspaceTemplate, terraformWorkspaceTemplateExist := componentMetadata["terraform_workspace_template"].(string); terraformWorkspaceTemplateExist { + tmpl, err = u.ProcessTmpl("terraform-workspace-template", terraformWorkspaceTemplate, configAndStacksInfo.ComponentSection, false) + if err != nil { + return "", err + } + workspace = tmpl + } else if terraformWorkspacePattern, terraformWorkspacePatternExist := componentMetadata["terraform_workspace_pattern"].(string); terraformWorkspacePatternExist { + workspace = cfg.ReplaceContextTokens(configAndStacksInfo.Context, terraformWorkspacePattern) } else if terraformWorkspace, terraformWorkspaceExist := componentMetadata["terraform_workspace"].(string); terraformWorkspaceExist { - // Terraform workspace can be overridden per component in YAML config `metadata.terraform_workspace` workspace = terraformWorkspace - } else if context.BaseComponent == "" { + } else if configAndStacksInfo.Context.BaseComponent == "" { workspace = contextPrefix } else { - workspace = fmt.Sprintf("%s-%s", contextPrefix, context.Component) + workspace = fmt.Sprintf("%s-%s", contextPrefix, configAndStacksInfo.Context.Component) } return strings.Replace(workspace, "/", "-", -1), nil @@ -158,3 +165,8 @@ func BuildComponentPath( return componentPath } + +// GetStackNamePattern returns stack name pattern +func GetStackNamePattern(cliConfig schema.CliConfiguration) string { + return cliConfig.Stacks.NamePattern +} diff --git a/internal/exec/terraform_generate_backends.go b/internal/exec/terraform_generate_backends.go index e1aa5f4d7..6844d7eda 100644 --- a/internal/exec/terraform_generate_backends.go +++ b/internal/exec/terraform_generate_backends.go @@ -141,7 +141,7 @@ func ExecuteTerraformGenerateBackends(cliConfig schema.CliConfiguration, fileTem context := cfg.GetContextFromVars(varsSection) context.Component = strings.Replace(componentName, "/", "-", -1) context.ComponentPath = terraformComponentPath - contextPrefix, err := cfg.GetContextPrefix(stackFileName, context, cliConfig.Stacks.NamePattern, stackFileName) + contextPrefix, err := cfg.GetContextPrefix(stackFileName, context, GetStackNamePattern(cliConfig), stackFileName) if err != nil { return err } diff --git a/internal/exec/terraform_generate_varfiles.go b/internal/exec/terraform_generate_varfiles.go index a650c50fd..fdfc47ce2 100644 --- a/internal/exec/terraform_generate_varfiles.go +++ b/internal/exec/terraform_generate_varfiles.go @@ -129,7 +129,7 @@ func ExecuteTerraformGenerateVarfiles(cliConfig schema.CliConfiguration, fileTem context := cfg.GetContextFromVars(varsSection) context.Component = strings.Replace(componentName, "/", "-", -1) context.ComponentPath = terraformComponentPath - contextPrefix, err := cfg.GetContextPrefix(stackFileName, context, cliConfig.Stacks.NamePattern, stackFileName) + contextPrefix, err := cfg.GetContextPrefix(stackFileName, context, GetStackNamePattern(cliConfig), stackFileName) if err != nil { return err } diff --git a/internal/exec/utils.go b/internal/exec/utils.go index e3166d529..2532abc98 100644 --- a/internal/exec/utils.go +++ b/internal/exec/utils.go @@ -10,6 +10,7 @@ import ( "github.com/spf13/cobra" cfg "github.com/cloudposse/atmos/pkg/config" + c "github.com/cloudposse/atmos/pkg/convert" "github.com/cloudposse/atmos/pkg/schema" s "github.com/cloudposse/atmos/pkg/stack" u "github.com/cloudposse/atmos/pkg/utils" @@ -48,8 +49,8 @@ var ( } ) -// FindComponentConfig finds component config sections -func FindComponentConfig( +// ProcessComponentConfig processes component config sections +func ProcessComponentConfig( configAndStacksInfo *schema.ConfigAndStacksInfo, stack string, stacksMap map[string]any, @@ -86,24 +87,24 @@ func FindComponentConfig( return fmt.Errorf("could not find the stack '%s'", stack) } if componentsSection, ok = stackSection["components"].(map[string]any); !ok { - return fmt.Errorf("'components' section is missing in the stack file '%s'", stack) + return fmt.Errorf("'components' section is missing in the stack manifest '%s'", stack) } if componentTypeSection, ok = componentsSection[componentType].(map[string]any); !ok { - return fmt.Errorf("'components/%s' section is missing in the stack file '%s'", componentType, stack) + return fmt.Errorf("'components.%s' section is missing in the stack manifest '%s'", componentType, stack) } if componentSection, ok = componentTypeSection[component].(map[string]any); !ok { - return fmt.Errorf("no config found for the component '%s' in the stack file '%s'", component, stack) + return fmt.Errorf("no config found for the component '%s' in the stack manifest '%s'", component, stack) } if componentVarsSection, ok = componentSection["vars"].(map[any]any); !ok { - return fmt.Errorf("missing 'vars' section for the component '%s' in the stack file '%s'", component, stack) + return fmt.Errorf("missing 'vars' section for the component '%s' in the stack manifest '%s'", component, stack) } if componentProvidersSection, ok = componentSection[cfg.ProvidersSectionName].(map[any]any); !ok { componentProvidersSection = map[any]any{} } - if componentBackendSection, ok = componentSection["backend"].(map[any]any); !ok { + if componentBackendSection, ok = componentSection[cfg.BackendSectionName].(map[any]any); !ok { componentBackendSection = nil } - if componentBackendType, ok = componentSection["backend_type"].(string); !ok { + if componentBackendType, ok = componentSection[cfg.BackendTypeSectionName].(string); !ok { componentBackendType = "" } if componentImportsSection, ok = stackSection["imports"].([]string); !ok { @@ -112,10 +113,10 @@ func FindComponentConfig( if command, ok = componentSection["command"].(string); !ok { command = "" } - if componentEnvSection, ok = componentSection["env"].(map[any]any); !ok { + if componentEnvSection, ok = componentSection[cfg.EnvSectionName].(map[any]any); !ok { componentEnvSection = map[any]any{} } - if componentSettingsSection, ok = componentSection["settings"].(map[any]any); !ok { + if componentSettingsSection, ok = componentSection[cfg.SettingsSectionName].(map[any]any); !ok { componentSettingsSection = map[any]any{} } if componentOverridesSection, ok = componentSection[cfg.OverridesSectionName].(map[any]any); !ok { @@ -284,9 +285,9 @@ func ProcessStacks( if cliConfig.Logs.Level == u.LogLevelTrace { var msg string if cliConfig.StackType == "Directory" { - msg = "\nFound the config file for the provided stack:" + msg = "\nFound stack manifest:" } else { - msg = "\nFound stack config files:" + msg = "\nFound stack manifests:" } u.LogTrace(cliConfig, msg) err = u.PrintAsYAML(cliConfig.StackConfigFilesRelativePaths) @@ -297,7 +298,7 @@ func ProcessStacks( // Check and process stacks if cliConfig.StackType == "Directory" { - err = FindComponentConfig( + err = ProcessComponentConfig( &configAndStacksInfo, configAndStacksInfo.Stack, stacksMap, @@ -318,7 +319,7 @@ func ProcessStacks( configAndStacksInfo.ContextPrefix, err = cfg.GetContextPrefix(configAndStacksInfo.Stack, configAndStacksInfo.Context, - cliConfig.Stacks.NamePattern, + GetStackNamePattern(cliConfig), configAndStacksInfo.Stack, ) if err != nil { @@ -330,8 +331,8 @@ func ProcessStacks( var foundConfigAndStacksInfo schema.ConfigAndStacksInfo for stackName := range stacksMap { - // Check if we've found the component config - err = FindComponentConfig( + // Check if we've found the component in the stack + err = ProcessComponentConfig( &configAndStacksInfo, stackName, stacksMap, @@ -344,20 +345,31 @@ func ProcessStacks( configAndStacksInfo.ComponentEnvList = u.ConvertEnvVars(configAndStacksInfo.ComponentEnvSection) - // Process context - configAndStacksInfo.Context = cfg.GetContextFromVars(configAndStacksInfo.ComponentVarsSection) + if cliConfig.Stacks.NameTemplate != "" { + tmpl, err2 := u.ProcessTmpl("name-template", cliConfig.Stacks.NameTemplate, configAndStacksInfo.ComponentSection, false) + if err2 != nil { + continue + } + configAndStacksInfo.ContextPrefix = tmpl + } else if cliConfig.Stacks.NamePattern != "" { + // Process context + configAndStacksInfo.Context = cfg.GetContextFromVars(configAndStacksInfo.ComponentVarsSection) + + configAndStacksInfo.ContextPrefix, err = cfg.GetContextPrefix(configAndStacksInfo.Stack, + configAndStacksInfo.Context, + GetStackNamePattern(cliConfig), + stackName, + ) + if err != nil { + continue + } + } else { + return configAndStacksInfo, errors.New("'stacks.name_pattern' or 'stacks.name_template' needs to be specified in 'atmos.yaml' CLI config") + } + configAndStacksInfo.Context.Component = configAndStacksInfo.ComponentFromArg configAndStacksInfo.Context.BaseComponent = configAndStacksInfo.BaseComponentPath - configAndStacksInfo.ContextPrefix, err = cfg.GetContextPrefix(configAndStacksInfo.Stack, - configAndStacksInfo.Context, - cliConfig.Stacks.NamePattern, - stackName, - ) - if err != nil { - continue - } - // Check if we've found the stack if configAndStacksInfo.Stack == configAndStacksInfo.ContextPrefix { configAndStacksInfo.StackFile = stackName @@ -367,7 +379,7 @@ func ProcessStacks( u.LogDebug( cliConfig, - fmt.Sprintf("Found config for the component '%s' for the stack '%s' in the stack config file '%s'", + fmt.Sprintf("Found component '%s' in the stack '%s' in the stack manifest '%s'", configAndStacksInfo.ComponentFromArg, configAndStacksInfo.Stack, stackName, @@ -384,21 +396,20 @@ func ProcessStacks( } return configAndStacksInfo, - fmt.Errorf("\nSearched all stack YAML files, but could not find config for the component '%s' in the stack '%s'.\n"+ - "Check that all variables in the stack name pattern '%s' are correctly defined in the stack config files.\n"+ + fmt.Errorf("\nCould not find the component '%s' in the stack '%s'.\n"+ + "Check that all the context variables are correctly defined in the stack manifests.\n"+ "Are the component and stack names correct? Did you forget an import?%v\n", configAndStacksInfo.ComponentFromArg, configAndStacksInfo.Stack, - cliConfig.Stacks.NamePattern, cliConfigYaml) } else if foundStackCount > 1 { - err = fmt.Errorf("\nFound duplicate config for the component '%s' for the stack '%s' in the files: %v.\n"+ - "Check that all context variables in the stack name pattern '%s' are correctly defined in the files and not duplicated.\n"+ + err = fmt.Errorf("\nFound duplicate config for the component '%s' in the stack '%s' in the manifests: %v.\n"+ + "Check that all the context variables are correctly defined in the manifests and not duplicated.\n"+ "Check that all imports are valid.", configAndStacksInfo.ComponentFromArg, configAndStacksInfo.Stack, strings.Join(foundStacks, ", "), - cliConfig.Stacks.NamePattern) + ) u.LogErrorAndExit(err) } else { configAndStacksInfo = foundConfigAndStacksInfo @@ -444,13 +455,8 @@ func ProcessStacks( configAndStacksInfo.FinalComponent = configAndStacksInfo.Component } - // workspace - workspace, err := BuildTerraformWorkspace( - configAndStacksInfo.Stack, - cliConfig.Stacks.NamePattern, - configAndStacksInfo.ComponentMetadataSection, - configAndStacksInfo.Context, - ) + // Terraform workspace + workspace, err := BuildTerraformWorkspace(cliConfig, configAndStacksInfo) if err != nil { return configAndStacksInfo, err } @@ -480,34 +486,19 @@ func ProcessStacks( } // Spacelift stack - spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig( - cliConfig, - configAndStacksInfo.ComponentFromArg, - configAndStacksInfo.Stack, - configAndStacksInfo.ComponentSettingsSection, - configAndStacksInfo.ComponentVarsSection, - ) - + spaceliftStackName, err := BuildSpaceliftStackNameFromComponentConfig(cliConfig, configAndStacksInfo) if err != nil { return configAndStacksInfo, err } - if spaceliftStackName != "" { configAndStacksInfo.ComponentSection["spacelift_stack"] = spaceliftStackName } // Atlantis project - atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig( - cliConfig, - configAndStacksInfo.ComponentFromArg, - configAndStacksInfo.ComponentSettingsSection, - configAndStacksInfo.ComponentVarsSection, - ) - + atlantisProjectName, err := BuildAtlantisProjectNameFromComponentConfig(cliConfig, configAndStacksInfo) if err != nil { return configAndStacksInfo, err } - if atlantisProjectName != "" { configAndStacksInfo.ComponentSection["atlantis_project"] = atlantisProjectName } @@ -543,6 +534,59 @@ func ProcessStacks( configAndStacksInfo.ComponentSection["deps"] = componentDeps configAndStacksInfo.ComponentSection["deps_all"] = componentDepsAll + // Process `Go` templates in sections + componentSectionStr, err := u.ConvertToYAML(configAndStacksInfo.ComponentSection) + if err != nil { + return configAndStacksInfo, err + } + + componentSectionProcessed, err := u.ProcessTmpl("all-sections", componentSectionStr, configAndStacksInfo.ComponentSection, true) + if err != nil { + return configAndStacksInfo, err + } + + componentSectionConverted, err := c.YAMLToMapOfInterfaces(componentSectionProcessed) + if err != nil { + return configAndStacksInfo, err + } + + configAndStacksInfo.ComponentSection = c.MapsOfInterfacesToMapsOfStrings(componentSectionConverted) + if err != nil { + return configAndStacksInfo, err + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.ProvidersSectionName].(map[any]any); ok { + configAndStacksInfo.ComponentProvidersSection = i + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.VarsSectionName].(map[any]any); ok { + configAndStacksInfo.ComponentVarsSection = i + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.SettingsSectionName].(map[any]any); ok { + configAndStacksInfo.ComponentSettingsSection = i + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.EnvSectionName].(map[any]any); ok { + configAndStacksInfo.ComponentEnvSection = i + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.OverridesSectionName].(map[any]any); ok { + configAndStacksInfo.ComponentOverridesSection = i + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.MetadataSectionName].(map[any]any); ok { + configAndStacksInfo.ComponentMetadataSection = i + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.BackendSectionName].(map[any]any); ok { + configAndStacksInfo.ComponentBackendSection = i + } + + if i, ok := configAndStacksInfo.ComponentSection[cfg.BackendTypeSectionName].(string); ok { + configAndStacksInfo.ComponentBackendType = i + } + return configAndStacksInfo, nil } diff --git a/pkg/component/component_processor.go b/pkg/component/component_processor.go index 0f460b967..766ed2d02 100644 --- a/pkg/component/component_processor.go +++ b/pkg/component/component_processor.go @@ -65,13 +65,13 @@ func ProcessComponentFromContext( return nil, err } - if len(cliConfig.Stacks.NamePattern) < 1 { + if len(e.GetStackNamePattern(cliConfig)) < 1 { er := errors.New("stack name pattern must be provided in 'stacks.name_pattern' CLI config or 'ATMOS_STACKS_NAME_PATTERN' ENV variable") u.LogError(er) return nil, er } - stack, err := cfg.GetStackNameFromContextAndStackNamePattern(namespace, tenant, environment, stage, cliConfig.Stacks.NamePattern) + stack, err := cfg.GetStackNameFromContextAndStackNamePattern(namespace, tenant, environment, stage, e.GetStackNamePattern(cliConfig)) if err != nil { u.LogError(err) return nil, err diff --git a/pkg/component/component_processor_test.go b/pkg/component/component_processor_test.go index 7bd6867ab..944dc35be 100644 --- a/pkg/component/component_processor_test.go +++ b/pkg/component/component_processor_test.go @@ -25,7 +25,7 @@ func TestComponentProcessor(t *testing.T) { tenant1Ue2DevTestTestComponentWorkspace := tenant1Ue2DevTestTestComponent["workspace"].(string) tenant1Ue2DevTestTestComponentBackendWorkspaceKeyPrefix := tenant1Ue2DevTestTestComponentBackend["workspace_key_prefix"].(string) tenant1Ue2DevTestTestComponentRemoteStateBackendWorkspaceKeyPrefix := tenant1Ue2DevTestTestComponentRemoteStateBackend["workspace_key_prefix"].(string) - tenant1Ue2DevTestTestComponentDeps := tenant1Ue2DevTestTestComponent["deps"].([]string) + tenant1Ue2DevTestTestComponentDeps := tenant1Ue2DevTestTestComponent["deps"].([]any) assert.Equal(t, "test-test-component", tenant1Ue2DevTestTestComponentBackendWorkspaceKeyPrefix) assert.Equal(t, "test-test-component", tenant1Ue2DevTestTestComponentRemoteStateBackendWorkspaceKeyPrefix) assert.Equal(t, "test/test-component", tenant1Ue2DevTestTestComponentBaseComponent) @@ -56,7 +56,7 @@ func TestComponentProcessor(t *testing.T) { tenant1Ue2DevTestTestComponentWorkspace2 := tenant1Ue2DevTestTestComponent2["workspace"].(string) tenant1Ue2DevTestTestComponentBackendWorkspaceKeyPrefix2 := tenant1Ue2DevTestTestComponentBackend2["workspace_key_prefix"].(string) tenant1Ue2DevTestTestComponentRemoteStateBackendWorkspaceKeyPrefix2 := tenant1Ue2DevTestTestComponentRemoteStateBackend2["workspace_key_prefix"].(string) - tenant1Ue2DevTestTestComponentDeps2 := tenant1Ue2DevTestTestComponent2["deps"].([]string) + tenant1Ue2DevTestTestComponentDeps2 := tenant1Ue2DevTestTestComponent2["deps"].([]any) assert.Equal(t, "test-test-component", tenant1Ue2DevTestTestComponentBackendWorkspaceKeyPrefix2) assert.Equal(t, "test-test-component", tenant1Ue2DevTestTestComponentRemoteStateBackendWorkspaceKeyPrefix2) assert.Equal(t, "test/test-component", tenant1Ue2DevTestTestComponentBaseComponent2) @@ -86,7 +86,7 @@ func TestComponentProcessor(t *testing.T) { tenant1Ue2DevTestTestComponentOverrideComponentBaseComponent := tenant1Ue2DevTestTestComponentOverrideComponent["component"].(string) tenant1Ue2DevTestTestComponentOverrideComponentWorkspace := tenant1Ue2DevTestTestComponentOverrideComponent["workspace"].(string) tenant1Ue2DevTestTestComponentOverrideComponentBackendWorkspaceKeyPrefix := tenant1Ue2DevTestTestComponentOverrideComponentBackend["workspace_key_prefix"].(string) - tenant1Ue2DevTestTestComponentOverrideComponentDeps := tenant1Ue2DevTestTestComponentOverrideComponent["deps"].([]string) + tenant1Ue2DevTestTestComponentOverrideComponentDeps := tenant1Ue2DevTestTestComponentOverrideComponent["deps"].([]any) tenant1Ue2DevTestTestComponentOverrideComponentRemoteStateBackend := tenant1Ue2DevTestTestComponentOverrideComponent["remote_state_backend"].(map[any]any) tenant1Ue2DevTestTestComponentOverrideComponentRemoteStateBackendVal2 := tenant1Ue2DevTestTestComponentOverrideComponentRemoteStateBackend["val2"].(string) assert.Equal(t, "test-test-component", tenant1Ue2DevTestTestComponentOverrideComponentBackendWorkspaceKeyPrefix) @@ -144,7 +144,7 @@ func TestComponentProcessor(t *testing.T) { tenant1Ue2DevTestTestComponentOverrideComponent3, err = ProcessComponentInStack(component, stack, "", "") assert.Nil(t, err) - tenant1Ue2DevTestTestComponentOverrideComponent3Deps := tenant1Ue2DevTestTestComponentOverrideComponent3["deps"].([]string) + tenant1Ue2DevTestTestComponentOverrideComponent3Deps := tenant1Ue2DevTestTestComponentOverrideComponent3["deps"].([]any) assert.Equal(t, 11, len(tenant1Ue2DevTestTestComponentOverrideComponent3Deps)) assert.Equal(t, "catalog/terraform/mixins/test-2", tenant1Ue2DevTestTestComponentOverrideComponent3Deps[0]) diff --git a/pkg/config/const.go b/pkg/config/const.go index cedf386ee..084330bc3 100644 --- a/pkg/config/const.go +++ b/pkg/config/const.go @@ -39,9 +39,15 @@ const ( ComponentVendorConfigFileName = "component.yaml" AtmosVendorConfigFileName = "vendor.yaml" - ImportSectionName = "import" - OverridesSectionName = "overrides" - ProvidersSectionName = "providers" + ImportSectionName = "import" + OverridesSectionName = "overrides" + ProvidersSectionName = "providers" + VarsSectionName = "vars" + SettingsSectionName = "settings" + EnvSectionName = "env" + BackendSectionName = "backend" + BackendTypeSectionName = "backend_type" + MetadataSectionName = "metadata" LogsLevelFlag = "--logs-level" LogsFileFlag = "--logs-file" diff --git a/pkg/config/utils.go b/pkg/config/utils.go index fa56867c0..404ad4075 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -180,6 +180,12 @@ func processEnvVars(cliConfig *schema.CliConfiguration) error { cliConfig.Stacks.NamePattern = stacksNamePattern } + stacksNameTemplate := os.Getenv("ATMOS_STACKS_NAME_TEMPLATE") + if len(stacksNameTemplate) > 0 { + u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_STACKS_NAME_TEMPLATE=%s", stacksNameTemplate)) + cliConfig.Stacks.NameTemplate = stacksNameTemplate + } + componentsTerraformBasePath := os.Getenv("ATMOS_COMPONENTS_TERRAFORM_BASE_PATH") if len(componentsTerraformBasePath) > 0 { u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_COMPONENTS_TERRAFORM_BASE_PATH=%s", componentsTerraformBasePath)) @@ -439,7 +445,7 @@ func GetContextPrefix(stack string, context schema.Context, stackNamePattern str if part == "{namespace}" { if len(context.Namespace) == 0 { return "", - fmt.Errorf("the stack name pattern '%s' specifies 'namespace`, but the stack '%s' does not have a namespace defined in the stack file '%s'", + fmt.Errorf("the stack name pattern '%s' specifies 'namespace', but the stack '%s' does not have a namespace defined in the stack file '%s'", stackNamePattern, stack, stackFile, @@ -453,7 +459,7 @@ func GetContextPrefix(stack string, context schema.Context, stackNamePattern str } else if part == "{tenant}" { if len(context.Tenant) == 0 { return "", - fmt.Errorf("the stack name pattern '%s' specifies 'tenant`, but the stack '%s' does not have a tenant defined in the stack file '%s'", + fmt.Errorf("the stack name pattern '%s' specifies 'tenant', but the stack '%s' does not have a tenant defined in the stack file '%s'", stackNamePattern, stack, stackFile, @@ -467,7 +473,7 @@ func GetContextPrefix(stack string, context schema.Context, stackNamePattern str } else if part == "{environment}" { if len(context.Environment) == 0 { return "", - fmt.Errorf("the stack name pattern '%s' specifies 'environment`, but the stack '%s' does not have an environment defined in the stack file '%s'", + fmt.Errorf("the stack name pattern '%s' specifies 'environment', but the stack '%s' does not have an environment defined in the stack file '%s'", stackNamePattern, stack, stackFile, @@ -481,7 +487,7 @@ func GetContextPrefix(stack string, context schema.Context, stackNamePattern str } else if part == "{stage}" { if len(context.Stage) == 0 { return "", - fmt.Errorf("the stack name pattern '%s' specifies 'stage`, but the stack '%s' does not have a stage defined in the stack file '%s'", + fmt.Errorf("the stack name pattern '%s' specifies 'stage', but the stack '%s' does not have a stage defined in the stack file '%s'", stackNamePattern, stack, stackFile, diff --git a/pkg/describe/describe_component_test.go b/pkg/describe/describe_component_test.go index 8e4438cb1..8830c0405 100644 --- a/pkg/describe/describe_component_test.go +++ b/pkg/describe/describe_component_test.go @@ -64,3 +64,15 @@ func TestDescribeComponent5(t *testing.T) { assert.Nil(t, err) t.Log(string(componentSectionYaml)) } + +func TestDescribeComponent6(t *testing.T) { + component := "infra/vpc" + stack := "tenant1-ue2-dev" + + componentSection, err := e.ExecuteDescribeComponent(component, stack) + assert.Nil(t, err) + + componentSectionYaml, err := yaml.Marshal(componentSection) + assert.Nil(t, err) + t.Log(string(componentSectionYaml)) +} diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index fbed7d14c..9648b99af 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -48,6 +48,7 @@ type Stacks struct { IncludedPaths []string `yaml:"included_paths" json:"included_paths" mapstructure:"included_paths"` ExcludedPaths []string `yaml:"excluded_paths" json:"excluded_paths" mapstructure:"excluded_paths"` NamePattern string `yaml:"name_pattern" json:"name_pattern" mapstructure:"name_pattern"` + NameTemplate string `yaml:"name_template" json:"name_template" mapstructure:"name_template"` } type Workflows struct { diff --git a/pkg/spacelift/spacelift_stack_processor.go b/pkg/spacelift/spacelift_stack_processor.go index 56d2ef2ed..4ac75d6da 100644 --- a/pkg/spacelift/spacelift_stack_processor.go +++ b/pkg/spacelift/spacelift_stack_processor.go @@ -28,6 +28,12 @@ func CreateSpaceliftStacks( stackConfigPathTemplate string, ) (map[string]any, error) { + cliConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, true) + if err != nil { + u.LogError(err) + return nil, err + } + if len(filePaths) > 0 { _, stacks, rawStackConfigs, err := s.ProcessYAMLConfigFiles( stacksBasePath, @@ -43,14 +49,15 @@ func CreateSpaceliftStacks( return nil, err } - return TransformStackConfigToSpaceliftStacks(stacks, stackConfigPathTemplate, "", processImports, rawStackConfigs) + return TransformStackConfigToSpaceliftStacks( + cliConfig, + stacks, + stackConfigPathTemplate, + "", + processImports, + rawStackConfigs, + ) } else { - cliConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, true) - if err != nil { - u.LogError(err) - return nil, err - } - _, stacks, rawStackConfigs, err := s.ProcessYAMLConfigFiles( cliConfig.StacksBaseAbsolutePath, cliConfig.TerraformDirAbsolutePath, @@ -66,9 +73,10 @@ func CreateSpaceliftStacks( } return TransformStackConfigToSpaceliftStacks( + cliConfig, stacks, stackConfigPathTemplate, - cliConfig.Stacks.NamePattern, + e.GetStackNamePattern(cliConfig), processImports, rawStackConfigs, ) @@ -77,6 +85,7 @@ func CreateSpaceliftStacks( // TransformStackConfigToSpaceliftStacks takes a map of stack manifests and transforms it to a map of Spacelift stacks func TransformStackConfigToSpaceliftStacks( + cliConfig schema.CliConfiguration, stacks map[string]any, stackConfigPathTemplate string, stackNamePattern string, @@ -207,7 +216,6 @@ func TransformStackConfigToSpaceliftStacks( } spaceliftConfig["backend"] = componentBackend - // Component dependencies configAndStacksInfo := schema.ConfigAndStacksInfo{ ComponentFromArg: component, ComponentType: "terraform", @@ -215,11 +223,22 @@ func TransformStackConfigToSpaceliftStacks( ComponentVarsSection: componentVars, ComponentEnvSection: componentEnv, ComponentSettingsSection: componentSettings, + ComponentMetadataSection: componentMetadata, ComponentBackendSection: componentBackend, ComponentBackendType: backendTypeName, ComponentInheritanceChain: componentInheritance, + Context: context, + ComponentSection: map[string]any{ + cfg.VarsSectionName: componentVars, + cfg.EnvSectionName: componentEnv, + cfg.SettingsSectionName: componentSettings, + cfg.MetadataSectionName: componentMetadata, + cfg.BackendSectionName: componentBackend, + cfg.BackendTypeSectionName: backendTypeName, + }, } + // Component dependencies sources, err := e.ProcessConfigSources(configAndStacksInfo, rawStackConfigs) if err != nil { return nil, err @@ -234,12 +253,7 @@ func TransformStackConfigToSpaceliftStacks( spaceliftConfig["deps_all"] = componentDepsAll // Terraform workspace - workspace, err := e.BuildTerraformWorkspace( - stackName, - stackNamePattern, - componentMetadata, - context, - ) + workspace, err := e.BuildTerraformWorkspace(cliConfig, configAndStacksInfo) if err != nil { u.LogError(err) return nil, err @@ -360,7 +374,11 @@ func TransformStackConfigToSpaceliftStacks( spaceliftConfig["labels"] = u.UniqueStrings(labels) // Spacelift stack name - spaceliftStackName, spaceliftStackNamePattern := e.BuildSpaceliftStackName(spaceliftSettings, context, contextPrefix) + spaceliftStackName, spaceliftStackNamePattern, err := e.BuildSpaceliftStackName(spaceliftSettings, context, contextPrefix) + if err != nil { + u.LogError(err) + return nil, err + } // Add Spacelift stack config to the final map spaceliftStackNameKey := strings.Replace(spaceliftStackName, "/", "-", -1) diff --git a/pkg/stack/stack_processor.go b/pkg/stack/stack_processor.go index e0d289725..c4f9b80de 100644 --- a/pkg/stack/stack_processor.go +++ b/pkg/stack/stack_processor.go @@ -3,19 +3,15 @@ package stack import ( "encoding/json" "fmt" + "github.com/pkg/errors" + "github.com/santhosh-tekuri/jsonschema/v5" + "gopkg.in/yaml.v2" "os" "path" "path/filepath" "sort" "strings" "sync" - "text/template" - "text/template/parse" - - "github.com/Masterminds/sprig/v3" - "github.com/pkg/errors" - "github.com/santhosh-tekuri/jsonschema/v5" - "gopkg.in/yaml.v2" cfg "github.com/cloudposse/atmos/pkg/config" c "github.com/cloudposse/atmos/pkg/convert" @@ -370,23 +366,13 @@ func ProcessYAMLConfigFile( importMatches, err = u.GetGlobMatches(impWithExtPath) if err != nil || len(importMatches) == 0 { // The import was not found -> check if the import is a Go template; if not, return the error - t, err2 := template.New(imp).Funcs(sprig.FuncMap()).Parse(imp) + isGolangTemplate, err2 := u.IsGolangTemplate(imp) if err2 != nil { return nil, nil, nil, err2 } - isGoTemplate := false - - // Iterate over all nodes in the template and check if any of them is of type `NodeAction` (field evaluation) - for _, node := range t.Root.Nodes { - if node.Type() == parse.NodeAction { - isGoTemplate = true - break - } - } - // If the import is not a Go template and SkipIfMissing is false, return the error - if !isGoTemplate && !importStruct.SkipIfMissing { + if !isGolangTemplate && !importStruct.SkipIfMissing { if err != nil { errorMessage := fmt.Sprintf("no matches found for the import '%s' in the file '%s'\nError: %s", imp, @@ -424,7 +410,7 @@ func ProcessYAMLConfigFile( c.MapsOfInterfacesToMapsOfStrings(mergedContext), ignoreMissingFiles, importStruct.SkipTemplatesProcessing, - importStruct.IgnoreMissingTemplateValues, + true, // importStruct.IgnoreMissingTemplateValues, importStruct.SkipIfMissing, finalTerraformOverrides, finalHelmfileOverrides, @@ -695,7 +681,7 @@ func ProcessStackConfig( } componentVars := map[any]any{} - if i, ok := componentMap["vars"]; ok { + if i, ok := componentMap[cfg.VarsSectionName]; ok { componentVars, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.vars' section in the file '%s'", component, stackName) @@ -703,7 +689,7 @@ func ProcessStackConfig( } componentSettings := map[any]any{} - if i, ok := componentMap["settings"]; ok { + if i, ok := componentMap[cfg.SettingsSectionName]; ok { componentSettings, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.settings' section in the file '%s'", component, stackName) @@ -718,7 +704,7 @@ func ProcessStackConfig( } componentEnv := map[any]any{} - if i, ok := componentMap["env"]; ok { + if i, ok := componentMap[cfg.EnvSectionName]; ok { componentEnv, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.env' section in the file '%s'", component, stackName) @@ -736,7 +722,7 @@ func ProcessStackConfig( // Component metadata. // This is per component, not deep-merged and not inherited from base components and globals. componentMetadata := map[any]any{} - if i, ok := componentMap["metadata"]; ok { + if i, ok := componentMap[cfg.MetadataSectionName]; ok { componentMetadata, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.metadata' section in the file '%s'", component, stackName) @@ -747,14 +733,14 @@ func ProcessStackConfig( componentBackendType := "" componentBackendSection := map[any]any{} - if i, ok := componentMap["backend_type"]; ok { + if i, ok := componentMap[cfg.BackendTypeSectionName]; ok { componentBackendType, ok = i.(string) if !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.backend_type' attribute in the file '%s'", component, stackName) } } - if i, ok := componentMap["backend"]; ok { + if i, ok := componentMap[cfg.BackendSectionName]; ok { componentBackendSection, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.backend' section in the file '%s'", component, stackName) @@ -795,24 +781,24 @@ func ProcessStackConfig( componentOverridesProviders := map[any]any{} componentOverridesTerraformCommand := "" - if i, ok := componentMap["overrides"]; ok { + if i, ok := componentMap[cfg.OverridesSectionName]; ok { if componentOverrides, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.overrides' in the manifest '%s'", component, stackName) } - if i, ok = componentOverrides["vars"]; ok { + if i, ok = componentOverrides[cfg.VarsSectionName]; ok { if componentOverridesVars, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.overrides.vars' in the manifest '%s'", component, stackName) } } - if i, ok = componentOverrides["settings"]; ok { + if i, ok = componentOverrides[cfg.SettingsSectionName]; ok { if componentOverridesSettings, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.overrides.settings' in the manifest '%s'", component, stackName) } } - if i, ok = componentOverrides["env"]; ok { + if i, ok = componentOverrides[cfg.EnvSectionName]; ok { if componentOverridesEnv, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.terraform.%s.overrides.env' in the manifest '%s'", component, stackName) } @@ -1147,16 +1133,16 @@ func ProcessStackConfig( } comp := map[string]any{} - comp["vars"] = finalComponentVars - comp["settings"] = finalComponentSettings - comp["env"] = finalComponentEnv - comp["backend_type"] = finalComponentBackendType - comp["backend"] = finalComponentBackend + comp[cfg.VarsSectionName] = finalComponentVars + comp[cfg.SettingsSectionName] = finalComponentSettings + comp[cfg.EnvSectionName] = finalComponentEnv + comp[cfg.BackendTypeSectionName] = finalComponentBackendType + comp[cfg.BackendSectionName] = finalComponentBackend comp["remote_state_backend_type"] = finalComponentRemoteStateBackendType comp["remote_state_backend"] = finalComponentRemoteStateBackend comp["command"] = finalComponentTerraformCommand comp["inheritance"] = componentInheritanceChain - comp["metadata"] = componentMetadata + comp[cfg.MetadataSectionName] = componentMetadata comp[cfg.OverridesSectionName] = componentOverrides comp[cfg.ProvidersSectionName] = finalComponentProviders @@ -1187,7 +1173,7 @@ func ProcessStackConfig( } componentVars := map[any]any{} - if i2, ok := componentMap["vars"]; ok { + if i2, ok := componentMap[cfg.VarsSectionName]; ok { componentVars, ok = i2.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.vars' section in the file '%s'", component, stackName) @@ -1195,7 +1181,7 @@ func ProcessStackConfig( } componentSettings := map[any]any{} - if i, ok := componentMap["settings"]; ok { + if i, ok := componentMap[cfg.SettingsSectionName]; ok { componentSettings, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.settings' section in the file '%s'", component, stackName) @@ -1203,7 +1189,7 @@ func ProcessStackConfig( } componentEnv := map[any]any{} - if i, ok := componentMap["env"]; ok { + if i, ok := componentMap[cfg.EnvSectionName]; ok { componentEnv, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.env' section in the file '%s'", component, stackName) @@ -1213,7 +1199,7 @@ func ProcessStackConfig( // Component metadata. // This is per component, not deep-merged and not inherited from base components and globals. componentMetadata := map[any]any{} - if i, ok := componentMap["metadata"]; ok { + if i, ok := componentMap[cfg.MetadataSectionName]; ok { componentMetadata, ok = i.(map[any]any) if !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.metadata' section in the file '%s'", component, stackName) @@ -1235,24 +1221,24 @@ func ProcessStackConfig( componentOverridesEnv := map[any]any{} componentOverridesHelmfileCommand := "" - if i, ok := componentMap["overrides"]; ok { + if i, ok := componentMap[cfg.OverridesSectionName]; ok { if componentOverrides, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.overrides' in the manifest '%s'", component, stackName) } - if i, ok = componentOverrides["vars"]; ok { + if i, ok = componentOverrides[cfg.VarsSectionName]; ok { if componentOverridesVars, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.overrides.vars' in the manifest '%s'", component, stackName) } } - if i, ok = componentOverrides["settings"]; ok { + if i, ok = componentOverrides[cfg.SettingsSectionName]; ok { if componentOverridesSettings, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.overrides.settings' in the manifest '%s'", component, stackName) } } - if i, ok = componentOverrides["env"]; ok { + if i, ok = componentOverrides[cfg.EnvSectionName]; ok { if componentOverridesEnv, ok = i.(map[any]any); !ok { return nil, fmt.Errorf("invalid 'components.helmfile.%s.overrides.env' in the manifest '%s'", component, stackName) } @@ -1418,13 +1404,13 @@ func ProcessStackConfig( } comp := map[string]any{} - comp["vars"] = finalComponentVars - comp["settings"] = finalComponentSettings - comp["env"] = finalComponentEnv + comp[cfg.VarsSectionName] = finalComponentVars + comp[cfg.SettingsSectionName] = finalComponentSettings + comp[cfg.EnvSectionName] = finalComponentEnv comp["command"] = finalComponentHelmfileCommand comp["inheritance"] = componentInheritanceChain - comp["metadata"] = componentMetadata - comp["overrides"] = componentOverrides + comp[cfg.MetadataSectionName] = componentMetadata + comp[cfg.OverridesSectionName] = componentOverrides if baseComponentName != "" { comp["component"] = baseComponentName diff --git a/pkg/utils/template_utils.go b/pkg/utils/template_utils.go index 2c24c46df..d3880c4d7 100644 --- a/pkg/utils/template_utils.go +++ b/pkg/utils/template_utils.go @@ -3,6 +3,7 @@ package utils import ( "bytes" "text/template" + "text/template/parse" "github.com/Masterminds/sprig/v3" ) @@ -35,3 +36,23 @@ func ProcessTmpl(tmplName string, tmplValue string, tmplData any, ignoreMissingT return res.String(), nil } + +// IsGolangTemplate checks if the provided string is a Go template +func IsGolangTemplate(str string) (bool, error) { + t, err := template.New(str).Funcs(sprig.FuncMap()).Parse(str) + if err != nil { + return false, err + } + + isGoTemplate := false + + // Iterate over all nodes in the template and check if any of them is of type `NodeAction` (field evaluation) + for _, node := range t.Root.Nodes { + if node.Type() == parse.NodeAction { + isGoTemplate = true + break + } + } + + return isGoTemplate, nil +} diff --git a/website/docs/cli/configuration.mdx b/website/docs/cli/configuration.mdx index 65eff06ce..6684a4ef7 100644 --- a/website/docs/cli/configuration.mdx +++ b/website/docs/cli/configuration.mdx @@ -61,7 +61,16 @@ stacks: - "orgs/**/*" excluded_paths: - "**/_defaults.yaml" - name_pattern: '{tenant}-{environment}-{stage}' + # To define Atmos stack naming convention, use either `name_pattern` or `name_template`. + # `name_template` has higher priority (if `name_template` is specified, `name_pattern` will be ignored). + # `name_pattern` uses the predefined context tokens {namespace}, {tenant}, {environment}, {stage}. + # `name_pattern` can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var + name_pattern: "{tenant}-{environment}-{stage}" + # `name_template` is a Golang template. + # For the template tokens, and you can use any Atmos sections and attributes that the Atmos command + # `atmos describe component -s ` generates (refer to https://atmos.tools/cli/commands/describe/component). + # `name_template` can also be set using 'ATMOS_STACKS_NAME_TEMPLATE' ENV var + # name_template: "{{.vars.tenant}}-{{.vars.environment}}-{{.vars.stage}}" workflows: base_path: stacks/workflows logs: @@ -159,7 +168,7 @@ components: ## Stacks -Define the stack name pattern and specify where to find stacks. +Define the stack name pattern or template and specify where to find stacks. ```yaml stacks: @@ -177,8 +186,16 @@ stacks: # Tell Atmos that all `_defaults.yaml` files are not top-level stack manifests - "**/_defaults.yaml" - # Can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var + # To define Atmos stack naming convention, use either `name_pattern` or `name_template`. + # `name_template` has higher priority (if `name_template` is specified, `name_pattern` will be ignored). + # `name_pattern` uses the predefined context tokens {namespace}, {tenant}, {environment}, {stage}. + # `name_pattern` can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var name_pattern: "{tenant}-{environment}-{stage}" + # `name_template` is a Golang template. + # For the template tokens, and you can use any Atmos sections and attributes that the Atmos command + # `atmos describe component -s ` generates (refer to https://atmos.tools/cli/commands/describe/component). + # `name_template` can also be set using 'ATMOS_STACKS_NAME_TEMPLATE' ENV var + # name_template: "{{.vars.tenant}}-{{.vars.environment}}-{{.vars.stage}}" ``` - `stacks.base_path` specifies the path to the folder where **all** Atmos stack config files (stack manifests) are defined. @@ -207,7 +224,7 @@ stacks:
- `stacks.name_pattern` configures the name pattern for the top-level Atmos stacks using the context variables `namespace`, `tenant`, `environment` - and `stage` as the template tokens. Depending on the structure of your organization, OUs, accounts and regions, set `stacks.name_pattern` to the + and `stage` as the tokens. Depending on the structure of your organization, OUs, accounts and regions, set `stacks.name_pattern` to the following: - `name_pattern: {stage}` - if you use just one region and a few accounts (stages) in just one organization and one OU. In this case, the @@ -235,6 +252,49 @@ stacks: and `atmos terraform apply --stack org2-plat-ue1-prod`, where `org1` and `org2` are the organization names (defined as `namespace` in the corresponding `_defaults.yaml` config files for the organizations) +- `stacks.name_template` serves the same purpose as `stacks.name_pattern` (defines the naming convention for the top-level Atmos stacks), but + provides much more functionality. Instead of using the predefined context variables as tokens, it uses [Go templates](https://pkg.go.dev/text/template). + [Sprig Functions](https://masterminds.github.io/sprig/) are supported as well + + - For the `Go` template tokens, and you can use any Atmos sections (e.g. `vars`, `providers`, `settings`) + that the Atmos command [`atmos describe component -s `](/cli/commands/describe/component) generates + for a component in a stack. + + - `name_template: "{{.vars.tenant}}-{{.vars.environment}}-{{.vars.stage}}"` defines the same name pattern for the top-level + Atmos stacks as `name_pattern: "{tenant}-{environment}-{stage}"` does + + - Since `stacks.name_template` allows using any variables form the `vars` section (and other sections), you can define + your own naming convention for your organization or for different clouds (AWS, Azure, GCP). For example, in the + corresponding `_defaults.yaml` stack manifests, you can use the following variables: + + - `org` instead of `namespace` + - `division` instead of `tenant` + - `region` instead of `environment` + - `account` instead of `stage` + + Then define the following `stacks.name_template` in `atmos.yaml`: + + ```yaml title="atmos.yaml" + stacks: + name_template: "{{.vars.division}}-{{.vars.account}}-{{.vars.region}}" + ``` + + You will be able to execute all Atmos commands using the newly defined naming convention: + + ```shell + atmos terraform plan -s + atmos terraform apply -s + atmos describe component -s + ``` + + :::note + Use either `stacks.name_pattern` or `stacks.name_template` to define the naming convention for the top-level Atmos stacks. + + `stacks.name_template` has higher priority. + + If `stacks.name_template` is specified, `stacks.name_pattern` will be ignored. + ::: +
:::tip @@ -655,6 +715,7 @@ setting `ATMOS_STACKS_BASE_PATH` to a path in `/localhost` to your local develop | ATMOS_STACKS_INCLUDED_PATHS | stacks.included_paths | List of paths to use as top-level stack manifests | | ATMOS_STACKS_EXCLUDED_PATHS | stacks.excluded_paths | List of paths to not consider as top-level stacks | | ATMOS_STACKS_NAME_PATTERN | stacks.name_pattern | Stack name pattern to use as Atmos stack names | +| ATMOS_STACKS_NAME_TEMPLATE | stacks.name_template | Stack name Golang template to use as Atmos stack names | | ATMOS_WORKFLOWS_BASE_PATH | workflows.base_path | Base path to Atmos workflows | | ATMOS_SCHEMAS_JSONSCHEMA_BASE_PATH | schemas.jsonschema.base_path | Base path to JSON schemas for component validation | | ATMOS_SCHEMAS_OPA_BASE_PATH | schemas.opa.base_path | Base path to OPA policies for component validation | diff --git a/website/docs/core-concepts/stacks/catalogs.md b/website/docs/core-concepts/stacks/catalogs.md index 4ef83eba9..97b8811df 100644 --- a/website/docs/core-concepts/stacks/catalogs.md +++ b/website/docs/core-concepts/stacks/catalogs.md @@ -1,6 +1,6 @@ --- title: Stack Catalogs -sidebar_position: 7 +sidebar_position: 3 sidebar_label: Catalogs id: catalogs description: Catalogs are how to organize all Stack configurations for easy imports. diff --git a/website/docs/core-concepts/stacks/describe-stacks.mdx b/website/docs/core-concepts/stacks/describe-stacks.mdx index 4ea6b9c8c..228baf6a5 100644 --- a/website/docs/core-concepts/stacks/describe-stacks.mdx +++ b/website/docs/core-concepts/stacks/describe-stacks.mdx @@ -1,6 +1,6 @@ --- title: Describe Stacks -sidebar_position: 7 +sidebar_position: 4 sidebar_label: Describing id: describing description: Describe stacks to view the fully deep-merged configuration diff --git a/website/docs/core-concepts/stacks/imports.md b/website/docs/core-concepts/stacks/imports.md index cf5bcfec5..1d8e57729 100644 --- a/website/docs/core-concepts/stacks/imports.md +++ b/website/docs/core-concepts/stacks/imports.md @@ -1,6 +1,6 @@ --- title: Stack Imports -sidebar_position: 7 +sidebar_position: 5 sidebar_label: Imports id: imports --- diff --git a/website/docs/core-concepts/stacks/mixins.md b/website/docs/core-concepts/stacks/mixins.md index 77e80f583..2b6029462 100644 --- a/website/docs/core-concepts/stacks/mixins.md +++ b/website/docs/core-concepts/stacks/mixins.md @@ -1,6 +1,6 @@ --- title: Stack Mixins -sidebar_position: 7 +sidebar_position: 6 sidebar_label: Mixins id: mixins --- @@ -17,7 +17,7 @@ Mixins are treated the same as all other imports in Atmos, with no special handl ## Use-cases -Here are some use-cases for when to Mixins. +Here are some use-cases for when to use mixins. ### Mixins by Region @@ -29,9 +29,7 @@ For example, here's what it would look like for AWS. Let's name this file `mixin Now, anytime we want a Parent Stack deployed in the `us-east-1` region, we just need to specify this import, and we'll automatically inherit all the settings for that region. -For example, let's define a mixin with the defaults for operating in the `us-east-1` region. - -file named `mixins/stage/prod.yaml` +For example, let's define a mixin with the defaults for operating in the `us-east-1` region: ```yaml title="mixins/region/us-east-1.yaml" vars: @@ -78,6 +76,8 @@ terraform: # ... ``` +
+ :::tip Use Mixins for Naming Conventions This simple example highlights a simple fix for one of the most common issues in enterprise organizations: naming inconsistency. Using a mixin is a great way for organizations ensure naming conventions are followed consistently. diff --git a/website/docs/core-concepts/stacks/templating.md b/website/docs/core-concepts/stacks/templating.md new file mode 100644 index 000000000..69f6e241e --- /dev/null +++ b/website/docs/core-concepts/stacks/templating.md @@ -0,0 +1,131 @@ +--- +title: Stack Manifest Templating +sidebar_position: 7 +sidebar_label: Templating +id: templating +--- + +Atmos supports [Go templates](https://pkg.go.dev/text/template) in stack manifests. +[Sprig Functions](https://masterminds.github.io/sprig/) are supported as well. + +You can use `Go` templates in the following Atmos section to refer to values in the same or other sections: + + - `vars` + - `settings` + - `env` + - `metadata` + - `providers` + - `overrides` + - `backend` + - `backend_type` + +
+ +:::tip +In the template tokens, you can refer to any value in any section that the Atmos command +[`atmos describe component -s `](/cli/commands/describe/component) generates +::: + +
+ +For example, let's say we have the following component configuration using `Go` templates: + +```yaml +component: + terraform: + vpc: + settings: + setting1: 1 + setting2: 2 + setting3: "{{ .vars.var3 }}" + setting4: "{{ .settings.setting1 }}" + component: vpc + backend_type: s3 + region: "us-east-2" + assume_role: "" + backend_type: "{{ .settings.backend_type }}" + metadata: + component: "{{ .settings.component }}" + providers: + aws: + region: "{{ .settings.region }}" + assume_role: "{{ .settings.assume_role }}" + env: + ENV1: e1 + ENV2: "{{ .settings.setting1 }}-{{ .settings.setting2 }}" + vars: + var1: "{{ .settings.setting1 }}" + var2: "{{ .settings.setting2 }}" + var3: 3 + # Add the tags to all the resources provisioned by this Atmos component + tags: + atmos_component: "{{ .atmos_component }}" + atmos_stack: "{{ .atmos_stack }}" + atmos_manifest: "{{ .atmos_stack_file }}" + region: "{{ .vars.region }}" + terraform_workspace: "{{ .workspace }}" + assumed_role: "{{ .providers.aws.assume_role }}" + description: "{{ .atmos_component }} component provisioned in {{ .atmos_stack }} stack by assuming IAM role {{ .providers.aws.assume_role }}" + # `provisioned_at` uses the Sprig functions + # https://masterminds.github.io/sprig/date.html + # https://pkg.go.dev/time#pkg-constants + provisioned_at: '{{ dateInZone "2006-01-02T15:04:05Z07:00" (now) "UTC" }}' +``` + +When executing Atmos commands like `atmos describe component` and `atmos terraform plan/apply`, Atmos processes all the template tokens +in the manifest and generates the final configuration for the component in the stack: + +```yaml title="atmos describe component vpc -s plat-ue2-dev" +settings: + setting1: 1 + setting2: 2 + setting3: 3 + setting4: 1 + component: vpc + backend_type: s3 + region: us-east-2 + assume_role: +backend_type: s3 +metadata: + component: vpc +providers: + aws: + region: us-east-2 + assume_role: +env: + ENV1: e1 + ENV2: 1-2 +vars: + var1: 1 + var2: 2 + var3: 3 + tags: + assumed_role: + atmos_component: vpc + atmos_manifest: orgs/acme/plat/dev/us-east-2 + atmos_stack: plat-ue2-dev + description: vpc component provisioned in plat-ue2-dev stack by assuming IAM role + provisioned_at: "2024-03-12T16:18:24Z" + region: us-east-2 + terraform_workspace: plat-ue2-dev +``` + +
+ +While `Go` templates in Atmos stack manifests offer great flexibility for various use-cases, one of the obvious use-cases +is to add a standard set of tags to all the resources in the infrastructure. + +For example, by adding this configuration to the `stacks/orgs/acme/_defaults.yaml` Org-level stack manifest: + +```yaml title="stacks/orgs/acme/_defaults.yaml" +terraform: + vars: + tags: + atmos_component: "{{ .atmos_component }}" + atmos_stack: "{{ .atmos_stack }}" + atmos_manifest: "{{ .atmos_stack_file }}" + terraform_workspace: "{{ .workspace }}" + provisioned_at: '{{ dateInZone "2006-01-02T15:04:05Z07:00" (now) "UTC" }}' +``` + +the tags will be processed and automatically added to all the resources provisioned in the infrastructure. diff --git a/website/docs/core-concepts/stacks/validation.md b/website/docs/core-concepts/stacks/validation.md index 1f41256e1..92e0b13ad 100644 --- a/website/docs/core-concepts/stacks/validation.md +++ b/website/docs/core-concepts/stacks/validation.md @@ -1,6 +1,6 @@ --- title: Stack Validation -sidebar_position: 4 +sidebar_position: 2 sidebar_label: Validation description: Validate all Stack configurations and YAML syntax. id: validation diff --git a/website/docs/design-patterns/component-catalog-template.md b/website/docs/design-patterns/component-catalog-template.md index d08a836e4..27ee7afeb 100644 --- a/website/docs/design-patterns/component-catalog-template.md +++ b/website/docs/design-patterns/component-catalog-template.md @@ -111,17 +111,17 @@ components: vars: enabled: true tags: - Service: { { .app_name } } - service_account_name: { { .service_account_name } } - service_account_namespace: { { .service_account_namespace } } + Service: {{ .app_name }} + service_account_name: {{ .service_account_name }} + service_account_namespace: {{ .service_account_namespace }} # Example of using the Sprig functions in `Go` templates. # Refer to https://masterminds.github.io/sprig for more details. - { { if hasKey . "iam_managed_policy_arns" } } + {{ if hasKey . "iam_managed_policy_arns" }} iam_managed_policy_arns: - { { range $i, $iam_managed_policy_arn := .iam_managed_policy_arns } } + {{ range $i, $iam_managed_policy_arn := .iam_managed_policy_arns }} - '{{ $iam_managed_policy_arn }}' - { { end } } - { { - end } } + {{ end }} + {{ - end }} ``` Import the `stacks/catalog/eks/iam-role/defaults.tmpl` manifest template into a top-level stack, diff --git a/website/docs/integrations/atlantis.mdx b/website/docs/integrations/atlantis.mdx index d84cdea35..deb65caed 100644 --- a/website/docs/integrations/atlantis.mdx +++ b/website/docs/integrations/atlantis.mdx @@ -686,7 +686,7 @@ on: branches: [ main ] env: - ATMOS_VERSION: 1.65.0 + ATMOS_VERSION: 1.66.0 ATMOS_CLI_CONFIG_PATH: ./ jobs: diff --git a/website/docs/integrations/github-actions/setup-atmos.md b/website/docs/integrations/github-actions/setup-atmos.md index f4b918a04..c85d11439 100644 --- a/website/docs/integrations/github-actions/setup-atmos.md +++ b/website/docs/integrations/github-actions/setup-atmos.md @@ -27,5 +27,5 @@ jobs: uses: cloudposse/github-action-setup-atmos with: # Make sure to pin to the latest version of atmos - atmos_version: 1.65.0 + atmos_version: 1.66.0 ``` diff --git a/website/docs/quick-start/configure-terraform-backend.md b/website/docs/quick-start/configure-terraform-backend.md index 051937849..1e3bead3f 100644 --- a/website/docs/quick-start/configure-terraform-backend.md +++ b/website/docs/quick-start/configure-terraform-backend.md @@ -339,7 +339,7 @@ different scopes and generate the final backend config for the components in the We mentioned before that you can configure the Terraform backend for the components manually (by creating a file `backend.tf` in each Terraform component's folder), or you can set up Atmos to generate the backend configuration for each component in the stacks automatically. While auto-generating the backend config file is helpful and saves you from creating the backend files for each component, it becomes a requirements -when you provision multiple instance of a Terraform component into the san=me environment (same account and region). +when you provision multiple instance of a Terraform component into the same environment (same account and region). You can provision more than one instance of the same Terraform component (with the same or different settings) into the same environment by defining many Atmos components that provide configuration for the Terraform component. For example, the following config shows how to define two Atmos diff --git a/website/package-lock.json b/website/package-lock.json index 6440ac74b..e2bf957f2 100644 --- a/website/package-lock.json +++ b/website/package-lock.json @@ -19,7 +19,7 @@ "custom-loaders": "file:plugins/custom-loaders", "docusaurus-plugin-image-zoom": "^2.0.0", "html-loader": "^5.0.0", - "marked": "^12.0.0", + "marked": "^12.0.1", "prism-react-renderer": "^2.3.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -9225,9 +9225,9 @@ } }, "node_modules/marked": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.0.tgz", - "integrity": "sha512-Vkwtq9rLqXryZnWaQc86+FHLC6tr/fycMfYAhiOIXkrNmeGAyhSxjqu0Rs1i0bBqw5u0S7+lV9fdH2ZSVaoa0w==", + "version": "12.0.1", + "resolved": "https://registry.npmjs.org/marked/-/marked-12.0.1.tgz", + "integrity": "sha512-Y1/V2yafOcOdWQCX0XpAKXzDakPOpn6U0YLxTJs3cww6VxOzZV1BTOOYWLvH3gX38cq+iLwljHHTnMtlDfg01Q==", "bin": { "marked": "bin/marked.js" }, diff --git a/website/package.json b/website/package.json index e6f73c182..550d46695 100644 --- a/website/package.json +++ b/website/package.json @@ -25,7 +25,7 @@ "custom-loaders": "file:plugins/custom-loaders", "docusaurus-plugin-image-zoom": "^2.0.0", "html-loader": "^5.0.0", - "marked": "^12.0.0", + "marked": "^12.0.1", "prism-react-renderer": "^2.3.1", "react": "^18.2.0", "react-dom": "^18.2.0",