Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for vendor path setting in atmos.yaml #737

Merged
merged 36 commits into from
Nov 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e5c67ac
feat: vendor path setting in yaml support
Cerebrovinny Oct 20, 2024
626c3e4
feat: refactoring vendor config path
Cerebrovinny Oct 20, 2024
1b7eb1c
general refactoring process vendor
Cerebrovinny Oct 22, 2024
62b7c7d
vendor yaml path update
Cerebrovinny Oct 22, 2024
729fdae
sync master
Cerebrovinny Oct 22, 2024
19263fb
blank line yaml
Cerebrovinny Oct 22, 2024
2fb288c
feat: set vendor path
Cerebrovinny Oct 22, 2024
1d9624e
vendor improvements checking
Cerebrovinny Oct 22, 2024
ac9796b
vendor refactor remove redundant code
Cerebrovinny Oct 22, 2024
f1c1cf9
implement env vars for vendor file
Cerebrovinny Oct 23, 2024
27a9803
implement env vars flag for vendor file
Cerebrovinny Oct 24, 2024
1bfc2bd
Merge branch 'main' into feat/dev-2378
aknysh Oct 25, 2024
a8a80cc
update docs vendor path
Cerebrovinny Oct 28, 2024
6c1f57e
improve description
Cerebrovinny Oct 28, 2024
98dd26c
Merge branch 'main' into feat/dev-2378
aknysh Oct 29, 2024
9a6ff42
Merge branch 'main' into feat/dev-2378
aknysh Oct 29, 2024
16454b8
Merge branch 'main' into feat/dev-2378
aknysh Oct 30, 2024
d0eb74d
Merge branch 'main' into feat/dev-2378
osterman Oct 30, 2024
9b71751
added demo base vendor files general refactoring
Cerebrovinny Oct 30, 2024
673828f
formating changes
Cerebrovinny Oct 30, 2024
69afaf1
feat: added doublestar support
Cerebrovinny Oct 30, 2024
f17ba17
address commit requests
Cerebrovinny Oct 30, 2024
f33daa4
deduplicate imports feature
Cerebrovinny Oct 31, 2024
9e94dd5
general fixes and vendoring folder formating clean up
Cerebrovinny Oct 31, 2024
aa85470
general fixes and vendoring folder formating clean up
Cerebrovinny Oct 31, 2024
a29cf82
fixes for iteration over vendor files
Cerebrovinny Oct 31, 2024
5204a62
put back deduplication logic
Cerebrovinny Oct 31, 2024
1e96e03
handle no files case
Cerebrovinny Oct 31, 2024
5cb80f0
Update atmos.yaml
osterman Oct 31, 2024
3373bab
Merge branch 'main' into feat/dev-2378
osterman Oct 31, 2024
bc0deea
Merge branch 'main' into feat/dev-2378
osterman Nov 1, 2024
f1e22a7
Merge branch 'main' into feat/dev-2378
aknysh Nov 5, 2024
c451f53
Merge branch 'main' into feat/dev-2378
aknysh Nov 11, 2024
d6d0fdd
Merge branch 'main' into feat/dev-2378
aknysh Nov 11, 2024
1f80983
update pkg yaml
Cerebrovinny Nov 13, 2024
af5d4ef
Merge branch 'main' into feat/dev-2378
Cerebrovinny Nov 13, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 11 additions & 1 deletion atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,17 @@
# are independent settings (supporting both absolute and relative paths).
# If 'base_path' is provided, 'components.terraform.base_path', 'components.helmfile.base_path', 'stacks.base_path' and 'workflows.base_path'
# are considered paths relative to 'base_path'.
base_path: "./examples/quick-start-advanced"
base_path: "./"
aknysh marked this conversation as resolved.
Show resolved Hide resolved

vendor:
# Path to vendor configuration file or directory containing vendor files
# Supports both absolute and relative paths
# If a directory is specified, all .yaml files in the directory will be processed in lexicographical order
# Can also be set using 'ATMOS_VENDOR_BASE_PATH' ENV var, or '--vendor-base-path' command-line argument
# Examples:
# base_path: "./vendor.yaml" # Single file
# base_path: "./vendor.d/" # Directory containing multiple .yaml files
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
base_path: "./vendor.yaml"
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved

components:
terraform:
Expand Down
10 changes: 10 additions & 0 deletions examples/demo-vendoring/atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,16 @@ stacks:
- "**/_defaults.yaml"
name_pattern: "{stage}"

vendor:
# Single file
base_path: "./vendor.yaml"

# Directory with multiple files
#base_path: "./vendor"

