From c50d071266f4b9291cd78dc39b1974bf12b775d1 Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 24 Dec 2024 10:31:41 -0500 Subject: [PATCH 01/38] Handle relative paths for stack imports --- internal/exec/stack_processor_utils.go | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index ea35c1cd3..496854222 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1786,6 +1786,14 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St return nil, fmt.Errorf("invalid empty import in the file '%s'", filePath) } + // NEW: Handle relative paths + if !filepath.IsAbs(s) && !strings.Contains(s, "://") { + // Get the directory of the current file + baseDir := filepath.Dir(filePath) + // Join the base directory with the relative path + s = filepath.Join(baseDir, s) + } + result = append(result, schema.StackImport{Path: s}) } From f79e16a85adcbc6915239deaa327383ec20c6c4a Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 24 Dec 2024 10:50:34 -0500 Subject: [PATCH 02/38] Tests for relative paths --- examples/tests/stacks/mixins/stage/test2.yaml | 8 ++++++++ .../tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml | 5 +++++ .../tests/stacks/orgs/cp/tenant1/test2/global-region.yaml | 5 +++++ internal/exec/stack_processor_utils.go | 2 +- pkg/stack/stack_processor_test.go | 2 ++ 5 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 examples/tests/stacks/mixins/stage/test2.yaml create mode 100644 examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml create mode 100644 examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml diff --git a/examples/tests/stacks/mixins/stage/test2.yaml b/examples/tests/stacks/mixins/stage/test2.yaml new file mode 100644 index 000000000..0ad7c63dc --- /dev/null +++ b/examples/tests/stacks/mixins/stage/test2.yaml @@ -0,0 +1,8 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +vars: + stage: test-2 + +settings: + config: + is_prod: false diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml new file mode 100644 index 000000000..d32ce9f16 --- /dev/null +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +import: + - mixins/stage/test2 + - ../_defaults # validate relative paths diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml new file mode 100644 index 000000000..5b38de7be --- /dev/null +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +import: + - mixins/region/global-region + - _defaults # validate relative paths diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 496854222..1ea697649 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1786,7 +1786,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St return nil, fmt.Errorf("invalid empty import in the file '%s'", filePath) } - // NEW: Handle relative paths + // Handle relative paths if !filepath.IsAbs(s) && !strings.Contains(s, "://") { // Get the directory of the current file baseDir := filepath.Dir(filePath) diff --git a/pkg/stack/stack_processor_test.go b/pkg/stack/stack_processor_test.go index e7c39d26d..9dc2af898 100644 --- a/pkg/stack/stack_processor_test.go +++ b/pkg/stack/stack_processor_test.go @@ -19,6 +19,7 @@ func TestStackProcessor(t *testing.T) { "../../examples/tests/stacks/orgs/cp/tenant1/prod/us-east-2.yaml", "../../examples/tests/stacks/orgs/cp/tenant1/staging/us-east-2.yaml", "../../examples/tests/stacks/orgs/cp/tenant1/test1/us-east-2.yaml", + "../../examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml", } processStackDeps := true @@ -58,6 +59,7 @@ func TestStackProcessor(t *testing.T) { assert.Equal(t, "orgs/cp/tenant1/prod/us-east-2", mapResultKeys[1]) assert.Equal(t, "orgs/cp/tenant1/staging/us-east-2", mapResultKeys[2]) assert.Equal(t, "orgs/cp/tenant1/test1/us-east-2", mapResultKeys[3]) + assert.Equal(t, "orgs/cp/tenant1/test2/global-region", mapResultKeys[4]) mapConfig1, err := u.UnmarshalYAML[schema.AtmosSectionMapType](listResult[0]) assert.Nil(t, err) From 74ab1d884db83f3a8fb13342acf1fc4f9faf3f47 Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 24 Dec 2024 11:30:38 -0500 Subject: [PATCH 03/38] Refactor handling of relative paths in ProcessImportSection --- .../tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml | 2 +- .../tests/stacks/orgs/cp/tenant1/test2/global-region.yaml | 2 +- internal/exec/stack_processor_utils.go | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml index d32ce9f16..ed81c842c 100644 --- a/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml @@ -2,4 +2,4 @@ import: - mixins/stage/test2 - - ../_defaults # validate relative paths + - ./../_defaults # validate relative paths diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml index 5b38de7be..8b45cc563 100644 --- a/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml @@ -2,4 +2,4 @@ import: - mixins/region/global-region - - _defaults # validate relative paths + - ./_defaults # validate relative paths diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 1ea697649..baffdd56a 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1786,12 +1786,12 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St return nil, fmt.Errorf("invalid empty import in the file '%s'", filePath) } - // Handle relative paths - if !filepath.IsAbs(s) && !strings.Contains(s, "://") { + // Handle relative paths - only if they explicitly start with "./" + if strings.HasPrefix(s, "./") { // Get the directory of the current file baseDir := filepath.Dir(filePath) - // Join the base directory with the relative path - s = filepath.Join(baseDir, s) + // Join the base directory with the relative path (removing the "./" prefix) + s = filepath.Join(baseDir, s[2:]) } result = append(result, schema.StackImport{Path: s}) From e2af2dbbf2045aac3da1c8db3ecfbf77b4cd5aa2 Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 24 Dec 2024 11:55:55 -0500 Subject: [PATCH 04/38] Handle relative paths in StackImport.Path and string imports --- .../orgs/cp/tenant1/test2/_defaults.yaml | 2 +- .../{global-region.yaml => us-east-2.yaml} | 2 +- internal/exec/stack_processor_utils.go | 18 +++++++++++++----- pkg/stack/stack_processor_test.go | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) rename examples/tests/stacks/orgs/cp/tenant1/test2/{global-region.yaml => us-east-2.yaml} (82%) diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml index ed81c842c..d32ce9f16 100644 --- a/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/_defaults.yaml @@ -2,4 +2,4 @@ import: - mixins/stage/test2 - - ./../_defaults # validate relative paths + - ../_defaults # validate relative paths diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/us-east-2.yaml similarity index 82% rename from examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml rename to examples/tests/stacks/orgs/cp/tenant1/test2/us-east-2.yaml index 8b45cc563..8c42471da 100644 --- a/examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/us-east-2.yaml @@ -1,5 +1,5 @@ # yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json import: - - mixins/region/global-region + - mixins/region/us-east-2 - ./_defaults # validate relative paths diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index baffdd56a..f07ec5b61 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1773,6 +1773,15 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St var importObj schema.StackImport err := mapstructure.Decode(imp, &importObj) if err == nil { + // Handle relative paths in StackImport.Path + if strings.HasPrefix(importObj.Path, "./") || strings.HasPrefix(importObj.Path, "../") { + // Get the directory of the current file + baseDir := filepath.Dir(filePath) + // Join the base directory with the relative path + importObj.Path = filepath.Join(baseDir, importObj.Path) + // Clean the path to resolve any .. segments + importObj.Path = filepath.Clean(importObj.Path) + } result = append(result, importObj) continue } @@ -1786,12 +1795,11 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St return nil, fmt.Errorf("invalid empty import in the file '%s'", filePath) } - // Handle relative paths - only if they explicitly start with "./" - if strings.HasPrefix(s, "./") { - // Get the directory of the current file + // Handle relative paths in string imports + if strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") { baseDir := filepath.Dir(filePath) - // Join the base directory with the relative path (removing the "./" prefix) - s = filepath.Join(baseDir, s[2:]) + s = filepath.Join(baseDir, s) + s = filepath.Clean(s) } result = append(result, schema.StackImport{Path: s}) diff --git a/pkg/stack/stack_processor_test.go b/pkg/stack/stack_processor_test.go index 9dc2af898..ed06a6818 100644 --- a/pkg/stack/stack_processor_test.go +++ b/pkg/stack/stack_processor_test.go @@ -19,7 +19,7 @@ func TestStackProcessor(t *testing.T) { "../../examples/tests/stacks/orgs/cp/tenant1/prod/us-east-2.yaml", "../../examples/tests/stacks/orgs/cp/tenant1/staging/us-east-2.yaml", "../../examples/tests/stacks/orgs/cp/tenant1/test1/us-east-2.yaml", - "../../examples/tests/stacks/orgs/cp/tenant1/test2/global-region.yaml", + "../../examples/tests/stacks/orgs/cp/tenant1/test2/us-east-2.yaml", } processStackDeps := true @@ -59,7 +59,7 @@ func TestStackProcessor(t *testing.T) { assert.Equal(t, "orgs/cp/tenant1/prod/us-east-2", mapResultKeys[1]) assert.Equal(t, "orgs/cp/tenant1/staging/us-east-2", mapResultKeys[2]) assert.Equal(t, "orgs/cp/tenant1/test1/us-east-2", mapResultKeys[3]) - assert.Equal(t, "orgs/cp/tenant1/test2/global-region", mapResultKeys[4]) + assert.Equal(t, "orgs/cp/tenant1/test2/us-east-2", mapResultKeys[4]) mapConfig1, err := u.UnmarshalYAML[schema.AtmosSectionMapType](listResult[0]) assert.Nil(t, err) From 9d3901258c45bebbc6873d1af48f135790530545 Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 24 Dec 2024 12:37:54 -0500 Subject: [PATCH 05/38] Add new test2/us-west-1.yaml file and update test counts --- examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml | 7 +++++++ pkg/stack/stack_processor_test.go | 7 ++++--- 2 files changed, 11 insertions(+), 3 deletions(-) create mode 100644 examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml new file mode 100644 index 000000000..5245b0039 --- /dev/null +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +import: + #- path: mixins/region/us-west-1 + #- path: ./_defaults # validate relative paths using the `path` field + - mixins/region/us-west-1 + - ./_defaults # validate relative paths using the `path` field diff --git a/pkg/stack/stack_processor_test.go b/pkg/stack/stack_processor_test.go index ed06a6818..4c5745cba 100644 --- a/pkg/stack/stack_processor_test.go +++ b/pkg/stack/stack_processor_test.go @@ -20,6 +20,7 @@ func TestStackProcessor(t *testing.T) { "../../examples/tests/stacks/orgs/cp/tenant1/staging/us-east-2.yaml", "../../examples/tests/stacks/orgs/cp/tenant1/test1/us-east-2.yaml", "../../examples/tests/stacks/orgs/cp/tenant1/test2/us-east-2.yaml", + "../../examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml", } processStackDeps := true @@ -51,8 +52,8 @@ func TestStackProcessor(t *testing.T) { ) assert.Nil(t, err) - assert.Equal(t, 4, len(listResult)) - assert.Equal(t, 4, len(mapResult)) + assert.Equal(t, 6, len(listResult)) + assert.Equal(t, 6, len(mapResult)) mapResultKeys := u.StringKeysFromMap(mapResult) assert.Equal(t, "orgs/cp/tenant1/dev/us-east-2", mapResultKeys[0]) @@ -60,7 +61,7 @@ func TestStackProcessor(t *testing.T) { assert.Equal(t, "orgs/cp/tenant1/staging/us-east-2", mapResultKeys[2]) assert.Equal(t, "orgs/cp/tenant1/test1/us-east-2", mapResultKeys[3]) assert.Equal(t, "orgs/cp/tenant1/test2/us-east-2", mapResultKeys[4]) - + assert.Equal(t, "orgs/cp/tenant1/test2/us-west-1", mapResultKeys[5]) mapConfig1, err := u.UnmarshalYAML[schema.AtmosSectionMapType](listResult[0]) assert.Nil(t, err) From 12358b01a547950130141e77a329ef6db8883a98 Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 24 Dec 2024 12:38:21 -0500 Subject: [PATCH 06/38] Update import paths in us-west-1.yaml file --- examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml index 5245b0039..c1e3b3864 100644 --- a/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml @@ -1,7 +1,5 @@ # yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json import: - #- path: mixins/region/us-west-1 - #- path: ./_defaults # validate relative paths using the `path` field - - mixins/region/us-west-1 - - ./_defaults # validate relative paths using the `path` field + - path: mixins/region/us-west-1 + - path: ./_defaults # validate relative paths using the `path` field From 8d7605949253c28cd22fba64bdddaf6a9edaa465 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 27 Dec 2024 11:08:57 -0500 Subject: [PATCH 07/38] Refactor handling of import paths in ProcessImportSection --- internal/exec/stack_processor_utils.go | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 651715f44..68dd9a58d 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1744,10 +1744,10 @@ func FindComponentDependenciesLegacy( } // ProcessImportSection processes the `import` section in stack manifests -// The `import` section` can be of the following types: -// 1. list of `StackImport` structs -// 2. list of strings -// 3. List of strings and `StackImport` structs in the same file +// The `import` section can contain: +// 1. Project-relative paths (e.g. "mixins/region/us-east-2") +// 2. Paths relative to the current file (starting with "./" or "../") +// 3. StackImport structs containing either of the above path types func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.StackImport, error) { stackImports, ok := stackMap[cfg.ImportSectionName] @@ -1773,7 +1773,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St var importObj schema.StackImport err := mapstructure.Decode(imp, &importObj) if err == nil { - // Handle relative paths in StackImport.Path + // Handle paths relative to current file (starting with ./ or ../) if strings.HasPrefix(importObj.Path, "./") || strings.HasPrefix(importObj.Path, "../") { // Get the directory of the current file baseDir := filepath.Dir(filePath) @@ -1795,7 +1795,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St return nil, fmt.Errorf("invalid empty import in the file '%s'", filePath) } - // Handle relative paths in string imports + // Handle paths relative to current file (starting with ./ or ../) if strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") { baseDir := filepath.Dir(filePath) s = filepath.Join(baseDir, s) From 1beeb8a94cb6065e70dbae8a6828a5dd04bd1f7a Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 27 Dec 2024 13:36:36 -0500 Subject: [PATCH 08/38] Resolve relative paths in ProcessImportSection function --- internal/exec/stack_processor_utils.go | 37 +++++++++++++------------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 68dd9a58d..f5936961e 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1743,11 +1743,26 @@ func FindComponentDependenciesLegacy( return unique, nil } +// resolveRelativePath checks if a path is relative to the current directory and if so, +// resolves it relative to the current file's directory +func resolveRelativePath(path string, currentFilePath string) string { + // Get the first element of the path + firstElement := filepath.Clean(strings.Split(path, string(filepath.Separator))[0]) + + // Check if the path starts with "." or ".." + if firstElement == "." || firstElement == ".." { + baseDir := filepath.Dir(currentFilePath) + result := filepath.ToSlash(filepath.Clean(filepath.Join(baseDir, path))) + return result + } + return path +} + // ProcessImportSection processes the `import` section in stack manifests // The `import` section can contain: // 1. Project-relative paths (e.g. "mixins/region/us-east-2") -// 2. Paths relative to the current file (starting with "./" or "../") -// 3. StackImport structs containing either of the above path types +// 2. Paths relative to the current stack file (e.g. "./_defaults") +// 3. StackImport structs containing either of the above path types (e.g. "path: mixins/region/us-east-2") func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.StackImport, error) { stackImports, ok := stackMap[cfg.ImportSectionName] @@ -1773,15 +1788,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St var importObj schema.StackImport err := mapstructure.Decode(imp, &importObj) if err == nil { - // Handle paths relative to current file (starting with ./ or ../) - if strings.HasPrefix(importObj.Path, "./") || strings.HasPrefix(importObj.Path, "../") { - // Get the directory of the current file - baseDir := filepath.Dir(filePath) - // Join the base directory with the relative path - importObj.Path = filepath.Join(baseDir, importObj.Path) - // Clean the path to resolve any .. segments - importObj.Path = filepath.Clean(importObj.Path) - } + importObj.Path = resolveRelativePath(importObj.Path, filePath) result = append(result, importObj) continue } @@ -1795,13 +1802,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St return nil, fmt.Errorf("invalid empty import in the file '%s'", filePath) } - // Handle paths relative to current file (starting with ./ or ../) - if strings.HasPrefix(s, "./") || strings.HasPrefix(s, "../") { - baseDir := filepath.Dir(filePath) - s = filepath.Join(baseDir, s) - s = filepath.Clean(s) - } - + s = resolveRelativePath(s, filePath) result = append(result, schema.StackImport{Path: s}) } From dd55c0f33b11243f6b7c642ba388d2e4a809993f Mon Sep 17 00:00:00 2001 From: Dan Miller Date: Fri, 27 Dec 2024 13:56:04 -0500 Subject: [PATCH 09/38] Update internal/exec/stack_processor_utils.go Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- internal/exec/stack_processor_utils.go | 34 ++++++++++++++++++-------- 1 file changed, 24 insertions(+), 10 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index f5936961e..5d38da9e7 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1746,16 +1746,30 @@ func FindComponentDependenciesLegacy( // resolveRelativePath checks if a path is relative to the current directory and if so, // resolves it relative to the current file's directory func resolveRelativePath(path string, currentFilePath string) string { - // Get the first element of the path - firstElement := filepath.Clean(strings.Split(path, string(filepath.Separator))[0]) - - // Check if the path starts with "." or ".." - if firstElement == "." || firstElement == ".." { - baseDir := filepath.Dir(currentFilePath) - result := filepath.ToSlash(filepath.Clean(filepath.Join(baseDir, path))) - return result - } - return path + if path == "" { + return path + } + + // Normalize input path + path = filepath.FromSlash(path) + currentFilePath = filepath.FromSlash(currentFilePath) + + // Get the first element of the path + firstElement := strings.Split(path, string(filepath.Separator))[0] + + // Check if the path starts with "." or ".." + if firstElement == "." || firstElement == ".." { + baseDir := filepath.Dir(currentFilePath) + // Clean the path and convert to forward slashes + result := filepath.ToSlash(filepath.Clean(filepath.Join(baseDir, path))) + + // Ensure the resolved path is still under the base directory + if !strings.HasPrefix(result, filepath.ToSlash(filepath.Clean(baseDir))) { + return path + } + return result + } + return path } // ProcessImportSection processes the `import` section in stack manifests From dfa5a844d1c4bad9f6c801c497ce5d63fe714f0e Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 27 Dec 2024 14:29:41 -0500 Subject: [PATCH 10/38] Refactor resolveRelativePath for improved path handling --- internal/exec/stack_processor_utils.go | 41 +++++++++++--------------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 5d38da9e7..99cd6427a 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1746,30 +1746,23 @@ func FindComponentDependenciesLegacy( // resolveRelativePath checks if a path is relative to the current directory and if so, // resolves it relative to the current file's directory func resolveRelativePath(path string, currentFilePath string) string { - if path == "" { - return path - } - - // Normalize input path - path = filepath.FromSlash(path) - currentFilePath = filepath.FromSlash(currentFilePath) - - // Get the first element of the path - firstElement := strings.Split(path, string(filepath.Separator))[0] - - // Check if the path starts with "." or ".." - if firstElement == "." || firstElement == ".." { - baseDir := filepath.Dir(currentFilePath) - // Clean the path and convert to forward slashes - result := filepath.ToSlash(filepath.Clean(filepath.Join(baseDir, path))) - - // Ensure the resolved path is still under the base directory - if !strings.HasPrefix(result, filepath.ToSlash(filepath.Clean(baseDir))) { - return path - } - return result - } - return path + if path == "" { + return path + } + + // Normalize input path to support multiple OS types + path = filepath.FromSlash(path) + currentFilePath = filepath.FromSlash(currentFilePath) + + // Check if the path starts with "." or ".." + firstElement := strings.Split(path, string(filepath.Separator))[0] + if firstElement == "." || firstElement == ".." { + // Join the current local path with the current stack file path + baseDir := filepath.Dir(currentFilePath) + result := filepath.Clean(filepath.Join(baseDir, path)) + return result + } + return path } // ProcessImportSection processes the `import` section in stack manifests From 507a5dd3a8bdc4aec6e06505634e5cf548aa3062 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 27 Dec 2024 14:37:56 -0500 Subject: [PATCH 11/38] Refactor filepath handling for better readability --- internal/exec/stack_processor_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 99cd6427a..f330cba75 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1759,7 +1759,7 @@ func resolveRelativePath(path string, currentFilePath string) string { if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path baseDir := filepath.Dir(currentFilePath) - result := filepath.Clean(filepath.Join(baseDir, path)) + result := filepath.ToSlash(filepath.Clean(filepath.Join(baseDir, path))) return result } return path From cafdfdddd2368332deadf75dc5371bf579cb7c4a Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 27 Dec 2024 14:49:06 -0500 Subject: [PATCH 12/38] Remove unnecessary path normalization code and simplify logic --- internal/exec/stack_processor_utils.go | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index f330cba75..5d5964fde 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1750,16 +1750,12 @@ func resolveRelativePath(path string, currentFilePath string) string { return path } - // Normalize input path to support multiple OS types - path = filepath.FromSlash(path) - currentFilePath = filepath.FromSlash(currentFilePath) - // Check if the path starts with "." or ".." firstElement := strings.Split(path, string(filepath.Separator))[0] if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path baseDir := filepath.Dir(currentFilePath) - result := filepath.ToSlash(filepath.Clean(filepath.Join(baseDir, path))) + result := filepath.Clean(filepath.Join(baseDir, path)) return result } return path From ddde3d54a2c6763f86a4fa637bab169b9e275be4 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 27 Dec 2024 15:10:12 -0500 Subject: [PATCH 13/38] Improve path resolution handling in stack_processor_utils --- internal/exec/stack_processor_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 5d5964fde..adc022a05 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1751,7 +1751,7 @@ func resolveRelativePath(path string, currentFilePath string) string { } // Check if the path starts with "." or ".." - firstElement := strings.Split(path, string(filepath.Separator))[0] + firstElement := filepath.Clean(strings.Split(path, string(filepath.Separator))[0]) if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path baseDir := filepath.Dir(currentFilePath) From 7fa809e98043b264e3e1da7418a145fa0a375a8b Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 2 Jan 2025 19:09:06 -0500 Subject: [PATCH 14/38] relative path handling improvements for windows; added security checks for import paths --- internal/exec/stack_processor_utils.go | 37 +++++++++++++++++++++++--- 1 file changed, 33 insertions(+), 4 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index fd080f59f..0dab426af 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1741,15 +1741,21 @@ func FindComponentDependenciesLegacy( return unique, nil } -// resolveRelativePath checks if a path is relative to the current directory and if so, -// resolves it relative to the current file's directory +// resolveRelativePath checks if a path is relative to the current directory and if so, resolves it relative to the current file's directory func resolveRelativePath(path string, currentFilePath string) string { if path == "" { return path } - // Check if the path starts with "." or ".." - firstElement := filepath.Clean(strings.Split(path, string(filepath.Separator))[0]) + // Check if the path starts with "." or ".." by splitting on either forward or back slashes + parts := strings.FieldsFunc(path, func(c rune) bool { + return c == '/' || c == '\\' + }) + if len(parts) == 0 { + return path + } + + firstElement := filepath.Clean(parts[0]) if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path baseDir := filepath.Dir(currentFilePath) @@ -2239,3 +2245,26 @@ func FindComponentsDerivedFromBaseComponents( return res, nil } + +// validateImportPath performs security checks on import paths to prevent path traversal attacks and ensure paths are valid. +// This is important when processing imports in stack manifests since they can reference external files that could potentially +// be malicious if not properly validated. The function checks for empty paths, invalid filesystem characters, and directory +// traversal attempts that could access files outside the intended directory structure. +func validateImportPath(path string) error { + // Ensure path is not empty + if path == "" { + return fmt.Errorf("empty path") + } + + // Check for invalid characters + if strings.ContainsAny(path, "<>:\"\\|?*") { + return fmt.Errorf("path contains invalid characters: %s", path) + } + + // Prevent directory traversal attempts + if strings.Contains(path, "..") { + return fmt.Errorf("path contains forbidden directory traversal pattern: %s", path) + } + + return nil +} From 3be0d81606cfcf33064f07d641ff5304eb46e728 Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 2 Jan 2025 19:27:04 -0500 Subject: [PATCH 15/38] Validate import paths for security and base path inclusion --- internal/exec/stack_processor_utils.go | 62 ++++++++++++++------------ 1 file changed, 33 insertions(+), 29 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 0dab426af..7b100ac60 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -4,6 +4,7 @@ import ( "encoding/json" "fmt" "os" + "path" "path/filepath" "reflect" "sort" @@ -59,7 +60,7 @@ func ProcessYAMLConfigFiles( stackBasePath := stacksBasePath if len(stackBasePath) < 1 { - stackBasePath = filepath.Dir(p) + stackBasePath = path.Dir(p) } stackFileName := strings.TrimSuffix( @@ -170,6 +171,10 @@ func ProcessYAMLConfigFile( map[string]any, error, ) { + // Validate the file path is within allowed base path + if err := validateImportPath(filePath, basePath); err != nil { + return nil, nil, nil, nil, nil, err + } var stackConfigs []map[string]any relativeFilePath := u.TrimBasePathFromPath(basePath+"/", filePath) @@ -356,7 +361,7 @@ func ProcessYAMLConfigFile( found := false for _, extension := range extensions { - testPath := filepath.Join(basePath, imp+extension) + testPath := path.Join(basePath, imp+extension) if _, err := os.Stat(testPath); err == nil { impWithExt = imp + extension found = true @@ -371,12 +376,12 @@ func ProcessYAMLConfigFile( } else if ext == u.YamlFileExtension || ext == u.YmlFileExtension { // Check if there's a template version of this file templatePath := impWithExt + u.TemplateExtension - if _, err := os.Stat(filepath.Join(basePath, templatePath)); err == nil { + if _, err := os.Stat(path.Join(basePath, templatePath)); err == nil { impWithExt = templatePath } } - impWithExtPath := filepath.Join(basePath, impWithExt) + impWithExtPath := path.Join(basePath, impWithExt) if impWithExtPath == filePath { errorMessage := fmt.Sprintf("invalid import in the manifest '%s'\nThe file imports itself in '%s'", @@ -468,7 +473,8 @@ func ProcessYAMLConfigFile( if err != nil { return nil, nil, nil, nil, nil, err } - importRelativePathWithExt := strings.Replace(filepath.ToSlash(importFile), filepath.ToSlash(basePath)+"/", "", 1) + + importRelativePathWithExt := strings.Replace(importFile, basePath+"/", "", 1) ext2 := filepath.Ext(importRelativePathWithExt) if ext2 == "" { ext2 = u.DefaultStackConfigFileExtension @@ -1741,21 +1747,15 @@ func FindComponentDependenciesLegacy( return unique, nil } -// resolveRelativePath checks if a path is relative to the current directory and if so, resolves it relative to the current file's directory +// resolveRelativePath checks if a path is relative to the current directory and if so, +// resolves it relative to the current file's directory func resolveRelativePath(path string, currentFilePath string) string { if path == "" { return path } - // Check if the path starts with "." or ".." by splitting on either forward or back slashes - parts := strings.FieldsFunc(path, func(c rune) bool { - return c == '/' || c == '\\' - }) - if len(parts) == 0 { - return path - } - - firstElement := filepath.Clean(parts[0]) + // Check if the path starts with "." or ".." + firstElement := filepath.Clean(strings.Split(path, string(filepath.Separator))[0]) if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path baseDir := filepath.Dir(currentFilePath) @@ -1796,6 +1796,10 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St err := mapstructure.Decode(imp, &importObj) if err == nil { importObj.Path = resolveRelativePath(importObj.Path, filePath) + // Validate the resolved path + if err := validateImportPath(importObj.Path, filepath.Dir(filePath)); err != nil { + return nil, fmt.Errorf("invalid import path '%s' in file '%s': %v", importObj.Path, filePath, err) + } result = append(result, importObj) continue } @@ -1810,6 +1814,10 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St } s = resolveRelativePath(s, filePath) + // Validate the resolved path + if err := validateImportPath(s, filepath.Dir(filePath)); err != nil { + return nil, fmt.Errorf("invalid import path '%s' in file '%s': %v", s, filePath, err) + } result = append(result, schema.StackImport{Path: s}) } @@ -1850,7 +1858,7 @@ func CreateComponentStackMap( componentStackMap["terraform"] = map[string][]string{} componentStackMap["helmfile"] = map[string][]string{} - dir := filepath.Dir(filePath) + dir := path.Dir(filePath) err := filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { @@ -2204,7 +2212,7 @@ func ProcessBaseComponentConfig( if checkBaseComponentExists { // Check if the base component exists as Terraform/Helmfile component // If it does exist, don't throw errors if it is not defined in YAML config - componentPath := filepath.Join(componentBasePath, baseComponent) + componentPath := path.Join(componentBasePath, baseComponent) componentPathExists, err := u.IsDirectory(componentPath) if err != nil || !componentPathExists { return errors.New("The component '" + component + "' inherits from the base component '" + @@ -2246,24 +2254,20 @@ func FindComponentsDerivedFromBaseComponents( return res, nil } -// validateImportPath performs security checks on import paths to prevent path traversal attacks and ensure paths are valid. -// This is important when processing imports in stack manifests since they can reference external files that could potentially -// be malicious if not properly validated. The function checks for empty paths, invalid filesystem characters, and directory -// traversal attempts that could access files outside the intended directory structure. -func validateImportPath(path string) error { +// validateImportPath checks if a path is valid and within stacksBasePath. +// This validation is necessary to prevent path traversal attacks and ensure that +// imported stack manifests can only reference files within the allowed stacks directory. +// Without these checks, malicious stack manifests could potentially access sensitive files +// outside of the stacks directory through directory traversal or symlinks. +func validateImportPath(path string, stacksBasePath string) error { // Ensure path is not empty if path == "" { - return fmt.Errorf("empty path") - } - - // Check for invalid characters - if strings.ContainsAny(path, "<>:\"\\|?*") { - return fmt.Errorf("path contains invalid characters: %s", path) + return fmt.Errorf("Empty path") } // Prevent directory traversal attempts if strings.Contains(path, "..") { - return fmt.Errorf("path contains forbidden directory traversal pattern: %s", path) + return fmt.Errorf("Resolved path contains forbidden directory traversal pattern: %s", path) } return nil From 2f89d4a33c81b4837eff60c6251124eaa12c52cd Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 2 Jan 2025 19:29:45 -0500 Subject: [PATCH 16/38] Update path.Join calls to use filepath.Join for consistency --- internal/exec/stack_processor_utils.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 7b100ac60..675eb4ea9 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -361,7 +361,7 @@ func ProcessYAMLConfigFile( found := false for _, extension := range extensions { - testPath := path.Join(basePath, imp+extension) + testPath := filepath.Join(basePath, imp+extension) if _, err := os.Stat(testPath); err == nil { impWithExt = imp + extension found = true @@ -376,12 +376,12 @@ func ProcessYAMLConfigFile( } else if ext == u.YamlFileExtension || ext == u.YmlFileExtension { // Check if there's a template version of this file templatePath := impWithExt + u.TemplateExtension - if _, err := os.Stat(path.Join(basePath, templatePath)); err == nil { + if _, err := os.Stat(filepath.Join(basePath, templatePath)); err == nil { impWithExt = templatePath } } - impWithExtPath := path.Join(basePath, impWithExt) + impWithExtPath := filepath.Join(basePath, impWithExt) if impWithExtPath == filePath { errorMessage := fmt.Sprintf("invalid import in the manifest '%s'\nThe file imports itself in '%s'", @@ -2212,7 +2212,7 @@ func ProcessBaseComponentConfig( if checkBaseComponentExists { // Check if the base component exists as Terraform/Helmfile component // If it does exist, don't throw errors if it is not defined in YAML config - componentPath := path.Join(componentBasePath, baseComponent) + componentPath := filepath.Join(componentBasePath, baseComponent) componentPathExists, err := u.IsDirectory(componentPath) if err != nil || !componentPathExists { return errors.New("The component '" + component + "' inherits from the base component '" + From 2c80a898a17a0a2f9b149f1f80bace76542e70bc Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 09:44:17 -0500 Subject: [PATCH 17/38] Update stack_processor_utils.go to use filepath package --- internal/exec/stack_processor_utils.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 675eb4ea9..3aa7a7e5c 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -4,7 +4,6 @@ import ( "encoding/json" "fmt" "os" - "path" "path/filepath" "reflect" "sort" @@ -60,7 +59,7 @@ func ProcessYAMLConfigFiles( stackBasePath := stacksBasePath if len(stackBasePath) < 1 { - stackBasePath = path.Dir(p) + stackBasePath = filepath.Dir(p) } stackFileName := strings.TrimSuffix( @@ -1858,7 +1857,7 @@ func CreateComponentStackMap( componentStackMap["terraform"] = map[string][]string{} componentStackMap["helmfile"] = map[string][]string{} - dir := path.Dir(filePath) + dir := filepath.Dir(filePath) err := filepath.Walk(dir, func(p string, info os.FileInfo, err error) error { From 062cf05420740eef3528a7fe3db508e5704e7cdf Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 10:38:48 -0500 Subject: [PATCH 18/38] validate stack paths to check against the global base path --- internal/exec/stack_processor_utils.go | 41 +++++++++++++++++++++----- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 3aa7a7e5c..1fa364ac8 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -25,8 +25,16 @@ var ( // Mutex to serialize updates of the result map of ProcessYAMLConfigFiles function processYAMLConfigFilesLock = &sync.Mutex{} + + // Add at the top of the file with other global variables + globalStacksBasePath string ) +// Add a setter function to initialize the global variable +func SetGlobalStacksBasePath(basePath string) { + globalStacksBasePath = basePath +} + // ProcessYAMLConfigFiles takes a list of paths to stack manifests, processes and deep-merges all imports, // and returns a list of stack configs func ProcessYAMLConfigFiles( @@ -44,6 +52,8 @@ func ProcessYAMLConfigFiles( map[string]map[string]any, error, ) { + // Set the global stacks base path at the start of processing + SetGlobalStacksBasePath(stacksBasePath) count := len(filePaths) listResult := make([]string, count) @@ -171,7 +181,7 @@ func ProcessYAMLConfigFile( error, ) { // Validate the file path is within allowed base path - if err := validateImportPath(filePath, basePath); err != nil { + if err := validateImportPath(filePath); err != nil { return nil, nil, nil, nil, nil, err } @@ -1796,7 +1806,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St if err == nil { importObj.Path = resolveRelativePath(importObj.Path, filePath) // Validate the resolved path - if err := validateImportPath(importObj.Path, filepath.Dir(filePath)); err != nil { + if err := validateImportPath(importObj.Path); err != nil { return nil, fmt.Errorf("invalid import path '%s' in file '%s': %v", importObj.Path, filePath, err) } result = append(result, importObj) @@ -1814,7 +1824,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St s = resolveRelativePath(s, filePath) // Validate the resolved path - if err := validateImportPath(s, filepath.Dir(filePath)); err != nil { + if err := validateImportPath(s); err != nil { return nil, fmt.Errorf("invalid import path '%s' in file '%s': %v", s, filePath, err) } result = append(result, schema.StackImport{Path: s}) @@ -2258,15 +2268,30 @@ func FindComponentsDerivedFromBaseComponents( // imported stack manifests can only reference files within the allowed stacks directory. // Without these checks, malicious stack manifests could potentially access sensitive files // outside of the stacks directory through directory traversal or symlinks. -func validateImportPath(path string, stacksBasePath string) error { +// The function allows traversal within stacksBasePath but prevents escaping above it. +func validateImportPath(path string) error { // Ensure path is not empty if path == "" { - return fmt.Errorf("Empty path") + return fmt.Errorf("empty path") + } + + // Join the base path with the given path + fullPath := filepath.Join(globalStacksBasePath, path) + + // Get absolute paths to properly check containment + absPath, err := filepath.Abs(fullPath) + if err != nil { + return fmt.Errorf("unable to resolve absolute path for %s: %v", fullPath, err) + } + + absBasePath, err := filepath.Abs(globalStacksBasePath) + if err != nil { + return fmt.Errorf("unable to resolve absolute path for stacks base path %s: %v", globalStacksBasePath, err) } - // Prevent directory traversal attempts - if strings.Contains(path, "..") { - return fmt.Errorf("Resolved path contains forbidden directory traversal pattern: %s", path) + // Check if the resolved path is within stacksBasePath + if !strings.HasPrefix(absPath, absBasePath) { + return fmt.Errorf("path %s is outside of allowed stacks directory %s", path, globalStacksBasePath) } return nil From 3d98cd44516e8857c2144ca74aa82e6d35a83a73 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 12:07:15 -0500 Subject: [PATCH 19/38] Refactor path resolution and validation logic --- internal/exec/stack_processor_utils.go | 57 +++----------------------- 1 file changed, 6 insertions(+), 51 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 1fa364ac8..c96c6b69b 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -180,11 +180,6 @@ func ProcessYAMLConfigFile( map[string]any, error, ) { - // Validate the file path is within allowed base path - if err := validateImportPath(filePath); err != nil { - return nil, nil, nil, nil, nil, err - } - var stackConfigs []map[string]any relativeFilePath := u.TrimBasePathFromPath(basePath+"/", filePath) @@ -1757,20 +1752,22 @@ func FindComponentDependenciesLegacy( } // resolveRelativePath checks if a path is relative to the current directory and if so, -// resolves it relative to the current file's directory +// resolves it relative to the current file's directory. It ensures the resolved path +// exists within the base path. func resolveRelativePath(path string, currentFilePath string) string { if path == "" { return path } - // Check if the path starts with "." or ".." + // Determine if this is a relative path by checking if the path starts with "." or ".." firstElement := filepath.Clean(strings.Split(path, string(filepath.Separator))[0]) if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path baseDir := filepath.Dir(currentFilePath) - result := filepath.Clean(filepath.Join(baseDir, path)) - return result + relativePath := filepath.Clean(filepath.Join(baseDir, path)) + return relativePath } + // For non-relative paths, return as-is return path } @@ -1805,10 +1802,6 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St err := mapstructure.Decode(imp, &importObj) if err == nil { importObj.Path = resolveRelativePath(importObj.Path, filePath) - // Validate the resolved path - if err := validateImportPath(importObj.Path); err != nil { - return nil, fmt.Errorf("invalid import path '%s' in file '%s': %v", importObj.Path, filePath, err) - } result = append(result, importObj) continue } @@ -1823,10 +1816,6 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St } s = resolveRelativePath(s, filePath) - // Validate the resolved path - if err := validateImportPath(s); err != nil { - return nil, fmt.Errorf("invalid import path '%s' in file '%s': %v", s, filePath, err) - } result = append(result, schema.StackImport{Path: s}) } @@ -2262,37 +2251,3 @@ func FindComponentsDerivedFromBaseComponents( return res, nil } - -// validateImportPath checks if a path is valid and within stacksBasePath. -// This validation is necessary to prevent path traversal attacks and ensure that -// imported stack manifests can only reference files within the allowed stacks directory. -// Without these checks, malicious stack manifests could potentially access sensitive files -// outside of the stacks directory through directory traversal or symlinks. -// The function allows traversal within stacksBasePath but prevents escaping above it. -func validateImportPath(path string) error { - // Ensure path is not empty - if path == "" { - return fmt.Errorf("empty path") - } - - // Join the base path with the given path - fullPath := filepath.Join(globalStacksBasePath, path) - - // Get absolute paths to properly check containment - absPath, err := filepath.Abs(fullPath) - if err != nil { - return fmt.Errorf("unable to resolve absolute path for %s: %v", fullPath, err) - } - - absBasePath, err := filepath.Abs(globalStacksBasePath) - if err != nil { - return fmt.Errorf("unable to resolve absolute path for stacks base path %s: %v", globalStacksBasePath, err) - } - - // Check if the resolved path is within stacksBasePath - if !strings.HasPrefix(absPath, absBasePath) { - return fmt.Errorf("path %s is outside of allowed stacks directory %s", path, globalStacksBasePath) - } - - return nil -} From 558c324c2c4e9aa8cee5d032d2e1c82f208a8ee6 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 12:09:42 -0500 Subject: [PATCH 20/38] Refactor resolving relative path logic for clarity --- internal/exec/stack_processor_utils.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index c96c6b69b..91d41a730 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1760,7 +1760,8 @@ func resolveRelativePath(path string, currentFilePath string) string { } // Determine if this is a relative path by checking if the path starts with "." or ".." - firstElement := filepath.Clean(strings.Split(path, string(filepath.Separator))[0]) + parts := strings.Split(path, string(filepath.Separator)) + firstElement := filepath.Clean(parts[0]) if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path baseDir := filepath.Dir(currentFilePath) From 88740a5fe6fd2529a806a4b1b43e61b6f9ab71ee Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 12:13:51 -0500 Subject: [PATCH 21/38] Revert changes --- internal/exec/stack_processor_utils.go | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 91d41a730..d54ab9886 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -25,16 +25,8 @@ var ( // Mutex to serialize updates of the result map of ProcessYAMLConfigFiles function processYAMLConfigFilesLock = &sync.Mutex{} - - // Add at the top of the file with other global variables - globalStacksBasePath string ) -// Add a setter function to initialize the global variable -func SetGlobalStacksBasePath(basePath string) { - globalStacksBasePath = basePath -} - // ProcessYAMLConfigFiles takes a list of paths to stack manifests, processes and deep-merges all imports, // and returns a list of stack configs func ProcessYAMLConfigFiles( @@ -52,9 +44,6 @@ func ProcessYAMLConfigFiles( map[string]map[string]any, error, ) { - // Set the global stacks base path at the start of processing - SetGlobalStacksBasePath(stacksBasePath) - count := len(filePaths) listResult := make([]string, count) mapResult := map[string]any{} @@ -478,7 +467,7 @@ func ProcessYAMLConfigFile( return nil, nil, nil, nil, nil, err } - importRelativePathWithExt := strings.Replace(importFile, basePath+"/", "", 1) + importRelativePathWithExt := strings.Replace(filepath.ToSlash(importFile), filepath.ToSlash(basePath)+"/", "", 1) ext2 := filepath.Ext(importRelativePathWithExt) if ext2 == "" { ext2 = u.DefaultStackConfigFileExtension From 94886b68015b9fb810e56a744c83b63a037df454 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 12:38:54 -0500 Subject: [PATCH 22/38] Refactor path resolution for consistency --- internal/exec/stack_processor_utils.go | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index d54ab9886..d5e6b77a9 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1748,16 +1748,32 @@ func resolveRelativePath(path string, currentFilePath string) string { return path } + // Store original path format (using \ or /) + originalPathSeparator := "/" + if strings.Contains(path, "\\") { + originalPathSeparator = "\\" + } + + // Convert all paths to use forward slashes for consistency in processing + normalizedPath := filepath.ToSlash(path) + normalizedCurrentFilePath := filepath.ToSlash(currentFilePath) + // Determine if this is a relative path by checking if the path starts with "." or ".." - parts := strings.Split(path, string(filepath.Separator)) + parts := strings.Split(normalizedPath, "/") firstElement := filepath.Clean(parts[0]) if firstElement == "." || firstElement == ".." { // Join the current local path with the current stack file path - baseDir := filepath.Dir(currentFilePath) - relativePath := filepath.Clean(filepath.Join(baseDir, path)) - return relativePath + baseDir := filepath.Dir(normalizedCurrentFilePath) + relativePath := filepath.Join(baseDir, normalizedPath) + // Convert to forward slashes for consistency in processing + normalizedResult := filepath.ToSlash(relativePath) + // Return in original format + if originalPathSeparator == "\\" { + return strings.ReplaceAll(normalizedResult, "/", "\\") + } + return normalizedResult } - // For non-relative paths, return as-is + // For non-relative paths, return as-is in original format return path } From 1bc256a81243c5028c8041e769f5487a3845a467 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 13:44:38 -0500 Subject: [PATCH 23/38] Refactor resolveRelativePath function for path consistency --- internal/exec/stack_processor_utils.go | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index d5e6b77a9..6410d5d05 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1748,12 +1748,6 @@ func resolveRelativePath(path string, currentFilePath string) string { return path } - // Store original path format (using \ or /) - originalPathSeparator := "/" - if strings.Contains(path, "\\") { - originalPathSeparator = "\\" - } - // Convert all paths to use forward slashes for consistency in processing normalizedPath := filepath.ToSlash(path) normalizedCurrentFilePath := filepath.ToSlash(currentFilePath) @@ -1768,7 +1762,7 @@ func resolveRelativePath(path string, currentFilePath string) string { // Convert to forward slashes for consistency in processing normalizedResult := filepath.ToSlash(relativePath) // Return in original format - if originalPathSeparator == "\\" { + if filepath.Separator == '\\' { return strings.ReplaceAll(normalizedResult, "/", "\\") } return normalizedResult From 037e0ba00216dafc855cef76faf0b51c1dd24fee Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 13:47:55 -0500 Subject: [PATCH 24/38] Convert relative path to base path if starts with "." or ".." --- internal/exec/stack_processor_utils.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 6410d5d05..bcf54cc1a 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1752,7 +1752,11 @@ func resolveRelativePath(path string, currentFilePath string) string { normalizedPath := filepath.ToSlash(path) normalizedCurrentFilePath := filepath.ToSlash(currentFilePath) - // Determine if this is a relative path by checking if the path starts with "." or ".." + // Atmos import paths are always relative paths, but they can be relative to either: + // 1. The base path (most common) - e.g. "mixins/region/us-east-2" + // 2. The current file's directory (less common) - e.g. "./_defaults" + // Here we check if the path starts with "." or ".." to identify if it's relative to the current file. + // If it is, we'll convert it to be relative to the base path instead, to maintain consistency. parts := strings.Split(normalizedPath, "/") firstElement := filepath.Clean(parts[0]) if firstElement == "." || firstElement == ".." { @@ -1761,11 +1765,8 @@ func resolveRelativePath(path string, currentFilePath string) string { relativePath := filepath.Join(baseDir, normalizedPath) // Convert to forward slashes for consistency in processing normalizedResult := filepath.ToSlash(relativePath) - // Return in original format - if filepath.Separator == '\\' { - return strings.ReplaceAll(normalizedResult, "/", "\\") - } - return normalizedResult + // Return in original format, OS-specific + return filepath.FromSlash(normalizedResult) } // For non-relative paths, return as-is in original format return path From 358128c0d2cf520fb7a2249c67055f8dc6280b38 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 14:20:59 -0500 Subject: [PATCH 25/38] Simplify resolving relative paths in stack_processor_utils --- internal/exec/stack_processor_utils.go | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index bcf54cc1a..8ab0d016b 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1763,10 +1763,8 @@ func resolveRelativePath(path string, currentFilePath string) string { // Join the current local path with the current stack file path baseDir := filepath.Dir(normalizedCurrentFilePath) relativePath := filepath.Join(baseDir, normalizedPath) - // Convert to forward slashes for consistency in processing - normalizedResult := filepath.ToSlash(relativePath) // Return in original format, OS-specific - return filepath.FromSlash(normalizedResult) + return filepath.FromSlash(relativePath) } // For non-relative paths, return as-is in original format return path From 7bf92080aca807d112b769c57640f2885b05a571 Mon Sep 17 00:00:00 2001 From: milldr Date: Fri, 3 Jan 2025 15:32:05 -0500 Subject: [PATCH 26/38] added tests for atmos stacks with relative paths --- examples/demo-relative-paths/.gitignore | 10 + examples/demo-relative-paths/atmos.yaml | 25 + .../components/terraform/myapp/README.md | 50 ++ .../components/terraform/myapp/main.tf | 22 + .../components/terraform/myapp/outputs.tf | 27 + .../components/terraform/myapp/variables.tf | 34 + .../components/terraform/myapp/versions.tf | 5 + .../stacks/catalog/myapp.yaml | 11 + .../stacks/orgs/acme/_defaults.yaml | 4 + .../stacks/orgs/acme/platform/_defaults.yaml | 7 + .../stacks/orgs/acme/platform/dev.yaml | 15 + .../stacks/orgs/acme/platform/prod.yaml | 15 + .../stacks/orgs/acme/platform/staging.yaml | 15 + .../atmos-manifest/1.0/atmos-manifest.json | 736 ++++++++++++++++++ tests/test_cases.yaml | 17 + 15 files changed, 993 insertions(+) create mode 100644 examples/demo-relative-paths/.gitignore create mode 100644 examples/demo-relative-paths/atmos.yaml create mode 100644 examples/demo-relative-paths/components/terraform/myapp/README.md create mode 100644 examples/demo-relative-paths/components/terraform/myapp/main.tf create mode 100644 examples/demo-relative-paths/components/terraform/myapp/outputs.tf create mode 100644 examples/demo-relative-paths/components/terraform/myapp/variables.tf create mode 100644 examples/demo-relative-paths/components/terraform/myapp/versions.tf create mode 100644 examples/demo-relative-paths/stacks/catalog/myapp.yaml create mode 100644 examples/demo-relative-paths/stacks/orgs/acme/_defaults.yaml create mode 100644 examples/demo-relative-paths/stacks/orgs/acme/platform/_defaults.yaml create mode 100644 examples/demo-relative-paths/stacks/orgs/acme/platform/dev.yaml create mode 100644 examples/demo-relative-paths/stacks/orgs/acme/platform/prod.yaml create mode 100644 examples/demo-relative-paths/stacks/orgs/acme/platform/staging.yaml create mode 100644 examples/demo-relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json diff --git a/examples/demo-relative-paths/.gitignore b/examples/demo-relative-paths/.gitignore new file mode 100644 index 000000000..ebd3eac49 --- /dev/null +++ b/examples/demo-relative-paths/.gitignore @@ -0,0 +1,10 @@ +**/.terraform/** +**/.terraform.lock.hcl +**/.terraform.tfstate/** +**/.terraform.tfstate.backup/** +**/*.tfvars.json +**/*.planfile +**/terraform.tfstate +**/terraform.tfstate.backup +**/terraform.tfstate.d/** +**/cache.*.txt diff --git a/examples/demo-relative-paths/atmos.yaml b/examples/demo-relative-paths/atmos.yaml new file mode 100644 index 000000000..6f091e75f --- /dev/null +++ b/examples/demo-relative-paths/atmos.yaml @@ -0,0 +1,25 @@ +base_path: "./" + +components: + terraform: + base_path: "components/terraform" + apply_auto_approve: false + deploy_run_init: true + init_run_reconfigure: true + auto_generate_backend_file: false + +stacks: + base_path: "stacks" + included_paths: + - "orgs/**/*" + excluded_paths: + - "**/_defaults.yaml" + name_pattern: "{namespace}-{tenant}-{stage}" + +logs: + file: "/dev/stderr" + level: Info + +schemas: + atmos: + manifest: "stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" diff --git a/examples/demo-relative-paths/components/terraform/myapp/README.md b/examples/demo-relative-paths/components/terraform/myapp/README.md new file mode 100644 index 000000000..1d7a7ee03 --- /dev/null +++ b/examples/demo-relative-paths/components/terraform/myapp/README.md @@ -0,0 +1,50 @@ +# Example Terraform Weather Component + +This Terraform "root" module fetches weather information for a specified location with custom display options. +It queries data from the [`wttr.in`](https://wttr.in) weather service and stores the result in a local file (`cache.txt`). +It also provides several outputs like weather information, request URL, stage, location, language, and units of measurement. + +## Features + +- Fetch weather updates for a location using HTTP request. +- Write the obtained weather data in a local file. +- Customizable display options. +- View the request URL. +- Get informed about the stage, location, language, and units in the metadata. + +## Usage + +To include this module in your [Atmos Stacks](https://atmos.tools/core-concepts/stacks) configuration: + +```yaml +components: + terraform: + weather: + vars: + stage: dev + location: New York + options: 0T + format: v2 + lang: en + units: m +``` + +### Inputs +- `stage`: Stage where it will be deployed. +- `location`: Location for which the weather is reported. Default is "Los Angeles". +- `options`: Options to customize the output. Default is "0T". +- `format`: Specifies the output format. Default is "v2". +- `lang`: Language in which the weather will be displayed. Default is "en". +- `units`: Units in which the weather will be displayed. Default is "m". + +### Outputs +- `weather`: The fetched weather data. +- `url`: Requested URL. +- `stage`: Stage of deployment. +- `location`: Location of the reported weather. +- `lang`: Language used for weather data. +- `units`: Units of measurement for the weather data. + +Please note, this module requires Terraform version >=1.0.0, and you need to specify no other required providers. + +Happy Weather Tracking! diff --git a/examples/demo-relative-paths/components/terraform/myapp/main.tf b/examples/demo-relative-paths/components/terraform/myapp/main.tf new file mode 100644 index 000000000..9971bf5b0 --- /dev/null +++ b/examples/demo-relative-paths/components/terraform/myapp/main.tf @@ -0,0 +1,22 @@ +locals { + url = format("https://wttr.in/%v?%v&format=%v&lang=%v&u=%v", + urlencode(var.location), + urlencode(var.options), + urlencode(var.format), + urlencode(var.lang), + urlencode(var.units), + ) +} + +data "http" "weather" { + url = local.url + request_headers = { + User-Agent = "curl" + } +} + +# Now write this to a file (as an example of a resource) +resource "local_file" "cache" { + filename = "cache.${var.stage}.txt" + content = data.http.weather.response_body +} diff --git a/examples/demo-relative-paths/components/terraform/myapp/outputs.tf b/examples/demo-relative-paths/components/terraform/myapp/outputs.tf new file mode 100644 index 000000000..f0e7fd442 --- /dev/null +++ b/examples/demo-relative-paths/components/terraform/myapp/outputs.tf @@ -0,0 +1,27 @@ +output "weather" { + value = data.http.weather.response_body +} + +output "url" { + value = local.url +} + +output "stage" { + value = var.stage + description = "Stage where it was deployed" +} + +output "location" { + value = var.location + description = "Location of the weather report." +} + +output "lang" { + value = var.lang + description = "Language which the weather is displayed." +} + +output "units" { + value = var.units + description = "Units the weather is displayed." +} diff --git a/examples/demo-relative-paths/components/terraform/myapp/variables.tf b/examples/demo-relative-paths/components/terraform/myapp/variables.tf new file mode 100644 index 000000000..c8a32310a --- /dev/null +++ b/examples/demo-relative-paths/components/terraform/myapp/variables.tf @@ -0,0 +1,34 @@ +variable "stage" { + description = "Stage where it will be deployed" + type = string +} + +variable "location" { + description = "Location for which the weather." + type = string + default = "Los Angeles" +} + +variable "options" { + description = "Options to customize the output." + type = string + default = "0T" +} + +variable "format" { + description = "Format of the output." + type = string + default = "v2" +} + +variable "lang" { + description = "Language in which the weather is displayed." + type = string + default = "en" +} + +variable "units" { + description = "Units in which the weather is displayed." + type = string + default = "m" +} diff --git a/examples/demo-relative-paths/components/terraform/myapp/versions.tf b/examples/demo-relative-paths/components/terraform/myapp/versions.tf new file mode 100644 index 000000000..e2a3d732d --- /dev/null +++ b/examples/demo-relative-paths/components/terraform/myapp/versions.tf @@ -0,0 +1,5 @@ +terraform { + required_version = ">= 1.0.0" + + required_providers {} +} diff --git a/examples/demo-relative-paths/stacks/catalog/myapp.yaml b/examples/demo-relative-paths/stacks/catalog/myapp.yaml new file mode 100644 index 000000000..4fef54e38 --- /dev/null +++ b/examples/demo-relative-paths/stacks/catalog/myapp.yaml @@ -0,0 +1,11 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +components: + terraform: + myapp: + vars: + location: Los Angeles + lang: en + format: '' + options: '0' + units: m diff --git a/examples/demo-relative-paths/stacks/orgs/acme/_defaults.yaml b/examples/demo-relative-paths/stacks/orgs/acme/_defaults.yaml new file mode 100644 index 000000000..ac81b4b36 --- /dev/null +++ b/examples/demo-relative-paths/stacks/orgs/acme/_defaults.yaml @@ -0,0 +1,4 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +vars: + namespace: acme diff --git a/examples/demo-relative-paths/stacks/orgs/acme/platform/_defaults.yaml b/examples/demo-relative-paths/stacks/orgs/acme/platform/_defaults.yaml new file mode 100644 index 000000000..613072fcc --- /dev/null +++ b/examples/demo-relative-paths/stacks/orgs/acme/platform/_defaults.yaml @@ -0,0 +1,7 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +import: + - ../_defaults + +vars: + tenant: platform diff --git a/examples/demo-relative-paths/stacks/orgs/acme/platform/dev.yaml b/examples/demo-relative-paths/stacks/orgs/acme/platform/dev.yaml new file mode 100644 index 000000000..060e05fdc --- /dev/null +++ b/examples/demo-relative-paths/stacks/orgs/acme/platform/dev.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +vars: + stage: dev + +import: + - ./_defaults + - catalog/myapp + +components: + terraform: + myapp: + vars: + location: Stockholm + lang: se diff --git a/examples/demo-relative-paths/stacks/orgs/acme/platform/prod.yaml b/examples/demo-relative-paths/stacks/orgs/acme/platform/prod.yaml new file mode 100644 index 000000000..3bb4193e4 --- /dev/null +++ b/examples/demo-relative-paths/stacks/orgs/acme/platform/prod.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +vars: + stage: prod + +import: + - ./_defaults + - catalog/myapp + +components: + terraform: + myapp: + vars: + location: Los Angeles + lang: en diff --git a/examples/demo-relative-paths/stacks/orgs/acme/platform/staging.yaml b/examples/demo-relative-paths/stacks/orgs/acme/platform/staging.yaml new file mode 100644 index 000000000..095822a27 --- /dev/null +++ b/examples/demo-relative-paths/stacks/orgs/acme/platform/staging.yaml @@ -0,0 +1,15 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +vars: + stage: staging + +import: + - ./_defaults + - catalog/myapp + +components: + terraform: + myapp: + vars: + location: Los Angeles + lang: en diff --git a/examples/demo-relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json b/examples/demo-relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json new file mode 100644 index 000000000..96e5c0ec1 --- /dev/null +++ b/examples/demo-relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json @@ -0,0 +1,736 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://json.schemastore.org/atmos-manifest.json", + "title": "JSON Schema for Atmos Stack Manifest files. Version 1.0. https://atmos.tools", + "type": "object", + "properties": { + "import": { + "$ref": "#/definitions/import" + }, + "terraform": { + "$ref": "#/definitions/terraform" + }, + "helmfile": { + "$ref": "#/definitions/helmfile" + }, + "vars": { + "$ref": "#/definitions/vars" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/settings" + }, + "components": { + "$ref": "#/definitions/components" + }, + "overrides": { + "$ref": "#/definitions/overrides" + }, + "workflows": { + "$ref": "#/definitions/workflows" + } + }, + "additionalProperties": true, + "oneOf": [ + { + "required": [ + "workflows" + ] + }, + { + "anyOf": [ + { + "additionalProperties": true, + "not": { + "required": [ + "workflows" + ] + } + }, + { + "required": [ + "import" + ] + }, + { + "required": [ + "terraform" + ] + }, + { + "required": [ + "helmfile" + ] + }, + { + "required": [ + "vars" + ] + }, + { + "required": [ + "env" + ] + }, + { + "required": [ + "settings" + ] + }, + { + "required": [ + "components" + ] + }, + { + "required": [ + "overrides" + ] + } + ] + } + ], + "definitions": { + "import": { + "type": "array", + "description": "Import section", + "items": { + "oneOf": [ + { + "type": "string" + }, + { + "type": "object", + "additionalProperties": false, + "properties": { + "path": { + "type": "string" + }, + "skip_templates_processing": { + "type": "boolean" + }, + "ignore_missing_template_values": { + "type": "boolean" + }, + "skip_if_missing": { + "type": "boolean" + }, + "context": { + "type": "object", + "additionalProperties": true + } + }, + "required": [ + "path" + ] + } + ] + } + }, + "components": { + "type": "object", + "description": "Components section", + "additionalProperties": false, + "properties": { + "terraform": { + "$ref": "#/definitions/terraform_components" + }, + "helmfile": { + "$ref": "#/definitions/helmfile_components" + } + }, + "required": [], + "title": "components" + }, + "terraform": { + "type": "object", + "description": "Terraform section", + "additionalProperties": false, + "properties": { + "vars": { + "$ref": "#/definitions/vars" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/settings" + }, + "command": { + "$ref": "#/definitions/command" + }, + "backend_type": { + "$ref": "#/definitions/backend_type" + }, + "backend": { + "$ref": "#/definitions/backend" + }, + "remote_state_backend_type": { + "$ref": "#/definitions/remote_state_backend_type" + }, + "remote_state_backend": { + "$ref": "#/definitions/remote_state_backend" + }, + "overrides": { + "$ref": "#/definitions/overrides" + }, + "providers": { + "$ref": "#/definitions/providers" + } + }, + "required": [], + "title": "terraform" + }, + "terraform_components": { + "type": "object", + "description": "Terraform components section", + "patternProperties": { + "^[\/a-zA-Z0-9-_{}. ]+$": { + "$ref": "#/definitions/terraform_component_manifest" + } + }, + "additionalProperties": false, + "title": "terraform_components" + }, + "terraform_component_manifest": { + "type": "object", + "description": "Terraform component manifest", + "additionalProperties": false, + "properties": { + "metadata": { + "$ref": "#/definitions/metadata" + }, + "component": { + "$ref": "#/definitions/component" + }, + "vars": { + "$ref": "#/definitions/vars" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/settings" + }, + "command": { + "$ref": "#/definitions/command" + }, + "backend_type": { + "$ref": "#/definitions/backend_type" + }, + "backend": { + "$ref": "#/definitions/backend" + }, + "remote_state_backend_type": { + "$ref": "#/definitions/remote_state_backend_type" + }, + "remote_state_backend": { + "$ref": "#/definitions/remote_state_backend" + }, + "providers": { + "$ref": "#/definitions/providers" + } + }, + "required": [], + "title": "terraform_component_manifest" + }, + "helmfile": { + "type": "object", + "description": "Helmfile section", + "additionalProperties": false, + "properties": { + "vars": { + "$ref": "#/definitions/vars" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/settings" + }, + "command": { + "$ref": "#/definitions/command" + }, + "overrides": { + "$ref": "#/definitions/overrides" + } + }, + "required": [], + "title": "helmfile" + }, + "helmfile_components": { + "type": "object", + "description": "Helmfile components section", + "patternProperties": { + "^[\/a-zA-Z0-9-_{}. ]+$": { + "$ref": "#/definitions/helmfile_component_manifest" + } + }, + "additionalProperties": false, + "title": "helmfile_components" + }, + "helmfile_component_manifest": { + "type": "object", + "description": "Helmfile component manifest", + "additionalProperties": false, + "properties": { + "metadata": { + "$ref": "#/definitions/metadata" + }, + "component": { + "$ref": "#/definitions/component" + }, + "vars": { + "$ref": "#/definitions/vars" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/settings" + }, + "command": { + "$ref": "#/definitions/command" + } + }, + "required": [], + "title": "helmfile_component_manifest" + }, + "command": { + "type": "string", + "description": "Command to execute", + "title": "command" + }, + "component": { + "type": "string", + "description": "Component section", + "title": "component" + }, + "metadata": { + "type": "object", + "description": "Metadata section", + "additionalProperties": false, + "properties": { + "type": { + "type": "string", + "enum": [ + "abstract", + "real" + ] + }, + "enabled": { + "type": "boolean", + "description": "Flag to enable or disable the component" + }, + "component": { + "type": "string", + "description": "Terraform/OpenTofu/Helmfile component" + }, + "inherits": { + "type": "array", + "description": "A list of Atmos components that the current component inherits from", + "uniqueItems": true, + "items": { + "type": "string" + } + }, + "terraform_workspace": { + "type": "string", + "description": "Terraform workspace" + }, + "terraform_workspace_pattern": { + "type": "string", + "description": "Terraform workspace pattern" + }, + "custom": { + "type": "object", + "description": "Custom configuration per component, not inherited by derived components", + "additionalProperties": true, + "title": "custom" + } + }, + "required": [], + "title": "metadata" + }, + "settings": { + "type": "object", + "description": "Settings section", + "additionalProperties": true, + "properties": { + "validation": { + "$ref": "#/definitions/validation" + }, + "depends_on": { + "$ref": "#/definitions/depends_on" + }, + "spacelift": { + "$ref": "#/definitions/spacelift" + }, + "atlantis": { + "$ref": "#/definitions/atlantis" + }, + "templates": { + "$ref": "#/definitions/templates" + } + }, + "required": [], + "title": "settings" + }, + "validation": { + "type": "object", + "description": "Validation section", + "patternProperties": { + "^[\/a-zA-Z0-9-_{}. ]+$": { + "$ref": "#/definitions/validation_manifest" + } + }, + "additionalProperties": false, + "title": "validation" + }, + "validation_manifest": { + "type": "object", + "description": "Validation manifest", + "properties": { + "schema_type": { + "type": "string", + "enum": [ + "jsonschema", + "opa" + ] + }, + "schema_path": { + "type": "string" + }, + "description": { + "type": "string" + }, + "disabled": { + "type": "boolean" + }, + "timeout": { + "type": "integer", + "minimum": 0 + }, + "module_paths": { + "type": "array", + "uniqueItems": true, + "items": { + "type": "string" + }, + "description": "List of paths to validation modules" + } + }, + "additionalProperties": false, + "required": [ + "schema_type", + "schema_path" + ], + "title": "validation_manifest" + }, + "vars": { + "type": "object", + "description": "Vars section", + "additionalProperties": true, + "title": "vars" + }, + "env": { + "type": "object", + "description": "Env section", + "additionalProperties": true, + "required": [], + "title": "env" + }, + "backend_type": { + "type": "string", + "enum": [ + "local", + "s3", + "remote", + "vault", + "static", + "azurerm", + "gcs", + "cloud" + ], + "description": "Backend type", + "title": "backend_type" + }, + "backend": { + "$ref": "#/definitions/backend_manifest", + "title": "backend" + }, + "remote_state_backend_type": { + "type": "string", + "enum": [ + "local", + "s3", + "remote", + "vault", + "static", + "azurerm", + "gcs", + "cloud" + ], + "description": "Remote state backend type", + "title": "remote_state_backend_type" + }, + "remote_state_backend": { + "$ref": "#/definitions/backend_manifest", + "title": "remote_state_backend" + }, + "backend_manifest": { + "type": "object", + "description": "Backend manifest", + "additionalProperties": false, + "properties": { + "local": { + "type": "object", + "additionalProperties": true + }, + "s3": { + "type": "object", + "additionalProperties": true + }, + "remote": { + "type": "object", + "additionalProperties": true + }, + "vault": { + "type": "object", + "additionalProperties": true + }, + "static": { + "type": "object", + "additionalProperties": true + }, + "azurerm": { + "type": "object", + "additionalProperties": true + }, + "gcs": { + "type": "object", + "additionalProperties": true + }, + "cloud": { + "type": "object", + "additionalProperties": true + } + }, + "required": [], + "title": "backend" + }, + "overrides": { + "type": "object", + "description": "Overrides section", + "additionalProperties": false, + "properties": { + "command": { + "$ref": "#/definitions/command" + }, + "vars": { + "$ref": "#/definitions/vars" + }, + "env": { + "$ref": "#/definitions/env" + }, + "settings": { + "$ref": "#/definitions/settings" + }, + "providers": { + "$ref": "#/definitions/providers" + } + }, + "required": [], + "title": "overrides" + }, + "depends_on": { + "type": "object", + "description": "Depends_on section", + "patternProperties": { + "^[\/a-zA-Z0-9-_{}. ]+$": { + "$ref": "#/definitions/depends_on_manifest" + } + }, + "additionalProperties": false, + "title": "depends_on" + }, + "depends_on_manifest": { + "type": "object", + "description": "Depends_on manifest", + "properties": { + "namespace": { + "type": "string" + }, + "tenant": { + "type": "string" + }, + "environment": { + "type": "string" + }, + "stage": { + "type": "string" + }, + "component": { + "type": "string" + }, + "file": { + "type": "string" + }, + "folder": { + "type": "string" + } + }, + "oneOf": [ + { + "required": [ + "component" + ] + }, + { + "required": [ + "file" + ] + }, + { + "required": [ + "folder" + ] + } + ], + "additionalProperties": false, + "title": "depends_on_manifest" + }, + "spacelift": { + "type": "object", + "description": "Spacelift section", + "additionalProperties": true, + "properties": { + "workspace_enabled": { + "type": "boolean" + }, + "stack_destructor_enabled": { + "type": "boolean" + }, + "protect_from_deletion": { + "type": "boolean" + }, + "autodeploy": { + "type": "boolean" + }, + "terraform_version": { + "anyOf": [ + { + "type": "number" + }, + { + "type": "string" + } + ] + } + }, + "required": [], + "title": "spacelift" + }, + "atlantis": { + "type": "object", + "description": "Atlantis section", + "additionalProperties": false, + "properties": { + "config_template_name": { + "type": "string" + }, + "config_template": { + "type": "object", + "additionalProperties": true + }, + "project_template_name": { + "type": "string" + }, + "project_template": { + "type": "object", + "additionalProperties": true + }, + "workflow_templates": { + "type": "object", + "additionalProperties": true + } + }, + "required": [], + "title": "atlantis" + }, + "workflows": { + "type": "object", + "description": "Workflows section", + "properties": { + "name": { + "type": "string" + }, + "description": { + "type": "string" + } + }, + "patternProperties": { + "^[\/a-zA-Z0-9-_{}. ]+$": { + "$ref": "#/definitions/workflow_manifest" + } + }, + "additionalProperties": false, + "title": "workflows" + }, + "workflow_manifest": { + "type": "object", + "description": "Atmos workflow manifest", + "additionalProperties": false, + "properties": { + "description": { + "type": "string" + }, + "stack": { + "type": "string" + }, + "steps": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "name": { + "type": "string" + }, + "command": { + "type": "string" + }, + "stack": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "required": [ + "command" + ] + } + } + }, + "required": [ + "steps" + ], + "title": "workflow_manifest" + }, + "providers": { + "type": "object", + "description": "Providers section", + "additionalProperties": true, + "title": "providers" + }, + "templates": { + "type": "object", + "description": "Templates section", + "additionalProperties": true, + "title": "templates" + } + } +} diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 9ebb6641f..532833239 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -168,3 +168,20 @@ tests: stderr: - "^$" exit_code: 0 + + - name: atmos stacks with relative paths + enabled: true + description: "Verify atmos describe stacks command lists all stacks with their configurations when using relative paths with . and .. in imports" + workdir: "../examples/demo-relative-paths" + command: "atmos" + args: + - "describe" + - "stacks" + expect: + stdout: + - "acme-platform-dev:" + - "acme-platform-staging:" + - "acme-platform-prod:" + stderr: + - "^$" + exit_code: 0 \ No newline at end of file From 10d41dc614f5aacde64e678195016e9cdff9705f Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 7 Jan 2025 16:41:43 -0500 Subject: [PATCH 27/38] PR feedback --- internal/exec/stack_processor_utils.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 8ab0d016b..6325fd82f 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1752,11 +1752,12 @@ func resolveRelativePath(path string, currentFilePath string) string { normalizedPath := filepath.ToSlash(path) normalizedCurrentFilePath := filepath.ToSlash(currentFilePath) - // Atmos import paths are always relative paths, but they can be relative to either: - // 1. The base path (most common) - e.g. "mixins/region/us-east-2" - // 2. The current file's directory (less common) - e.g. "./_defaults" + // Atmos import paths are generally relative paths, however, there are two types of relative paths: + // 1. Paths relative to the base path (most common) - e.g. "mixins/region/us-east-2" + // 2. Paths relative to the current file's directory (less common) - e.g. "./_defaults" imports will be relative to `./` + // // Here we check if the path starts with "." or ".." to identify if it's relative to the current file. - // If it is, we'll convert it to be relative to the base path instead, to maintain consistency. + // If it is, we'll convert it to be relative to the file doing the import, rather than the `base_path`. parts := strings.Split(normalizedPath, "/") firstElement := filepath.Clean(parts[0]) if firstElement == "." || firstElement == ".." { From 9767879e2e76ac2c90dca1a398fd6c0e11b6b8dc Mon Sep 17 00:00:00 2001 From: milldr Date: Tue, 7 Jan 2025 16:43:57 -0500 Subject: [PATCH 28/38] merged main, incorporate new test fixture pattern --- tests/test_cases.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_cases.yaml b/tests/test_cases.yaml index 22da9d071..97301c431 100644 --- a/tests/test_cases.yaml +++ b/tests/test_cases.yaml @@ -213,7 +213,7 @@ tests: - name: atmos stacks with relative paths enabled: true description: "Verify atmos describe stacks command lists all stacks with their configurations when using relative paths with . and .. in imports" - workdir: "../examples/demo-relative-paths" + workdir: "fixtures/scenarios/relative-paths" command: "atmos" args: - "describe" From 8e47d610994edb0b76e7af8d8476f9bbfa81fcdc Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 10:30:14 -0500 Subject: [PATCH 29/38] Delete nonprod and prod cache weather files --- .../fixtures/components/terraform/myapp/cache.nonprod.txt | 7 ------- tests/fixtures/components/terraform/myapp/cache.prod.txt | 7 ------- 2 files changed, 14 deletions(-) delete mode 100755 tests/fixtures/components/terraform/myapp/cache.nonprod.txt delete mode 100755 tests/fixtures/components/terraform/myapp/cache.prod.txt diff --git a/tests/fixtures/components/terraform/myapp/cache.nonprod.txt b/tests/fixtures/components/terraform/myapp/cache.nonprod.txt deleted file mode 100755 index 9cb14f0c3..000000000 --- a/tests/fixtures/components/terraform/myapp/cache.nonprod.txt +++ /dev/null @@ -1,7 +0,0 @@ -Weather report: Stockholm - -  \ /  Clear -  .-.  +4(-1) °C -  ― ( ) ―  ↑ 34 km/h -  `-’  10 km -  / \  0.0 mm diff --git a/tests/fixtures/components/terraform/myapp/cache.prod.txt b/tests/fixtures/components/terraform/myapp/cache.prod.txt deleted file mode 100755 index f4ddba534..000000000 --- a/tests/fixtures/components/terraform/myapp/cache.prod.txt +++ /dev/null @@ -1,7 +0,0 @@ -Weather report: Los+Angeles - -  \ /  Sunny -  .-.  18 °C -  ― ( ) ―  ← 5 km/h -  `-’  16 km -  / \  0.0 mm From b215eca5a0b07e64f5dbdf4eaec185a4801be997 Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 10:31:41 -0500 Subject: [PATCH 30/38] Delete outdated Atmos manifest JSON schema --- .../scenarios/relative-paths/atmos.yaml | 4 - .../atmos-manifest/1.0/atmos-manifest.json | 736 ------------------ 2 files changed, 740 deletions(-) delete mode 100644 tests/fixtures/scenarios/relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json diff --git a/tests/fixtures/scenarios/relative-paths/atmos.yaml b/tests/fixtures/scenarios/relative-paths/atmos.yaml index 6f091e75f..5d6807473 100644 --- a/tests/fixtures/scenarios/relative-paths/atmos.yaml +++ b/tests/fixtures/scenarios/relative-paths/atmos.yaml @@ -19,7 +19,3 @@ stacks: logs: file: "/dev/stderr" level: Info - -schemas: - atmos: - manifest: "stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" diff --git a/tests/fixtures/scenarios/relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json b/tests/fixtures/scenarios/relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json deleted file mode 100644 index 96e5c0ec1..000000000 --- a/tests/fixtures/scenarios/relative-paths/stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json +++ /dev/null @@ -1,736 +0,0 @@ -{ - "$schema": "https://json-schema.org/draft/2020-12/schema", - "$id": "https://json.schemastore.org/atmos-manifest.json", - "title": "JSON Schema for Atmos Stack Manifest files. Version 1.0. https://atmos.tools", - "type": "object", - "properties": { - "import": { - "$ref": "#/definitions/import" - }, - "terraform": { - "$ref": "#/definitions/terraform" - }, - "helmfile": { - "$ref": "#/definitions/helmfile" - }, - "vars": { - "$ref": "#/definitions/vars" - }, - "env": { - "$ref": "#/definitions/env" - }, - "settings": { - "$ref": "#/definitions/settings" - }, - "components": { - "$ref": "#/definitions/components" - }, - "overrides": { - "$ref": "#/definitions/overrides" - }, - "workflows": { - "$ref": "#/definitions/workflows" - } - }, - "additionalProperties": true, - "oneOf": [ - { - "required": [ - "workflows" - ] - }, - { - "anyOf": [ - { - "additionalProperties": true, - "not": { - "required": [ - "workflows" - ] - } - }, - { - "required": [ - "import" - ] - }, - { - "required": [ - "terraform" - ] - }, - { - "required": [ - "helmfile" - ] - }, - { - "required": [ - "vars" - ] - }, - { - "required": [ - "env" - ] - }, - { - "required": [ - "settings" - ] - }, - { - "required": [ - "components" - ] - }, - { - "required": [ - "overrides" - ] - } - ] - } - ], - "definitions": { - "import": { - "type": "array", - "description": "Import section", - "items": { - "oneOf": [ - { - "type": "string" - }, - { - "type": "object", - "additionalProperties": false, - "properties": { - "path": { - "type": "string" - }, - "skip_templates_processing": { - "type": "boolean" - }, - "ignore_missing_template_values": { - "type": "boolean" - }, - "skip_if_missing": { - "type": "boolean" - }, - "context": { - "type": "object", - "additionalProperties": true - } - }, - "required": [ - "path" - ] - } - ] - } - }, - "components": { - "type": "object", - "description": "Components section", - "additionalProperties": false, - "properties": { - "terraform": { - "$ref": "#/definitions/terraform_components" - }, - "helmfile": { - "$ref": "#/definitions/helmfile_components" - } - }, - "required": [], - "title": "components" - }, - "terraform": { - "type": "object", - "description": "Terraform section", - "additionalProperties": false, - "properties": { - "vars": { - "$ref": "#/definitions/vars" - }, - "env": { - "$ref": "#/definitions/env" - }, - "settings": { - "$ref": "#/definitions/settings" - }, - "command": { - "$ref": "#/definitions/command" - }, - "backend_type": { - "$ref": "#/definitions/backend_type" - }, - "backend": { - "$ref": "#/definitions/backend" - }, - "remote_state_backend_type": { - "$ref": "#/definitions/remote_state_backend_type" - }, - "remote_state_backend": { - "$ref": "#/definitions/remote_state_backend" - }, - "overrides": { - "$ref": "#/definitions/overrides" - }, - "providers": { - "$ref": "#/definitions/providers" - } - }, - "required": [], - "title": "terraform" - }, - "terraform_components": { - "type": "object", - "description": "Terraform components section", - "patternProperties": { - "^[\/a-zA-Z0-9-_{}. ]+$": { - "$ref": "#/definitions/terraform_component_manifest" - } - }, - "additionalProperties": false, - "title": "terraform_components" - }, - "terraform_component_manifest": { - "type": "object", - "description": "Terraform component manifest", - "additionalProperties": false, - "properties": { - "metadata": { - "$ref": "#/definitions/metadata" - }, - "component": { - "$ref": "#/definitions/component" - }, - "vars": { - "$ref": "#/definitions/vars" - }, - "env": { - "$ref": "#/definitions/env" - }, - "settings": { - "$ref": "#/definitions/settings" - }, - "command": { - "$ref": "#/definitions/command" - }, - "backend_type": { - "$ref": "#/definitions/backend_type" - }, - "backend": { - "$ref": "#/definitions/backend" - }, - "remote_state_backend_type": { - "$ref": "#/definitions/remote_state_backend_type" - }, - "remote_state_backend": { - "$ref": "#/definitions/remote_state_backend" - }, - "providers": { - "$ref": "#/definitions/providers" - } - }, - "required": [], - "title": "terraform_component_manifest" - }, - "helmfile": { - "type": "object", - "description": "Helmfile section", - "additionalProperties": false, - "properties": { - "vars": { - "$ref": "#/definitions/vars" - }, - "env": { - "$ref": "#/definitions/env" - }, - "settings": { - "$ref": "#/definitions/settings" - }, - "command": { - "$ref": "#/definitions/command" - }, - "overrides": { - "$ref": "#/definitions/overrides" - } - }, - "required": [], - "title": "helmfile" - }, - "helmfile_components": { - "type": "object", - "description": "Helmfile components section", - "patternProperties": { - "^[\/a-zA-Z0-9-_{}. ]+$": { - "$ref": "#/definitions/helmfile_component_manifest" - } - }, - "additionalProperties": false, - "title": "helmfile_components" - }, - "helmfile_component_manifest": { - "type": "object", - "description": "Helmfile component manifest", - "additionalProperties": false, - "properties": { - "metadata": { - "$ref": "#/definitions/metadata" - }, - "component": { - "$ref": "#/definitions/component" - }, - "vars": { - "$ref": "#/definitions/vars" - }, - "env": { - "$ref": "#/definitions/env" - }, - "settings": { - "$ref": "#/definitions/settings" - }, - "command": { - "$ref": "#/definitions/command" - } - }, - "required": [], - "title": "helmfile_component_manifest" - }, - "command": { - "type": "string", - "description": "Command to execute", - "title": "command" - }, - "component": { - "type": "string", - "description": "Component section", - "title": "component" - }, - "metadata": { - "type": "object", - "description": "Metadata section", - "additionalProperties": false, - "properties": { - "type": { - "type": "string", - "enum": [ - "abstract", - "real" - ] - }, - "enabled": { - "type": "boolean", - "description": "Flag to enable or disable the component" - }, - "component": { - "type": "string", - "description": "Terraform/OpenTofu/Helmfile component" - }, - "inherits": { - "type": "array", - "description": "A list of Atmos components that the current component inherits from", - "uniqueItems": true, - "items": { - "type": "string" - } - }, - "terraform_workspace": { - "type": "string", - "description": "Terraform workspace" - }, - "terraform_workspace_pattern": { - "type": "string", - "description": "Terraform workspace pattern" - }, - "custom": { - "type": "object", - "description": "Custom configuration per component, not inherited by derived components", - "additionalProperties": true, - "title": "custom" - } - }, - "required": [], - "title": "metadata" - }, - "settings": { - "type": "object", - "description": "Settings section", - "additionalProperties": true, - "properties": { - "validation": { - "$ref": "#/definitions/validation" - }, - "depends_on": { - "$ref": "#/definitions/depends_on" - }, - "spacelift": { - "$ref": "#/definitions/spacelift" - }, - "atlantis": { - "$ref": "#/definitions/atlantis" - }, - "templates": { - "$ref": "#/definitions/templates" - } - }, - "required": [], - "title": "settings" - }, - "validation": { - "type": "object", - "description": "Validation section", - "patternProperties": { - "^[\/a-zA-Z0-9-_{}. ]+$": { - "$ref": "#/definitions/validation_manifest" - } - }, - "additionalProperties": false, - "title": "validation" - }, - "validation_manifest": { - "type": "object", - "description": "Validation manifest", - "properties": { - "schema_type": { - "type": "string", - "enum": [ - "jsonschema", - "opa" - ] - }, - "schema_path": { - "type": "string" - }, - "description": { - "type": "string" - }, - "disabled": { - "type": "boolean" - }, - "timeout": { - "type": "integer", - "minimum": 0 - }, - "module_paths": { - "type": "array", - "uniqueItems": true, - "items": { - "type": "string" - }, - "description": "List of paths to validation modules" - } - }, - "additionalProperties": false, - "required": [ - "schema_type", - "schema_path" - ], - "title": "validation_manifest" - }, - "vars": { - "type": "object", - "description": "Vars section", - "additionalProperties": true, - "title": "vars" - }, - "env": { - "type": "object", - "description": "Env section", - "additionalProperties": true, - "required": [], - "title": "env" - }, - "backend_type": { - "type": "string", - "enum": [ - "local", - "s3", - "remote", - "vault", - "static", - "azurerm", - "gcs", - "cloud" - ], - "description": "Backend type", - "title": "backend_type" - }, - "backend": { - "$ref": "#/definitions/backend_manifest", - "title": "backend" - }, - "remote_state_backend_type": { - "type": "string", - "enum": [ - "local", - "s3", - "remote", - "vault", - "static", - "azurerm", - "gcs", - "cloud" - ], - "description": "Remote state backend type", - "title": "remote_state_backend_type" - }, - "remote_state_backend": { - "$ref": "#/definitions/backend_manifest", - "title": "remote_state_backend" - }, - "backend_manifest": { - "type": "object", - "description": "Backend manifest", - "additionalProperties": false, - "properties": { - "local": { - "type": "object", - "additionalProperties": true - }, - "s3": { - "type": "object", - "additionalProperties": true - }, - "remote": { - "type": "object", - "additionalProperties": true - }, - "vault": { - "type": "object", - "additionalProperties": true - }, - "static": { - "type": "object", - "additionalProperties": true - }, - "azurerm": { - "type": "object", - "additionalProperties": true - }, - "gcs": { - "type": "object", - "additionalProperties": true - }, - "cloud": { - "type": "object", - "additionalProperties": true - } - }, - "required": [], - "title": "backend" - }, - "overrides": { - "type": "object", - "description": "Overrides section", - "additionalProperties": false, - "properties": { - "command": { - "$ref": "#/definitions/command" - }, - "vars": { - "$ref": "#/definitions/vars" - }, - "env": { - "$ref": "#/definitions/env" - }, - "settings": { - "$ref": "#/definitions/settings" - }, - "providers": { - "$ref": "#/definitions/providers" - } - }, - "required": [], - "title": "overrides" - }, - "depends_on": { - "type": "object", - "description": "Depends_on section", - "patternProperties": { - "^[\/a-zA-Z0-9-_{}. ]+$": { - "$ref": "#/definitions/depends_on_manifest" - } - }, - "additionalProperties": false, - "title": "depends_on" - }, - "depends_on_manifest": { - "type": "object", - "description": "Depends_on manifest", - "properties": { - "namespace": { - "type": "string" - }, - "tenant": { - "type": "string" - }, - "environment": { - "type": "string" - }, - "stage": { - "type": "string" - }, - "component": { - "type": "string" - }, - "file": { - "type": "string" - }, - "folder": { - "type": "string" - } - }, - "oneOf": [ - { - "required": [ - "component" - ] - }, - { - "required": [ - "file" - ] - }, - { - "required": [ - "folder" - ] - } - ], - "additionalProperties": false, - "title": "depends_on_manifest" - }, - "spacelift": { - "type": "object", - "description": "Spacelift section", - "additionalProperties": true, - "properties": { - "workspace_enabled": { - "type": "boolean" - }, - "stack_destructor_enabled": { - "type": "boolean" - }, - "protect_from_deletion": { - "type": "boolean" - }, - "autodeploy": { - "type": "boolean" - }, - "terraform_version": { - "anyOf": [ - { - "type": "number" - }, - { - "type": "string" - } - ] - } - }, - "required": [], - "title": "spacelift" - }, - "atlantis": { - "type": "object", - "description": "Atlantis section", - "additionalProperties": false, - "properties": { - "config_template_name": { - "type": "string" - }, - "config_template": { - "type": "object", - "additionalProperties": true - }, - "project_template_name": { - "type": "string" - }, - "project_template": { - "type": "object", - "additionalProperties": true - }, - "workflow_templates": { - "type": "object", - "additionalProperties": true - } - }, - "required": [], - "title": "atlantis" - }, - "workflows": { - "type": "object", - "description": "Workflows section", - "properties": { - "name": { - "type": "string" - }, - "description": { - "type": "string" - } - }, - "patternProperties": { - "^[\/a-zA-Z0-9-_{}. ]+$": { - "$ref": "#/definitions/workflow_manifest" - } - }, - "additionalProperties": false, - "title": "workflows" - }, - "workflow_manifest": { - "type": "object", - "description": "Atmos workflow manifest", - "additionalProperties": false, - "properties": { - "description": { - "type": "string" - }, - "stack": { - "type": "string" - }, - "steps": { - "type": "array", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "name": { - "type": "string" - }, - "command": { - "type": "string" - }, - "stack": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "command" - ] - } - } - }, - "required": [ - "steps" - ], - "title": "workflow_manifest" - }, - "providers": { - "type": "object", - "description": "Providers section", - "additionalProperties": true, - "title": "providers" - }, - "templates": { - "type": "object", - "description": "Templates section", - "additionalProperties": true, - "title": "templates" - } - } -} From bcd13ac6ec72e0f4c41368f5bc09f187f52eeb8d Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 10:34:10 -0500 Subject: [PATCH 31/38] consistent indentation --- internal/exec/stack_processor_utils.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index 6ab9cfe04..9ab228cb3 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1790,7 +1790,7 @@ func resolveRelativePath(path string, currentFilePath string) string { normalizedPath := filepath.ToSlash(path) normalizedCurrentFilePath := filepath.ToSlash(currentFilePath) - // Atmos import paths are generally relative paths, however, there are two types of relative paths: + // Atmos import paths are generally relative paths, however, there are two types of relative paths: // 1. Paths relative to the base path (most common) - e.g. "mixins/region/us-east-2" // 2. Paths relative to the current file's directory (less common) - e.g. "./_defaults" imports will be relative to `./` // From 57b557f67a5030e1df972b381bee8250b58f0d5c Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 10:35:10 -0500 Subject: [PATCH 32/38] Add ignore rule for cache text files --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index 7eeb05d79..0dc522621 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,4 @@ .direnv/ .atmos/cache.yaml +**/cache.*.txt From 6faf6dd4513912ff00dbeeaf0aeb4cc2b320a3c1 Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 12:46:10 -0500 Subject: [PATCH 33/38] Update stack processor test cases and relative paths --- internal/exec/validate_stacks.go | 1 + pkg/stack/stack_processor_test.go | 61 ++++++++++++++++++++++++++++--- 2 files changed, 56 insertions(+), 6 deletions(-) diff --git a/internal/exec/validate_stacks.go b/internal/exec/validate_stacks.go index e272340e7..bac3ece02 100644 --- a/internal/exec/validate_stacks.go +++ b/internal/exec/validate_stacks.go @@ -86,6 +86,7 @@ func ValidateStacks(atmosConfig schema.AtmosConfiguration) error { // The path to the Atmos manifest JSON Schema can be absolute path or a path relative to the `base_path` setting in `atmos.yaml` var atmosManifestJsonSchemaFilePath string + u.LogTrace(atmosConfig, fmt.Sprintf("Using the schema '%s'", atmosConfig.Schemas.Atmos.Manifest)) if atmosConfig.Schemas.Atmos.Manifest == "" { atmosConfig.Schemas.Atmos.Manifest = atmosManifestDefault u.LogTrace(atmosConfig, fmt.Sprintf("The Atmos JSON Schema file is not configured. Using the default schema '%s'", atmosManifestDefault)) diff --git a/pkg/stack/stack_processor_test.go b/pkg/stack/stack_processor_test.go index 069969411..78257f69b 100644 --- a/pkg/stack/stack_processor_test.go +++ b/pkg/stack/stack_processor_test.go @@ -19,8 +19,6 @@ func TestStackProcessor(t *testing.T) { "../../tests/fixtures/scenarios/complete/stacks/orgs/cp/tenant1/prod/us-east-2.yaml", "../../tests/fixtures/scenarios/complete/stacks/orgs/cp/tenant1/staging/us-east-2.yaml", "../../tests/fixtures/scenarios/complete/stacks/orgs/cp/tenant1/test1/us-east-2.yaml", - "../../tests/fixtures/scenarios/complete/stacks/orgs/cp/tenant1/test2/us-east-2.yaml", - "../../tests/fixtures/scenarios/complete/stacks/orgs/cp/tenant1/test2/us-west-1.yaml", } processStackDeps := true @@ -52,16 +50,15 @@ func TestStackProcessor(t *testing.T) { ) assert.Nil(t, err) - assert.Equal(t, 6, len(listResult)) - assert.Equal(t, 6, len(mapResult)) + assert.Equal(t, 4, len(listResult)) + assert.Equal(t, 4, len(mapResult)) mapResultKeys := u.StringKeysFromMap(mapResult) assert.Equal(t, "orgs/cp/tenant1/dev/us-east-2", mapResultKeys[0]) assert.Equal(t, "orgs/cp/tenant1/prod/us-east-2", mapResultKeys[1]) assert.Equal(t, "orgs/cp/tenant1/staging/us-east-2", mapResultKeys[2]) assert.Equal(t, "orgs/cp/tenant1/test1/us-east-2", mapResultKeys[3]) - assert.Equal(t, "orgs/cp/tenant1/test2/us-east-2", mapResultKeys[4]) - assert.Equal(t, "orgs/cp/tenant1/test2/us-west-1", mapResultKeys[5]) + mapConfig1, err := u.UnmarshalYAML[schema.AtmosSectionMapType](listResult[0]) assert.Nil(t, err) @@ -196,3 +193,55 @@ func TestStackProcessor(t *testing.T) { assert.Nil(t, err) t.Log(string(yamlConfig)) } + +func TestStackProcessorRelativePaths(t *testing.T) { + stacksBasePath := "../../tests/fixtures/scenarios/relative-paths/stacks" + terraformComponentsBasePath := "../../tests/fixtures/components/terraform" + + filePaths := []string{ + "../../tests/fixtures/scenarios/relative-paths/stacks/orgs/acme/platform/dev.yaml", + "../../tests/fixtures/scenarios/relative-paths/stacks/orgs/acme/platform/prod.yaml", + } + + atmosConfig := schema.AtmosConfiguration{ + Templates: schema.Templates{ + Settings: schema.TemplatesSettings{ + Enabled: true, + Sprig: schema.TemplatesSettingsSprig{ + Enabled: true, + }, + Gomplate: schema.TemplatesSettingsGomplate{ + Enabled: true, + }, + }, + }, + } + + listResult, mapResult, _, err := ProcessYAMLConfigFiles( + atmosConfig, + stacksBasePath, + terraformComponentsBasePath, + "", + filePaths, + true, + true, + false, + ) + + assert.Nil(t, err) + assert.Equal(t, 2, len(listResult)) + assert.Equal(t, 2, len(mapResult)) + + mapResultKeys := u.StringKeysFromMap(mapResult) + assert.Equal(t, "orgs/acme/platform/dev", mapResultKeys[0]) + assert.Equal(t, "orgs/acme/platform/prod", mapResultKeys[1]) + + mapConfig1, err := u.UnmarshalYAML[schema.AtmosSectionMapType](listResult[0]) + assert.Nil(t, err) + + components := mapConfig1["components"].(map[string]any) + terraformComponents := components["terraform"].(map[string]any) + + myappComponent := terraformComponents["myapp"].(map[string]any) + assert.NotNil(t, myappComponent) +} From 187e61b07ef6f897f92e0e61540b2099e9209ea6 Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 12:46:36 -0500 Subject: [PATCH 34/38] revert cahnges --- internal/exec/validate_stacks.go | 1 - 1 file changed, 1 deletion(-) diff --git a/internal/exec/validate_stacks.go b/internal/exec/validate_stacks.go index bac3ece02..e272340e7 100644 --- a/internal/exec/validate_stacks.go +++ b/internal/exec/validate_stacks.go @@ -86,7 +86,6 @@ func ValidateStacks(atmosConfig schema.AtmosConfiguration) error { // The path to the Atmos manifest JSON Schema can be absolute path or a path relative to the `base_path` setting in `atmos.yaml` var atmosManifestJsonSchemaFilePath string - u.LogTrace(atmosConfig, fmt.Sprintf("Using the schema '%s'", atmosConfig.Schemas.Atmos.Manifest)) if atmosConfig.Schemas.Atmos.Manifest == "" { atmosConfig.Schemas.Atmos.Manifest = atmosManifestDefault u.LogTrace(atmosConfig, fmt.Sprintf("The Atmos JSON Schema file is not configured. Using the default schema '%s'", atmosManifestDefault)) From a482907c04003d5781406ceeff6446cb99f14ae5 Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 12:52:37 -0500 Subject: [PATCH 35/38] use relative paths in advanced usage example --- .../advanced/create-atmos-stacks.mdx | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/website/docs/quick-start/advanced/create-atmos-stacks.mdx b/website/docs/quick-start/advanced/create-atmos-stacks.mdx index b1a0bc300..7e474d7f8 100644 --- a/website/docs/quick-start/advanced/create-atmos-stacks.mdx +++ b/website/docs/quick-start/advanced/create-atmos-stacks.mdx @@ -410,7 +410,7 @@ In `stacks/orgs/acme/core/_defaults.yaml`, add the following config for the `cor ```yaml import: - - orgs/acme/_defaults + - ../_defaults - mixins/tenant/core ``` @@ -420,7 +420,7 @@ In `stacks/orgs/acme/plat/_defaults.yaml`, add the following config for the `pla ```yaml import: - - orgs/acme/_defaults + - ../_defaults - mixins/tenant/plat ``` @@ -434,7 +434,7 @@ In `stacks/orgs/acme/plat/dev/_defaults.yaml`, add the following config for the ```yaml import: - - orgs/acme/plat/_defaults + - ../_defaults - mixins/stage/dev ``` @@ -449,7 +449,7 @@ Similar to the `dev` account, add the following configs for the `prod` and `stag ```yaml import: - - orgs/acme/plat/_defaults + - ../_defaults - mixins/stage/prod ``` @@ -457,7 +457,7 @@ import: ```yaml import: - - orgs/acme/plat/_defaults + - ../_defaults - mixins/stage/staging ``` @@ -474,7 +474,7 @@ In `stacks/orgs/acme/plat/dev/us-east-2.yaml`, add the following config: # `import` supports POSIX-style Globs for file names/paths (double-star `**` is supported). # File extensions are optional (if not specified, `.yaml` is used by default). import: - - orgs/acme/plat/dev/_defaults + - ./_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev @@ -488,7 +488,7 @@ Similarly, create the top-level Atmos stack for the `dev` account in `us-west-2` ```yaml import: - - orgs/acme/plat/dev/_defaults + - ./_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev @@ -500,7 +500,7 @@ In `stacks/orgs/acme/plat/staging/us-east-2.yaml`, add the following config: ```yaml import: - - orgs/acme/plat/staging/_defaults + - ./_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging @@ -512,7 +512,7 @@ Similarly, create the top-level Atmos stack for the `staging` account in `us-wes ```yaml import: - - orgs/acme/plat/staging/_defaults + - ./_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging @@ -527,7 +527,7 @@ In `stacks/orgs/acme/plat/prod/us-east-2.yaml`, add the following config: # `import` supports POSIX-style Globs for file names/paths (double-star `**` is supported). # File extensions are optional (if not specified, `.yaml` is used by default). import: - - orgs/acme/plat/prod/_defaults + - ./_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod @@ -541,7 +541,7 @@ Similarly, create the top-level Atmos stack for the `prod` account in `us-west-2 ```yaml import: - - orgs/acme/plat/prod/_defaults + - ./_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod From dc897c740bd0a3aadc814882639d1571ddc502ec Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 12:55:25 -0500 Subject: [PATCH 36/38] use relative paths in advanced usage example --- .../quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml | 2 +- .../quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/dev/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/dev/us-east-2.yaml | 2 +- .../stacks/orgs/acme/plat/dev/us-west-2.yaml | 2 +- .../stacks/orgs/acme/plat/prod/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/prod/us-east-2.yaml | 2 +- .../stacks/orgs/acme/plat/prod/us-west-2.yaml | 2 +- .../stacks/orgs/acme/plat/staging/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/staging/us-east-2.yaml | 2 +- .../stacks/orgs/acme/plat/staging/us-west-2.yaml | 2 +- 11 files changed, 11 insertions(+), 11 deletions(-) diff --git a/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml index 6bbd0f9da..02244cb8d 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml @@ -1,3 +1,3 @@ import: - - orgs/acme/_defaults + - ../_defaults - mixins/tenant/core diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml index 158402731..6d5562ee0 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml @@ -1,3 +1,3 @@ import: - - orgs/acme/_defaults + - ../_defaults - mixins/tenant/plat diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml index 2792ad2d0..d702a3c19 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml @@ -1,3 +1,3 @@ import: - - orgs/acme/plat/_defaults + - ../_defaults - mixins/stage/dev diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml index b6811c3fc..0763c3af0 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml @@ -1,5 +1,5 @@ import: - - orgs/acme/plat/dev/_defaults + - ./_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml index e413fe7e7..2da4b3b1c 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml @@ -1,5 +1,5 @@ import: - - orgs/acme/plat/dev/_defaults + - ./_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml index 3f26aa372..d119c8f1b 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml @@ -1,3 +1,3 @@ import: - - orgs/acme/plat/_defaults + - ../_defaults - mixins/stage/prod diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml index f1e7c248e..67d3de873 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml @@ -1,5 +1,5 @@ import: - - orgs/acme/plat/prod/_defaults + - ./_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml index 7d14df16d..cc10d68aa 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml @@ -1,5 +1,5 @@ import: - - orgs/acme/plat/prod/_defaults + - ./_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml index e368a6c79..6779da954 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml @@ -1,3 +1,3 @@ import: - - orgs/acme/plat/_defaults + - ../_defaults - mixins/stage/staging diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml index 47c9ce5c1..e98f10189 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml @@ -1,5 +1,5 @@ import: - - orgs/acme/plat/staging/_defaults + - ./_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml index b3cd3dd16..7ff643ef4 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml @@ -1,5 +1,5 @@ import: - - orgs/acme/plat/staging/_defaults + - ./_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging From a427817fda1bfed4242eec34fd5b8cbf4881293f Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 15:15:39 -0500 Subject: [PATCH 37/38] reset advanced usage --- .../stacks/orgs/acme/core/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/dev/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/dev/us-east-2.yaml | 2 +- .../stacks/orgs/acme/plat/dev/us-west-2.yaml | 2 +- .../stacks/orgs/acme/plat/prod/_defaults.yaml | 2 +- .../stacks/orgs/acme/plat/prod/us-east-2.yaml | 2 +- .../stacks/orgs/acme/plat/prod/us-west-2.yaml | 2 +- .../orgs/acme/plat/staging/_defaults.yaml | 2 +- .../orgs/acme/plat/staging/us-east-2.yaml | 2 +- .../orgs/acme/plat/staging/us-west-2.yaml | 2 +- .../complete/stacks/mixins/stage/test2.yaml | 8 ------- .../advanced/create-atmos-stacks.mdx | 22 +++++++++---------- 13 files changed, 22 insertions(+), 30 deletions(-) delete mode 100644 tests/fixtures/scenarios/complete/stacks/mixins/stage/test2.yaml diff --git a/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml index 02244cb8d..6bbd0f9da 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/core/_defaults.yaml @@ -1,3 +1,3 @@ import: - - ../_defaults + - orgs/acme/_defaults - mixins/tenant/core diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml index 6d5562ee0..158402731 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/_defaults.yaml @@ -1,3 +1,3 @@ import: - - ../_defaults + - orgs/acme/_defaults - mixins/tenant/plat diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml index d702a3c19..2792ad2d0 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/_defaults.yaml @@ -1,3 +1,3 @@ import: - - ../_defaults + - orgs/acme/plat/_defaults - mixins/stage/dev diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml index 0763c3af0..b6811c3fc 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-east-2.yaml @@ -1,5 +1,5 @@ import: - - ./_defaults + - orgs/acme/plat/dev/_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml index 2da4b3b1c..e413fe7e7 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/dev/us-west-2.yaml @@ -1,5 +1,5 @@ import: - - ./_defaults + - orgs/acme/plat/dev/_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml index d119c8f1b..3f26aa372 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/_defaults.yaml @@ -1,3 +1,3 @@ import: - - ../_defaults + - orgs/acme/plat/_defaults - mixins/stage/prod diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml index 67d3de873..f1e7c248e 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-east-2.yaml @@ -1,5 +1,5 @@ import: - - ./_defaults + - orgs/acme/plat/prod/_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml index cc10d68aa..7d14df16d 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/prod/us-west-2.yaml @@ -1,5 +1,5 @@ import: - - ./_defaults + - orgs/acme/plat/prod/_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml index 6779da954..e368a6c79 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/_defaults.yaml @@ -1,3 +1,3 @@ import: - - ../_defaults + - orgs/acme/plat/_defaults - mixins/stage/staging diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml index e98f10189..47c9ce5c1 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-east-2.yaml @@ -1,5 +1,5 @@ import: - - ./_defaults + - orgs/acme/plat/staging/_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging diff --git a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml index 7ff643ef4..b3cd3dd16 100644 --- a/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml +++ b/examples/quick-start-advanced/stacks/orgs/acme/plat/staging/us-west-2.yaml @@ -1,5 +1,5 @@ import: - - ./_defaults + - orgs/acme/plat/staging/_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging diff --git a/tests/fixtures/scenarios/complete/stacks/mixins/stage/test2.yaml b/tests/fixtures/scenarios/complete/stacks/mixins/stage/test2.yaml deleted file mode 100644 index 0ad7c63dc..000000000 --- a/tests/fixtures/scenarios/complete/stacks/mixins/stage/test2.yaml +++ /dev/null @@ -1,8 +0,0 @@ -# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json - -vars: - stage: test-2 - -settings: - config: - is_prod: false diff --git a/website/docs/quick-start/advanced/create-atmos-stacks.mdx b/website/docs/quick-start/advanced/create-atmos-stacks.mdx index 7e474d7f8..b1a0bc300 100644 --- a/website/docs/quick-start/advanced/create-atmos-stacks.mdx +++ b/website/docs/quick-start/advanced/create-atmos-stacks.mdx @@ -410,7 +410,7 @@ In `stacks/orgs/acme/core/_defaults.yaml`, add the following config for the `cor ```yaml import: - - ../_defaults + - orgs/acme/_defaults - mixins/tenant/core ``` @@ -420,7 +420,7 @@ In `stacks/orgs/acme/plat/_defaults.yaml`, add the following config for the `pla ```yaml import: - - ../_defaults + - orgs/acme/_defaults - mixins/tenant/plat ``` @@ -434,7 +434,7 @@ In `stacks/orgs/acme/plat/dev/_defaults.yaml`, add the following config for the ```yaml import: - - ../_defaults + - orgs/acme/plat/_defaults - mixins/stage/dev ``` @@ -449,7 +449,7 @@ Similar to the `dev` account, add the following configs for the `prod` and `stag ```yaml import: - - ../_defaults + - orgs/acme/plat/_defaults - mixins/stage/prod ``` @@ -457,7 +457,7 @@ import: ```yaml import: - - ../_defaults + - orgs/acme/plat/_defaults - mixins/stage/staging ``` @@ -474,7 +474,7 @@ In `stacks/orgs/acme/plat/dev/us-east-2.yaml`, add the following config: # `import` supports POSIX-style Globs for file names/paths (double-star `**` is supported). # File extensions are optional (if not specified, `.yaml` is used by default). import: - - ./_defaults + - orgs/acme/plat/dev/_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev @@ -488,7 +488,7 @@ Similarly, create the top-level Atmos stack for the `dev` account in `us-west-2` ```yaml import: - - ./_defaults + - orgs/acme/plat/dev/_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `dev` by importing the `catalog/vpc/dev` manifest - catalog/vpc/dev @@ -500,7 +500,7 @@ In `stacks/orgs/acme/plat/staging/us-east-2.yaml`, add the following config: ```yaml import: - - ./_defaults + - orgs/acme/plat/staging/_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging @@ -512,7 +512,7 @@ Similarly, create the top-level Atmos stack for the `staging` account in `us-wes ```yaml import: - - ./_defaults + - orgs/acme/plat/staging/_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `staging` by importing the `catalog/vpc/staging` manifest - catalog/vpc/staging @@ -527,7 +527,7 @@ In `stacks/orgs/acme/plat/prod/us-east-2.yaml`, add the following config: # `import` supports POSIX-style Globs for file names/paths (double-star `**` is supported). # File extensions are optional (if not specified, `.yaml` is used by default). import: - - ./_defaults + - orgs/acme/plat/prod/_defaults - mixins/region/us-east-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod @@ -541,7 +541,7 @@ Similarly, create the top-level Atmos stack for the `prod` account in `us-west-2 ```yaml import: - - ./_defaults + - orgs/acme/plat/prod/_defaults - mixins/region/us-west-2 # Override the `vpc` component configuration for `prod` by importing the `catalog/vpc/prod` manifest - catalog/vpc/prod From fb6ac32014f8967a9495da6d2742ace8285b440a Mon Sep 17 00:00:00 2001 From: milldr Date: Thu, 9 Jan 2025 15:16:30 -0500 Subject: [PATCH 38/38] added relative paths to demo-context --- examples/demo-context/stacks/deploy/dev/demo.yaml | 2 +- examples/demo-context/stacks/deploy/prod/demo.yaml | 2 +- examples/demo-context/stacks/deploy/staging/demo.yaml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/demo-context/stacks/deploy/dev/demo.yaml b/examples/demo-context/stacks/deploy/dev/demo.yaml index 4143e7826..eb3076ff3 100644 --- a/examples/demo-context/stacks/deploy/dev/demo.yaml +++ b/examples/demo-context/stacks/deploy/dev/demo.yaml @@ -1,7 +1,7 @@ # yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json import: -- deploy/_defaults +- ../_defaults - catalog/demo - mixins/west-coast diff --git a/examples/demo-context/stacks/deploy/prod/demo.yaml b/examples/demo-context/stacks/deploy/prod/demo.yaml index 69eed337c..bf7c53d0b 100644 --- a/examples/demo-context/stacks/deploy/prod/demo.yaml +++ b/examples/demo-context/stacks/deploy/prod/demo.yaml @@ -1,7 +1,7 @@ # yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json import: -- deploy/_defaults +- ../_defaults - catalog/demo - mixins/east-coast diff --git a/examples/demo-context/stacks/deploy/staging/demo.yaml b/examples/demo-context/stacks/deploy/staging/demo.yaml index 1a0d4f6a0..27200e6b7 100644 --- a/examples/demo-context/stacks/deploy/staging/demo.yaml +++ b/examples/demo-context/stacks/deploy/staging/demo.yaml @@ -1,7 +1,7 @@ # yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json import: -- deploy/_defaults +- ../_defaults - catalog/demo - mixins/east-coast