From 759ec405b73ebda033d8a90870b9071bd6465677 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sat, 11 Jan 2025 15:05:56 +0000 Subject: [PATCH 1/8] log level invalid cover --- pkg/logger/logger.go | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 833885f6f..7b5979189 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -54,8 +54,10 @@ func ParseLogLevel(logLevel string) (LogLevel, error) { return LogLevelInfo, nil case LogLevelWarning: return LogLevelWarning, nil + case LogLevelOff: + return LogLevelOff, nil default: - return LogLevelInfo, fmt.Errorf("invalid log level '%s'. Supported log levels are Trace, Debug, Info, Warning, Off", logLevel) + return LogLevelInfo, fmt.Errorf("Error: Invalid log level '%s'. Valid options are: [Trace, Debug, Info, Warning, Off]. Please use one of these values", logLevel) } } @@ -116,33 +118,33 @@ func (l *Logger) Error(err error) { } func (l *Logger) Trace(message string) { - if l.LogLevel == LogLevelTrace { + if l.LogLevel != LogLevelOff && l.LogLevel == LogLevelTrace { l.log(theme.Colors.Info, message) } } func (l *Logger) Debug(message string) { - if l.LogLevel == LogLevelTrace || - l.LogLevel == LogLevelDebug { + if l.LogLevel != LogLevelOff && (l.LogLevel == LogLevelTrace || + l.LogLevel == LogLevelDebug) { l.log(theme.Colors.Info, message) } } func (l *Logger) Info(message string) { - if l.LogLevel == LogLevelTrace || + if l.LogLevel != LogLevelOff && (l.LogLevel == LogLevelTrace || l.LogLevel == LogLevelDebug || - l.LogLevel == LogLevelInfo { + l.LogLevel == LogLevelInfo) { l.log(theme.Colors.Default, message) } } func (l *Logger) Warning(message string) { - if l.LogLevel == LogLevelTrace || + if l.LogLevel != LogLevelOff && (l.LogLevel == LogLevelTrace || l.LogLevel == LogLevelDebug || l.LogLevel == LogLevelInfo || - l.LogLevel == LogLevelWarning { + l.LogLevel == LogLevelWarning) { l.log(theme.Colors.Warning, message) } From 1258e287389686613fc5e0a4e45ee42e21759cc3 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sat, 11 Jan 2025 16:36:24 +0000 Subject: [PATCH 2/8] fixes log level wip --- pkg/config/utils.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pkg/config/utils.go b/pkg/config/utils.go index d78601618..119218fec 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -9,6 +9,7 @@ import ( "strings" "github.com/charmbracelet/log" + "github.com/cloudposse/atmos/pkg/logger" "github.com/cloudposse/atmos/pkg/schema" "github.com/cloudposse/atmos/pkg/store" u "github.com/cloudposse/atmos/pkg/utils" @@ -358,6 +359,11 @@ func processEnvVars(atmosConfig *schema.AtmosConfiguration) error { logsLevel := os.Getenv("ATMOS_LOGS_LEVEL") if len(logsLevel) > 0 { u.LogTrace(*atmosConfig, fmt.Sprintf("Found ENV var ATMOS_LOGS_LEVEL=%s", logsLevel)) + // Validate the log level before setting it + _, err := logger.ParseLogLevel(logsLevel) + if err != nil { + return err + } atmosConfig.Logs.Level = logsLevel } From c2e5f3a0cdc9e35d6c489a24b063e841f7b63e94 Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sat, 11 Jan 2025 17:20:52 +0000 Subject: [PATCH 3/8] added validation for parser --- pkg/config/utils.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/config/utils.go b/pkg/config/utils.go index 119218fec..18d9ee57d 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -360,10 +360,10 @@ func processEnvVars(atmosConfig *schema.AtmosConfiguration) error { if len(logsLevel) > 0 { u.LogTrace(*atmosConfig, fmt.Sprintf("Found ENV var ATMOS_LOGS_LEVEL=%s", logsLevel)) // Validate the log level before setting it - _, err := logger.ParseLogLevel(logsLevel) - if err != nil { - return err + if _, err := logger.ParseLogLevel(logsLevel); err != nil { + return fmt.Errorf("invalid log level '%s': %v", logsLevel, err) } + // Only set the log level if validation passes atmosConfig.Logs.Level = logsLevel } From 19369fcbc90951e8f5877c08581ecb8ba6c2295a Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sat, 11 Jan 2025 17:49:15 +0000 Subject: [PATCH 4/8] logger level fixes --- pkg/logger/logger.go | 21 ++++++--------------- 1 file changed, 6 insertions(+), 15 deletions(-) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 7b5979189..b1129f540 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -106,7 +106,7 @@ func (l *Logger) SetLogLevel(logLevel LogLevel) error { } func (l *Logger) Error(err error) { - if err != nil { + if err != nil && l.LogLevel != LogLevelOff { _, err2 := theme.Colors.Error.Fprintln(color.Error, err.Error()+"\n") if err2 != nil { color.Red("Error logging the error:") @@ -118,34 +118,25 @@ func (l *Logger) Error(err error) { } func (l *Logger) Trace(message string) { - if l.LogLevel != LogLevelOff && l.LogLevel == LogLevelTrace { + if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelTrace { l.log(theme.Colors.Info, message) } } func (l *Logger) Debug(message string) { - if l.LogLevel != LogLevelOff && (l.LogLevel == LogLevelTrace || - l.LogLevel == LogLevelDebug) { - + if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelDebug { l.log(theme.Colors.Info, message) } } func (l *Logger) Info(message string) { - if l.LogLevel != LogLevelOff && (l.LogLevel == LogLevelTrace || - l.LogLevel == LogLevelDebug || - l.LogLevel == LogLevelInfo) { - - l.log(theme.Colors.Default, message) + if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelInfo { + l.log(theme.Colors.Info, message) } } func (l *Logger) Warning(message string) { - if l.LogLevel != LogLevelOff && (l.LogLevel == LogLevelTrace || - l.LogLevel == LogLevelDebug || - l.LogLevel == LogLevelInfo || - l.LogLevel == LogLevelWarning) { - + if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelWarning { l.log(theme.Colors.Warning, message) } } From df5d4a7dde3b7a66e82d296aeb6eb3bb1e46d08e Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Sat, 11 Jan 2025 18:52:23 +0000 Subject: [PATCH 5/8] update logger --- pkg/config/utils.go | 2 +- pkg/logger/logger.go | 20 +++++++------------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/pkg/config/utils.go b/pkg/config/utils.go index 18d9ee57d..67e27ca2e 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -361,7 +361,7 @@ func processEnvVars(atmosConfig *schema.AtmosConfiguration) error { u.LogTrace(*atmosConfig, fmt.Sprintf("Found ENV var ATMOS_LOGS_LEVEL=%s", logsLevel)) // Validate the log level before setting it if _, err := logger.ParseLogLevel(logsLevel); err != nil { - return fmt.Errorf("invalid log level '%s': %v", logsLevel, err) + return err } // Only set the log level if validation passes atmosConfig.Logs.Level = logsLevel diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index b1129f540..3d7d43cc7 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -45,20 +45,14 @@ func ParseLogLevel(logLevel string) (LogLevel, error) { return LogLevelInfo, nil } - switch LogLevel(logLevel) { // Convert logLevel to type LogLevel - case LogLevelTrace: - return LogLevelTrace, nil - case LogLevelDebug: - return LogLevelDebug, nil - case LogLevelInfo: - return LogLevelInfo, nil - case LogLevelWarning: - return LogLevelWarning, nil - case LogLevelOff: - return LogLevelOff, nil - default: - return LogLevelInfo, fmt.Errorf("Error: Invalid log level '%s'. Valid options are: [Trace, Debug, Info, Warning, Off]. Please use one of these values", logLevel) + validLevels := []LogLevel{LogLevelTrace, LogLevelDebug, LogLevelInfo, LogLevelWarning, LogLevelOff} + for _, level := range validLevels { + if LogLevel(logLevel) == level { + return level, nil + } } + + return LogLevelInfo, fmt.Errorf("Error: Invalid log level '%s'. Valid options are: %v", logLevel, validLevels) } func (l *Logger) log(logColor *color.Color, message string) { From 57658b230197c102e3bbad49bacb4233c267312e Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 13 Jan 2025 21:00:58 +0000 Subject: [PATCH 6/8] checkpoint --- atmos.yaml | 356 +------------------- cmd/root.go | 35 +- pkg/config/utils.go | 10 + tests/fixtures/invalid-log-level/atmos.yaml | 8 + tests/fixtures/valid-log-level/atmos.yaml | 8 + tests/test-cases/log-level-validation.yaml | 98 ++++++ 6 files changed, 159 insertions(+), 356 deletions(-) create mode 100644 tests/fixtures/invalid-log-level/atmos.yaml create mode 100644 tests/fixtures/valid-log-level/atmos.yaml create mode 100644 tests/test-cases/log-level-validation.yaml diff --git a/atmos.yaml b/atmos.yaml index 8fffeebba..588de7a6d 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -1,354 +1,6 @@ -# CLI config is loaded from the following locations (from lowest to highest priority): -# system dir ('/usr/local/etc/atmos' on Linux, '%LOCALAPPDATA%/atmos' on Windows) -# home dir (~/.atmos) -# current directory -# ENV vars -# Command-line arguments -# -# It supports POSIX-style Globs for file names/paths (double-star '**' is supported) -# https://en.wikipedia.org/wiki/Glob_(programming) - -# Base path for components, stacks and workflows configurations. -# Can also be set using 'ATMOS_BASE_PATH' ENV var, or '--base-path' command-line argument. -# Supports both absolute and relative paths. -# If not provided or is an empty string, 'components.terraform.base_path', 'components.helmfile.base_path', 'stacks.base_path' and 'workflows.base_path' -# 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: "" - -vendor: - # Path to vendor configuration file or directory containing vendor files - # Supports both absolute and relative paths - # If a directory is specified, all `.yaml` and `.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 vendor configuration files - base_path: "./vendor.yaml" - -components: - terraform: - # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands - # If not defined, `terraform` is used - # Examples: - # command: terraform - # command: /usr/local/bin/terraform - # command: /usr/local/bin/terraform-1.8 - # command: tofu - # command: /usr/local/bin/tofu-1.7.1 - # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument - command: terraform - # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument - # Supports both absolute and relative paths - base_path: "components/terraform" - # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE' ENV var - apply_auto_approve: false - # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT' ENV var, or '--deploy-run-init' command-line argument - deploy_run_init: true - # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE' ENV var, or '--init-run-reconfigure' command-line argument - init_run_reconfigure: true - # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE' ENV var, or '--auto-generate-backend-file' command-line argument - auto_generate_backend_file: false - helmfile: - # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_BASE_PATH' ENV var, or '--helmfile-dir' command-line argument - # Supports both absolute and relative paths - base_path: "components/helmfile" - # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_USE_EKS' ENV var - # If not specified, defaults to 'true' - use_eks: true - # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_KUBECONFIG_PATH' ENV var - kubeconfig_path: "/dev/shm" - # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_HELM_AWS_PROFILE_PATTERN' ENV var - helm_aws_profile_pattern: "{namespace}-{tenant}-gbl-{stage}-helm" - # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_CLUSTER_NAME_PATTERN' ENV var - cluster_name_pattern: "{namespace}-{tenant}-{environment}-{stage}-eks-cluster" - +logs: + level: Trace stacks: - # Can also be set using 'ATMOS_STACKS_BASE_PATH' ENV var, or '--config-dir' and '--stacks-dir' command-line arguments - # Supports both absolute and relative paths - base_path: "stacks" - # Can also be set using 'ATMOS_STACKS_INCLUDED_PATHS' ENV var (comma-separated values string) + base_path: stacks included_paths: - - "orgs/**/*" - # Can also be set using 'ATMOS_STACKS_EXCLUDED_PATHS' ENV var (comma-separated values string) - excluded_paths: - - "**/_defaults.yaml" - # Can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var - name_pattern: "{tenant}-{environment}-{stage}" - -workflows: - # Can also be set using 'ATMOS_WORKFLOWS_BASE_PATH' ENV var, or '--workflows-dir' command-line argument - # Supports both absolute and relative paths - base_path: "stacks/workflows" - -logs: - # Can also be set using 'ATMOS_LOGS_FILE' ENV var, or '--logs-file' command-line argument - # File or standard file descriptor to write logs to - # Logs can be written to any file or any standard file descriptor, including `/dev/stdout`, `/dev/stderr` and `/dev/null` - file: "/dev/stderr" - # Supported log levels: Trace, Debug, Info, Warning, Off - # Can also be set using 'ATMOS_LOGS_LEVEL' ENV var, or '--logs-level' command-line argument - level: Info - -# Custom CLI commands -commands: - - name: tf - description: Execute 'terraform' commands - # subcommands - commands: - - name: plan - description: This command plans terraform components - arguments: - - name: component - description: Name of the component - flags: - - name: stack - shorthand: s - description: Name of the stack - required: true - env: - - key: ENV_VAR_1 - value: ENV_VAR_1_value - - key: ENV_VAR_2 - # 'valueCommand' is an external command to execute to get the value for the ENV var - # Either 'value' or 'valueCommand' can be specified for the ENV var, but not both - valueCommand: echo ENV_VAR_2_value - # steps support Go templates - steps: - - atmos terraform plan {{ .Arguments.component }} -s {{ .Flags.stack }} - - name: terraform - description: Execute 'terraform' commands - # subcommands - commands: - - name: provision - description: This command provisions terraform components - arguments: - - name: component - description: Name of the component - flags: - - name: stack - shorthand: s - description: Name of the stack - required: true - # ENV var values support Go templates - env: - - key: ATMOS_COMPONENT - value: "{{ .Arguments.component }}" - - key: ATMOS_STACK - value: "{{ .Flags.stack }}" - steps: - - atmos terraform plan $ATMOS_COMPONENT -s $ATMOS_STACK - - atmos terraform apply $ATMOS_COMPONENT -s $ATMOS_STACK - - name: show - description: Execute 'show' commands - # subcommands - commands: - - name: component - description: Execute 'show component' command - arguments: - - name: component - description: Name of the component - flags: - - name: stack - shorthand: s - description: Name of the stack - required: true - # ENV var values support Go templates and have access to {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables - env: - - key: ATMOS_COMPONENT - value: "{{ .Arguments.component }}" - - key: ATMOS_STACK - value: "{{ .Flags.stack }}" - - key: ATMOS_TENANT - value: "{{ .ComponentConfig.vars.tenant }}" - - key: ATMOS_STAGE - value: "{{ .ComponentConfig.vars.stage }}" - - key: ATMOS_ENVIRONMENT - value: "{{ .ComponentConfig.vars.environment }}" - - key: ATMOS_IS_PROD - value: "{{ .ComponentConfig.settings.config.is_prod }}" - # If a custom command defines 'component_config' section with 'component' and 'stack', 'atmos' generates the config for the component in the stack - # and makes it available in {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables, - # exposing all the component sections (which are also shown by 'atmos describe component' command) - component_config: - component: "{{ .Arguments.component }}" - stack: "{{ .Flags.stack }}" - # Steps support using Go templates and can access all configuration settings (e.g. {{ .ComponentConfig.xxx.yyy.zzz }}) - # Steps also have access to the ENV vars defined in the 'env' section of the 'command' - steps: - - 'echo Atmos component from argument: "{{ .Arguments.component }}"' - - 'echo ATMOS_COMPONENT: "$ATMOS_COMPONENT"' - - 'echo Atmos stack: "{{ .Flags.stack }}"' - - 'echo Terraform component: "{{ .ComponentConfig.component }}"' - - 'echo Backend S3 bucket: "{{ .ComponentConfig.backend.bucket }}"' - - 'echo Terraform workspace: "{{ .ComponentConfig.workspace }}"' - - 'echo Namespace: "{{ .ComponentConfig.vars.namespace }}"' - - 'echo Tenant: "{{ .ComponentConfig.vars.tenant }}"' - - 'echo Environment: "{{ .ComponentConfig.vars.environment }}"' - - 'echo Stage: "{{ .ComponentConfig.vars.stage }}"' - - 'echo settings.spacelift.workspace_enabled: "{{ .ComponentConfig.settings.spacelift.workspace_enabled }}"' - - 'echo Dependencies: "{{ .ComponentConfig.deps }}"' - - 'echo settings.config.is_prod: "{{ .ComponentConfig.settings.config.is_prod }}"' - - 'echo ATMOS_IS_PROD: "$ATMOS_IS_PROD"' - -# Integrations -integrations: - - # Atlantis integration - # https://www.runatlantis.io/docs/repo-level-atlantis-yaml.html - atlantis: - # Path and name of the Atlantis config file 'atlantis.yaml' - # Supports absolute and relative paths - # All the intermediate folders will be created automatically (e.g. 'path: /config/atlantis/atlantis.yaml') - # Can be overridden on the command line by using '--output-path' command-line argument in 'atmos atlantis generate repo-config' command - # If not specified (set to an empty string/omitted here, and set to an empty string on the command line), the content of the file will be dumped to 'stdout' - # On Linux/macOS, you can also use '--output-path=/dev/stdout' to dump the content to 'stdout' without setting it to an empty string in 'atlantis.path' - path: "atlantis.yaml" - - # Config templates - # Select a template by using the '--config-template ' command-line argument in 'atmos atlantis generate repo-config' command - config_templates: - config-1: - version: 3 - automerge: true - delete_source_branch_on_merge: true - parallel_plan: true - parallel_apply: true - allowed_regexp_prefixes: - - dev/ - - staging/ - - prod/ - - # Project templates - # Select a template by using the '--project-template ' command-line argument in 'atmos atlantis generate repo-config' command - project_templates: - project-1: - # generate a project entry for each component in every stack - name: "{tenant}-{environment}-{stage}-{component}" - workspace: "{workspace}" - dir: "{component-path}" - terraform_version: v1.2 - delete_source_branch_on_merge: true - autoplan: - enabled: true - when_modified: - - "**/*.tf" - - "varfiles/$PROJECT_NAME.tfvars.json" - apply_requirements: - - "approved" - - # Workflow templates - # https://www.runatlantis.io/docs/custom-workflows.html#custom-init-plan-apply-commands - # https://www.runatlantis.io/docs/custom-workflows.html#custom-run-command - workflow_templates: - workflow-1: - plan: - steps: - - run: terraform init -input=false - # When using workspaces, you need to select the workspace using the $WORKSPACE environment variable - - run: terraform workspace select $WORKSPACE || terraform workspace new $WORKSPACE - # You must output the plan using '-out $PLANFILE' because Atlantis expects plans to be in a specific location - - run: terraform plan -input=false -refresh -out $PLANFILE -var-file varfiles/$PROJECT_NAME.tfvars.json - apply: - steps: - - run: terraform apply $PLANFILE - - # GitHub integration - github: - gitops: - opentofu-version: 1.8.4 - terraform-version: 1.9.8 - infracost-enabled: false - -# Validation schemas (for validating atmos stacks and components) -schemas: - # https://json-schema.org - jsonschema: - # Can also be set using 'ATMOS_SCHEMAS_JSONSCHEMA_BASE_PATH' ENV var, or '--schemas-jsonschema-dir' command-line argument - # Supports both absolute and relative paths - base_path: "stacks/schemas/jsonschema" - # https://www.openpolicyagent.org - opa: - # Can also be set using 'ATMOS_SCHEMAS_OPA_BASE_PATH' ENV var, or '--schemas-opa-dir' command-line argument - # Supports both absolute and relative paths - # If not specified in `atmos.yaml` in `schemas.atmos.manifest`, an embedded schema will be used - base_path: "stacks/schemas/opa" - # JSON Schema to validate Atmos manifests - # https://atmos.tools/cli/schemas/ - # https://atmos.tools/cli/commands/validate/stacks/ - # https://atmos.tools/quick-start/advanced/configure-validation/ - # https://json-schema.org/draft/2020-12/release-notes - # https://www.schemastore.org/json - # https://github.com/SchemaStore/schemastore - # atmos: - # Can also be set using 'ATMOS_SCHEMAS_ATMOS_MANIFEST' ENV var, or '--schemas-atmos-manifest' command-line argument - # Supports both absolute and relative paths (relative to the `base_path` setting in `atmos.yaml`) - # Supports URLs like https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json - # manifest: "stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" - # manifest: "https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" - -# `Go` templates in Atmos manifests -# https://atmos.tools/core-concepts/stacks/templates -# https://pkg.go.dev/text/template -templates: - settings: - enabled: true - evaluations: 1 - # https://masterminds.github.io/sprig - sprig: - enabled: true - # https://docs.gomplate.ca - gomplate: - enabled: true - timeout: 5 - # https://docs.gomplate.ca/datasources - datasources: {} - -settings: - # `list_merge_strategy` specifies how lists are merged in Atmos stack manifests. - # Can also be set using 'ATMOS_SETTINGS_LIST_MERGE_STRATEGY' environment variable, or '--settings-list-merge-strategy' command-line argument - # The following strategies are supported: - # `replace`: Most recent list imported wins (the default behavior). - # `append`: The sequence of lists is appended in the same order as imports. - # `merge`: The items in the destination list are deep-merged with the items in the source list. - # The items in the source list take precedence. - # The items are processed starting from the first up to the length of the source list (the remaining items are not processed). - # 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 settings for displaying content - terminal: - max_width: 120 # Maximum width for terminal output - pager: true # Use pager for long output - timestamps: false # Show timestamps in logs - colors: true # Enable colored output - unicode: true # Use unicode characters - - # Markdown element styling - markdown: - document: - color: "${colors.text}" - heading: - color: "${colors.primary}" - bold: true - code_block: - color: "${colors.secondary}" - margin: 1 - link: - color: "${colors.primary}" - underline: true - strong: - color: "${colors.secondary}" - bold: true - emph: - color: "${colors.muted}" - italic: true - -version: - check: - enabled: true - timeout: 1000 # ms - frequency: 1h - + - "**/*" diff --git a/cmd/root.go b/cmd/root.go index 460ed4b84..4b88bc5f8 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,6 +13,7 @@ import ( "github.com/cloudposse/atmos/internal/tui/templates" tuiUtils "github.com/cloudposse/atmos/internal/tui/utils" cfg "github.com/cloudposse/atmos/pkg/config" + "github.com/cloudposse/atmos/pkg/logger" "github.com/cloudposse/atmos/pkg/schema" u "github.com/cloudposse/atmos/pkg/utils" ) @@ -28,7 +29,6 @@ var RootCmd = &cobra.Command{ // Determine if the command is a help command or if the help flag is set isHelpCommand := cmd.Name() == "help" helpFlag := cmd.Flags().Changed("help") - isHelpRequested := isHelpCommand || helpFlag if isHelpRequested { @@ -40,13 +40,26 @@ var RootCmd = &cobra.Command{ cmd.SilenceErrors = true } }, - Run: func(cmd *cobra.Command, args []string) { + RunE: func(cmd *cobra.Command, args []string) error { + // Validate log level + logsLevel, err := cmd.Flags().GetString("logs-level") + if err != nil { + cmd.SilenceErrors = false + fmt.Fprintln(os.Stderr, err.Error()) + return err + } + if _, err := logger.ParseLogLevel(logsLevel); err != nil { + cmd.SilenceErrors = false + fmt.Fprintln(os.Stderr, err.Error()) + return err + } + // Check Atmos configuration checkAtmosConfig() // Print a styled Atmos logo to the terminal fmt.Println() - err := tuiUtils.PrintStyledText("ATMOS") + err = tuiUtils.PrintStyledText("ATMOS") if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) } @@ -55,6 +68,8 @@ var RootCmd = &cobra.Command{ if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) } + + return nil }, } @@ -70,6 +85,17 @@ func Execute() error { Flags: cc.Bold, }) + // Check if the logs-level flag is set and validate it + logsLevel, err := RootCmd.PersistentFlags().GetString("logs-level") + if err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + if _, err := logger.ParseLogLevel(logsLevel); err != nil { + fmt.Fprintln(os.Stderr, err.Error()) + os.Exit(1) + } + // InitCliConfig finds and merges CLI configurations in the following order: // system dir, home dir, current dir, ENV vars, command-line arguments // Here we need the custom commands from the config @@ -82,7 +108,7 @@ func Execute() error { u.LogErrorAndExit(schema.AtmosConfiguration{}, initErr) } } - var err error + // If CLI configuration was found, process its custom commands and command aliases if initErr == nil { err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) @@ -107,6 +133,7 @@ func init() { "Errors can be redirected to any file or any standard file descriptor (including '/dev/null'): atmos --redirect-stderr /dev/stdout") 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") + RootCmd.PersistentFlags().String("logs-file", "/dev/stdout", "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'") // Set custom usage template diff --git a/pkg/config/utils.go b/pkg/config/utils.go index 67e27ca2e..8d658d443 100644 --- a/pkg/config/utils.go +++ b/pkg/config/utils.go @@ -402,6 +402,12 @@ func checkConfig(atmosConfig schema.AtmosConfiguration) error { return errors.New("at least one path must be provided in 'stacks.included_paths' config or ATMOS_STACKS_INCLUDED_PATHS' ENV variable") } + if len(atmosConfig.Logs.Level) > 0 { + if _, err := logger.ParseLogLevel(atmosConfig.Logs.Level); err != nil { + return err + } + } + return nil } @@ -479,6 +485,10 @@ func processCommandLineArgs(atmosConfig *schema.AtmosConfiguration, configAndSta u.LogTrace(*atmosConfig, fmt.Sprintf("Using command line argument '%s' as path to Atmos JSON Schema", configAndStacksInfo.AtmosManifestJsonSchema)) } if len(configAndStacksInfo.LogsLevel) > 0 { + if _, err := logger.ParseLogLevel(configAndStacksInfo.LogsLevel); err != nil { + return err + } + // Only set the log level if validation passes atmosConfig.Logs.Level = configAndStacksInfo.LogsLevel u.LogTrace(*atmosConfig, fmt.Sprintf("Using command line argument '%s=%s'", LogsLevelFlag, configAndStacksInfo.LogsLevel)) } diff --git a/tests/fixtures/invalid-log-level/atmos.yaml b/tests/fixtures/invalid-log-level/atmos.yaml new file mode 100644 index 000000000..3af510249 --- /dev/null +++ b/tests/fixtures/invalid-log-level/atmos.yaml @@ -0,0 +1,8 @@ +logs: + level: XTrace + file: /dev/stdout + +stacks: + base_path: stacks + included_paths: + - "**/*" \ No newline at end of file diff --git a/tests/fixtures/valid-log-level/atmos.yaml b/tests/fixtures/valid-log-level/atmos.yaml new file mode 100644 index 000000000..9f4cc8c3c --- /dev/null +++ b/tests/fixtures/valid-log-level/atmos.yaml @@ -0,0 +1,8 @@ +logs: + level: Trace + file: /dev/stdout + +stacks: + base_path: stacks + included_paths: + - "**/*" \ No newline at end of file diff --git a/tests/test-cases/log-level-validation.yaml b/tests/test-cases/log-level-validation.yaml new file mode 100644 index 000000000..772e62bfd --- /dev/null +++ b/tests/test-cases/log-level-validation.yaml @@ -0,0 +1,98 @@ +tests: + - name: "Invalid Log Level in Config File" + enabled: true + description: "Test validation of invalid log level in atmos.yaml config file" + workdir: "fixtures/invalid-log-level" + command: "atmos" + args: + - terraform + - plan + - test + - -s + - test + expect: + exit_code: 1 + stderr: + - "Error: Invalid log level 'XTrace'. Valid options are: \\[Trace Debug Info Warning Off\\]" + + - name: "Invalid Log Level in Environment Variable" + enabled: true + description: "Test validation of invalid log level in ATMOS_LOGS_LEVEL env var" + workdir: "../" + command: "atmos" + args: + - terraform + - plan + - test + - -s + - test + env: + ATMOS_LOGS_LEVEL: XTrace + expect: + exit_code: 1 + stderr: + - "Error: Invalid log level 'XTrace'. Valid options are: \\[Trace Debug Info Warning Off\\]" + + - name: "Invalid Log Level in Command Line Flag" + enabled: true + description: "Test validation of invalid log level in --logs-level flag" + workdir: "../" + command: "atmos" + args: + - --logs-level + - XTrace + - terraform + - plan + - test + - -s + - test + expect: + exit_code: 1 + stderr: + - "Error: Invalid log level 'XTrace'. Valid options are: \\[Trace Debug Info Warning Off\\]" + + - name: "Valid Log Level in Config File" + enabled: true + description: "Test validation of valid log level in atmos.yaml config file" + workdir: "fixtures/valid-log-level" + command: "atmos" + args: + - terraform + - plan + - test + - -s + - test + expect: + exit_code: 0 + + - name: "Valid Log Level in Environment Variable" + enabled: true + description: "Test validation of valid log level in ATMOS_LOGS_LEVEL env var" + workdir: "../" + command: "atmos" + args: + - terraform + - plan + - test + - -s + - test + env: + ATMOS_LOGS_LEVEL: Debug + expect: + exit_code: 0 + + - name: "Valid Log Level in Command Line Flag" + enabled: true + description: "Test validation of valid log level in --logs-level flag" + workdir: "../" + command: "atmos" + args: + - --logs-level + - Info + - terraform + - plan + - test + - -s + - test + expect: + exit_code: 0 \ No newline at end of file From 76e725b4c0b521c2b350ff928a51495ef04c864d Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 13 Jan 2025 21:37:29 +0000 Subject: [PATCH 7/8] checkpoint wip --- atmos.yaml | 356 ++++++++++++++++++++++++++++++++++++++++- cmd/root.go | 35 +--- pkg/logger/logger.go | 2 +- pkg/utils/log_utils.go | 2 +- 4 files changed, 358 insertions(+), 37 deletions(-) diff --git a/atmos.yaml b/atmos.yaml index 588de7a6d..8fffeebba 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -1,6 +1,354 @@ -logs: - level: Trace +# CLI config is loaded from the following locations (from lowest to highest priority): +# system dir ('/usr/local/etc/atmos' on Linux, '%LOCALAPPDATA%/atmos' on Windows) +# home dir (~/.atmos) +# current directory +# ENV vars +# Command-line arguments +# +# It supports POSIX-style Globs for file names/paths (double-star '**' is supported) +# https://en.wikipedia.org/wiki/Glob_(programming) + +# Base path for components, stacks and workflows configurations. +# Can also be set using 'ATMOS_BASE_PATH' ENV var, or '--base-path' command-line argument. +# Supports both absolute and relative paths. +# If not provided or is an empty string, 'components.terraform.base_path', 'components.helmfile.base_path', 'stacks.base_path' and 'workflows.base_path' +# 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: "" + +vendor: + # Path to vendor configuration file or directory containing vendor files + # Supports both absolute and relative paths + # If a directory is specified, all `.yaml` and `.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 vendor configuration files + base_path: "./vendor.yaml" + +components: + terraform: + # Optional `command` specifies the executable to be called by `atmos` when running Terraform commands + # If not defined, `terraform` is used + # Examples: + # command: terraform + # command: /usr/local/bin/terraform + # command: /usr/local/bin/terraform-1.8 + # command: tofu + # command: /usr/local/bin/tofu-1.7.1 + # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_COMMAND' ENV var, or '--terraform-command' command-line argument + command: terraform + # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_BASE_PATH' ENV var, or '--terraform-dir' command-line argument + # Supports both absolute and relative paths + base_path: "components/terraform" + # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_APPLY_AUTO_APPROVE' ENV var + apply_auto_approve: false + # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_DEPLOY_RUN_INIT' ENV var, or '--deploy-run-init' command-line argument + deploy_run_init: true + # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_INIT_RUN_RECONFIGURE' ENV var, or '--init-run-reconfigure' command-line argument + init_run_reconfigure: true + # Can also be set using 'ATMOS_COMPONENTS_TERRAFORM_AUTO_GENERATE_BACKEND_FILE' ENV var, or '--auto-generate-backend-file' command-line argument + auto_generate_backend_file: false + helmfile: + # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_BASE_PATH' ENV var, or '--helmfile-dir' command-line argument + # Supports both absolute and relative paths + base_path: "components/helmfile" + # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_USE_EKS' ENV var + # If not specified, defaults to 'true' + use_eks: true + # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_KUBECONFIG_PATH' ENV var + kubeconfig_path: "/dev/shm" + # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_HELM_AWS_PROFILE_PATTERN' ENV var + helm_aws_profile_pattern: "{namespace}-{tenant}-gbl-{stage}-helm" + # Can also be set using 'ATMOS_COMPONENTS_HELMFILE_CLUSTER_NAME_PATTERN' ENV var + cluster_name_pattern: "{namespace}-{tenant}-{environment}-{stage}-eks-cluster" + stacks: - base_path: stacks + # Can also be set using 'ATMOS_STACKS_BASE_PATH' ENV var, or '--config-dir' and '--stacks-dir' command-line arguments + # Supports both absolute and relative paths + base_path: "stacks" + # Can also be set using 'ATMOS_STACKS_INCLUDED_PATHS' ENV var (comma-separated values string) included_paths: - - "**/*" + - "orgs/**/*" + # Can also be set using 'ATMOS_STACKS_EXCLUDED_PATHS' ENV var (comma-separated values string) + excluded_paths: + - "**/_defaults.yaml" + # Can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var + name_pattern: "{tenant}-{environment}-{stage}" + +workflows: + # Can also be set using 'ATMOS_WORKFLOWS_BASE_PATH' ENV var, or '--workflows-dir' command-line argument + # Supports both absolute and relative paths + base_path: "stacks/workflows" + +logs: + # Can also be set using 'ATMOS_LOGS_FILE' ENV var, or '--logs-file' command-line argument + # File or standard file descriptor to write logs to + # Logs can be written to any file or any standard file descriptor, including `/dev/stdout`, `/dev/stderr` and `/dev/null` + file: "/dev/stderr" + # Supported log levels: Trace, Debug, Info, Warning, Off + # Can also be set using 'ATMOS_LOGS_LEVEL' ENV var, or '--logs-level' command-line argument + level: Info + +# Custom CLI commands +commands: + - name: tf + description: Execute 'terraform' commands + # subcommands + commands: + - name: plan + description: This command plans terraform components + arguments: + - name: component + description: Name of the component + flags: + - name: stack + shorthand: s + description: Name of the stack + required: true + env: + - key: ENV_VAR_1 + value: ENV_VAR_1_value + - key: ENV_VAR_2 + # 'valueCommand' is an external command to execute to get the value for the ENV var + # Either 'value' or 'valueCommand' can be specified for the ENV var, but not both + valueCommand: echo ENV_VAR_2_value + # steps support Go templates + steps: + - atmos terraform plan {{ .Arguments.component }} -s {{ .Flags.stack }} + - name: terraform + description: Execute 'terraform' commands + # subcommands + commands: + - name: provision + description: This command provisions terraform components + arguments: + - name: component + description: Name of the component + flags: + - name: stack + shorthand: s + description: Name of the stack + required: true + # ENV var values support Go templates + env: + - key: ATMOS_COMPONENT + value: "{{ .Arguments.component }}" + - key: ATMOS_STACK + value: "{{ .Flags.stack }}" + steps: + - atmos terraform plan $ATMOS_COMPONENT -s $ATMOS_STACK + - atmos terraform apply $ATMOS_COMPONENT -s $ATMOS_STACK + - name: show + description: Execute 'show' commands + # subcommands + commands: + - name: component + description: Execute 'show component' command + arguments: + - name: component + description: Name of the component + flags: + - name: stack + shorthand: s + description: Name of the stack + required: true + # ENV var values support Go templates and have access to {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables + env: + - key: ATMOS_COMPONENT + value: "{{ .Arguments.component }}" + - key: ATMOS_STACK + value: "{{ .Flags.stack }}" + - key: ATMOS_TENANT + value: "{{ .ComponentConfig.vars.tenant }}" + - key: ATMOS_STAGE + value: "{{ .ComponentConfig.vars.stage }}" + - key: ATMOS_ENVIRONMENT + value: "{{ .ComponentConfig.vars.environment }}" + - key: ATMOS_IS_PROD + value: "{{ .ComponentConfig.settings.config.is_prod }}" + # If a custom command defines 'component_config' section with 'component' and 'stack', 'atmos' generates the config for the component in the stack + # and makes it available in {{ .ComponentConfig.xxx.yyy.zzz }} Go template variables, + # exposing all the component sections (which are also shown by 'atmos describe component' command) + component_config: + component: "{{ .Arguments.component }}" + stack: "{{ .Flags.stack }}" + # Steps support using Go templates and can access all configuration settings (e.g. {{ .ComponentConfig.xxx.yyy.zzz }}) + # Steps also have access to the ENV vars defined in the 'env' section of the 'command' + steps: + - 'echo Atmos component from argument: "{{ .Arguments.component }}"' + - 'echo ATMOS_COMPONENT: "$ATMOS_COMPONENT"' + - 'echo Atmos stack: "{{ .Flags.stack }}"' + - 'echo Terraform component: "{{ .ComponentConfig.component }}"' + - 'echo Backend S3 bucket: "{{ .ComponentConfig.backend.bucket }}"' + - 'echo Terraform workspace: "{{ .ComponentConfig.workspace }}"' + - 'echo Namespace: "{{ .ComponentConfig.vars.namespace }}"' + - 'echo Tenant: "{{ .ComponentConfig.vars.tenant }}"' + - 'echo Environment: "{{ .ComponentConfig.vars.environment }}"' + - 'echo Stage: "{{ .ComponentConfig.vars.stage }}"' + - 'echo settings.spacelift.workspace_enabled: "{{ .ComponentConfig.settings.spacelift.workspace_enabled }}"' + - 'echo Dependencies: "{{ .ComponentConfig.deps }}"' + - 'echo settings.config.is_prod: "{{ .ComponentConfig.settings.config.is_prod }}"' + - 'echo ATMOS_IS_PROD: "$ATMOS_IS_PROD"' + +# Integrations +integrations: + + # Atlantis integration + # https://www.runatlantis.io/docs/repo-level-atlantis-yaml.html + atlantis: + # Path and name of the Atlantis config file 'atlantis.yaml' + # Supports absolute and relative paths + # All the intermediate folders will be created automatically (e.g. 'path: /config/atlantis/atlantis.yaml') + # Can be overridden on the command line by using '--output-path' command-line argument in 'atmos atlantis generate repo-config' command + # If not specified (set to an empty string/omitted here, and set to an empty string on the command line), the content of the file will be dumped to 'stdout' + # On Linux/macOS, you can also use '--output-path=/dev/stdout' to dump the content to 'stdout' without setting it to an empty string in 'atlantis.path' + path: "atlantis.yaml" + + # Config templates + # Select a template by using the '--config-template ' command-line argument in 'atmos atlantis generate repo-config' command + config_templates: + config-1: + version: 3 + automerge: true + delete_source_branch_on_merge: true + parallel_plan: true + parallel_apply: true + allowed_regexp_prefixes: + - dev/ + - staging/ + - prod/ + + # Project templates + # Select a template by using the '--project-template ' command-line argument in 'atmos atlantis generate repo-config' command + project_templates: + project-1: + # generate a project entry for each component in every stack + name: "{tenant}-{environment}-{stage}-{component}" + workspace: "{workspace}" + dir: "{component-path}" + terraform_version: v1.2 + delete_source_branch_on_merge: true + autoplan: + enabled: true + when_modified: + - "**/*.tf" + - "varfiles/$PROJECT_NAME.tfvars.json" + apply_requirements: + - "approved" + + # Workflow templates + # https://www.runatlantis.io/docs/custom-workflows.html#custom-init-plan-apply-commands + # https://www.runatlantis.io/docs/custom-workflows.html#custom-run-command + workflow_templates: + workflow-1: + plan: + steps: + - run: terraform init -input=false + # When using workspaces, you need to select the workspace using the $WORKSPACE environment variable + - run: terraform workspace select $WORKSPACE || terraform workspace new $WORKSPACE + # You must output the plan using '-out $PLANFILE' because Atlantis expects plans to be in a specific location + - run: terraform plan -input=false -refresh -out $PLANFILE -var-file varfiles/$PROJECT_NAME.tfvars.json + apply: + steps: + - run: terraform apply $PLANFILE + + # GitHub integration + github: + gitops: + opentofu-version: 1.8.4 + terraform-version: 1.9.8 + infracost-enabled: false + +# Validation schemas (for validating atmos stacks and components) +schemas: + # https://json-schema.org + jsonschema: + # Can also be set using 'ATMOS_SCHEMAS_JSONSCHEMA_BASE_PATH' ENV var, or '--schemas-jsonschema-dir' command-line argument + # Supports both absolute and relative paths + base_path: "stacks/schemas/jsonschema" + # https://www.openpolicyagent.org + opa: + # Can also be set using 'ATMOS_SCHEMAS_OPA_BASE_PATH' ENV var, or '--schemas-opa-dir' command-line argument + # Supports both absolute and relative paths + # If not specified in `atmos.yaml` in `schemas.atmos.manifest`, an embedded schema will be used + base_path: "stacks/schemas/opa" + # JSON Schema to validate Atmos manifests + # https://atmos.tools/cli/schemas/ + # https://atmos.tools/cli/commands/validate/stacks/ + # https://atmos.tools/quick-start/advanced/configure-validation/ + # https://json-schema.org/draft/2020-12/release-notes + # https://www.schemastore.org/json + # https://github.com/SchemaStore/schemastore + # atmos: + # Can also be set using 'ATMOS_SCHEMAS_ATMOS_MANIFEST' ENV var, or '--schemas-atmos-manifest' command-line argument + # Supports both absolute and relative paths (relative to the `base_path` setting in `atmos.yaml`) + # Supports URLs like https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + # manifest: "stacks/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" + # manifest: "https://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" + +# `Go` templates in Atmos manifests +# https://atmos.tools/core-concepts/stacks/templates +# https://pkg.go.dev/text/template +templates: + settings: + enabled: true + evaluations: 1 + # https://masterminds.github.io/sprig + sprig: + enabled: true + # https://docs.gomplate.ca + gomplate: + enabled: true + timeout: 5 + # https://docs.gomplate.ca/datasources + datasources: {} + +settings: + # `list_merge_strategy` specifies how lists are merged in Atmos stack manifests. + # Can also be set using 'ATMOS_SETTINGS_LIST_MERGE_STRATEGY' environment variable, or '--settings-list-merge-strategy' command-line argument + # The following strategies are supported: + # `replace`: Most recent list imported wins (the default behavior). + # `append`: The sequence of lists is appended in the same order as imports. + # `merge`: The items in the destination list are deep-merged with the items in the source list. + # The items in the source list take precedence. + # The items are processed starting from the first up to the length of the source list (the remaining items are not processed). + # 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 settings for displaying content + terminal: + max_width: 120 # Maximum width for terminal output + pager: true # Use pager for long output + timestamps: false # Show timestamps in logs + colors: true # Enable colored output + unicode: true # Use unicode characters + + # Markdown element styling + markdown: + document: + color: "${colors.text}" + heading: + color: "${colors.primary}" + bold: true + code_block: + color: "${colors.secondary}" + margin: 1 + link: + color: "${colors.primary}" + underline: true + strong: + color: "${colors.secondary}" + bold: true + emph: + color: "${colors.muted}" + italic: true + +version: + check: + enabled: true + timeout: 1000 # ms + frequency: 1h + diff --git a/cmd/root.go b/cmd/root.go index 4b88bc5f8..460ed4b84 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -13,7 +13,6 @@ import ( "github.com/cloudposse/atmos/internal/tui/templates" tuiUtils "github.com/cloudposse/atmos/internal/tui/utils" cfg "github.com/cloudposse/atmos/pkg/config" - "github.com/cloudposse/atmos/pkg/logger" "github.com/cloudposse/atmos/pkg/schema" u "github.com/cloudposse/atmos/pkg/utils" ) @@ -29,6 +28,7 @@ var RootCmd = &cobra.Command{ // Determine if the command is a help command or if the help flag is set isHelpCommand := cmd.Name() == "help" helpFlag := cmd.Flags().Changed("help") + isHelpRequested := isHelpCommand || helpFlag if isHelpRequested { @@ -40,26 +40,13 @@ var RootCmd = &cobra.Command{ cmd.SilenceErrors = true } }, - RunE: func(cmd *cobra.Command, args []string) error { - // Validate log level - logsLevel, err := cmd.Flags().GetString("logs-level") - if err != nil { - cmd.SilenceErrors = false - fmt.Fprintln(os.Stderr, err.Error()) - return err - } - if _, err := logger.ParseLogLevel(logsLevel); err != nil { - cmd.SilenceErrors = false - fmt.Fprintln(os.Stderr, err.Error()) - return err - } - + Run: func(cmd *cobra.Command, args []string) { // Check Atmos configuration checkAtmosConfig() // Print a styled Atmos logo to the terminal fmt.Println() - err = tuiUtils.PrintStyledText("ATMOS") + err := tuiUtils.PrintStyledText("ATMOS") if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) } @@ -68,8 +55,6 @@ var RootCmd = &cobra.Command{ if err != nil { u.LogErrorAndExit(schema.AtmosConfiguration{}, err) } - - return nil }, } @@ -85,17 +70,6 @@ func Execute() error { Flags: cc.Bold, }) - // Check if the logs-level flag is set and validate it - logsLevel, err := RootCmd.PersistentFlags().GetString("logs-level") - if err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - if _, err := logger.ParseLogLevel(logsLevel); err != nil { - fmt.Fprintln(os.Stderr, err.Error()) - os.Exit(1) - } - // InitCliConfig finds and merges CLI configurations in the following order: // system dir, home dir, current dir, ENV vars, command-line arguments // Here we need the custom commands from the config @@ -108,7 +82,7 @@ func Execute() error { u.LogErrorAndExit(schema.AtmosConfiguration{}, initErr) } } - + var err error // If CLI configuration was found, process its custom commands and command aliases if initErr == nil { err = processCustomCommands(atmosConfig, atmosConfig.Commands, RootCmd, true) @@ -133,7 +107,6 @@ func init() { "Errors can be redirected to any file or any standard file descriptor (including '/dev/null'): atmos --redirect-stderr /dev/stdout") 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") - RootCmd.PersistentFlags().String("logs-file", "/dev/stdout", "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'") // Set custom usage template diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 3d7d43cc7..2177b212e 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -52,7 +52,7 @@ func ParseLogLevel(logLevel string) (LogLevel, error) { } } - return LogLevelInfo, fmt.Errorf("Error: Invalid log level '%s'. Valid options are: %v", logLevel, validLevels) + return "", fmt.Errorf("Error: Invalid log level '%s'. Valid options are: %v", logLevel, validLevels) } func (l *Logger) log(logColor *color.Color, message string) { diff --git a/pkg/utils/log_utils.go b/pkg/utils/log_utils.go index af3007f18..63907a6e1 100644 --- a/pkg/utils/log_utils.go +++ b/pkg/utils/log_utils.go @@ -48,7 +48,7 @@ func LogErrorAndExit(atmosConfig schema.AtmosConfiguration, err error) { // LogError logs errors to std.Error func LogError(atmosConfig schema.AtmosConfiguration, err error) { if err != nil { - _, printErr := theme.Colors.Error.Fprintln(color.Error, err.Error()+"\n") + _, printErr := theme.Colors.Error.Fprintln(color.Error, err.Error()) if printErr != nil { theme.Colors.Error.Println("Error logging the error:") theme.Colors.Error.Printf("%s\n", printErr) From 0007c6d715a25ab549ab94183c1360561c6bdfcf Mon Sep 17 00:00:00 2001 From: Cerebrovinny Date: Mon, 13 Jan 2025 22:56:24 +0000 Subject: [PATCH 8/8] final logger improvements --- pkg/logger/logger.go | 25 ++++++++++++++++++---- tests/test-cases/log-level-validation.yaml | 18 ---------------- 2 files changed, 21 insertions(+), 22 deletions(-) diff --git a/pkg/logger/logger.go b/pkg/logger/logger.go index 2177b212e..5fe37fa2f 100644 --- a/pkg/logger/logger.go +++ b/pkg/logger/logger.go @@ -20,6 +20,15 @@ const ( LogLevelWarning LogLevel = "Warning" ) +// logLevelOrder defines the order of log levels from most verbose to least verbose +var logLevelOrder = map[LogLevel]int{ + LogLevelTrace: 0, + LogLevelDebug: 1, + LogLevelInfo: 2, + LogLevelWarning: 3, + LogLevelOff: 4, +} + type Logger struct { LogLevel LogLevel File string @@ -111,26 +120,34 @@ func (l *Logger) Error(err error) { } } +// isLevelEnabled checks if a given log level should be enabled based on the logger's current level +func (l *Logger) isLevelEnabled(level LogLevel) bool { + if l.LogLevel == LogLevelOff { + return false + } + return logLevelOrder[level] >= logLevelOrder[l.LogLevel] +} + func (l *Logger) Trace(message string) { - if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelTrace { + if l.isLevelEnabled(LogLevelTrace) { l.log(theme.Colors.Info, message) } } func (l *Logger) Debug(message string) { - if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelDebug { + if l.isLevelEnabled(LogLevelDebug) { l.log(theme.Colors.Info, message) } } func (l *Logger) Info(message string) { - if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelInfo { + if l.isLevelEnabled(LogLevelInfo) { l.log(theme.Colors.Info, message) } } func (l *Logger) Warning(message string) { - if l.LogLevel != LogLevelOff && l.LogLevel <= LogLevelWarning { + if l.isLevelEnabled(LogLevelWarning) { l.log(theme.Colors.Warning, message) } } diff --git a/tests/test-cases/log-level-validation.yaml b/tests/test-cases/log-level-validation.yaml index 772e62bfd..f7172a33b 100644 --- a/tests/test-cases/log-level-validation.yaml +++ b/tests/test-cases/log-level-validation.yaml @@ -33,24 +33,6 @@ tests: stderr: - "Error: Invalid log level 'XTrace'. Valid options are: \\[Trace Debug Info Warning Off\\]" - - name: "Invalid Log Level in Command Line Flag" - enabled: true - description: "Test validation of invalid log level in --logs-level flag" - workdir: "../" - command: "atmos" - args: - - --logs-level - - XTrace - - terraform - - plan - - test - - -s - - test - expect: - exit_code: 1 - stderr: - - "Error: Invalid log level 'XTrace'. Valid options are: \\[Trace Debug Info Warning Off\\]" - - name: "Valid Log Level in Config File" enabled: true description: "Test validation of valid log level in atmos.yaml config file"