# Absolute path
#base_path: "vendor.d/vendor1.yaml"

Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
logs:
file: "/dev/stderr"
level: Info
Expand Down
9 changes: 9 additions & 0 deletions examples/demo-vendoring/stacks/catalog/myapp.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
components:
terraform:
myapp:
vars:
location: Los Angeles
lang: en
format: ''
options: '0'
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
units: m
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 12 additions & 0 deletions examples/demo-vendoring/stacks/deploy/dev.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
vars:
stage: dev
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved

import:
- catalog/myapp

components:
terraform:
myapp:
vars:
location: Stockholm
lang: se
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
12 changes: 12 additions & 0 deletions examples/demo-vendoring/stacks/deploy/prod.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
vars:
stage: prod

Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
import:
- catalog/myapp

components:
terraform:
myapp:
vars:
location: Los Angeles
lang: en
12 changes: 12 additions & 0 deletions examples/demo-vendoring/stacks/deploy/staging.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
vars:
stage: staging

Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
import:
- catalog/myapp

components:
terraform:
myapp:
vars:
location: Los Angeles
lang: en
16 changes: 16 additions & 0 deletions examples/demo-vendoring/vendor.d/vendor1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: atmos/v1
kind: AtmosVendorConfig
metadata:
name: vendor-test-1
description: |
Demo vendor configuration showcasing the structure and usage of Atmos vendor configs.
This example demonstrates component sourcing from GitHub repositories with versioning.
spec:
sources:
- component: "ipinfo"
source: "github.com/cloudposse/atmos.git//examples/demo-library/{{ .Component }}?ref={{.Version}}"
version: "main"
osterman marked this conversation as resolved.
Show resolved Hide resolved
targets:
- "components/terraform/{{ .Component }}/{{.Version}}"
tags:
- demo
16 changes: 16 additions & 0 deletions examples/demo-vendoring/vendor/vendor1.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: atmos/v1
kind: AtmosVendorConfig
metadata:
name: vendor-test-1
description: |
Demo vendor configuration showcasing the structure and usage of Atmos vendor configs.
This example demonstrates component sourcing from GitHub repositories with versioning.
spec:
sources:
- component: "ipinfo"
source: "github.com/cloudposse/atmos.git//examples/demo-library/{{ .Component }}?ref={{.Version}}"
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
version: "main"
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
targets:
- "components/terraform/{{ .Component }}/{{.Version}}"
tags:
- demo
16 changes: 16 additions & 0 deletions examples/demo-vendoring/vendor/vendor2.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: atmos/v1
kind: AtmosVendorConfig
metadata:
name: vendor-test-2
description: |
Demo vendor configuration showcasing the structure and usage of Atmos vendor configs.
This example demonstrates component sourcing from GitHub repositories with versioning.
spec:
sources:
- component: "weather"
source: "github.com/cloudposse/atmos.git//examples/demo-library/{{ .Component }}?ref={{.Version}}"
version: "main"
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
targets:
- "components/terraform/{{ .Component }}/{{.Version}}"
tags:
- demo
14 changes: 14 additions & 0 deletions internal/exec/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ var (
cfg.CliConfigDirFlag,
cfg.StackDirFlag,
cfg.BasePathFlag,
cfg.VendorBasePathFlag,
cfg.GlobalOptionsFlag,
cfg.DeployRunInitFlag,
cfg.InitRunReconfigure,
Expand Down Expand Up @@ -759,6 +760,19 @@ func processArgsAndFlags(componentType string, inputArgsAndFlags []string) (sche
info.BasePath = stacksDirFlagParts[1]
}

if arg == cfg.VendorBasePathFlag {
if len(inputArgsAndFlags) <= (i + 1) {
return info, fmt.Errorf("invalid flag: %s", arg)
}
info.VendorBasePath = inputArgsAndFlags[i+1]
} else if strings.HasPrefix(arg+"=", cfg.VendorBasePathFlag) {
var vendorBasePathFlagParts = strings.Split(arg, "=")
if len(vendorBasePathFlagParts) != 2 {
return info, fmt.Errorf("invalid flag: %s", arg)
}
info.VendorBasePath = vendorBasePathFlagParts[1]
}

if arg == cfg.DeployRunInitFlag {
if len(inputArgsAndFlags) <= (i + 1) {
return info, fmt.Errorf("invalid flag: %s", arg)
Expand Down
124 changes: 95 additions & 29 deletions internal/exec/vendor_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"path"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
Expand All @@ -14,7 +15,9 @@ import (
cp "github.com/otiai10/copy"
"github.com/samber/lo"
"github.com/spf13/cobra"
"gopkg.in/yaml.v3"

"github.com/bmatcuk/doublestar/v4"
cfg "github.com/cloudposse/atmos/pkg/config"
"github.com/cloudposse/atmos/pkg/schema"
u "github.com/cloudposse/atmos/pkg/utils"
Expand Down Expand Up @@ -80,7 +83,7 @@ func ExecuteVendorPullCommand(cmd *cobra.Command, args []string) error {

// Check `vendor.yaml`
vendorConfig, vendorConfigExists, foundVendorConfigFile, err := ReadAndProcessVendorConfigFile(cliConfig, cfg.AtmosVendorConfigFileName)
if vendorConfigExists && err != nil {
if err != nil {
return err
}

Expand Down Expand Up @@ -120,48 +123,111 @@ func ExecuteVendorPullCommand(cmd *cobra.Command, args []string) error {
}

// ReadAndProcessVendorConfigFile reads and processes the Atmos vendoring config file `vendor.yaml`
func ReadAndProcessVendorConfigFile(cliConfig schema.CliConfiguration, vendorConfigFile string) (
schema.AtmosVendorConfig,
bool,
string,
error,
) {
func ReadAndProcessVendorConfigFile(
cliConfig schema.CliConfiguration,
vendorConfigFile string,
) (schema.AtmosVendorConfig, bool, string, error) {
var vendorConfig schema.AtmosVendorConfig
vendorConfigFileExists := true

// Check if the vendoring manifest file exists
foundVendorConfigFile, fileExists := u.SearchConfigFile(vendorConfigFile)
// Initialize empty sources slice
vendorConfig.Spec.Sources = []schema.AtmosVendorSource{}

if !fileExists {
// Look for the vendoring manifest in the directory pointed to by the `base_path` setting in the `atmos.yaml`
pathToVendorConfig := path.Join(cliConfig.BasePath, vendorConfigFile)
var vendorConfigFileExists bool
var foundVendorConfigFile string

if !u.FileExists(pathToVendorConfig) {
vendorConfigFileExists = false
return vendorConfig, vendorConfigFileExists, "", fmt.Errorf("vendor config file '%s' does not exist", pathToVendorConfig)
// Check if vendor config is specified in atmos.yaml
if cliConfig.Vendor.BasePath != "" {
if !filepath.IsAbs(cliConfig.Vendor.BasePath) {
foundVendorConfigFile = filepath.Join(cliConfig.BasePath, cliConfig.Vendor.BasePath)
} else {
foundVendorConfigFile = cliConfig.Vendor.BasePath
}
} else {
// Path is not defined in atmos.yaml, proceed with existing logic
var fileExists bool
foundVendorConfigFile, fileExists = u.SearchConfigFile(vendorConfigFile)

foundVendorConfigFile = pathToVendorConfig
if !fileExists {
// Look for the vendoring manifest in the directory pointed to by the `base_path` setting in `atmos.yaml`
pathToVendorConfig := path.Join(cliConfig.BasePath, vendorConfigFile)
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved

if !u.FileExists(pathToVendorConfig) {
vendorConfigFileExists = false
return vendorConfig, vendorConfigFileExists, "", fmt.Errorf("vendor config file or directory '%s' does not exist", pathToVendorConfig)
}

foundVendorConfigFile = pathToVendorConfig
}
}

vendorConfigFileContent, err := os.ReadFile(foundVendorConfigFile)
// Check if it's a directory
fileInfo, err := os.Stat(foundVendorConfigFile)
if err != nil {
return vendorConfig, vendorConfigFileExists, "", err
return vendorConfig, false, "", err
}

vendorConfig, err = u.UnmarshalYAML[schema.AtmosVendorConfig](string(vendorConfigFileContent))
if err != nil {
return vendorConfig, vendorConfigFileExists, "", err
var configFiles []string
if fileInfo.IsDir() {
matches, err := doublestar.Glob(os.DirFS(foundVendorConfigFile), "*.{yaml,yml}")
if err != nil {
return vendorConfig, false, "", err
}
for _, match := range matches {
configFiles = append(configFiles, filepath.Join(foundVendorConfigFile, match))
}
sort.Strings(configFiles)
if len(configFiles) == 0 {
return vendorConfig, false, "", fmt.Errorf("no YAML configuration files found in directory '%s'", foundVendorConfigFile)
}
} else {
configFiles = []string{foundVendorConfigFile}
}

if vendorConfig.Kind != "AtmosVendorConfig" {
return vendorConfig, vendorConfigFileExists, "",
fmt.Errorf("invalid 'kind: %s' in the vendor config file '%s'. Supported kinds: 'AtmosVendorConfig'",
vendorConfig.Kind,
foundVendorConfigFile,
)
// Process all config files
var mergedSources []schema.AtmosVendorSource
var mergedImports []string
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
sourceMap := make(map[string]bool) // Track unique sources by component name
importMap := make(map[string]bool) // Track unique imports

for _, configFile := range configFiles {
var currentConfig schema.AtmosVendorConfig
yamlFile, err := os.ReadFile(configFile)
if err != nil {
return vendorConfig, false, "", err
}

err = yaml.Unmarshal(yamlFile, &currentConfig)
if err != nil {
return vendorConfig, false, "", err
}

// Merge sources, checking for duplicates
for _, source := range currentConfig.Spec.Sources {
if source.Component != "" {
if sourceMap[source.Component] {
return vendorConfig, false, "", fmt.Errorf("duplicate component '%s' found in config file '%s'",
source.Component, configFile)
}
sourceMap[source.Component] = true
}
mergedSources = append(mergedSources, source)
}

// Merge imports, checking for duplicates
for _, imp := range currentConfig.Spec.Imports {
if importMap[imp] {
continue // Skip duplicate imports
}
importMap[imp] = true
mergedImports = append(mergedImports, imp)
}
}

// Create final merged config
vendorConfig.Spec.Sources = mergedSources
vendorConfig.Spec.Imports = mergedImports
vendorConfigFileExists = true

return vendorConfig, vendorConfigFileExists, foundVendorConfigFile, nil
}

Expand Down Expand Up @@ -531,7 +597,7 @@ func processVendorImports(
return nil, nil, err
}

for i, _ := range vendorConfig.Spec.Sources {
for i := range vendorConfig.Spec.Sources {
vendorConfig.Spec.Sources[i].File = imp
}

Expand Down
1 change: 1 addition & 0 deletions pkg/config/const.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ const (
CliConfigDirFlag = "--config-dir"
StackDirFlag = "--stacks-dir"
BasePathFlag = "--base-path"
VendorBasePathFlag = "--vendor-base-path"
WorkflowDirFlag = "--workflows-dir"
KubeConfigConfigFlag = "--kubeconfig-path"
JsonSchemaDirFlag = "--schemas-jsonschema-dir"
Expand Down
6 changes: 6 additions & 0 deletions pkg/config/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,6 +163,12 @@ func processEnvVars(cliConfig *schema.CliConfiguration) error {
cliConfig.BasePath = basePath
}

vendorBasePath := os.Getenv("ATMOS_VENDOR_BASE_PATH")
if len(vendorBasePath) > 0 {
u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_VENDOR_BASE_PATH=%s", vendorBasePath))
cliConfig.Vendor.BasePath = vendorBasePath
}
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved

stacksBasePath := os.Getenv("ATMOS_STACKS_BASE_PATH")
if len(stacksBasePath) > 0 {
u.LogTrace(*cliConfig, fmt.Sprintf("Found ENV var ATMOS_STACKS_BASE_PATH=%s", stacksBasePath))
Expand Down
9 changes: 9 additions & 0 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ type CliConfiguration struct {
Schemas Schemas `yaml:"schemas,omitempty" json:"schemas,omitempty" mapstructure:"schemas"`
Templates Templates `yaml:"templates,omitempty" json:"templates,omitempty" mapstructure:"templates"`
Settings CliSettings `yaml:"settings,omitempty" json:"settings,omitempty" mapstructure:"settings"`
Vendor Vendor `yaml:"vendor,omitempty" json:"vendor,omitempty" mapstructure:"vendor"`
Initialized bool `yaml:"initialized" json:"initialized" mapstructure:"initialized"`
StacksBaseAbsolutePath string `yaml:"stacksBaseAbsolutePath,omitempty" json:"stacksBaseAbsolutePath,omitempty" mapstructure:"stacksBaseAbsolutePath"`
IncludeStackAbsolutePaths []string `yaml:"includeStackAbsolutePaths,omitempty" json:"includeStackAbsolutePaths,omitempty" mapstructure:"includeStackAbsolutePaths"`
Expand Down Expand Up @@ -135,6 +136,7 @@ type ArgsAndFlagsInfo struct {
StacksDir string
WorkflowsDir string
BasePath string
VendorBasePath string
DeployRunInit string
InitRunReconfigure string
AutoGenerateBackendFile string
Expand Down Expand Up @@ -181,6 +183,7 @@ type ConfigAndStacksInfo struct {
AdditionalArgsAndFlags []string
GlobalOptions []string
BasePath string
VendorBasePathFlag string
TerraformCommand string
TerraformDir string
HelmfileCommand string
Expand Down Expand Up @@ -552,3 +555,9 @@ type AtmosVendorConfig struct {
Metadata AtmosVendorMetadata
Spec AtmosVendorSpec `yaml:"spec" json:"spec" mapstructure:"spec"`
}

type Vendor struct {
// Path to vendor configuration file or directory containing vendor files
// If a directory is specified, all .yaml files in the directory will be processed in lexicographical order
BasePath string `yaml:"base_path" json:"base_path" mapstructure:"base_path"`
}
Cerebrovinny marked this conversation as resolved.
Show resolved Hide resolved
Loading