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/us-east-2.yaml b/examples/tests/stacks/orgs/cp/tenant1/test2/us-east-2.yaml new file mode 100644 index 000000000..8c42471da --- /dev/null +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/us-east-2.yaml @@ -0,0 +1,5 @@ +# yaml-language-server: $schema=https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + +import: + - mixins/region/us-east-2 + - ./_defaults # validate relative paths 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..c1e3b3864 --- /dev/null +++ b/examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml @@ -0,0 +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 diff --git a/internal/exec/stack_processor_utils.go b/internal/exec/stack_processor_utils.go index d2b9cd9ce..adc022a05 100644 --- a/internal/exec/stack_processor_utils.go +++ b/internal/exec/stack_processor_utils.go @@ -1743,11 +1743,29 @@ 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 { + if path == "" { + return path + } + + // 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) + result := filepath.Clean(filepath.Join(baseDir, path)) + return result + } + return path +} + // 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 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,6 +1791,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St var importObj schema.StackImport err := mapstructure.Decode(imp, &importObj) if err == nil { + importObj.Path = resolveRelativePath(importObj.Path, filePath) result = append(result, importObj) continue } @@ -1786,6 +1805,7 @@ func ProcessImportSection(stackMap map[string]any, filePath string) ([]schema.St return nil, fmt.Errorf("invalid empty import in the file '%s'", filePath) } + s = resolveRelativePath(s, filePath) result = append(result, schema.StackImport{Path: s}) } diff --git a/pkg/stack/stack_processor_test.go b/pkg/stack/stack_processor_test.go index e7c39d26d..4c5745cba 100644 --- a/pkg/stack/stack_processor_test.go +++ b/pkg/stack/stack_processor_test.go @@ -19,6 +19,8 @@ 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/us-east-2.yaml", + "../../examples/tests/stacks/orgs/cp/tenant1/test2/us-west-1.yaml", } processStackDeps := true @@ -50,15 +52,16 @@ 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]) 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)