Skip to content

Commit

Permalink
Merge branch 'main' into issues-888
Browse files Browse the repository at this point in the history
  • Loading branch information
osterman authored Jan 6, 2025
2 parents bc02f6e + 5d93ce1 commit 713ab4b
Show file tree
Hide file tree
Showing 10 changed files with 540 additions and 25 deletions.
10 changes: 10 additions & 0 deletions atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -315,6 +315,16 @@ settings:
# If the source and destination lists have the same length, all items in the destination lists are
# deep-merged with all items in the source list.
list_merge_strategy: replace
terminal:
syntax_highlighting:
enabled: true
lexer: yaml # Default lexer for the content
formatter: terminal # Output formatter (e.g., terminal, html)
style: dracula # Highlighting style
pager: true # Enable pager
options:
line_numbers: true # Display line numbers
wrap: false # Wrap long lines

# Terminal settings for displaying content
terminal:
Expand Down
7 changes: 7 additions & 0 deletions examples/tests/atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,13 @@ commands:
steps:
- atmos vendor pull

# test for infinite loop
- name: loop
description: This command tests circuit breaker for infinite loop
steps:
- "echo Hello world!"
- atmos loop

- name: tf
description: Execute 'terraform' commands
# subcommands
Expand Down
48 changes: 45 additions & 3 deletions internal/exec/shell_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,30 @@ import (
u "github.com/cloudposse/atmos/pkg/utils"
)

// MaxShellDepth is the maximum number of nested shell commands that can be executed
const MaxShellDepth = 10

// getNextShellLevel increments the ATMOS_SHLVL and returns the new value or an error if maximum depth is exceeded
func getNextShellLevel() (int, error) {
atmosShellLvl := os.Getenv("ATMOS_SHLVL")
shellVal := 0
if atmosShellLvl != "" {
val, err := strconv.Atoi(atmosShellLvl)
if err != nil {
return 0, fmt.Errorf("invalid ATMOS_SHLVL value: %s", atmosShellLvl)
}
shellVal = val
}

shellVal++

if shellVal > MaxShellDepth {
return 0, fmt.Errorf("ATMOS_SHLVL (%d) exceeds maximum allowed depth (%d). Infinite recursion?",
shellVal, MaxShellDepth)
}
return shellVal, nil
}

// ExecuteShellCommand prints and executes the provided command with args and flags
func ExecuteShellCommand(
atmosConfig schema.AtmosConfiguration,
Expand All @@ -31,8 +55,14 @@ func ExecuteShellCommand(
dryRun bool,
redirectStdError string,
) error {
newShellLevel, err := getNextShellLevel()
if err != nil {
return err
}
updatedEnv := append(env, fmt.Sprintf("ATMOS_SHLVL=%d", newShellLevel))

cmd := exec.Command(command, args...)
cmd.Env = append(os.Environ(), env...)
cmd.Env = append(os.Environ(), updatedEnv...)
cmd.Dir = dir
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
Expand Down Expand Up @@ -83,14 +113,20 @@ func ExecuteShell(
env []string,
dryRun bool,
) error {
newShellLevel, err := getNextShellLevel()
if err != nil {
return err
}
updatedEnv := append(env, fmt.Sprintf("ATMOS_SHLVL=%d", newShellLevel))

u.LogDebug(atmosConfig, "\nExecuting command:")
u.LogDebug(atmosConfig, command)

if dryRun {
return nil
}

return shellRunner(command, name, dir, env, os.Stdout)
return shellRunner(command, name, dir, updatedEnv, os.Stdout)
}

// ExecuteShellAndReturnOutput runs a shell script and capture its standard output
Expand All @@ -104,14 +140,20 @@ func ExecuteShellAndReturnOutput(
) (string, error) {
var b bytes.Buffer

newShellLevel, err := getNextShellLevel()
if err != nil {
return "", err
}
updatedEnv := append(env, fmt.Sprintf("ATMOS_SHLVL=%d", newShellLevel))

u.LogDebug(atmosConfig, "\nExecuting command:")
u.LogDebug(atmosConfig, command)

if dryRun {
return "", nil
}

err := shellRunner(command, name, dir, env, &b)
err = shellRunner(command, name, dir, updatedEnv, &b)
if err != nil {
return "", err
}
Expand Down
101 changes: 99 additions & 2 deletions pkg/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,21 @@ var (
Frequency: "daily",
},
},
Settings: &schema.AtmosSettings{
Terminal: &schema.TerminalSettings{
SyntaxHighlighting: &schema.SyntaxHighlightingSettings{
Enabled: true,
Lexer: "yaml",
Formatter: "terminal",
Style: "dracula",
Pager: false,
Options: &schema.SyntaxHighlightOptions{
LineNumbers: false,
Wrap: false,
},
},
},
},
}
)

