Skip to content

Commit

Permalink
Merge branch 'main' into pulak/DEV-2805
Browse files Browse the repository at this point in the history
  • Loading branch information
osterman authored Jan 7, 2025
2 parents fa0c9f2 + 2019d88 commit b4bc19f
Show file tree
Hide file tree
Showing 269 changed files with 6,764 additions and 2,752 deletions.
42 changes: 21 additions & 21 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -298,25 +298,25 @@ jobs:
- { os: windows-latest, target: windows }
- { os: macos-latest, target: macos }
demo-folder:
- demo-atlantis
# - demo-component-manifest
- demo-component-versions
- demo-context
# - demo-custom-command
# - demo-json-validation
# - demo-opa-validation
# - demo-opentofu
# - demo-project
# - demo-stacks
# - demo-terraform
# - demo-terraform-overrides
# - demo-workflows
# - demo-yaml-anchors
# - demo-mock-architecture
# - demo-stack-templating
# - demo-multi-cloud
- demo-vendoring
- tests
- examples/demo-atlantis
# - examples/demo-component-manifest
- examples/demo-component-versions
- examples/demo-context
# - examples/demo-custom-command
# - examples/demo-json-validation
# - examples/demo-opa-validation
# - examples/demo-opentofu
# - examples/demo-project
# - examples/demo-stacks
# - examples/demo-terraform
# - examples/demo-terraform-overrides
# - examples/demo-workflows
# - examples/demo-yaml-anchors
# - examples/demo-mock-architecture
# - examples/demo-stack-templating
# - examples/demo-multi-cloud
- examples/demo-vendoring
- tests/fixtures/scenarios/complete

timeout-minutes: 20
steps:
Expand Down Expand Up @@ -350,7 +350,7 @@ jobs:
terraform_wrapper: false

- name: Run tests in ${{ matrix.demo-folder }} for ${{ matrix.flavor.target }}
working-directory: examples/${{ matrix.demo-folder }}
working-directory: ${{ matrix.demo-folder }}
if: matrix.flavor.target == 'linux' || matrix.flavor.target == 'macos'
run: |
atmos test
Expand All @@ -366,7 +366,7 @@ jobs:
atmos version
- name: Run tests in ${{ matrix.demo-folder }} for ${{ matrix.flavor.target }}
working-directory: examples/${{ matrix.demo-folder }}
working-directory: ${{ matrix.demo-folder }}
if: matrix.flavor.target == 'windows'
shell: pwsh
run: |
Expand Down
29 changes: 29 additions & 0 deletions atmos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -316,8 +316,37 @@ settings:
# 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

45 changes: 45 additions & 0 deletions cmd/about.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package cmd

import (
_ "embed"
"fmt"
"os"

"github.com/charmbracelet/glamour"
"github.com/spf13/cobra"
)

//go:embed markdown/about.md
var aboutMarkdown string

// aboutCmd represents the about command
var aboutCmd = &cobra.Command{
Use: "about",
Short: "Learn about Atmos",
Long: `Display information about Atmos, its features, and benefits.`,
Args: cobra.NoArgs,
DisableSuggestions: true,
SilenceUsage: true,
SilenceErrors: true,
RunE: func(cmd *cobra.Command, args []string) error {
renderer, err := glamour.NewTermRenderer(
glamour.WithAutoStyle(),
glamour.WithWordWrap(80),
)
if err != nil {
return fmt.Errorf("failed to create markdown renderer: %w", err)
}

out, err := renderer.Render(aboutMarkdown)
if err != nil {
return fmt.Errorf("failed to render about documentation: %w", err)
}

fmt.Fprint(os.Stdout, out)
return nil
},
}

