Skip to content

Commit

Permalink
refactor LoadConfig into smaller helper functions
Browse files Browse the repository at this point in the history
  • Loading branch information
haitham911 committed Feb 21, 2025
1 parent eae3f97 commit 6c2f103
Show file tree
Hide file tree
Showing 4 changed files with 72 additions and 44 deletions.
14 changes: 14 additions & 0 deletions pkg/config/default.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
package config

import (
"bytes"
"encoding/json"
"fmt"

"github.com/cloudposse/atmos/internal/tui/templates"
"github.com/cloudposse/atmos/pkg/schema"
"github.com/cloudposse/atmos/pkg/version"
"github.com/pkg/errors"
"github.com/spf13/viper"
)

var (
Expand Down Expand Up @@ -98,3 +101,14 @@ var (
},
}
)

// mergeDefaultConfig merges the contents of defaultCliConfig into the
// current Viper instance if no other configuration file was located.
func mergeDefaultConfig(v *viper.Viper) error {
j, err := json.Marshal(defaultCliConfig)
if err != nil {
return err
}
reader := bytes.NewReader(j)
return v.MergeConfig(reader)
}
10 changes: 5 additions & 5 deletions pkg/config/import_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (

func setupTestFile(content, tempDir string, filename string) (string, error) {
filePath := filepath.Join(tempDir, filename)
err := os.WriteFile(filePath, []byte(content), 0o644)
err := os.WriteFile(filePath, []byte(content), 0o600)
return filePath, err
}

Expand All @@ -35,23 +35,23 @@ func TestProcessImports(t *testing.T) {

// Create mock configuration files in the directory
configFile1 := filepath.Join(configDir, "config1.yaml")
err = os.WriteFile(configFile1, []byte("key1: value1"), 0o644)
err = os.WriteFile(configFile1, []byte("key1: value1"), 0o600)
assert.NoError(t, err)

configFile2 := filepath.Join(configDir, "config2.yaml")
err = os.WriteFile(configFile2, []byte("key2: value2"), 0o644)
err = os.WriteFile(configFile2, []byte("key2: value2"), 0o600)
assert.NoError(t, err)

// Step 2.2: Create a specific local file
localFile := filepath.Join(baseDir, "logs.yaml")
err = os.WriteFile(localFile, []byte("key3: value3"), 0o644)
err = os.WriteFile(localFile, []byte("key3: value3"), 0o600)
assert.NoError(t, err)
// step 2.3
configDir2 := filepath.Join(baseDir, "config")
err = os.MkdirAll(configDir2, 0o755)
assert.NoError(t, err)
configFile3 := filepath.Join(configDir2, "config3.yaml")
err = os.WriteFile(configFile3, []byte("key4: value4"), 0o644)
err = os.WriteFile(configFile3, []byte("key4: value4"), 0o600)
assert.NoError(t, err)
// Step 3: Define test imports
imports := []string{
Expand Down
20 changes: 15 additions & 5 deletions pkg/config/imports.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,10 +53,11 @@ func processConfigImports(source schema.AtmosConfiguration, dst *viper.Viper) er
for _, resolvedPath := range resolvedPaths {
err := MergeConfigFile(resolvedPath.filePath, dst)
if err != nil {
//nolint:revive
log.Debug("error loading config file", "import", resolvedPath.importPaths, "file_path", resolvedPath.filePath, "error", err)
continue
}

//nolint:revive
log.Debug("atmos merged config from import", "import", resolvedPath.importPaths, "file_path", resolvedPath.filePath)
}
}
Expand Down Expand Up @@ -87,6 +88,7 @@ func processImports(basePath string, importPaths []string, tempDir string, curre
// Handle remote imports
paths, err := processRemoteImport(basePath, importPath, tempDir, currentDepth, maxDepth)
if err != nil {
//nolint:revive
log.Debug("failed to process remote import", "path", importPath, "error", err)
continue
}
Expand All @@ -95,6 +97,7 @@ func processImports(basePath string, importPaths []string, tempDir string, curre
// Handle local imports
paths, err := processLocalImport(basePath, importPath, tempDir, currentDepth, maxDepth)
if err != nil {
//nolint:revive
log.Debug("failed to process local import", "path", importPath, "error", err)
continue
}
Expand Down Expand Up @@ -125,6 +128,7 @@ func processRemoteImport(basePath, importPath, tempDir string, currentDepth, max
v.SetConfigFile(tempFile)
err = v.ReadInConfig()
if err != nil {
//nolint:revive
log.Debug("failed to read remote config", "path", importPath, "error", err)
return nil, fmt.Errorf("failed to read remote config")

Check failure on line 133 in pkg/config/imports.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/config/imports.go#L133

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to read remote config\")" (err113)
Raw output
pkg/config/imports.go:133:15: do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to read remote config\")" (err113)
		return nil, fmt.Errorf("failed to read remote config")
		            ^
}
Expand All @@ -145,6 +149,7 @@ func processRemoteImport(basePath, importPath, tempDir string, currentDepth, max
if Imports != nil && len(Imports) > 0 {

Check failure on line 149 in pkg/config/imports.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/config/imports.go#L149

S1009: should omit nil check; len() for nil slices is defined as zero (gosimple)
Raw output
pkg/config/imports.go:149:5: S1009: should omit nil check; len() for nil slices is defined as zero (gosimple)
	if Imports != nil && len(Imports) > 0 {
	   ^
nestedPaths, err := processImports(importBasePath, Imports, tempDir, currentDepth+1, maxDepth)
if err != nil {
//nolint:revive
log.Debug("failed to process nested imports", "import", importPath, "err", err)
return nil, fmt.Errorf("failed to process nested imports")

Check failure on line 154 in pkg/config/imports.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/config/imports.go#L154

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to process nested imports\")" (err113)
Raw output
pkg/config/imports.go:154:16: do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to process nested imports\")" (err113)
			return nil, fmt.Errorf("failed to process nested imports")
			            ^
}
Expand All @@ -170,6 +175,7 @@ func processLocalImport(basePath string, importPath, tempDir string, currentDept
}
paths, err := SearchAtmosConfig(importPath)
if err != nil {
//nolint:revive
log.Debug("failed to resolve local import path", "path", importPath, "err", err)
return nil, fmt.Errorf("failed to resolve local import path")

Check failure on line 180 in pkg/config/imports.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/config/imports.go#L180

do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to resolve local import path\")" (err113)
Raw output
pkg/config/imports.go:180:15: do not define dynamic errors, use wrapped static errors instead: "fmt.Errorf(\"failed to resolve local import path\")" (err113)
		return nil, fmt.Errorf("failed to resolve local import path")
		            ^
}
Expand All @@ -182,6 +188,7 @@ func processLocalImport(basePath string, importPath, tempDir string, currentDept
v.SetConfigType("yaml")
err := v.ReadInConfig()
if err != nil {
//nolint:revive
log.Debug("failed to load local config", "path", path, "error", err)
continue
}
Expand All @@ -200,6 +207,7 @@ func processLocalImport(basePath string, importPath, tempDir string, currentDept
if Imports != nil {
nestedPaths, err := processImports(importBasePath, Imports, tempDir, currentDepth+1, maxDepth)
if err != nil {
//nolint:revive
log.Debug("failed to process nested imports from", "path", path, "error", err)
continue
}
Expand All @@ -210,7 +218,7 @@ func processLocalImport(basePath string, importPath, tempDir string, currentDept
return resolvedPaths, nil
}

// SearchAtmosConfig searches for a config file in path. path is directory,file or a pattern .
// SearchAtmosConfig searches for a config file in path. The path is directory, file or a pattern.
func SearchAtmosConfig(path string) ([]string, error) {
if stat, err := os.Stat(path); err == nil {
if !stat.IsDir() {
Expand Down Expand Up @@ -247,8 +255,8 @@ func generatePatterns(path string) ([]string, error) {
if isDir {
// Search for all .yaml and .yml
patterns := []string{
filepath.Join(path, "**/*.yaml"),
filepath.Join(path, "**/*.yml"),
filepath.Join(path, "**", "*.yaml"),
filepath.Join(path, "**", "*.yml"),
}
return patterns, nil
}
Expand All @@ -273,6 +281,7 @@ func convertToAbsolutePaths(filePaths []string) ([]string, error) {
for _, path := range filePaths {
absPath, err := filepath.Abs(path)
if err != nil {
//nolint:revive
log.Debug("Error getting absolute path for file", "path", path, "error", err)
continue
}
Expand All @@ -286,7 +295,7 @@ func convertToAbsolutePaths(filePaths []string) ([]string, error) {
return absPaths, nil
}

// detectPriorityFiles detects which files will have priority . yaml win over yml if file has same path
// detectPriorityFiles detects which files will have priority. The longer .yaml extensions win over the shorter .yml extensions, if both files exist in the same path.
func detectPriorityFiles(files []string) []string {
// Map to store the highest priority file for each base name
priorityMap := make(map[string]string)
Expand Down Expand Up @@ -363,6 +372,7 @@ func findMatchingFiles(patterns []string) ([]string, error) {
for _, pattern := range patterns {
matches, err := u.GetGlobMatches(pattern)
if err != nil {
//nolint:revive
log.Debug("Error getting glob matches for path", "path", pattern, "error", err)
continue
}
Expand Down
72 changes: 38 additions & 34 deletions pkg/config/load.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package config

import (
"bytes"
"encoding/json"
"fmt"
"os"
"path/filepath"
Expand All @@ -29,45 +27,22 @@ func LoadConfig(configAndStacksInfo schema.ConfigAndStacksInfo) (schema.AtmosCon
v.SetConfigType("yaml")
v.SetTypeByDefaultValue(true)
setDefaultConfiguration(v)
err := readSystemConfig(v)
if err != nil {
return atmosConfig, err
}

err = readHomeConfig(v)
if err != nil {
return atmosConfig, err
}

err = readWorkDirConfig(v)
if err != nil {
return atmosConfig, err
}
err = readEnvAmosConfigPath(v)
if err != nil {
return atmosConfig, err
}
err = readAtmosConfigCli(v, configAndStacksInfo.AtmosCliConfigPath)
if err != nil {
// Load configuration from different sources.
if err := loadConfigSources(v, configAndStacksInfo.AtmosCliConfigPath); err != nil {
return atmosConfig, err
}

atmosConfig.CliConfigPath = v.ConfigFileUsed()

if atmosConfig.CliConfigPath == "" {
// If no config file is used, fall back to the default CLI config.
if v.ConfigFileUsed() == "" {
log.Debug("'atmos.yaml' CLI config was not found", "paths", "system dir, home dir, current dir, ENV vars")
log.Debug("Refer to https://atmos.tools/cli/configuration for details on how to configure 'atmos.yaml'")
log.Debug("Using the default CLI config")
j, err := json.Marshal(defaultCliConfig)
if err != nil {
return atmosConfig, err
}
reader := bytes.NewReader(j)
err = v.MergeConfig(reader)
if err != nil {

if err := mergeDefaultConfig(v); err != nil {
return atmosConfig, err
}
}
atmosConfig.CliConfigPath = v.ConfigFileUsed()

// Set the CLI config path in the atmosConfig struct
if atmosConfig.CliConfigPath != "" && !filepath.IsAbs(atmosConfig.CliConfigPath) {
absPath, err := filepath.Abs(atmosConfig.CliConfigPath)
Expand All @@ -80,7 +55,7 @@ func LoadConfig(configAndStacksInfo schema.ConfigAndStacksInfo) (schema.AtmosCon
atmosConfig.Validate.EditorConfig.Color = true
// https://gist.github.com/chazcheadle/45bf85b793dea2b71bd05ebaa3c28644
// https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/
err = v.Unmarshal(&atmosConfig)
err := v.Unmarshal(&atmosConfig)
if err != nil {
return atmosConfig, err
}
Expand All @@ -97,6 +72,28 @@ func setDefaultConfiguration(v *viper.Viper) {
v.SetDefault("logs.level", "Info")
}

// loadConfigSources delegates reading configs from each source,
// returning early if any step in the chain fails.
func loadConfigSources(v *viper.Viper, cliConfigPath string) error {
if err := readSystemConfig(v); err != nil {
return err
}

if err := readHomeConfig(v); err != nil {
return err
}

if err := readWorkDirConfig(v); err != nil {
return err
}

if err := readEnvAmosConfigPath(v); err != nil {
return err
}

return readAtmosConfigCli(v, cliConfigPath)
}

// readSystemConfig load config from system dir

Check failure on line 97 in pkg/config/load.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/config/load.go#L97

Comment should end in a period (godot)
Raw output
pkg/config/load.go:97:1: Comment should end in a period (godot)
// readSystemConfig load config from system dir
^
func readSystemConfig(v *viper.Viper) error {
configFilePath := ""
Expand Down Expand Up @@ -169,6 +166,7 @@ func readEnvAmosConfigPath(v *viper.Viper) error {
if err != nil {
switch err.(type) {
case viper.ConfigFileNotFoundError:
//nolint:revive
log.Debug("config not found ENV var ATMOS_CLI_CONFIG_PATH", "file", configFilePath)
return nil
default:
Expand All @@ -187,6 +185,7 @@ func readAtmosConfigCli(v *viper.Viper, atmosCliConfigPath string) error {
err := mergeConfig(v, atmosCliConfigPath, CliConfigFileName, true)
switch err.(type) {
case viper.ConfigFileNotFoundError:
//nolint:revive
log.Debug("config not found", "file", atmosCliConfigPath)
default:
return err
Expand All @@ -207,9 +206,11 @@ func mergeConfig(v *viper.Viper, path string, fileName string, processImports bo
return nil
}
if err := mergeDefaultImports(path, v); err != nil {
//nolint:revive
log.Debug("error process imports", "path", path, "error", err)
}
if err := mergeImports(v); err != nil {
//nolint:revive
log.Debug("error process imports", "file", v.ConfigFileUsed(), "error", err)
}
return nil
Expand Down Expand Up @@ -239,6 +240,7 @@ func mergeDefaultImports(dirPath string, dst *viper.Viper) error {
searchDir = filepath.Join(filepath.FromSlash(dirPath), ".atmos.d/**/*")

Check failure on line 240 in pkg/config/load.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/config/load.go#L240

filepathJoin: ".atmos.d/**/*" contains a path separator (gocritic)
Raw output
pkg/config/load.go:240:57: filepathJoin: ".atmos.d/**/*" contains a path separator (gocritic)
	searchDir = filepath.Join(filepath.FromSlash(dirPath), ".atmos.d/**/*")
	                                                       ^
foundPaths2, err := SearchAtmosConfig(searchDir)
if err != nil {
//nolint:revive
log.Debug("Failed to find atmos config file", "path", searchDir, "error", err)
}
if len(foundPaths2) > 0 {
Expand All @@ -247,9 +249,11 @@ func mergeDefaultImports(dirPath string, dst *viper.Viper) error {
for _, filePath := range atmosFoundFilePaths {
err := MergeConfigFile(filePath, dst)
if err != nil {
//nolint:revive
log.Debug("error loading config file", "path", filePath, "error", err)
continue
}
//nolint:revive
log.Debug("atmos merged config", "path", filePath)

}

Check failure on line 259 in pkg/config/load.go

View workflow job for this annotation

GitHub Actions / golangci

[golangci] pkg/config/load.go#L259

unnecessary trailing newline (whitespace)
Raw output
pkg/config/load.go:259:2: unnecessary trailing newline (whitespace)
	}
	^
Expand Down

0 comments on commit 6c2f103

Please sign in to comment.