Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin' into pulak/DEV-2327
Browse files Browse the repository at this point in the history
  • Loading branch information
pkbhowmick committed Nov 11, 2024
2 parents 2879e2c + ffb64eb commit 25028e5
Show file tree
Hide file tree
Showing 14 changed files with 346 additions and 11 deletions.
9 changes: 9 additions & 0 deletions cmd/cmd_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,15 @@ func preCustomCommand(
if len(args) != len(commandConfig.Arguments) {
var sb strings.Builder
missingArgCount := 0
if len(commandConfig.Arguments) == 0 {
u.LogError(schema.CliConfiguration{}, errors.New("invalid command"))
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))
}
u.LogInfo(schema.CliConfiguration{}, sb.String())
os.Exit(1)
}
sb.WriteString(fmt.Sprintf("Command requires %d argument(s):\n", len(commandConfig.Arguments)))
for _, arg := range commandConfig.Arguments {
if !arg.Required || arg.Default != "" {
Expand Down
7 changes: 7 additions & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"github.com/spf13/pflag"

e "github.com/cloudposse/atmos/internal/exec"
"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/schema"
Expand Down Expand Up @@ -107,12 +108,17 @@ func Execute() error {
}

func init() {
// Add template function for wrapped flag usages
cobra.AddTemplateFunc("wrappedFlagUsages", templates.WrappedFlagUsages)

RootCmd.PersistentFlags().String("redirect-stderr", "", "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")

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
templates.SetCustomUsageFunc(RootCmd)
cobra.OnInitialize(initConfig)
}

Expand All @@ -131,6 +137,7 @@ func initConfig() {
}

b.HelpFunc(command, strings)
command.Usage()
})
}