Expand All @@ -111,6 +126,17 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
v.SetConfigType("yaml")
v.SetTypeByDefaultValue(true)

// Load default configuration first
defaultConfigJSON, err := json.Marshal(defaultCliConfig)
if err != nil {
return atmosConfig, err
}

defaultReader := bytes.NewReader(defaultConfigJSON)
if err := v.ReadConfig(defaultReader); err != nil {
return atmosConfig, err
}

// Default configuration values
v.SetDefault("components.helmfile.use_eks", true)
v.SetDefault("components.terraform.append_user_agent", fmt.Sprintf("Atmos/%s (Cloud Posse; +https://atmos.tools)", version.Version))
Expand Down Expand Up @@ -263,6 +289,31 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks
atmosConfig.Components.Terraform.AppendUserAgent = fmt.Sprintf("Atmos/%s (Cloud Posse; +https://atmos.tools)", version.Version)
}

// Initialize settings with defaults if not set
if atmosConfig.Settings == nil {
atmosConfig.Settings = defaultCliConfig.Settings
} else {
// Only initialize nil fields with defaults
if atmosConfig.Settings.Terminal == nil {
atmosConfig.Settings.Terminal = defaultCliConfig.Settings.Terminal
} else if atmosConfig.Settings.Terminal.SyntaxHighlighting == nil {
atmosConfig.Settings.Terminal.SyntaxHighlighting = defaultCliConfig.Settings.Terminal.SyntaxHighlighting
} else {
// Update settings from viper
atmosConfig.Settings.Terminal.SyntaxHighlighting.Enabled = v.GetBool("settings.terminal.syntax_highlighting.enabled")
atmosConfig.Settings.Terminal.SyntaxHighlighting.Lexer = v.GetString("settings.terminal.syntax_highlighting.lexer")
atmosConfig.Settings.Terminal.SyntaxHighlighting.Formatter = v.GetString("settings.terminal.syntax_highlighting.formatter")
atmosConfig.Settings.Terminal.SyntaxHighlighting.Style = v.GetString("settings.terminal.syntax_highlighting.style")
atmosConfig.Settings.Terminal.SyntaxHighlighting.Pager = v.GetBool("settings.terminal.syntax_highlighting.pager")

if atmosConfig.Settings.Terminal.SyntaxHighlighting.Options == nil {
atmosConfig.Settings.Terminal.SyntaxHighlighting.Options = &schema.SyntaxHighlightOptions{}
}
atmosConfig.Settings.Terminal.SyntaxHighlighting.Options.LineNumbers = v.GetBool("settings.terminal.syntax_highlighting.options.line_numbers")
atmosConfig.Settings.Terminal.SyntaxHighlighting.Options.Wrap = v.GetBool("settings.terminal.syntax_highlighting.options.wrap")
}
}

// Check config
err = checkConfig(atmosConfig)
if err != nil {
Expand Down Expand Up @@ -375,10 +426,56 @@ func processConfigFile(
}
}(reader)

