diff --git a/atmos.yaml b/atmos.yaml index c3e10fc53..5b115043b 100644 --- a/atmos.yaml +++ b/atmos.yaml @@ -91,6 +91,43 @@ logs: # Can also be set using 'ATMOS_LOGS_LEVEL' ENV var, or '--logs-level' command-line argument level: Info +validate: + # The configuration settings for the editorconfig-checker. + + editorconfig: + # A list of file paths or patterns to exclude from checks. + exclude: [] + + # If set to true, the default ignore patterns (like .git/*) will not be applied. + ignore_defaults: false + + # Runs the checker without making any changes or producing output, useful for testing configuration. + dry_run: false + + # Specifies the output format. Options: "default", "json". + format: "default" + + # Enables/Disables colored output in the terminal if . + color: true + + # Disables checking for trailing whitespace at the end of lines. + disable_trim_trailing_whitespace: false + + # Disables checking for consistent line endings (e.g., LF vs. CRLF). + disable_end_of_line: false + + # Disables checking for the presence of a newline at the end of files. + disable_insert_final_newline: false + + # Disables checking for consistent indentation style (e.g., tabs or spaces). + disable_indentation: false + + # Disables checking for consistent indentation size (e.g., 2 spaces or 4 spaces). + disable_indent_size: false + + # Disables checking for lines exceeding a maximum length. + disable_max_line_length: false + # Custom CLI commands commands: - name: tf diff --git a/cmd/validate_editorconfig.go b/cmd/validate_editorconfig.go new file mode 100644 index 000000000..adff7b4eb --- /dev/null +++ b/cmd/validate_editorconfig.go @@ -0,0 +1,194 @@ +package cmd + +import ( + "fmt" + "os" + "strings" + + "github.com/cloudposse/atmos/pkg/schema" + u "github.com/cloudposse/atmos/pkg/utils" + "github.com/cloudposse/atmos/pkg/version" + + "github.com/editorconfig-checker/editorconfig-checker/v3/pkg/config" + er "github.com/editorconfig-checker/editorconfig-checker/v3/pkg/error" + "github.com/editorconfig-checker/editorconfig-checker/v3/pkg/files" + "github.com/editorconfig-checker/editorconfig-checker/v3/pkg/utils" + "github.com/editorconfig-checker/editorconfig-checker/v3/pkg/validation" + "github.com/spf13/cobra" +) + +var ( + defaultConfigFilePath = ".editorconfig" + initEditorConfig bool + currentConfig *config.Config + cliConfig config.Config + configFilePath string + tmpExclude string +) + +var editorConfigCmd *cobra.Command = &cobra.Command{ + Use: "editorconfig", + Short: "Validate all files against the EditorConfig", + Long: "Validate all files against the project's EditorConfig rules", + PersistentPreRun: func(cmd *cobra.Command, args []string) { + initializeConfig(cmd) + }, + Run: func(cmd *cobra.Command, args []string) { + if cliConfig.Help { + cmd.Help() + os.Exit(0) + } + runMainLogic() + }, +} + +// initializeConfig breaks the initialization cycle by separating the config setup +func initializeConfig(cmd *cobra.Command) { + replaceAtmosConfigInConfig(cmd, atmosConfig) + + if configFilePath == "" { + configFilePath = defaultConfigFilePath + } + + var err error + currentConfig, err = config.NewConfig(configFilePath) + if err != nil { + u.LogErrorAndExit(atmosConfig, err) + } + + if initEditorConfig { + err := currentConfig.Save(version.Version) + if err != nil { + u.LogErrorAndExit(atmosConfig, err) + } + } + + _ = currentConfig.Parse() + + if tmpExclude != "" { + currentConfig.Exclude = append(currentConfig.Exclude, tmpExclude) + } + + currentConfig.Merge(cliConfig) +} + +func replaceAtmosConfigInConfig(cmd *cobra.Command, atmosConfig schema.AtmosConfiguration) { + if !cmd.Flags().Changed("config") && atmosConfig.Validate.EditorConfig.ConfigFilePath != "" { + configFilePath = atmosConfig.Validate.EditorConfig.ConfigFilePath + } + if !cmd.Flags().Changed("exclude") && len(atmosConfig.Validate.EditorConfig.Exclude) > 0 { + tmpExclude = strings.Join(atmosConfig.Validate.EditorConfig.Exclude, ",") + } + if !cmd.Flags().Changed("init") && atmosConfig.Validate.EditorConfig.Init { + initEditorConfig = atmosConfig.Validate.EditorConfig.Init + } + if !cmd.Flags().Changed("ignore-defaults") && atmosConfig.Validate.EditorConfig.IgnoreDefaults { + cliConfig.IgnoreDefaults = atmosConfig.Validate.EditorConfig.IgnoreDefaults + } + if !cmd.Flags().Changed("dry-run") && atmosConfig.Validate.EditorConfig.DryRun { + cliConfig.DryRun = atmosConfig.Validate.EditorConfig.DryRun + } + if !cmd.Flags().Changed("format") && atmosConfig.Validate.EditorConfig.Format != "" { + cliConfig.Format = atmosConfig.Validate.EditorConfig.Format + } + if !cmd.Flags().Changed("logs-level") && atmosConfig.Logs.Level == "trace" { + cliConfig.Verbose = true + } else if cmd.Flags().Changed("logs-level") { + if v, err := cmd.Flags().GetString("logs-level"); err == nil && v == "trace" { + cliConfig.Verbose = true + } + } + if !cmd.Flags().Changed("no-color") && !atmosConfig.Validate.EditorConfig.Color { + cliConfig.NoColor = !atmosConfig.Validate.EditorConfig.Color + } + if !cmd.Flags().Changed("disable-trim-trailing-whitespace") && atmosConfig.Validate.EditorConfig.DisableTrimTrailingWhitespace { + cliConfig.Disable.TrimTrailingWhitespace = atmosConfig.Validate.EditorConfig.DisableTrimTrailingWhitespace + } + if !cmd.Flags().Changed("disable-end-of-line") && atmosConfig.Validate.EditorConfig.DisableEndOfLine { + cliConfig.Disable.EndOfLine = atmosConfig.Validate.EditorConfig.DisableEndOfLine + } + if !cmd.Flags().Changed("disable-insert-final-newline") && atmosConfig.Validate.EditorConfig.DisableInsertFinalNewline { + cliConfig.Disable.InsertFinalNewline = atmosConfig.Validate.EditorConfig.DisableInsertFinalNewline + } + if !cmd.Flags().Changed("disable-indentation") && atmosConfig.Validate.EditorConfig.DisableIndentation { + cliConfig.Disable.Indentation = atmosConfig.Validate.EditorConfig.DisableIndentation + } + if !cmd.Flags().Changed("disable-indent-size") && atmosConfig.Validate.EditorConfig.DisableIndentSize { + cliConfig.Disable.IndentSize = atmosConfig.Validate.EditorConfig.DisableIndentSize + } + if !cmd.Flags().Changed("disable-max-line-length") && atmosConfig.Validate.EditorConfig.DisableMaxLineLength { + cliConfig.Disable.MaxLineLength = atmosConfig.Validate.EditorConfig.DisableMaxLineLength + } +} + +// runMainLogic contains the main logic +func runMainLogic() { + config := *currentConfig + u.LogDebug(atmosConfig, config.GetAsString()) + u.LogTrace(atmosConfig, fmt.Sprintf("Exclude Regexp: %s", config.GetExcludesAsRegularExpression())) + + if err := checkVersion(config); err != nil { + u.LogErrorAndExit(atmosConfig, err) + } + + filePaths, err := files.GetFiles(config) + if err != nil { + u.LogErrorAndExit(atmosConfig, err) + } + + if config.DryRun { + for _, file := range filePaths { + u.LogInfo(atmosConfig, file) + } + os.Exit(0) + } + + errors := validation.ProcessValidation(filePaths, config) + errorCount := er.GetErrorCount(errors) + + if errorCount != 0 { + er.PrintErrors(errors, config) + u.LogErrorAndExit(atmosConfig, fmt.Errorf("\n%d errors found", errorCount)) + } + + u.LogDebug(atmosConfig, fmt.Sprintf("%d files checked", len(filePaths))) + u.PrintMessage("No errors found") +} + +func checkVersion(config config.Config) error { + if !utils.FileExists(config.Path) || config.Version == "" { + return nil + } + if config.Version != version.Version { + return fmt.Errorf("version mismatch: binary=%s, config=%s", + version.Version, config.Version) + } + + return nil +} + +// addPersistentFlags adds flags to the root command +func addPersistentFlags(cmd *cobra.Command) { + cmd.PersistentFlags().StringVar(&configFilePath, "config", "", "Path to the configuration file") + cmd.PersistentFlags().StringVar(&tmpExclude, "exclude", "", "Regex to exclude files from checking") + cmd.PersistentFlags().BoolVar(&initEditorConfig, "init", false, "creates an initial configuration") + + cmd.PersistentFlags().BoolVar(&cliConfig.IgnoreDefaults, "ignore-defaults", false, "Ignore default excludes") + cmd.PersistentFlags().BoolVar(&cliConfig.DryRun, "dry-run", false, "Show which files would be checked") + cmd.PersistentFlags().BoolVar(&cliConfig.ShowVersion, "version", false, "Print the version number") + cmd.PersistentFlags().StringVar(&cliConfig.Format, "format", "default", "Specify the output format: default, gcc") + cmd.PersistentFlags().BoolVar(&cliConfig.NoColor, "no-color", false, "Don't print colors") + cmd.PersistentFlags().BoolVar(&cliConfig.Disable.TrimTrailingWhitespace, "disable-trim-trailing-whitespace", false, "Disable trailing whitespace check") + cmd.PersistentFlags().BoolVar(&cliConfig.Disable.EndOfLine, "disable-end-of-line", false, "Disable end-of-line check") + cmd.PersistentFlags().BoolVar(&cliConfig.Disable.InsertFinalNewline, "disable-insert-final-newline", false, "Disable final newline check") + cmd.PersistentFlags().BoolVar(&cliConfig.Disable.Indentation, "disable-indentation", false, "Disable indentation check") + cmd.PersistentFlags().BoolVar(&cliConfig.Disable.IndentSize, "disable-indent-size", false, "Disable indent size check") + cmd.PersistentFlags().BoolVar(&cliConfig.Disable.MaxLineLength, "disable-max-line-length", false, "Disable max line length check") +} + +func init() { + // Add flags + addPersistentFlags(editorConfigCmd) + // Add command + validateCmd.AddCommand(editorConfigCmd) +} diff --git a/examples/quick-start-simple/.editorconfig b/examples/quick-start-simple/.editorconfig new file mode 100644 index 000000000..836760da3 --- /dev/null +++ b/examples/quick-start-simple/.editorconfig @@ -0,0 +1,38 @@ +# EditorConfig for Terraform repository + +root = true + +# Terraform files +[*.tf] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# HCL files (for Terraform configurations) +[*.hcl] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Atmos configurations (if they exist as YAML or TOML) +[*.yml] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.toml] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/examples/quick-start-simple/atmos.yaml b/examples/quick-start-simple/atmos.yaml index 555d07515..5456297a0 100644 --- a/examples/quick-start-simple/atmos.yaml +++ b/examples/quick-start-simple/atmos.yaml @@ -17,5 +17,5 @@ stacks: name_pattern: "{stage}" logs: - file: "/dev/stderr" level: Info + file: "/dev/stderr" diff --git a/go.mod b/go.mod index c13309fea..55cb90289 100644 --- a/go.mod +++ b/go.mod @@ -18,6 +18,7 @@ require ( github.com/charmbracelet/lipgloss v1.0.0 github.com/charmbracelet/log v0.4.0 github.com/creack/pty v1.1.23 + github.com/editorconfig-checker/editorconfig-checker/v3 v3.0.3 github.com/elewis787/boa v0.1.2 github.com/fatih/color v1.18.0 github.com/go-git/go-git/v5 v5.13.1 @@ -107,6 +108,7 @@ require ( github.com/aws/smithy-go v1.22.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/aymerick/douceur v0.2.0 // indirect + github.com/baulk/chardet v0.1.0 // indirect github.com/beorn7/perks v1.0.1 // indirect github.com/bgentry/go-netrc v0.0.0-20140422174119-9fd32a8b3d3d // indirect github.com/bytecodealliance/wasmtime-go/v3 v3.0.2 // indirect @@ -136,12 +138,14 @@ require ( github.com/dsnet/compress v0.0.1 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad // indirect + github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 // indirect github.com/elliotchance/orderedmap v1.7.1 // indirect github.com/emirpasic/gods v1.18.1 // indirect github.com/erikgeiser/coninput v0.0.0-20211004153227-1c3628e74d0f // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/forPelevin/gomoji v1.2.0 // indirect github.com/fsnotify/fsnotify v1.8.0 // indirect + github.com/gabriel-vasile/mimetype v1.4.4 // indirect github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect github.com/go-git/go-billy/v5 v5.6.1 // indirect github.com/go-ini/ini v1.67.0 // indirect diff --git a/go.sum b/go.sum index 7a5991166..92ef88391 100644 --- a/go.sum +++ b/go.sum @@ -818,6 +818,8 @@ github.com/aymanbagabas/go-udiff v0.2.0 h1:TK0fH4MteXUDspT88n8CKzvK0X9O2xu9yQjWp github.com/aymanbagabas/go-udiff v0.2.0/go.mod h1:RE4Ex0qsGkTAJoQdQQCA0uG+nAzJO/pI/QwceO5fgrA= github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk= github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4= +github.com/baulk/chardet v0.1.0 h1:6/r5nPMikB9OG1Njs10VfVHZTDMFH6BdybHPISpfUVA= +github.com/baulk/chardet v0.1.0/go.mod h1:0ibN6068qswel5Hv54U7GNJUU57njfzPJrLIq7Y8xas= github.com/benbjohnson/clock v1.1.0/go.mod h1:J11/hYXuz8f4ySSvYwY0FKfm+ezbsZBKZxNJlLklBHA= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= @@ -961,6 +963,10 @@ github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+m github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad h1:Qk76DOWdOp+GlyDKBAG3Klr9cn7N+LcYc82AZ2S7+cA= github.com/dustin/gojson v0.0.0-20160307161227-2e71ec9dd5ad/go.mod h1:mPKfmRa823oBIgl2r20LeMSpTAteW5j7FLkc0vjmzyQ= github.com/dvyukov/go-fuzz v0.0.0-20210103155950-6a8e9d1f2415/go.mod h1:11Gm+ccJnvAhCNLlf5+cS9KjtbaD5I5zaZpFMsTHWTw= +github.com/editorconfig-checker/editorconfig-checker/v3 v3.0.3 h1:WO9Yd7/KjfGDYUeSBsGKh+Uj+K+/oTnJ3elDQ7Oq7yU= +github.com/editorconfig-checker/editorconfig-checker/v3 v3.0.3/go.mod h1:9mvpU+I3xMoU+l0vNtR98SUX/AsoAhBCntntRtNIu3Y= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.2 h1:dKG8sc7n321deIVRcQtwlMNoBEra7j0qQ8RwxO8RN0w= +github.com/editorconfig/editorconfig-core-go/v2 v2.6.2/go.mod h1:7dvD3GCm7eBw53xZ/lsiq72LqobdMg3ITbMBxnmJmqY= github.com/elazarl/goproxy v1.2.3 h1:xwIyKHbaP5yfT6O9KIeYJR5549MXRQkoQMRXGztz8YQ= github.com/elazarl/goproxy v1.2.3/go.mod h1:YfEbZtqP4AetfO6d40vWchF3znWX7C7Vd6ZMfdL8z64= github.com/elewis787/boa v0.1.2 h1:xNKWJ9X2MWbLSLLOA31N4l1Jdec9FZSkbTvXy3C8rw4= @@ -1012,8 +1018,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z github.com/fsnotify/fsnotify v1.5.1/go.mod h1:T3375wBYaZdLLcVNkcVbzGHY7f1l/uK5T5Ai1i3InKU= github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= -github.com/gabriel-vasile/mimetype v1.4.3 h1:in2uUcidCuFcDKtdcBxlR0rJ1+fsokWf+uqxgUFjbI0= -github.com/gabriel-vasile/mimetype v1.4.3/go.mod h1:d8uq/6HKRL6CGdk+aubisF/M5GcPfT7nKyLpA0lbSSk= +github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= +github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= diff --git a/pkg/config/config.go b/pkg/config/config.go index 37ddc13fd..5e0f72bef 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -245,7 +245,8 @@ func InitCliConfig(configAndStacksInfo schema.ConfigAndStacksInfo, processStacks return atmosConfig, err } } - + // We want the editorconfig color by default to be true + atmosConfig.Validate.EditorConfig.Color = true // https://gist.github.com/chazcheadle/45bf85b793dea2b71bd05ebaa3c28644 // https://sagikazarmark.hu/blog/decoding-custom-formats-with-viper/ err = v.Unmarshal(&atmosConfig) diff --git a/pkg/schema/schema.go b/pkg/schema/schema.go index 30e75f003..efde6f247 100644 --- a/pkg/schema/schema.go +++ b/pkg/schema/schema.go @@ -1,6 +1,8 @@ package schema -import "github.com/cloudposse/atmos/pkg/store" +import ( + "github.com/cloudposse/atmos/pkg/store" +) type AtmosSectionMapType = map[string]any @@ -30,13 +32,34 @@ type AtmosConfiguration struct { StackType string `yaml:"stackType,omitempty" json:"StackType,omitempty" mapstructure:"stackType"` Default bool `yaml:"default" json:"default" mapstructure:"default"` Version Version `yaml:"version,omitempty" json:"version,omitempty" mapstructure:"version"` - + Validate Validate `yaml:"validate,omitempty" json:"validate,omitempty" mapstructure:"validate"` // Stores is never read from yaml, it is populated in processStoreConfig and it's used to pass to the populated store // registry through to the yaml parsing functions when !store is run and to pass the registry to the hooks // functions to be able to call stores from within hooks. Stores store.StoreRegistry `yaml:"stores_registry,omitempty" json:"stores_registry,omitempty" mapstructure:"stores_registry"` } +type Validate struct { + EditorConfig EditorConfig `yaml:"editorconfig,omitempty" json:"editorconfig,omitempty" mapstructure:"editorconfig"` +} + +type EditorConfig struct { + IgnoreDefaults bool `yaml:"ignore_defaults,omitempty" json:"ignore_defaults,omitempty" mapstructure:"ignore_defaults"` + DryRun bool `yaml:"dry_run,omitempty" json:"dry_run,omitempty" mapstructure:"dry_run"` + Format string `yaml:"format,omitempty" json:"format,omitempty" mapstructure:"format"` + Color bool `yaml:"color,omitempty" json:"color,omitempty" mapstructure:"color"` + ConfigFilePath string `yaml:"config_file_path,omitempty" json:"config_file_path,omitempty" mapstructure:"config_file_path"` + Exclude []string `yaml:"exclude,omitempty" json:"exclude,omitempty" mapstructure:"exclude"` + Init bool `yaml:"init,omitempty" json:"init,omitempty" mapstructure:"init"` + + DisableEndOfLine bool `yaml:"disable_end_of_line,omitempty" json:"disable_end_of_line,omitempty" mapstructure:"disable_end_of_line"` + DisableInsertFinalNewline bool `yaml:"disable_insert_final_newline,omitempty" json:"disable_insert_final_newline,omitempty" mapstructure:"disable_insert_final_newline"` + DisableIndentation bool `yaml:"disable_indentation,omitempty" json:"disable_indentation,omitempty" mapstructure:"disable_indentation"` + DisableIndentSize bool `yaml:"disable_indent_size,omitempty" json:"disable_indent_size,omitempty" mapstructure:"disable_indent_size"` + DisableMaxLineLength bool `yaml:"disable_max_line_length,omitempty" json:"disable_max_line_length,omitempty" mapstructure:"disable_max_line_length"` + DisableTrimTrailingWhitespace bool `yaml:"disable_trim_trailing_whitespace,omitempty" json:"disable_trim_trailing_whitespace,omitempty" mapstructure:"disable_trim_trailing_whitespace"` +} + type Terminal struct { MaxWidth int `yaml:"max_width" json:"max_width" mapstructure:"max_width"` Pager bool `yaml:"pager" json:"pager" mapstructure:"pager"` diff --git a/tests/fixtures/scenarios/editorconfig/.editorconfig b/tests/fixtures/scenarios/editorconfig/.editorconfig new file mode 100644 index 000000000..836760da3 --- /dev/null +++ b/tests/fixtures/scenarios/editorconfig/.editorconfig @@ -0,0 +1,38 @@ +# EditorConfig for Terraform repository + +root = true + +# Terraform files +[*.tf] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# HCL files (for Terraform configurations) +[*.hcl] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +# Atmos configurations (if they exist as YAML or TOML) +[*.yml] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.toml] +indent_style = space +indent_size = 2 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true diff --git a/tests/fixtures/scenarios/editorconfig/README.md b/tests/fixtures/scenarios/editorconfig/README.md new file mode 100644 index 000000000..c245c9cb1 --- /dev/null +++ b/tests/fixtures/scenarios/editorconfig/README.md @@ -0,0 +1,7 @@ +# Atmos Editor Config Test Scenarios + +This directory contains configurations specifically designed to test and validate the behavior of editor configurations when working with Atmos. + +These files include intentionally broken examples and edge cases to help ensure that editors and tools handle Atmos configurations correctly. + +**Do not use these examples for actual deployments.** diff --git a/tests/fixtures/scenarios/editorconfig/atmos.yaml b/tests/fixtures/scenarios/editorconfig/atmos.yaml new file mode 100644 index 000000000..41811f20a --- /dev/null +++ b/tests/fixtures/scenarios/editorconfig/atmos.yaml @@ -0,0 +1,396 @@ +# 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: "." + +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: + # 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" + # To define Atmos stack naming convention, use either `name_pattern` or `name_template`. + # `name_template` has higher priority (if `name_template` is specified, `name_pattern` will be ignored). + # `name_pattern` can also be set using 'ATMOS_STACKS_NAME_PATTERN' ENV var + # name_pattern: "{tenant}-{environment}-{stage}" + # `name_template` is a Golang template. + # For the template tokens, and you can use any Atmos sections and attributes that the Atmos command + # `atmos describe component -s ` generates (refer to https://atmos.tools/cli/commands/describe/component). + # `name_template` can also be set using 'ATMOS_STACKS_NAME_TEMPLATE' ENV var + name_template: "{{.vars.tenant}}-{{.vars.environment}}-{{.vars.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: + # No arguments or flags are required + - name: "test" + description: "Run all tests" + 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 + 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"' + + - name: set-eks-cluster + description: | + Download 'kubeconfig' and set EKS cluster. + + Example usage: + atmos set-eks-cluster eks/cluster -s tenant1-ue1-dev -r admin + atmos set-eks-cluster eks/cluster -s tenant2-uw2-prod --role reader + verbose: false # Set to `true` to see verbose outputs + arguments: + - name: component + description: Name of the component + flags: + - name: stack + shorthand: s + description: Name of the stack + required: true + - name: role + shorthand: r + description: IAM role to use + required: true + # 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 }}" + env: + - key: KUBECONFIG + value: /dev/shm/kubecfg.{{ .Flags.stack }}-{{ .Flags.role }} + steps: + - > + aws + --profile {{ .ComponentConfig.vars.namespace }}-{{ .ComponentConfig.vars.tenant }}-gbl-{{ .ComponentConfig.vars.stage }}-{{ .Flags.role }} + --region {{ .ComponentConfig.vars.region }} + eks update-kubeconfig + --name={{ .ComponentConfig.vars.namespace }}-{{ .Flags.stack }}-eks-cluster + --kubeconfig="${KUBECONFIG}" + > /dev/null + - chmod 600 ${KUBECONFIG} + - echo ${KUBECONFIG} + +# 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 + + # Atmos Pro integration + pro: + host: app.cloudposse.com + timeout: 3 + events: + pull_request: + - on: [open, synchronize, reopen] + workflow: atmos-plan.yml + dispatch_only_top_level_stacks: true + - on: [merge] + workflow: atmos-apply.yaml + release: + - on: [publish] + workflow: atmos-apply.yaml + + # 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 + 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://atmos.tools/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json + # 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`) + manifest: "../../schemas/atmos/atmos-manifest/1.0/atmos-manifest.json" + +# CLI command aliases +aliases: + # Aliases for Atmos native commands + tf: terraform + tp: terraform plan + up: terraform apply + down: terraform destroy + ds: describe stacks + dc: describe component + # Aliases for Atmos custom commands + ls: list stacks + lc: list components + +# `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 diff --git a/tests/fixtures/scenarios/editorconfig/context.tf b/tests/fixtures/scenarios/editorconfig/context.tf new file mode 100644 index 000000000..5e0ef8856 --- /dev/null +++ b/tests/fixtures/scenarios/editorconfig/context.tf @@ -0,0 +1,279 @@ +# +# ONLY EDIT THIS FILE IN github.com/cloudposse/terraform-null-label +# All other instances of this file should be a copy of that one +# +# +# Copy this file from https://github.com/cloudposse/terraform-null-label/blob/master/exports/context.tf +# and then place it in your Terraform module to automatically get +# Cloud Posse's standard configuration inputs suitable for passing +# to Cloud Posse modules. +# +# curl -sL https://raw.githubusercontent.com/cloudposse/terraform-null-label/master/exports/context.tf -o context.tf +# +# Modules should access the whole context as `module.this.context` +# to get the input variables with nulls for defaults, +# for example `context = module.this.context`, +# and access individual variables as `module.this.`, +# with final values filled in. +# +# For example, when using defaults, `module.this.context.delimiter` +# will be null, and `module.this.delimiter` will be `-` (hyphen). +# + +module "this" { + source = "cloudposse/label/null" + version = "0.25.0" # requires Terraform >= 0.13.0 + + enabled = var.enabled + namespace = var.namespace + tenant = var.tenant + environment = var.environment + stage = var.stage + name = var.name + delimiter = var.delimiter + attributes = var.attributes + tags = var.tags + additional_tag_map = var.additional_tag_map + label_order = var.label_order + regex_replace_chars = var.regex_replace_chars + id_length_limit = var.id_length_limit + label_key_case = var.label_key_case + label_value_case = var.label_value_case + descriptor_formats = var.descriptor_formats + labels_as_tags = var.labels_as_tags + + context = var.context +} + +# Copy contents of cloudposse/terraform-null-label/variables.tf here + +variable "context" { + type = any + default = { + enabled = true + namespace = null + tenant = null + environment = null + stage = null + name = null + delimiter = null + attributes = [] + tags = {} + additional_tag_map = {} + regex_replace_chars = null + label_order = [] + id_length_limit = null + label_key_case = null + label_value_case = null + descriptor_formats = {} + # Note: we have to use [] instead of null for unset lists due to + # https://github.com/hashicorp/terraform/issues/28137 + # which was not fixed until Terraform 1.0.0, + # but we want the default to be all the labels in `label_order` + # and we want users to be able to prevent all tag generation + # by setting `labels_as_tags` to `[]`, so we need + # a different sentinel to indicate "default" + labels_as_tags = ["unset"] + } + description = <<-EOT + Single object for setting entire context at once. + See description of individual variables for details. + Leave string and numeric variables as `null` to use default value. + Individual variable settings (non-null) override settings in context object, + except for attributes, tags, and additional_tag_map, which are merged. + EOT + + validation { + condition = lookup(var.context, "label_key_case", null) == null ? true : contains(["lower", "title", "upper"], var.context["label_key_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`." + } + + validation { + condition = lookup(var.context, "label_value_case", null) == null ? true : contains(["lower", "title", "upper", "none"], var.context["label_value_case"]) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "enabled" { + type = bool + default = null + description = "Set to false to prevent the module from creating any resources" +} + +variable "namespace" { + type = string + default = null + description = "ID element. Usually an abbreviation of your organization name, e.g. 'eg' or 'cp', to help ensure generated IDs are globally unique" +} + +variable "tenant" { + type = string + default = null + description = "ID element _(Rarely used, not included by default)_. A customer identifier, indicating who this instance of a resource is for" +} + +variable "environment" { + type = string + default = null + description = "ID element. Usually used for region e.g. 'uw2', 'us-west-2', OR role 'prod', 'staging', 'dev', 'UAT'" +} + +variable "stage" { + type = string + default = null + description = "ID element. Usually used to indicate role, e.g. 'prod', 'staging', 'source', 'build', 'test', 'deploy', 'release'" +} + +variable "name" { + type = string + default = null + description = <<-EOT + ID element. Usually the component or solution name, e.g. 'app' or 'jenkins'. + This is the only ID element not also included as a `tag`. + The "name" tag is set to the full `id` string. There is no tag with the value of the `name` input. + EOT +} + +variable "delimiter" { + type = string + default = null + description = <<-EOT + Delimiter to be used between ID elements. + Defaults to `-` (hyphen). Set to `""` to use no delimiter at all. + EOT +} + +variable "attributes" { + type = list(string) + default = [] + description = <<-EOT + ID element. Additional attributes (e.g. `workers` or `cluster`) to add to `id`, + in the order they appear in the list. New attributes are appended to the + end of the list. The elements of the list are joined by the `delimiter` + and treated as a single ID element. + EOT +} + +variable "labels_as_tags" { + type = set(string) + default = ["default"] + description = <<-EOT + Set of labels (ID elements) to include as tags in the `tags` output. + Default is to include all labels. + Tags with empty values will not be included in the `tags` output. + Set to `[]` to suppress all generated tags. + **Notes:** + The value of the `name` tag, if included, will be the `id`, not the `name`. + Unlike other `null-label` inputs, the initial setting of `labels_as_tags` cannot be + changed in later chained modules. Attempts to change it will be silently ignored. + EOT +} + +variable "tags" { + type = map(string) + default = {} + description = <<-EOT + Additional tags (e.g. `{'BusinessUnit': 'XYZ'}`). + Neither the tag keys nor the tag values will be modified by this module. + EOT +} + +variable "additional_tag_map" { + type = map(string) + default = {} + description = <<-EOT + Additional key-value pairs to add to each map in `tags_as_list_of_maps`. Not added to `tags` or `id`. + This is for some rare cases where resources want additional configuration of tags + and therefore take a list of maps with tag key, value, and additional configuration. + EOT +} + +variable "label_order" { + type = list(string) + default = null + description = <<-EOT + The order in which the labels (ID elements) appear in the `id`. + Defaults to ["namespace", "environment", "stage", "name", "attributes"]. + You can omit any of the 6 labels ("tenant" is the 6th), but at least one must be present. + EOT +} + +variable "regex_replace_chars" { + type = string + default = null + description = <<-EOT + Terraform regular expression (regex) string. + Characters matching the regex will be removed from the ID elements. + If not set, `"/[^a-zA-Z0-9-]/"` is used to remove all characters other than hyphens, letters and digits. + EOT +} + +variable "id_length_limit" { + type = number + default = null + description = <<-EOT + Limit `id` to this many characters (minimum 6). + Set to `0` for unlimited length. + Set to `null` for keep the existing setting, which defaults to `0`. + Does not affect `id_full`. + EOT + validation { + condition = var.id_length_limit == null ? true : var.id_length_limit >= 6 || var.id_length_limit == 0 + error_message = "The id_length_limit must be >= 6 if supplied (not null), or 0 for unlimited length." + } +} + +variable "label_key_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of the `tags` keys (label names) for tags generated by this module. + Does not affect keys of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper`. + Default value: `title`. + EOT + + validation { + condition = var.label_key_case == null ? true : contains(["lower", "title", "upper"], var.label_key_case) + error_message = "Allowed values: `lower`, `title`, `upper`." + } +} + +variable "label_value_case" { + type = string + default = null + description = <<-EOT + Controls the letter case of ID elements (labels) as included in `id`, + set as tag values, and output by this module individually. + Does not affect values of tags passed in via the `tags` input. + Possible values: `lower`, `title`, `upper` and `none` (no transformation). + Set this to `title` and set `delimiter` to `""` to yield Pascal Case IDs. + Default value: `lower`. + EOT + + validation { + condition = var.label_value_case == null ? true : contains(["lower", "title", "upper", "none"], var.label_value_case) + error_message = "Allowed values: `lower`, `title`, `upper`, `none`." + } +} + +variable "descriptor_formats" { + type = any + default = {} + description = <<-EOT + Describe additional descriptors to be output in the `descriptors` output map. + Map of maps. Keys are names of descriptors. Values are maps of the form + `{ + format = string + labels = list(string) + }` + (Type is `any` so the map values can later be enhanced to provide additional options.) + `format` is a Terraform format string to be passed to the `format()` function. + `labels` is a list of labels, in order, to pass to `format()` function. + Label values will be normalized before being passed to `format()` so they will be + identical to how they appear in `id`. + Default is `{}` (`descriptors` output will be empty). + EOT +} + +#### End of copy of cloudposse/terraform-null-label/variables.tf diff --git a/tests/fixtures/scenarios/editorconfig/vendor.yaml b/tests/fixtures/scenarios/editorconfig/vendor.yaml new file mode 100644 index 000000000..17bba24fc --- /dev/null +++ b/tests/fixtures/scenarios/editorconfig/vendor.yaml @@ -0,0 +1,102 @@ +# atmos vendor pull +# atmos vendor pull --component vpc-mixin-1 +# atmos vendor pull -c vpc-mixin-2 +# atmos vendor pull -c vpc-mixin-3 +# atmos vendor pull -c vpc-mixin-4 +# atmos vendor pull --tags test +# atmos vendor pull --tags networking,storage + +apiVersion: atmos/v1 +kind: AtmosVendorConfig +metadata: + name: example-vendor-config + description: Atmos vendoring manifest +spec: + # `imports` or `sources` (or both) must be defined in a vendoring manifest + imports: + - "vendor/vendor2" + - "vendor/vendor3.yaml" + + sources: + # `source` supports the following protocols: local paths (absolute and relative), OCI (https://opencontainers.org), + # Git, Mercurial, HTTP, HTTPS, Amazon S3, Google GCP, + # and all URL and archive formats as described in https://github.com/hashicorp/go-getter. + # In 'source', Golang templates are supported https://pkg.go.dev/text/template. + # If 'version' is provided, '{{.Version}}' will be replaced with the 'version' value before pulling the files from 'source'. + # Download the component from the AWS public ECR registry (https://docs.aws.amazon.com/AmazonECR/latest/public/public-registries.html). + - component: "vpc" + source: "oci://public.ecr.aws/cloudposse/components/terraform/stable/aws/vpc:{{.Version}}" + version: "latest" + targets: + - "components/terraform/infra/vpc3" + # Only include the files that match the 'included_paths' patterns. + # If 'included_paths' is not specified, all files will be matched except those that match the patterns from 'excluded_paths'. + # 'included_paths' support POSIX-style Globs for file names/paths (double-star `**` is supported). + # https://en.wikipedia.org/wiki/Glob_(programming) + # https://github.com/bmatcuk/doublestar#patterns + included_paths: + - "**/*.tf" + - "**/*.tfvars" + - "**/*.md" + # Tags can be used to vendor component that have the specific tags + # `atmos vendor pull --tags test` + # Refer to https://atmos.tools/cli/commands/vendor/pull + tags: + - test + - networking + - component: "vpc-flow-logs-bucket" + source: "github.com/cloudposse/terraform-aws-components.git//modules/vpc-flow-logs-bucket?ref={{.Version}}" + version: "1.323.0" + targets: + - "components/terraform/infra/vpc-flow-logs-bucket/{{.Version}}" + excluded_paths: + - "**/*.yaml" + - "**/*.yml" + # Tags can be used to vendor component that have the specific tags + # `atmos vendor pull --tags networking,storage` + # Refer to https://atmos.tools/cli/commands/vendor/pull + tags: + - test + - storage + - component: "vpc-mixin-1" + source: "https://raw.githubusercontent.com/cloudposse/terraform-null-label/0.25.0/exports/context.tf" + targets: + - "components/terraform/infra/vpc3" + # Tags can be used to vendor component that have the specific tags + # `atmos vendor pull --tags test` + # Refer to https://atmos.tools/cli/commands/vendor/pull + tags: + - test + - component: "vpc-mixin-2" + # Copy a local file into a local folder (keeping the same file name) + # This `source` is relative to the current folder + source: "components/terraform/mixins/context.tf" + targets: + - "components/terraform/infra/vpc3" + # Tags can be used to vendor component that have the specific tags + # `atmos vendor pull --tags test` + # Refer to https://atmos.tools/cli/commands/vendor/pull + tags: + - test + - component: "vpc-mixin-3" + # Copy a local folder into a local folder + # This `source` is relative to the current folder + source: "components/terraform/mixins" + targets: + - "components/terraform/infra/vpc3" + # Tags can be used to vendor component that have the specific tags + # `atmos vendor pull --tags test` + # Refer to https://atmos.tools/cli/commands/vendor/pull + tags: + - test + - component: "vpc-mixin-4" + # Copy a local file into a local file with a different file name + # This `source` is relative to the current folder + source: "components/terraform/mixins/context.tf" + targets: + - "components/terraform/infra/vpc3/context-copy.tf" + # Tags can be used to vendor component that have the specific tags + # `atmos vendor pull --tags test` + # Refer to https://atmos.tools/cli/commands/vendor/pull + tags: + - test diff --git a/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden b/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden index 1e3ddb9d0..0e2e1c06d 100644 --- a/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_describe_config.stdout.golden @@ -135,5 +135,10 @@ "Timeout": 0, "Frequency": "" } + }, + "validate": { + "editorconfig": { + "color": true + } } } diff --git a/tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden b/tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden index d41b52220..cd6dcd4f0 100644 --- a/tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden +++ b/tests/snapshots/TestCLICommands_atmos_describe_config_-f_yaml.stdout.golden @@ -40,4 +40,7 @@ excludeStackAbsolutePaths: terraformDirAbsolutePath: /Users/erik/Dev/cloudposse/tools/atmos/examples/demo-stacks/components/terraform helmfileDirAbsolutePath: /Users/erik/Dev/cloudposse/tools/atmos/examples/demo-stacks default: false +validate: + editorconfig: + color: true diff --git a/tests/snapshots/TestCLICommands_atmos_validate_editorconfig.stderr.golden b/tests/snapshots/TestCLICommands_atmos_validate_editorconfig.stderr.golden new file mode 100644 index 000000000..e69de29bb diff --git a/tests/snapshots/TestCLICommands_atmos_validate_editorconfig.stdout.golden b/tests/snapshots/TestCLICommands_atmos_validate_editorconfig.stdout.golden new file mode 100644 index 000000000..db9cf1c1e --- /dev/null +++ b/tests/snapshots/TestCLICommands_atmos_validate_editorconfig.stdout.golden @@ -0,0 +1 @@ +No errors found diff --git a/tests/snapshots/TestCLICommands_atmos_validate_editorconfig_error.stderr.golden b/tests/snapshots/TestCLICommands_atmos_validate_editorconfig_error.stderr.golden new file mode 100644 index 000000000..effe1718a --- /dev/null +++ b/tests/snapshots/TestCLICommands_atmos_validate_editorconfig_error.stderr.golden @@ -0,0 +1,2 @@ + +2 errors found diff --git a/tests/snapshots/TestCLICommands_atmos_validate_editorconfig_error.stdout.golden b/tests/snapshots/TestCLICommands_atmos_validate_editorconfig_error.stdout.golden new file mode 100644 index 000000000..e36acb217 --- /dev/null +++ b/tests/snapshots/TestCLICommands_atmos_validate_editorconfig_error.stdout.golden @@ -0,0 +1,3 @@ +context.tf: + 267: Wrong amount of left-padding spaces(want multiple of 2) + 268: Wrong amount of left-padding spaces(want multiple of 2) diff --git a/tests/test-cases/validate-editorconfig.yaml b/tests/test-cases/validate-editorconfig.yaml new file mode 100644 index 000000000..bba8240a7 --- /dev/null +++ b/tests/test-cases/validate-editorconfig.yaml @@ -0,0 +1,31 @@ +tests: + - name: atmos validate editorconfig + enabled: true + snapshot: true + description: "Ensure atmos CLI validates success case" + workdir: "../examples/quick-start-simple/" + command: "atmos" + args: + - "validate" + - "editorconfig" + expect: + diff: [] + stdout: + - "No errors found" + stderr: + - "^$" + exit_code: 0 + - name: atmos validate editorconfig error + enabled: true + snapshot: true + description: "Ensure atmos CLI gives error during validation based on editorconfig" + workdir: "../tests/fixtures/scenarios/editorconfig" + command: "atmos" + args: + - "validate" + - "editorconfig" + expect: + diff: [] + stderr: + - "2 errors found" + exit_code: 1 diff --git a/website/docs/cli/commands/validate/validate-editorconfig.mdx b/website/docs/cli/commands/validate/validate-editorconfig.mdx new file mode 100644 index 000000000..2fe56a53b --- /dev/null +++ b/website/docs/cli/commands/validate/validate-editorconfig.mdx @@ -0,0 +1,57 @@ +--- +title: atmos validate editorconfig +sidebar_label: editorconfig +sidebar_class_name: command +id: editorconfig +description: Use this command to validate the workdir of the Atmos project against the rules defined in the .editorconfig file. +--- +import Screengrab from '@site/src/components/Screengrab' + +:::note purpose +Use this command to validate files against the rules defined in .editorconfig file. +::: + + + +## Usage + +Execute the `validate editorconfig` command like this: + +```shell +atmos validate editorconfig +``` + +This command validates files against the formatting rules defined in your .editorconfig file. + +:::tip +Run `atmos validate editorconfig --help` to see all the available options +::: + +## Examples + +```shell +atmos validate editorconfig +atmos validate editorconfig --logs-level trace +atmos validate editorconfig --no-color +atmos validate editorconfig --dry-run +``` + +## Flags + +| Flag | Description | Alias | Required | +|:---------------------------------------|:----------------------------------------------------------------------|:------|:---------| +|`--config string` |Path to the configuration file | |no | +|`--disable-end-of-line` |Disable end-of-line check (default "false") | |no | +|`--disable-indent-size` |Disable indent size check (default "false") | |no | +|`--disable-indentation` |Disable indentation check (default "false")||no| +|`--disable-insert-final-newline` |Disable final newline check (default "false")||no| +|`--disable-max-line-length` |Disable max line length check (default "false")||no| +|`--disable-trim-trailing-whitespace` |Disable trailing whitespace check (default "false")||no| +|`--dry-run` |Show which files would be checked (default "false")||no| +|`--exclude string` |Regex to exclude files from checking||no| +|`--format string` |Specify the output format: default, gcc (default "default")||no| +|`--help` |help for editorconfig (default "false")||no| +|`--ignore-defaults` |Ignore default excludes (default "false")||no| +|`--init` |creates an initial configuration (default "false")||no| +|`--no-color` |Don't print colors (default "false")||no| +|`--version` |Print the version number (default "false")||no| \ No newline at end of file diff --git a/website/docs/core-concepts/validate/editorconfig.mdx b/website/docs/core-concepts/validate/editorconfig.mdx new file mode 100644 index 000000000..6cc375641 --- /dev/null +++ b/website/docs/core-concepts/validate/editorconfig.mdx @@ -0,0 +1,70 @@ +--- +title: EditorConfig Validation +sidebar_position: 2 +sidebar_label: EditorConfig +description: Use EditorConfig Checker to validate your configurations. +id: editorconfig-validation +--- +import Terminal from '@site/src/components/Terminal' +import File from '@site/src/components/File' +import Intro from '@site/src/components/Intro' + + +Atmos supports validation of EditorConfigs to check the formatting of your configuration files. By enforcing the canonical rules specified in your `.editorconfig` file, it helps ensure consistent formatting across your project. + + +## Example + + +```shell +# Validate all files in the current project using EditorConfig +atmos validate editorconfig +``` + + +### Configuration + +To use the `atmos validate editorconfig` command, ensure that your project contains a properly configured `.editorconfig` file at the root level or in relevant directories. This file defines the coding styles for the project, such as indentation, line endings, and character encodings. + + +```ini +# EditorConfig is awesome: https://editorconfig.org +root = true + +[*] +indent_style = space +indent_size = 4 +end_of_line = lf +charset = utf-8 +trim_trailing_whitespace = true +insert_final_newline = true + +[*.md] +trim_trailing_whitespace = false +``` + + + +### Output + +The `atmos validate editorconfig` command will provide detailed output indicating whether the files comply with the `.editorconfig` rules or if there are any violations. For example: + + +```console +scenarios/complete/modules/label/context.tf: + 267: Wrong amount of left-padding spaces(want multiple of 2) + 268: Wrong amount of left-padding spaces(want multiple of 2) + +2 errors found +``` + + +### Troubleshooting + +If validation fails, review your `.editorconfig` file and ensure the rules align with your project's requirements. You can also run the command with verbose output for more details: + + +```shell +atmos validate editorconfig --logs-level trace +``` + diff --git a/website/docs/core-concepts/validate/validate.mdx b/website/docs/core-concepts/validate/validate.mdx index 1e602fe26..92c935539 100644 --- a/website/docs/core-concepts/validate/validate.mdx +++ b/website/docs/core-concepts/validate/validate.mdx @@ -2,7 +2,7 @@ title: Validating Stack Configurations sidebar_position: 5 sidebar_label: Validate Configurations -description: Use JSON Schema and OPA policies to validate Components. +description: Use JSON Schema, OPA policies, and EditorConfig Checker to validate Components. id: validating --- import Terminal from '@site/src/components/Terminal' @@ -12,12 +12,12 @@ import Intro from '@site/src/components/Intro' Validation is essential for ensuring clean and correct configurations, especially in environments where multiple teams contribute to the development and deployment processes. -Atmos enhances this validation process in two significant ways with [JSON Schema](https://json-schema.org/) and [OPA](https://www.openpolicyagent.org/) policies. +Atmos enhances this validation process in three significant ways with [JSON Schema](https://json-schema.org/), [OPA](https://www.openpolicyagent.org/) policies, and the [EditorConfig Checker](https://github.com/editorconfig-checker/editorconfig-checker). ## Types of Validation -Atmos supports two types of native validation. +Atmos supports three types of native validation. ### JSON Schema @@ -29,10 +29,14 @@ JSON Schema is an industry standard and provides a vocabulary to annotate and va The [Open Policy Agent](https://www.openpolicyagent.org/docs/latest/) (OPA, pronounced “oh-pa”) is another open-source industry standard that provides a general-purpose policy engine to unify policy enforcement across your stacks. The OPA language (Rego) is a high-level declarative language for specifying policy as code. -Atmos has native support for the OPA decision-making engine to enforce policies across all the components in your stacks (e.g. for microservice configurations). +Atmos has native support for the OPA decision-making engine to enforce policies across all the components in your stacks (e.g., for microservice configurations). This is powerful stuff: because you can define many policies, it's possible to validate components differently for different environments or teams. +### EditorConfig Checker + +The [EditorConfig Checker](https://github.com/editorconfig-checker/editorconfig-checker) is a tool that ensures adherence to the rules defined in your `.editorconfig` file. This ensures consistency in coding styles across teams, which is particularly important in collaborative environments. Atmos supports running the EditorConfig Checker to validate the configurations in your project. + ## Validate Your Configurations ### Validate Components diff --git a/website/src/components/Screengrabs/atmos-validate-editorconfig--help.html b/website/src/components/Screengrabs/atmos-validate-editorconfig--help.html new file mode 100644 index 000000000..1274b2987 --- /dev/null +++ b/website/src/components/Screengrabs/atmos-validate-editorconfig--help.html @@ -0,0 +1,389 @@ + + + + + + terminal-to-html Preview + + + +
  +  +Usage: +  + atmos validate editorconfig [flags] +  +  +Flags: +  + --config string Path to the configuration file +  + --disable-end-of-line Disable end-of-line check (default + "false") +  + --disable-indent-size Disable indent size check (default + "false") +  + --disable-indentation Disable indentation check (default + "false") +  + --disable-insert-final-newline Disable final newline check + (default "false") +  + --disable-max-line-length Disable max line length check + (default "false") +  + --disable-trim-trailing-whitespace Disable trailing whitespace check + (default "false") +  + --dry-run Show which files would be checked + (default "false") +  + --exclude string Regex to exclude files from + checking +  + --format string Specify the output format: + default, gcc (default "default") +  + -h, --help help for editorconfig (default + "false") +  + --ignore-defaults Ignore default excludes (default + "false") +  + --init creates an initial configuration + (default "false") +  + --no-color Don't print colors (default + "false") +  + --version Print the version number (default + "false") +  +  +Global Flags: +  + --logs-file string 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' (default + "/dev/stdout") +  + --logs-level string 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 (default "Info") +  + --redirect-stderr string File descriptor to redirect 'stderr' to. + Errors can be redirected to any file or any + standard file descriptor (including + '/dev/null'): atmos <command> + --redirect-stderr /dev/stdout +  +The '--' (double-dash) can be used to signify the end of Atmos-specific options +and the beginning of additional native arguments and flags for the specific command being run. +  +Example: + atmos atmos <subcommand> <component> -s <stack> -- <native-flags>
+ +