Expand Down
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ require (
github.com/kubescape/go-git-url v0.0.30
github.com/lrstanley/bubblezone v0.0.0-20240914071701-b48c55a5e78e
github.com/mitchellh/go-homedir v1.1.0
github.com/mitchellh/go-wordwrap v1.0.0
github.com/mitchellh/mapstructure v1.5.0
github.com/open-policy-agent/opa v0.70.0
github.com/otiai10/copy v1.14.0
Expand All @@ -41,6 +42,7 @@ require (
github.com/spf13/viper v1.19.0
github.com/stretchr/testify v1.9.0
github.com/zclconf/go-cty v1.15.0
golang.org/x/term v0.26.0
gopkg.in/yaml.v3 v3.0.1
mvdan.cc/sh/v3 v3.10.0
)
Expand Down Expand Up @@ -174,7 +176,6 @@ require (
github.com/microcosm-cc/bluemonday v1.0.27 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/go-testing-interface v1.14.1 // indirect
github.com/mitchellh/go-wordwrap v1.0.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/moby/locker v1.0.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
Expand Down Expand Up @@ -241,8 +242,7 @@ require (
golang.org/x/net v0.30.0 // indirect
golang.org/x/oauth2 v0.22.0 // indirect
golang.org/x/sync v0.8.0 // indirect
golang.org/x/sys v0.26.0 // indirect
golang.org/x/term v0.25.0 // indirect
golang.org/x/sys v0.27.0 // indirect
golang.org/x/text v0.19.0 // indirect
golang.org/x/time v0.7.0 // indirect
golang.org/x/tools v0.22.0 // indirect
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1533,13 +1533,17 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.26.0 h1:KHjCJyddX0LoSTb3J+vWpupP9p0oznkqVk/IfjymZbo=
golang.org/x/sys v0.26.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s=
golang.org/x/sys v0.27.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.25.0 h1:WtHI/ltw4NvSUig5KARz9h521QvRC8RmF/cuYqifU24=
golang.org/x/term v0.25.0/go.mod h1:RPyXicDX+6vLxogjjRxjgD2TKtmAO6NZBsBRfrOLu7M=
golang.org/x/term v0.26.0 h1:WEQa6V3Gja/BhNxg540hBip/kkaYtRg3cxg4oXSw4AU=
golang.org/x/term v0.26.0/go.mod h1:Si5m1o57C5nBNQo5z1iq+XDijt21BDBDp2bK0QI8e3E=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
Expand Down
5 changes: 5 additions & 0 deletions internal/exec/helmfile.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ func ExecuteHelmfile(info schema.ConfigAndStacksInfo) error {
return errors.New("stack must be specified")
}

if !info.ComponentIsEnabled {
u.LogInfo(cliConfig, fmt.Sprintf("component '%s' is not enabled and skipped", info.ComponentFromArg))
return nil
}

err = checkHelmfileConfig(cliConfig)
if err != nil {
return err
Expand Down
12 changes: 9 additions & 3 deletions internal/exec/stack_utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,14 @@ func BuildTerraformWorkspace(cliConfig schema.CliConfiguration, configAndStacksI
return strings.Replace(workspace, "/", "-", -1), nil
}

// ProcessComponentMetadata processes component metadata and returns a base component (if any) and whether the component is real or abstract
// ProcessComponentMetadata processes component metadata and returns a base component (if any) and whether the component is real or abstract and whether the component is disabled or not
func ProcessComponentMetadata(
component string,
componentSection map[string]any,
) (map[string]any, string, bool) {
) (map[string]any, string, bool, bool) {
baseComponentName := ""
componentIsAbstract := false
componentIsEnabled := true
var componentMetadata map[string]any

// Find base component in the `component` attribute
Expand All @@ -75,6 +76,11 @@ func ProcessComponentMetadata(
componentIsAbstract = true
}
}
if enabledValue, exists := componentMetadata["enabled"]; exists {
if enabled, ok := enabledValue.(bool); ok && !enabled {
componentIsEnabled = false
}
}
// Find base component in the `metadata.component` attribute
// `metadata.component` overrides `component`
if componentMetadataComponent, componentMetadataComponentExists := componentMetadata[cfg.ComponentSectionName].(string); componentMetadataComponentExists {
Expand All @@ -87,7 +93,7 @@ func ProcessComponentMetadata(
baseComponentName = ""
}

return componentMetadata, baseComponentName, componentIsAbstract
return componentMetadata, baseComponentName, componentIsAbstract, componentIsEnabled
}

// BuildDependentStackNameFromDependsOnLegacy builds the dependent stack name from "settings.spacelift.depends_on" config
Expand Down
5 changes: 5 additions & 0 deletions internal/exec/terraform.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ func ExecuteTerraform(info schema.ConfigAndStacksInfo) error {
return errors.New("stack must be specified")
}

if !info.ComponentIsEnabled {
u.LogInfo(cliConfig, fmt.Sprintf("component '%s' is not enabled and skipped", info.ComponentFromArg))
return nil
}

err = checkTerraformConfig(cliConfig)
if err != nil {
return err
Expand Down
8 changes: 5 additions & 3 deletions internal/exec/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,8 @@ func ProcessComponentConfig(
}

// Process component metadata and find a base component (if any) and whether the component is real or abstract
componentMetadata, baseComponentName, componentIsAbstract := ProcessComponentMetadata(component, componentSection)
componentMetadata, baseComponentName, componentIsAbstract, componentIsEnabled := ProcessComponentMetadata(component, componentSection)
configAndStacksInfo.ComponentIsEnabled = componentIsEnabled

// Remove the ENV vars that are set to `null` in the `env` section.
// Setting an ENV var to `null` in stack config has the effect of unsetting it
Expand Down Expand Up @@ -391,7 +392,7 @@ func ProcessStacks(
}
}

if foundStackCount == 0 {
if foundStackCount == 0 && configAndStacksInfo.ComponentIsEnabled {
cliConfigYaml := ""

if cliConfig.Logs.Level == u.LogLevelTrace {
Expand Down Expand Up @@ -573,8 +574,9 @@ func ProcessStacks(
configAndStacksInfo.ComponentEnvList = u.ConvertEnvVars(configAndStacksInfo.ComponentEnvSection)

// Process component metadata
_, baseComponentName, _ := ProcessComponentMetadata(configAndStacksInfo.ComponentFromArg, configAndStacksInfo.ComponentSection)
_, baseComponentName, _, componentIsEnabled := ProcessComponentMetadata(configAndStacksInfo.ComponentFromArg, configAndStacksInfo.ComponentSection)
configAndStacksInfo.BaseComponentPath = baseComponentName
configAndStacksInfo.ComponentIsEnabled = componentIsEnabled

// Process component path and name
configAndStacksInfo.ComponentFolderPrefix = ""
Expand Down
122 changes: 122 additions & 0 deletions internal/tui/templates/help_printer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
package templates

import (
"fmt"
"io"
"strings"

"github.com/mitchellh/go-wordwrap"
"github.com/spf13/pflag"
)

const (
defaultOffset = 10
minWidth = 80
flagIndent = " "
nameIndentWidth = 4
minDescWidth = 20
)

type HelpFlagPrinter struct {
wrapLimit uint
out io.Writer
maxFlagLen int
}

func NewHelpFlagPrinter(out io.Writer, wrapLimit uint, flags *pflag.FlagSet) *HelpFlagPrinter {
if out == nil {
panic("output writer cannot be nil")
}
if flags == nil {
panic("flag set cannot be nil")
}
if wrapLimit < minWidth {
wrapLimit = minWidth
}

return &HelpFlagPrinter{
wrapLimit: wrapLimit,
out: out,
maxFlagLen: calculateMaxFlagLength(flags),
}
}

func calculateMaxFlagLength(flags *pflag.FlagSet) int {
maxLen := 0
flags.VisitAll(func(flag *pflag.Flag) {
length := len(flagIndent)

if len(flag.Shorthand) > 0 {
if flag.Value.Type() != "bool" {
length += len(fmt.Sprintf("-%s, --%s %s", flag.Shorthand, flag.Name, flag.Value.Type()))
} else {
length += len(fmt.Sprintf("-%s, --%s", flag.Shorthand, flag.Name))
}
} else {
if flag.Value.Type() != "bool" {
length += len(fmt.Sprintf(" --%s %s", flag.Name, flag.Value.Type()))
} else {
length += len(fmt.Sprintf(" --%s", flag.Name))
}
}

if length > maxLen {
maxLen = length
}
})
return maxLen
}

func (p *HelpFlagPrinter) PrintHelpFlag(flag *pflag.Flag) {
nameIndent := nameIndentWidth

flagName := ""
if flag.Shorthand != "" {
if flag.Value.Type() != "bool" {
flagName = fmt.Sprintf("%s-%s, --%s %s", strings.Repeat(" ", nameIndent),
flag.Shorthand, flag.Name, flag.Value.Type())
} else {
flagName = fmt.Sprintf("%s-%s, --%s", strings.Repeat(" ", nameIndent),
flag.Shorthand, flag.Name)
}
} else {
if flag.Value.Type() != "bool" {
flagName = fmt.Sprintf("%s --%s %s", strings.Repeat(" ", nameIndent),
flag.Name, flag.Value.Type())
} else {
flagName = fmt.Sprintf("%s --%s", strings.Repeat(" ", nameIndent),
flag.Name)
}
}

flagSection := fmt.Sprintf("%-*s", p.maxFlagLen, flagName)
descIndent := p.maxFlagLen + 4

description := flag.Usage
if flag.DefValue != "" {
description = fmt.Sprintf("%s (default %q)", description, flag.DefValue)
}

descWidth := int(p.wrapLimit) - descIndent
if descWidth < minDescWidth {
descWidth = minDescWidth
}

wrapped := wordwrap.WrapString(description, uint(descWidth))
lines := strings.Split(wrapped, "\n")

if _, err := fmt.Fprintf(p.out, "%-*s%s\n", descIndent, flagSection, lines[0]); err != nil {
return
}

// Print remaining lines with proper indentation
for _, line := range lines[1:] {
if _, err := fmt.Fprintf(p.out, "%s%s\n", strings.Repeat(" ", descIndent), line); err != nil {
return
}
}

if _, err := fmt.Fprintln(p.out); err != nil {
return
}
}
73 changes: 73 additions & 0 deletions internal/tui/templates/templater.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package templates

import (
"fmt"
"strings"

"github.com/spf13/cobra"
"github.com/spf13/pflag"
)

// Templater handles the generation and management of command usage templates.
type Templater struct {
UsageTemplate string
}

// SetCustomUsageFunc configures a custom usage template for the provided cobra command.
// It returns an error if the command is nil.
func SetCustomUsageFunc(cmd *cobra.Command) error {
if cmd == nil {
return fmt.Errorf("command cannot be nil")
}
t := &Templater{
UsageTemplate: MainUsageTemplate(),
}

cmd.SetUsageTemplate(t.UsageTemplate)
return nil
}

// MainUsageTemplate returns the usage template for the root command and wrap cobra flag usages to the terminal width
func MainUsageTemplate() string {
return `Usage:{{if .Runnable}}
{{.UseLine}}{{end}}{{if .HasAvailableSubCommands}}
{{.CommandPath}} [command]{{end}}{{if gt (len .Aliases) 0}}
Aliases:
{{.NameAndAliases}}{{end}}{{if .HasExample}}
Examples:
{{.Example}}{{end}}{{if .HasAvailableSubCommands}}
Available Commands:{{range .Commands}}{{if (or .IsAvailableCommand (eq .Name "help"))}}
{{rpad .Name .NamePadding }} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableLocalFlags}}
Flags:
{{wrappedFlagUsages .LocalFlags | trimTrailingWhitespaces}}{{end}}{{if .HasAvailableInheritedFlags}}
Global Flags:
{{wrappedFlagUsages .InheritedFlags | trimTrailingWhitespaces}}{{end}}{{if .HasHelpSubCommands}}
Additional help topics:{{range .Commands}}{{if .IsAdditionalHelpTopicCommand}}
{{rpad .CommandPath .CommandPathPadding}} {{.Short}}{{end}}{{end}}{{end}}{{if .HasAvailableSubCommands}}
Use "{{.CommandPath}} [command] --help" for more information about a command.{{end}}
`
}

// Default terminal width if actual width cannot be determined
const maxWidth = 80

// WrappedFlagUsages formats the flag usage string to fit within the terminal width
func WrappedFlagUsages(f *pflag.FlagSet) string {
var builder strings.Builder
printer := NewHelpFlagPrinter(&builder, maxWidth, f)

printer.maxFlagLen = calculateMaxFlagLength(f)

f.VisitAll(func(flag *pflag.Flag) {
printer.PrintHelpFlag(flag)
})

return builder.String()
}
Loading

0 comments on commit 25028e5

Please sign in to comment.