Skip to content

Commit 6cb3697

Browse files
authored
Added support for -force-color to the CLI. (#10975)
1 parent c21493a commit 6cb3697

File tree

7 files changed

+116
-59
lines changed

7 files changed

+116
-59
lines changed

.changelog/10975.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:improvement
2+
cli: Added support for `-force-color` to the CLI to force colored output.
3+
```

command/commands.go

+3
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ import (
1313
const (
1414
// EnvNomadCLINoColor is an env var that toggles colored UI output.
1515
EnvNomadCLINoColor = `NOMAD_CLI_NO_COLOR`
16+
17+
// EnvNomadCLIForceColor is an env var that forces colored UI output.
18+
EnvNomadCLIForceColor = `NOMAD_CLI_FORCE_COLOR`
1619
)
1720

1821
// DeprecatedCommand is a command that wraps an existing command and prints a

command/meta.go

+48-5
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"strings"
77

88
"github.com/hashicorp/nomad/api"
9+
colorable "github.com/mattn/go-colorable"
910
"github.com/mitchellh/cli"
1011
"github.com/mitchellh/colorstring"
1112
"github.com/posener/complete"
@@ -39,6 +40,9 @@ type Meta struct {
3940
// Whether to not-colorize output
4041
noColor bool
4142

43+
// Whether to force colorized output
44+
forceColor bool
45+
4246
// The region to send API requests
4347
region string
4448

@@ -70,6 +74,7 @@ func (m *Meta) FlagSet(n string, fs FlagSetFlags) *flag.FlagSet {
7074
f.StringVar(&m.region, "region", "", "")
7175
f.StringVar(&m.namespace, "namespace", "", "")
7276
f.BoolVar(&m.noColor, "no-color", false, "")
77+
f.BoolVar(&m.forceColor, "force-color", false, "")
7378
f.StringVar(&m.caCert, "ca-cert", "", "")
7479
f.StringVar(&m.caPath, "ca-path", "", "")
7580
f.StringVar(&m.clientCert, "client-cert", "", "")
@@ -97,6 +102,7 @@ func (m *Meta) AutocompleteFlags(fs FlagSetFlags) complete.Flags {
97102
"-region": complete.PredictAnything,
98103
"-namespace": NamespacePredictor(m.Client, nil),
99104
"-no-color": complete.PredictNothing,
105+
"-force-color": complete.PredictNothing,
100106
"-ca-cert": complete.PredictFiles("*"),
101107
"-ca-path": complete.PredictDirs("*"),
102108
"-client-cert": complete.PredictFiles("*"),
@@ -155,15 +161,47 @@ func (m *Meta) allNamespaces() bool {
155161

156162
func (m *Meta) Colorize() *colorstring.Colorize {
157163
_, coloredUi := m.Ui.(*cli.ColoredUi)
158-
noColor := m.noColor || !coloredUi || !terminal.IsTerminal(int(os.Stdout.Fd()))
159164

160165
return &colorstring.Colorize{
161166
Colors: colorstring.DefaultColors,
162-
Disable: noColor,
167+
Disable: !coloredUi,
163168
Reset: true,
164169
}
165170
}
166171

172+
func (m *Meta) SetupUi(args []string) {
173+
noColor := os.Getenv(EnvNomadCLINoColor) != ""
174+
forceColor := os.Getenv(EnvNomadCLIForceColor) != ""
175+
176+
for _, arg := range args {
177+
// Check if color is set
178+
if arg == "-no-color" || arg == "--no-color" {
179+
noColor = true
180+
} else if arg == "-force-color" || arg == "--force-color" {
181+
forceColor = true
182+
}
183+
}
184+
185+
m.Ui = &cli.BasicUi{
186+
Reader: os.Stdin,
187+
Writer: colorable.NewColorableStdout(),
188+
ErrorWriter: colorable.NewColorableStderr(),
189+
}
190+
191+
// Only use colored UI if not disabled and stdout is a tty or colors are
192+
// forced.
193+
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
194+
useColor := !noColor && (isTerminal || forceColor)
195+
if useColor {
196+
m.Ui = &cli.ColoredUi{
197+
ErrorColor: cli.UiColorRed,
198+
WarnColor: cli.UiColorYellow,
199+
InfoColor: cli.UiColorGreen,
200+
Ui: m.Ui,
201+
}
202+
}
203+
}
204+
167205
type usageOptsFlags uint8
168206

169207
const (
@@ -196,12 +234,17 @@ func generalOptionsUsage(usageOpts usageOptsFlags) string {
196234
`
197235

198236
// note: that although very few commands use color explicitly, all of them
199-
// return red-colored text on error so we don't want to make this
200-
// configurable
237+
// return red-colored text on error so we want the color flags to always be
238+
// present in the help messages.
201239
remainingText := `
202240
-no-color
203241
Disables colored command output. Alternatively, NOMAD_CLI_NO_COLOR may be
204-
set.
242+
set. This option takes precedence over -force-color.
243+
244+
-force-color
245+
Forces colored command output. This can be used in cases where the usual
246+
terminal detection fails. Alternatively, NOMAD_CLI_FORCE_COLOR may be set.
247+
This option has no effect if -no-color is also used.
205248
206249
-ca-cert=<path>
207250
Path to a PEM encoded CA cert file to use to verify the

command/meta_test.go

+49-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"os"
66
"reflect"
77
"sort"
8+
"strings"
89
"testing"
910

1011
"github.com/kr/pty"
@@ -27,6 +28,7 @@ func TestMeta_FlagSet(t *testing.T) {
2728
[]string{
2829
"address",
2930
"no-color",
31+
"force-color",
3032
"region",
3133
"namespace",
3234
"ca-cert",
@@ -81,11 +83,45 @@ func TestMeta_Colorize(t *testing.T) {
8183
{
8284
Name: "disable colors via CLI flag",
8385
SetupFn: func(t *testing.T, m *Meta) {
84-
m.Ui = &cli.ColoredUi{}
85-
86-
fs := m.FlagSet("colorize_test", FlagSetDefault)
87-
err := fs.Parse([]string{"-no-color"})
88-
assert.NoError(t, err)
86+
m.SetupUi([]string{"-no-color"})
87+
},
88+
ExpectColor: false,
89+
},
90+
{
91+
Name: "disable colors via env var",
92+
SetupFn: func(t *testing.T, m *Meta) {
93+
os.Setenv(EnvNomadCLINoColor, "1")
94+
m.SetupUi([]string{})
95+
},
96+
ExpectColor: false,
97+
},
98+
{
99+
Name: "force colors via CLI flag",
100+
SetupFn: func(t *testing.T, m *Meta) {
101+
m.SetupUi([]string{"-force-color"})
102+
},
103+
ExpectColor: true,
104+
},
105+
{
106+
Name: "force colors via env var",
107+
SetupFn: func(t *testing.T, m *Meta) {
108+
os.Setenv(EnvNomadCLIForceColor, "1")
109+
m.SetupUi([]string{})
110+
},
111+
ExpectColor: true,
112+
},
113+
{
114+
Name: "no color take predecence over force color via CLI flag",
115+
SetupFn: func(t *testing.T, m *Meta) {
116+
m.SetupUi([]string{"-no-color", "-force-color"})
117+
},
118+
ExpectColor: false,
119+
},
120+
{
121+
Name: "no color take predecence over force color via env var",
122+
SetupFn: func(t *testing.T, m *Meta) {
123+
os.Setenv(EnvNomadCLINoColor, "1")
124+
m.SetupUi([]string{"-force-color"})
89125
},
90126
ExpectColor: false,
91127
},
@@ -104,6 +140,14 @@ func TestMeta_Colorize(t *testing.T) {
104140
defer func() { os.Stdout = oldStdout }()
105141
os.Stdout = tty
106142

143+
// Make sure Nomad environment variables are clean.
144+
for _, envVar := range os.Environ() {
145+
if strings.HasPrefix(envVar, "NOMAD") {
146+
k := strings.SplitN(envVar, "=", 2)[0]
147+
os.Unsetenv(k)
148+
}
149+
}
150+
107151
// Run test case.
108152
m := &Meta{}
109153
if tc.SetupFn != nil {

main.go

+1-47
Original file line numberDiff line numberDiff line change
@@ -19,10 +19,8 @@ import (
1919

2020
"github.com/hashicorp/nomad/command"
2121
"github.com/hashicorp/nomad/version"
22-
colorable "github.com/mattn/go-colorable"
2322
"github.com/mitchellh/cli"
2423
"github.com/sean-/seed"
25-
"golang.org/x/crypto/ssh/terminal"
2624
)
2725

2826
var (
@@ -88,24 +86,9 @@ func Run(args []string) int {
8886
}
8987

9088
func RunCustom(args []string) int {
91-
// Parse flags into env vars for global use
92-
args = setupEnv(args)
93-
9489
// Create the meta object
9590
metaPtr := new(command.Meta)
96-
97-
// Don't use color if disabled
98-
color := true
99-
if os.Getenv(command.EnvNomadCLINoColor) != "" {
100-
color = false
101-
}
102-
103-
isTerminal := terminal.IsTerminal(int(os.Stdout.Fd()))
104-
metaPtr.Ui = &cli.BasicUi{
105-
Reader: os.Stdin,
106-
Writer: colorable.NewColorableStdout(),
107-
ErrorWriter: colorable.NewColorableStderr(),
108-
}
91+
metaPtr.SetupUi(args)
10992

11093
// The Nomad agent never outputs color
11194
agentUi := &cli.BasicUi{
@@ -114,16 +97,6 @@ func RunCustom(args []string) int {
11497
ErrorWriter: os.Stderr,
11598
}
11699

117-
// Only use colored UI if stdout is a tty, and not disabled
118-
if isTerminal && color {
119-
metaPtr.Ui = &cli.ColoredUi{
120-
ErrorColor: cli.UiColorRed,
121-
WarnColor: cli.UiColorYellow,
122-
InfoColor: cli.UiColorGreen,
123-
Ui: metaPtr.Ui,
124-
}
125-
}
126-
127100
commands := command.Commands(metaPtr, agentUi)
128101
cli := &cli.CLI{
129102
Name: "nomad",
@@ -203,22 +176,3 @@ func printCommand(w io.Writer, name string, cmdFn cli.CommandFactory) {
203176
}
204177
fmt.Fprintf(w, " %s\t%s\n", name, cmd.Synopsis())
205178
}
206-
207-
// setupEnv parses args and may replace them and sets some env vars to known
208-
// values based on format options
209-
func setupEnv(args []string) []string {
210-
noColor := false
211-
for _, arg := range args {
212-
// Check if color is set
213-
if arg == "-no-color" || arg == "--no-color" {
214-
noColor = true
215-
}
216-
}
217-
218-
// Put back into the env for later
219-
if noColor {
220-
os.Setenv(command.EnvNomadCLINoColor, "true")
221-
}
222-
223-
return args
224-
}

website/content/partials/general_options.mdx

+6-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@
1111
user. Defaults to the "default" namespace.
1212

1313
- `-no-color`: Disables colored command output. Alternatively,
14-
`NOMAD_CLI_NO_COLOR` may be set.
14+
`NOMAD_CLI_NO_COLOR` may be set. This option takes precedence over
15+
`-force-color`.
16+
17+
-`-force-color`: Forces colored command output. This can be used in cases where
18+
the usual terminal detection fails. Alternatively, `NOMAD_CLI_FORCE_COLOR`
19+
may be set. This option has no effect if `-no-color` is also used.
1520

1621
- `-ca-cert=<path>`: Path to a PEM encoded CA cert file to use to verify the
1722
Nomad server SSL certificate. Overrides the `NOMAD_CACERT` environment

website/content/partials/general_options_no_namespace.mdx

+6-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@
66
Agent's local region.
77

88
- `-no-color`: Disables colored command output. Alternatively,
9-
`NOMAD_CLI_NO_COLOR` may be set.
9+
`NOMAD_CLI_NO_COLOR` may be set. This option takes precedence over
10+
`-force-color`.
11+
12+
-`-force-color`: Forces colored command output. This can be used in cases where
13+
the usual terminal detection fails. Alternatively, `NOMAD_CLI_FORCE_COLOR`
14+
may be set. This option has no effect if `-no-color` is also used.
1015

1116
- `-ca-cert=<path>`: Path to a PEM encoded CA cert file to use to verify the
1217
Nomad server SSL certificate. Overrides the `NOMAD_CACERT` environment

0 commit comments

Comments
 (0)