Skip to content

Commit 48a6b1f

Browse files
haitham911autofix-ci[bot]ostermanaknysh
authored
Enhance Atmos CLI: Add Support for Custom Base Path and Config Paths (#1091)
* refactor InitCliConfig * add readConfig function * add load config * [autofix.ci] apply automated fixes * add default.go & update snapshots * resolve comments * remove unused function processConfigFile * improve log * [autofix.ci] apply automated fixes * add imports * update snapshots * add import config test * [autofix.ci] apply automated fixes * refactor LoadConfig into smaller helper functions * update Valid_Log_Level snapshots * update log snapshots * update snapshots * update snapshots Valid_Log_Level * remove //nolint:revive * Exclude any line containing "log." * fix lint revive.add-constant * update add-constant to include "error","path" * improve code lint * improve log * improve log charmbracelet aliased * Preprocess Atmos config with custom functions * [autofix.ci] apply automated fixes * remove print line * improve logs * refactor processScalarNode * improve logs * [autofix.ci] apply automated fixes * fix linter error * fix linter errors * update snapshots * fix linter errors * fix linter * add global --base-path ,--config ,--config-path * add embed atmos config * add global --base-path ,--config ,--config-path * enhance code * go format file * [autofix.ci] apply automated fixes * disable atmos_vendor_pull * update snapshots Valid_Log_Level_in_Environment_Variable * update snapshots * updates snapshots * update snapshots * set config path dir * update snapshots * add processStackConfigs on config.go * [autofix.ci] apply automated fixes * fix linter errors config.go * add nolint:gocritic for InitCliConfig * fix readEnvAmosConfigPath * [autofix.ci] apply automated fixes * improve log * fix import * update snapshots * fix linter error * Apply suggestions from code review * [autofix.ci] apply automated fixes * remove log * update snap shots * add ProcessSchemas * resolve comments * fix base path * add doc * move functions to helper * update snapshots * [autofix.ci] apply automated fixes * update snapshot * update snapshots * update snapshots * remove repository root configuration from CLI docs * update snapshots * update doc * update snapshot * fix test TestExecuteVendorPullCommand * fix tests TestExecuteWorkflowCmd,TestExecuteVendorPull * add test cases * [autofix.ci] apply automated fixes * remove comment * fix test * set --config as global on editorconfig * modify description flag --config --config-path * add test * add test * update snapshots * [autofix.ci] apply automated fixes * update doc * update doc * set configFilePaths value on editorConfigCmd * fix linter error * fix linter error * fix linter --------- Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Erik Osterman (CEO @ Cloud Posse) <[email protected]> Co-authored-by: Andriy Knysh <[email protected]>
1 parent c930bbb commit 48a6b1f

32 files changed

+948
-335
lines changed

cmd/root.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,10 @@ func init() {
190190
"Errors can be redirected to any file or any standard file descriptor (including `/dev/null`)")
191191

192192
RootCmd.PersistentFlags().String("logs-level", "Info", "Logs level. Supported log levels are Trace, Debug, Info, Warning, Off. If the log level is set to Off, Atmos will not log any messages")
193-
RootCmd.PersistentFlags().String("logs-file", "/dev/stderr", "The file to write Atmos logs to. Logs can be written to any file or any standard file descriptor, including `/dev/stdout`, `/dev/stderr` and `/dev/null`")
194-
193+
RootCmd.PersistentFlags().String("logs-file", "/dev/stderr", "The file to write Atmos logs to. Logs can be written to any file or any standard file descriptor, including '/dev/stdout', '/dev/stderr' and '/dev/null'")
194+
RootCmd.PersistentFlags().String("base-path", "", "Base path for Atmos project")
195+
RootCmd.PersistentFlags().StringSlice("config", []string{}, "Paths to configuration files (comma-separated or repeated flag)")
196+
RootCmd.PersistentFlags().StringSlice("config-path", []string{}, "Paths to configuration directories (comma-separated or repeated flag)")
195197
// Set custom usage template
196198
err := templates.SetCustomUsageFunc(RootCmd)
197199
if err != nil {

cmd/validate_editorconfig.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ func initializeConfig(cmd *cobra.Command) {
5050
replaceAtmosConfigInConfig(cmd, atmosConfig)
5151

5252
configPaths := []string{}
53+
if cmd.Flags().Changed("config") {
54+
config := cmd.Flags().Lookup("config")
55+
if config != nil {
56+
configFilePaths = strings.Split(config.Value.String(), ",")
57+
}
58+
}
5359
if len(configFilePaths) == 0 {
5460
configPaths = append(configPaths, defaultConfigFileNames...)
5561
} else {
@@ -179,7 +185,6 @@ func checkVersion(config config.Config) error {
179185

180186
// addPersistentFlags adds flags to the root command
181187
func addPersistentFlags(cmd *cobra.Command) {
182-
cmd.PersistentFlags().StringSliceVar(&configFilePaths, "config", defaultConfigFileNames, "Paths to the configuration files")
183188
cmd.PersistentFlags().StringVar(&tmpExclude, "exclude", "", "Regex to exclude files from checking")
184189
cmd.PersistentFlags().BoolVar(&initEditorConfig, "init", false, "Create an initial configuration")
185190

internal/exec/utils.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,18 @@ func ProcessCommandLineArgs(
209209
return configAndStacksInfo, err
210210
}
211211

212+
configAndStacksInfo.BasePath, err = cmd.Flags().GetString("base-path")
213+
if err != nil {
214+
return configAndStacksInfo, err
215+
}
216+
configAndStacksInfo.AtmosConfigFilesFromArg, err = cmd.Flags().GetStringSlice("config")
217+
if err != nil {
218+
return configAndStacksInfo, err
219+
}
220+
configAndStacksInfo.AtmosConfigDirsFromArg, err = cmd.Flags().GetStringSlice("config-path")
221+
if err != nil {
222+
return configAndStacksInfo, err
223+
}
212224
finalAdditionalArgsAndFlags := argsAndFlagsInfo.AdditionalArgsAndFlags
213225
if len(additionalArgsAndFlags) > 0 {
214226
finalAdditionalArgsAndFlags = append(finalAdditionalArgsAndFlags, additionalArgsAndFlags...)
@@ -220,7 +232,6 @@ func ProcessCommandLineArgs(
220232
configAndStacksInfo.ComponentType = componentType
221233
configAndStacksInfo.ComponentFromArg = argsAndFlagsInfo.ComponentFromArg
222234
configAndStacksInfo.GlobalOptions = argsAndFlagsInfo.GlobalOptions
223-
configAndStacksInfo.BasePath = argsAndFlagsInfo.BasePath
224235
configAndStacksInfo.TerraformCommand = argsAndFlagsInfo.TerraformCommand
225236
configAndStacksInfo.TerraformDir = argsAndFlagsInfo.TerraformDir
226237
configAndStacksInfo.HelmfileCommand = argsAndFlagsInfo.HelmfileCommand

internal/exec/vendor_utils_test.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,9 @@ func TestExecuteVendorPullCommand(t *testing.T) {
4848
cmd.PersistentFlags().Bool("dry-run", false, "Simulate pulling the latest version of the specified component from the remote repository without making any changes.")
4949
cmd.PersistentFlags().String("tags", "", "Only vendor the components that have the specified tags")
5050
cmd.PersistentFlags().Bool("everything", false, "Vendor all components")
51-
51+
cmd.PersistentFlags().String("base-path", "", "Base path for Atmos project")
52+
cmd.PersistentFlags().StringSlice("config", []string{}, "Paths to configuration file")
53+
cmd.PersistentFlags().StringSlice("config-path", []string{}, "Path to configuration directory")
5254
// Execute the command
5355
err = cmd.RunE(cmd, []string{})
5456
assert.NoError(t, err, "'atmos vendor pull' command should execute without error")
@@ -103,6 +105,9 @@ func TestExecuteVendorPull(t *testing.T) {
103105
}
104106
// set vendor pull command
105107
cmd := cobra.Command{}
108+
cmd.PersistentFlags().String("base-path", "", "Base path for Atmos project")
109+
cmd.PersistentFlags().StringSlice("config", []string{}, "Paths to configuration file")
110+
cmd.PersistentFlags().StringSlice("config-path", []string{}, "Path to configuration directory")
106111
flags := cmd.Flags()
107112
flags.String("component", "", "")
108113
flags.String("stack", "", "")

internal/exec/workflow_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ atmos describe component c1 -s test
5555
cmd.PersistentFlags().Bool("dry-run", false, "Simulate the workflow without making any changes")
5656
cmd.PersistentFlags().String("from-step", "", "Resume the workflow from the specified step")
5757
cmd.PersistentFlags().String("stack", "", "Execute the workflow for the specified stack")
58-
58+
cmd.PersistentFlags().String("base-path", "", "Base path for Atmos project")
59+
cmd.PersistentFlags().StringSlice("config", []string{}, "Paths to configuration file")
60+
cmd.PersistentFlags().StringSlice("config-path", []string{}, "Path to configuration directory")
5961
// Execute the command
6062
cmd.SetArgs([]string{"--file", "workflows", "show-all-describe-component-commands"})
6163
err = cmd.Execute()

pkg/config/atmos.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
logs:
2+
# Can also be set using 'ATMOS_LOGS_FILE' ENV var, or '--logs-file' command-line argument
3+
# File or standard file descriptor to write logs to
4+
# Logs can be written to any file or any standard file descriptor, including `/dev/stdout`, `/dev/stderr` and `/dev/null`
5+
file: "/dev/stderr"
6+
# Supported log levels: Trace, Debug, Info, Warning, Off
7+
# Can also be set using 'ATMOS_LOGS_LEVEL' ENV var, or '--logs-level' command-line argument
8+
level: Info

pkg/config/config_test.go

+13-3
Original file line numberDiff line numberDiff line change
@@ -244,7 +244,7 @@ func TestMergeConfig_ConfigFileNotFound(t *testing.T) {
244244
tempDir := t.TempDir() // Empty directory, no config file
245245

246246
v := viper.New()
247-
err := mergeConfig(v, tempDir, true)
247+
err := mergeConfig(v, tempDir, CliConfigFileName, true)
248248

249249
assert.Error(t, err)
250250
assert.Contains(t, err.Error(), "Config File \"atmos\" Not Found")
@@ -262,7 +262,7 @@ logs:
262262
createConfigFile(t, tempDir, "atmos.yaml", content)
263263
v := viper.New()
264264
v.SetConfigType("yaml")
265-
err := mergeConfig(v, tempDir, false)
265+
err := mergeConfig(v, tempDir, CliConfigFileName, false)
266266
assert.NoError(t, err)
267267
assert.Equal(t, "./", v.GetString("base_path"))
268268
content2 := `
@@ -272,10 +272,20 @@ vendor:
272272
`
273273
tempDir2 := t.TempDir()
274274
createConfigFile(t, tempDir2, "atmos.yml", content2)
275-
err = mergeConfig(v, tempDir2, false)
275+
err = mergeConfig(v, tempDir2, CliConfigFileName, false)
276276
assert.NoError(t, err)
277277
assert.Equal(t, "./test", v.GetString("base_path"))
278278
assert.Equal(t, "./test2-vendor.yaml", v.GetString("vendor.base_path"))
279279
assert.Equal(t, "Debug", v.GetString("logs.level"))
280280
assert.Equal(t, filepath.Join(tempDir2, "atmos.yml"), v.ConfigFileUsed())
281281
}
282+
283+
func TestMergeDefaultConfig(t *testing.T) {
284+
v := viper.New()
285+
286+
err := mergeDefaultConfig(v)
287+
assert.Error(t, err, "cannot decode configuration: unable to determine config type")
288+
v.SetConfigType("yaml")
289+
err = mergeDefaultConfig(v)
290+
assert.NoError(t, err, "should not return error if config type is yaml")
291+
}

pkg/config/const.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package config
22

33
const (
4-
CliConfigFileName = "atmos"
4+
CliConfigFileName = "atmos"
5+
DotCliConfigFileName = ".atmos"
6+
57
SystemDirConfigFilePath = "/usr/local/etc/atmos"
68
WindowsAppDataEnvVar = "LOCALAPPDATA"
79

pkg/config/load.go

+40-10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package config
22

33
import (
44
"bytes"
5+
_ "embed"
56
"errors"
67
"fmt"
78
"os"
@@ -16,10 +17,14 @@ import (
1617
"gopkg.in/yaml.v3"
1718
)
1819

20+
//go:embed atmos.yaml
21+
var embeddedConfigData []byte
22+
1923
const MaximumImportLvL = 10
2024

2125
var ErrAtmosDIrConfigNotFound = errors.New("atmos config directory not found")
2226

27+
// * Embedded atmos.yaml (`atmos/pkg/config/atmos.yaml`)
2328
// * System dir (`/usr/local/etc/atmos` on Linux, `%LOCALAPPDATA%/atmos` on Windows).
2429
// * Home directory (~/.atmos).
2530
// * Current working directory.
@@ -31,8 +36,20 @@ func LoadConfig(configAndStacksInfo *schema.ConfigAndStacksInfo) (schema.AtmosCo
3136
v.SetConfigType("yaml")
3237
v.SetTypeByDefaultValue(true)
3338
setDefaultConfiguration(v)
39+
// Load embed atmos.yaml
40+
if err := loadEmbeddedConfig(v); err != nil {
41+
return atmosConfig, err
42+
}
43+
if len(configAndStacksInfo.AtmosConfigFilesFromArg) > 0 || len(configAndStacksInfo.AtmosConfigDirsFromArg) > 0 {
44+
err := loadConfigFromCLIArgs(v, configAndStacksInfo, &atmosConfig)
45+
if err != nil {
46+
return atmosConfig, err
47+
}
48+
return atmosConfig, nil
49+
}
50+
3451
// Load configuration from different sources.
35-
if err := loadConfigSources(v, configAndStacksInfo.AtmosCliConfigPath); err != nil {
52+
if err := loadConfigSources(v, configAndStacksInfo); err != nil {
3653
return atmosConfig, err
3754
}
3855
// If no config file is used, fall back to the default CLI config.
@@ -103,7 +120,7 @@ func setDefaultConfiguration(v *viper.Viper) {
103120

104121
// loadConfigSources delegates reading configs from each source,
105122
// returning early if any step in the chain fails.
106-
func loadConfigSources(v *viper.Viper, cliConfigPath string) error {
123+
func loadConfigSources(v *viper.Viper, configAndStacksInfo *schema.ConfigAndStacksInfo) error {
107124
if err := readSystemConfig(v); err != nil {
108125
return err
109126
}
@@ -120,7 +137,7 @@ func loadConfigSources(v *viper.Viper, cliConfigPath string) error {
120137
return err
121138
}
122139

123-
return readAtmosConfigCli(v, cliConfigPath)
140+
return readAtmosConfigCli(v, configAndStacksInfo.AtmosCliConfigPath)
124141
}
125142

126143
// readSystemConfig load config from system dir .
@@ -136,7 +153,7 @@ func readSystemConfig(v *viper.Viper) error {
136153
}
137154

138155
if len(configFilePath) > 0 {
139-
err := mergeConfig(v, configFilePath, false)
156+
err := mergeConfig(v, configFilePath, CliConfigFileName, false)
140157
switch err.(type) {
141158
case viper.ConfigFileNotFoundError:
142159
return nil
@@ -154,7 +171,7 @@ func readHomeConfig(v *viper.Viper) error {
154171
return err
155172
}
156173
configFilePath := filepath.Join(home, ".atmos")
157-
err = mergeConfig(v, configFilePath, true)
174+
err = mergeConfig(v, configFilePath, CliConfigFileName, true)
158175
if err != nil {
159176
switch err.(type) {
160177
case viper.ConfigFileNotFoundError:
@@ -173,7 +190,7 @@ func readWorkDirConfig(v *viper.Viper) error {
173190
if err != nil {
174191
return err
175192
}
176-
err = mergeConfig(v, wd, true)
193+
err = mergeConfig(v, wd, CliConfigFileName, true)
177194
if err != nil {
178195
switch err.(type) {
179196
case viper.ConfigFileNotFoundError:
@@ -190,7 +207,7 @@ func readEnvAmosConfigPath(v *viper.Viper) error {
190207
if atmosPath == "" {
191208
return nil
192209
}
193-
err := mergeConfig(v, atmosPath, true)
210+
err := mergeConfig(v, atmosPath, CliConfigFileName, true)
194211
if err != nil {
195212
switch err.(type) {
196213
case viper.ConfigFileNotFoundError:
@@ -209,7 +226,7 @@ func readAtmosConfigCli(v *viper.Viper, atmosCliConfigPath string) error {
209226
if len(atmosCliConfigPath) == 0 {
210227
return nil
211228
}
212-
err := mergeConfig(v, atmosCliConfigPath, true)
229+
err := mergeConfig(v, atmosCliConfigPath, CliConfigFileName, true)
213230
switch err.(type) {
214231
case viper.ConfigFileNotFoundError:
215232
log.Debug("config not found", "file", atmosCliConfigPath)
@@ -221,11 +238,11 @@ func readAtmosConfigCli(v *viper.Viper, atmosCliConfigPath string) error {
221238
}
222239

223240
// mergeConfig merge config from a specified path directory and process imports. Return error if config file does not exist.
224-
func mergeConfig(v *viper.Viper, path string, processImports bool) error {
241+
func mergeConfig(v *viper.Viper, path string, fileName string, processImports bool) error {
225242
// Create a temporary Viper instance to isolate this configuration load
226243
tempViper := viper.New()
227244
tempViper.AddConfigPath(path)
228-
tempViper.SetConfigName(CliConfigFileName)
245+
tempViper.SetConfigName(fileName)
229246
tempViper.SetConfigType("yaml")
230247
// Read configuration into temporary instance
231248
if err := tempViper.ReadInConfig(); err != nil {
@@ -413,3 +430,16 @@ func mergeConfigFile(
413430

414431
return nil
415432
}
433+
434+
// loadEmbeddedConfig loads the embedded atmos.yaml configuration.
435+
func loadEmbeddedConfig(v *viper.Viper) error {
436+
// Create a reader from the embedded YAML data
437+
reader := bytes.NewReader(embeddedConfigData)
438+
439+
// Merge the embedded configuration into Viper
440+
if err := v.MergeConfig(reader); err != nil {
441+
return fmt.Errorf("failed to merge embedded config: %w", err)
442+
}
443+
444+
return nil
445+
}

0 commit comments

Comments
 (0)