func init() {
RootCmd.AddCommand(aboutCmd)
}
89 changes: 71 additions & 18 deletions cmd/cmd_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"errors"
"fmt"
"os"
"path"
"path/filepath"
"strings"
"time"
Expand Down Expand Up @@ -169,29 +168,80 @@ func preCustomCommand(
commandConfig *schema.Command,
) {
var sb strings.Builder
if len(args) != len(commandConfig.Arguments) {
if len(commandConfig.Arguments) == 0 {
u.LogError(schema.AtmosConfiguration{}, errors.New("invalid command"))

//checking for zero arguments in config
if len(commandConfig.Arguments) == 0 {
if len(commandConfig.Steps) > 0 {
// do nothing here; let the code proceed
} else if len(commandConfig.Commands) > 0 {
// show sub-commands
sb.WriteString("Available command(s):\n")
for i, c := range commandConfig.Commands {
sb.WriteString(fmt.Sprintf("%d. %s %s %s\n", i+1, parentCommand.Use, commandConfig.Name, c.Name))
sb.WriteString(
fmt.Sprintf("%d. %s %s %s\n", i+1, parentCommand.Use, commandConfig.Name, c.Name),
)
}
u.LogInfo(schema.AtmosConfiguration{}, sb.String())
os.Exit(1)
} else {
// truly invalid, nothing to do
u.LogError(schema.AtmosConfiguration{}, errors.New(
"invalid command: no args, no steps, no sub-commands",
))
os.Exit(1)
}
}

//Check on many arguments required and have no default value
requiredNoDefaultCount := 0
for _, arg := range commandConfig.Arguments {
if arg.Required && arg.Default == "" {
requiredNoDefaultCount++
}
sb.WriteString(fmt.Sprintf("Command requires %d argument(s):\n", len(commandConfig.Arguments)))
for i, arg := range commandConfig.Arguments {
if arg.Name == "" {
u.LogErrorAndExit(schema.AtmosConfiguration{}, errors.New("invalid argument configuration: empty argument name"))
}

// Check if the number of arguments provided is less than the required number of arguments
if len(args) < requiredNoDefaultCount {
sb.WriteString(
fmt.Sprintf("Command requires at least %d argument(s) (no defaults provided for them):\n",
requiredNoDefaultCount))

// List out which arguments are missing
missingIndex := 1
for _, arg := range commandConfig.Arguments {
if arg.Required && arg.Default == "" {
sb.WriteString(fmt.Sprintf(" %d. %s\n", missingIndex, arg.Name))
missingIndex++
}
sb.WriteString(fmt.Sprintf(" %d. %s\n", i+1, arg.Name))
}
if len(args) > 0 {
sb.WriteString(fmt.Sprintf("\nReceived %d argument(s): %s", len(args), strings.Join(args, ", ")))
sb.WriteString(fmt.Sprintf("\nReceived %d argument(s): %s\n", len(args), strings.Join(args, ", ")))
}
u.LogErrorAndExit(schema.AtmosConfiguration{}, errors.New(sb.String()))
}

// Merge user-supplied arguments with defaults
finalArgs := make([]string, len(commandConfig.Arguments))

for i, arg := range commandConfig.Arguments {
if i < len(args) {
finalArgs[i] = args[i]
} else {
if arg.Default != "" {
finalArgs[i] = fmt.Sprintf("%v", arg.Default)
} else {
// This theoretically shouldn't happen:
sb.WriteString(fmt.Sprintf("Missing required argument '%s' with no default!\n", arg.Name))
u.LogErrorAndExit(schema.AtmosConfiguration{}, errors.New(sb.String()))
}
}
}
// Set the resolved arguments as annotations on the command
if cmd.Annotations == nil {
cmd.Annotations = make(map[string]string)
}
cmd.Annotations["resolvedArgs"] = strings.Join(finalArgs, ",")

// no "steps" means a sub command should be specified
if len(commandConfig.Steps) == 0 {
_ = cmd.Help()
Expand Down Expand Up @@ -224,12 +274,19 @@ func executeCustomCommand(
atmosConfig.Logs.Level = u.LogLevelTrace
}

mergedArgsStr := cmd.Annotations["resolvedArgs"]
finalArgs := strings.Split(mergedArgsStr, ",")
if mergedArgsStr == "" {
// If for some reason no annotation was set, just fallback
finalArgs = args
}

// Execute custom command's steps
for i, step := range commandConfig.Steps {
// Prepare template data for arguments
argumentsData := map[string]string{}
for ix, arg := range commandConfig.Arguments {
argumentsData[arg.Name] = args[ix]
argumentsData[arg.Name] = finalArgs[ix]
}

// Prepare template data for flags
Expand Down Expand Up @@ -405,7 +462,8 @@ func printMessageForMissingAtmosConfig(atmosConfig schema.AtmosConfiguration) {
u.PrintMessageInColor("atmos.yaml", c1)
fmt.Println(" CLI config file was not found.")
fmt.Print("\nThe default Atmos stacks directory is set to ")
u.PrintMessageInColor(path.Join(atmosConfig.BasePath, atmosConfig.Stacks.BasePath), c1)

u.PrintMessageInColor(filepath.Join(atmosConfig.BasePath, atmosConfig.Stacks.BasePath), c1)
fmt.Println(",\nbut the directory does not exist in the current path.")
} else {
// If Atmos found an `atmos.yaml` config file, but it defines invalid paths to Atmos stacks and components
Expand Down Expand Up @@ -481,11 +539,6 @@ func CheckForAtmosUpdateAndPrintMessage(atmosConfig schema.AtmosConfiguration) {
}
}

func customHelpMessageToUpgradeToAtmosLatestRelease(cmd *cobra.Command, args []string) {
originalHelpFunc(cmd, args)
CheckForAtmosUpdateAndPrintMessage(atmosConfig)
}

// Check Atmos is version command
func isVersionCommand() bool {
return len(os.Args) > 1 && os.Args[1] == "version"
Expand Down
20 changes: 15 additions & 5 deletions cmd/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"fmt"
"os"
"os/exec"
"path"
"path/filepath"
"runtime"

"github.com/charmbracelet/glamour"
Expand Down Expand Up @@ -41,7 +41,11 @@ var docsCmd = &cobra.Command{

// Detect terminal width if not specified in `atmos.yaml`
// The default screen width is 120 characters, but uses maxWidth if set and greater than zero
maxWidth := atmosConfig.Settings.Docs.MaxWidth
maxWidth := atmosConfig.Settings.Terminal.MaxWidth
if maxWidth == 0 && atmosConfig.Settings.Docs.MaxWidth > 0 {
maxWidth = atmosConfig.Settings.Docs.MaxWidth
u.LogWarning(atmosConfig, "'settings.docs.max-width' is deprecated and will be removed in a future version. Please use 'settings.terminal.max_width' instead")
}
defaultWidth := 120
screenWidth := defaultWidth

Expand All @@ -59,7 +63,7 @@ var docsCmd = &cobra.Command{
}

// Construct the full path to the Terraform component by combining the Atmos base path, Terraform base path, and component name
componentPath := path.Join(atmosConfig.BasePath, atmosConfig.Components.Terraform.BasePath, info.Component)
componentPath := filepath.Join(atmosConfig.BasePath, atmosConfig.Components.Terraform.BasePath, info.Component)
componentPathExists, err := u.IsDirectory(componentPath)
if err != nil {
u.LogErrorAndExit(schema.AtmosConfiguration{}, err)
Expand All @@ -68,7 +72,7 @@ var docsCmd = &cobra.Command{
u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("Component '%s' not found in path: '%s'", info.Component, componentPath))
}

readmePath := path.Join(componentPath, "README.md")
readmePath := filepath.Join(componentPath, "README.md")
if _, err := os.Stat(readmePath); err != nil {
if os.IsNotExist(err) {
u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("No README found for component: %s", info.Component))
Expand Down Expand Up @@ -97,7 +101,13 @@ var docsCmd = &cobra.Command{
u.LogErrorAndExit(schema.AtmosConfiguration{}, err)
}

if err := u.DisplayDocs(componentDocs, atmosConfig.Settings.Docs.Pagination); err != nil {
usePager := atmosConfig.Settings.Terminal.Pager
if !usePager && atmosConfig.Settings.Docs.Pagination {
usePager = atmosConfig.Settings.Docs.Pagination
u.LogWarning(atmosConfig, "'settings.docs.pagination' is deprecated and will be removed in a future version. Please use 'settings.terminal.pager' instead")
}

if err := u.DisplayDocs(componentDocs, usePager); err != nil {
u.LogErrorAndExit(schema.AtmosConfiguration{}, fmt.Errorf("failed to display documentation: %w", err))
}

Expand Down
6 changes: 4 additions & 2 deletions cmd/helmfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,11 @@ var helmfileCmd = &cobra.Command{
u.LogErrorAndExit(schema.AtmosConfiguration{}, err)
}
// Exit on help
if info.NeedHelp {
if info.NeedHelp || (info.SubCommand == "" && info.SubCommand2 == "") {
// Check for the latest Atmos release on GitHub and print update message
CheckForAtmosUpdateAndPrintMessage(atmosConfig)
if err := cmd.Help(); err != nil {
u.LogErrorAndExit(schema.AtmosConfiguration{}, err)
}
return
}
// Check Atmos configuration
Expand Down
20 changes: 20 additions & 0 deletions cmd/markdown/about.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# About Atmos

Atmos is an open-source framework for managing the configuration of Infrastructure as Code (IaC) at scale using Stacks.

It simplifies the deployment of infrastructure by providing a consistent, YAML-driven configuration system using tools like Terraform and Helmfile. Atmos helps teams adopt best practices, enforce standards, and automate complex workflows across environments.

## Key Features

- **Reusable Components**: Modular building blocks for defining infrastructure.
- **Stacks**: Environment-specific configurations for managing multiple AWS accounts.
- **YAML-Based**: Declarative configurations for easy maintenance.
- **Terraform & Helmfile**: Unified commands for seamless integration.
- **Automation-Ready**: Perfect for CI/CD pipelines and DevOps workflows.
- **Scalable**: Designed for multi-account, multi-environment AWS setups.

## Why Atmos?
Atmos reduces complexity, promotes consistency, and accelerates delivery, making it an ideal choice
for organizations managing large-scale AWS infrastructure.

Learn more [here](https://atmos.tools)
Loading

0 comments on commit b4bc19f

Please sign in to comment.