err = v.MergeConfig(reader)
if err != nil {
// Create a new viper instance for this config file
fileViper := viper.New()
fileViper.SetConfigType("yaml")

// Read the config file
if err := fileViper.ReadConfig(reader); err != nil {
return false, err
}

// Get all settings from the file
settings := fileViper.AllSettings()

// Merge settings into the main viper instance
for key, value := range settings {
if key == "settings" {
// Handle settings section separately to preserve nested values
if settingsMap, ok := value.(map[string]interface{}); ok {
if terminalMap, ok := settingsMap["terminal"].(map[string]interface{}); ok {
if syntaxMap, ok := terminalMap["syntax_highlighting"].(map[string]interface{}); ok {
// Set each field individually to preserve nested values
if enabled, ok := syntaxMap["enabled"].(bool); ok {
v.Set("settings.terminal.syntax_highlighting.enabled", enabled)
}
if lexer, ok := syntaxMap["lexer"].(string); ok {
v.Set("settings.terminal.syntax_highlighting.lexer", lexer)
}
if formatter, ok := syntaxMap["formatter"].(string); ok {
v.Set("settings.terminal.syntax_highlighting.formatter", formatter)
}
if style, ok := syntaxMap["style"].(string); ok {
v.Set("settings.terminal.syntax_highlighting.style", style)
}
if pager, ok := syntaxMap["pager"].(bool); ok {
v.Set("settings.terminal.syntax_highlighting.pager", pager)
}
if options, ok := syntaxMap["options"].(map[string]interface{}); ok {
if lineNumbers, ok := options["line_numbers"].(bool); ok {
v.Set("settings.terminal.syntax_highlighting.options.line_numbers", lineNumbers)
}
if wrap, ok := options["wrap"].(bool); ok {
v.Set("settings.terminal.syntax_highlighting.options.wrap", wrap)
}
}
}
}
}
} else {
v.Set(key, value)
}
}

return true, nil
}
54 changes: 36 additions & 18 deletions pkg/schema/schema.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type AtmosConfiguration struct {
Integrations Integrations `yaml:"integrations,omitempty" json:"integrations,omitempty" mapstructure:"integrations"`
Schemas Schemas `yaml:"schemas,omitempty" json:"schemas,omitempty" mapstructure:"schemas"`
Templates Templates `yaml:"templates,omitempty" json:"templates,omitempty" mapstructure:"templates"`
Settings AtmosSettings `yaml:"settings,omitempty" json:"settings,omitempty" mapstructure:"settings"`
Settings *AtmosSettings `yaml:"settings,omitempty" json:"settings,omitempty" mapstructure:"settings"`
StoresConfig store.StoresConfig `yaml:"stores,omitempty" json:"stores,omitempty" mapstructure:"stores"`
Vendor Vendor `yaml:"vendor,omitempty" json:"vendor,omitempty" mapstructure:"vendor"`
Initialized bool `yaml:"initialized" json:"initialized" mapstructure:"initialized"`
Expand All @@ -37,19 +37,43 @@ type AtmosConfiguration struct {
Stores store.StoreRegistry `yaml:"stores_registry,omitempty" json:"stores_registry,omitempty" mapstructure:"stores_registry"`
}

type Terminal struct {
MaxWidth int `yaml:"max_width" json:"max_width" mapstructure:"max_width"`
Pager bool `yaml:"pager" json:"pager" mapstructure:"pager"`
Timestamps bool `yaml:"timestamps" json:"timestamps" mapstructure:"timestamps"`
Colors bool `yaml:"colors" json:"colors" mapstructure:"colors"`
Unicode bool `yaml:"unicode" json:"unicode" mapstructure:"unicode"`
type AtmosSettings struct {
ListMergeStrategy string `yaml:"list_merge_strategy" json:"list_merge_strategy" mapstructure:"list_merge_strategy"`
Terminal *TerminalSettings `yaml:"terminal,omitempty" json:"terminal,omitempty" mapstructure:"terminal"`
Docs Docs `yaml:"docs,omitempty" json:"docs,omitempty" mapstructure:"docs"`
Markdown MarkdownSettings `yaml:"markdown,omitempty" json:"markdown,omitempty" mapstructure:"markdown"`
}

type AtmosSettings struct {
ListMergeStrategy string `yaml:"list_merge_strategy" json:"list_merge_strategy" mapstructure:"list_merge_strategy"`
Terminal Terminal `yaml:"terminal,omitempty" json:"terminal,omitempty" mapstructure:"terminal"`
Docs Docs `yaml:"docs,omitempty" json:"docs,omitempty" mapstructure:"docs"`
Markdown MarkdownSettings `yaml:"markdown,omitempty" json:"markdown,omitempty" mapstructure:"markdown"`
type TerminalSettings struct {
MaxWidth int `yaml:"max_width,omitempty" json:"max_width,omitempty" mapstructure:"max_width"`
Pager bool `yaml:"pager,omitempty" json:"pager,omitempty" mapstructure:"pager"`
Timestamps bool `yaml:"timestamps,omitempty" json:"timestamps,omitempty" mapstructure:"timestamps"`
Colors bool `yaml:"colors,omitempty" json:"colors,omitempty" mapstructure:"colors"`
Unicode bool `yaml:"unicode,omitempty" json:"unicode,omitempty" mapstructure:"unicode"`
SyntaxHighlighting *SyntaxHighlightingSettings `yaml:"syntax_highlighting,omitempty" json:"syntax_highlighting,omitempty" mapstructure:"syntax_highlighting"`
}

type SyntaxHighlightOptions struct {
LineNumbers bool `yaml:"line_numbers" json:"line_numbers" mapstructure:"line_numbers"`
Wrap bool `yaml:"wrap" json:"wrap" mapstructure:"wrap"`
}

type SyntaxHighlightingSettings struct {
Enabled bool `yaml:"enabled" json:"enabled" mapstructure:"enabled"`
Lexer string `yaml:"lexer" json:"lexer" mapstructure:"lexer"`
Formatter string `yaml:"formatter" json:"formatter" mapstructure:"formatter"`
Style string `yaml:"style" json:"style" mapstructure:"style"`
Pager bool `yaml:"pager" json:"pager" mapstructure:"pager"`
Options *SyntaxHighlightOptions `yaml:"options,omitempty" json:"options,omitempty" mapstructure:"options"`
}

type Settings struct {
DependsOn DependsOn `yaml:"depends_on,omitempty" json:"depends_on,omitempty" mapstructure:"depends_on"`
Spacelift SettingsSpacelift `yaml:"spacelift,omitempty" json:"spacelift,omitempty" mapstructure:"spacelift"`
Templates Templates `yaml:"templates,omitempty" json:"templates,omitempty" mapstructure:"templates"`
ListMergeStrategy string `yaml:"list_merge_strategy,omitempty" json:"list_merge_strategy,omitempty"`
Terminal *TerminalSettings `yaml:"terminal,omitempty" json:"terminal,omitempty" mapstructure:"terminal"`
Docs *Docs `yaml:"docs,omitempty" json:"docs,omitempty" mapstructure:"docs"`
}

type Docs struct {
Expand Down Expand Up @@ -541,12 +565,6 @@ type Dependent struct {

type SettingsSpacelift AtmosSectionMapType

type Settings struct {
DependsOn DependsOn `yaml:"depends_on,omitempty" json:"depends_on,omitempty" mapstructure:"depends_on"`
Spacelift SettingsSpacelift `yaml:"spacelift,omitempty" json:"spacelift,omitempty" mapstructure:"spacelift"`
Templates Templates `yaml:"templates,omitempty" json:"templates,omitempty" mapstructure:"templates"`
}

// ConfigSourcesStackDependency defines schema for sources of config sections
type ConfigSourcesStackDependency struct {
StackFile string `yaml:"stack_file" json:"stack_file" mapstructure:"stack_file"`
Expand Down
Loading

0 comments on commit 713ab4b

Please sign in to comment.