Skip to content

Commit e109baa

Browse files
authored
ROX-28263: Custom roxctl command usage formatting (#14376)
1 parent 795aeaf commit e109baa

24 files changed

+1052
-88
lines changed

CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Please avoid adding duplicate information across this changelog and JIRA/doc inp
1010
## [NEXT RELEASE]
1111

1212
### Added Features
13+
1314
- ROX-13493: Support for scale subresource in the admission controller to enable policy detection and enforcement on admission review requests on the scale subresource.
1415

1516
### Removed Features
@@ -18,9 +19,9 @@ Please avoid adding duplicate information across this changelog and JIRA/doc inp
1819

1920
### Technical Changes
2021

21-
## [4.7.0]
22-
22+
- ROX-28263: New `roxctl` help formatting.
2323

24+
## [4.7.0]
2425

2526
### Added Features
2627

roxctl/central/db/backup/backup.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,7 @@ import (
77
)
88

99
const (
10-
warningDeprecatedDbBackup = `WARNING: The backup command has been deprecated. Please use "roxctl central backup"
11-
to create central backup with database, keys and certificates.`
10+
warningDeprecatedDbBackup = `Please use "roxctl central backup" to create central backup with database, keys and certificates.`
1211
)
1312

1413
// Command defines the db backup command. This command is deprecated and can be removed on or after 3.0.57.

roxctl/central/db/restore/v2.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ func V2Command(cliEnvironment environment.Environment) *cobra.Command {
6969

7070
c.Flags().StringVar(&centralDbRestoreCmd.file, "file", "", "File to restore the DB from (deprecated; use positional argument)")
7171
c.Flags().BoolVar(&centralDbRestoreCmd.interrupt, "interrupt", false, "Interrupt ongoing restore process (if any) to allow resuming")
72-
utils.Must(c.Flags().MarkDeprecated("file", "--file is deprecated; use the positional argument instead"))
72+
utils.Must(c.Flags().MarkDeprecated("file", "use the positional argument instead"))
7373
flags.AddForce(c)
7474

7575
return c

roxctl/main.go

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/stackrox/rox/pkg/sync"
1010
"github.com/stackrox/rox/roxctl/common"
1111
"github.com/stackrox/rox/roxctl/maincommand"
12+
"github.com/stackrox/rox/roxctl/utils"
1213

1314
// Make sure devbuild setting is registered.
1415
_ "github.com/stackrox/rox/pkg/devbuild"
@@ -17,18 +18,7 @@ import (
1718
func main() {
1819
c := maincommand.Command()
1920

20-
// This is a workaround. Cobra/pflag takes care of presenting flag usage information
21-
// to the user including the respective flag default values.
22-
//
23-
// But, as an exception, showing the default value for a flag is skipped in pflag if
24-
// that value is the zero value for a certain standard type.
25-
//
26-
// In our case this caused the unintended behaviour of not showing the default values
27-
// for our boolean flags which default to `false`.
28-
//
29-
// Until we have a better solution (e.g. way to control this behaviour in upstream pflag)
30-
// we simply add the usage information "(default false)" to our affected boolean flags.
31-
AddMissingDefaultsToFlagUsage(c)
21+
c.SetHelpFunc(utils.FormatHelp)
3222

3323
once := sync.Once{}
3424
// Peak only the deepest command path. The hooks are added to all commands.

roxctl/main_test.go

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ func mockRunE(_ *cobra.Command, _ []string) error { return nil }
2626

2727
func TestCommandReconstruction(t *testing.T) {
2828
root := maincommand.Command()
29-
AddMissingDefaultsToFlagUsage(root)
3029

3130
type testCase struct {
3231
args []string

roxctl/maincommand/command.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ func versionCommand(cliEnvironment environment.Environment) *cobra.Command {
5353
// Command constructs and returns the roxctl command tree
5454
func Command() *cobra.Command {
5555
c := &cobra.Command{
56+
Long: "roxctl is a command-line interface (CLI) for running commands" +
57+
" on Red Hat Advanced Cluster Security for Kubernetes (RHACS)",
5658
SilenceUsage: true,
5759
Use: os.Args[0],
5860
}

roxctl/maincommand/command_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func checkUsageFirstCharacter(t *testing.T, command *cobra.Command) {
3636
if !isCapitalized(command.Short) {
3737
t.Errorf("%q, short usage: %q", getCommandPath(command), command.Short)
3838
}
39-
if !isCapitalized(command.Long) {
39+
if !isCapitalized(command.Long) && !strings.HasPrefix(command.Long, "roxctl ") {
4040
t.Errorf("%q, long usage: %q", getCommandPath(command), command.Long)
4141
}
4242
for _, subcommand := range command.Commands() {

roxctl/patch_flag_usage.go

Lines changed: 0 additions & 30 deletions
This file was deleted.

roxctl/utils/formatting_writer.go

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
package utils
2+
3+
import (
4+
"bufio"
5+
"io"
6+
"strings"
7+
)
8+
9+
const defaultTabWidth = 8
10+
11+
// formattingWriter implements StringWriter interface.
12+
// It writes strings to the underlying writer indenting and wrapping the text.
13+
type formattingWriter struct {
14+
raw io.StringWriter
15+
width int
16+
indent indents
17+
tabWidth int
18+
19+
currentLine int
20+
written int
21+
indentReset bool
22+
}
23+
24+
var _ io.StringWriter = (*formattingWriter)(nil)
25+
26+
func makeFormattingWriter(w io.StringWriter, width int, tabWidth int, indent ...int) *formattingWriter {
27+
return &formattingWriter{raw: w, width: width, indent: indent, tabWidth: tabWidth}
28+
}
29+
30+
// write is an internal method that writes the string to the underlying writer.
31+
func (w *formattingWriter) write(s string) error {
32+
n, err := w.raw.WriteString(s)
33+
w.currentLine += n
34+
w.written += n
35+
return err //nolint:wrapcheck
36+
}
37+
38+
// writePadding is an internal method, that takes the next indent value and the
39+
// writes the according number of spaces. If the indent value is negative, it is
40+
// calculated as tabulation offset, i.e. the spaces are added to reach the
41+
// required offset. Returns true if a new line has been written.
42+
func (w *formattingWriter) writePadding() (bool, error) {
43+
w.indentReset = false
44+
padding := w.indent.popNotLast()
45+
if padding < 0 {
46+
// Indent to tabulation.
47+
padding = -padding
48+
if padding > w.currentLine {
49+
padding -= w.currentLine
50+
} else {
51+
return true, nil
52+
}
53+
} else if w.currentLine+padding > w.width {
54+
return true, nil
55+
}
56+
return false, w.write(strings.Repeat(" ", padding))
57+
}
58+
59+
// ln is an internal method that writes a new line to the underlying writer.
60+
func (w *formattingWriter) ln() error {
61+
_, err := w.raw.WriteString("\n")
62+
w.currentLine = 0
63+
return err //nolint:wrapcheck
64+
}
65+
66+
// SetIndent updates the indentation for the following writings.
67+
func (w *formattingWriter) SetIndent(indent ...int) {
68+
w.indentReset = true
69+
w.indent = indent
70+
}
71+
72+
// WriteString writes the string to the underlying writer, with the configured
73+
// indentation and wrapping.
74+
// Implements the StringWriter interface.
75+
func (w *formattingWriter) WriteString(s string) (int, error) {
76+
w.written = 0
77+
tokenScanner := bufio.NewScanner(strings.NewReader(s))
78+
tokenScanner.Split(wordsAndDelimeters)
79+
var err error
80+
for err == nil && tokenScanner.Scan() {
81+
token := tokenScanner.Text()
82+
length := len(token)
83+
switch token {
84+
case "\t":
85+
length = defaultTabWidth
86+
case "\n":
87+
if w.currentLine == 0 {
88+
w.indent.popNotLast()
89+
w.indentReset = false
90+
}
91+
err = w.ln()
92+
continue
93+
}
94+
ln := false
95+
if w.currentLine == 0 || w.indentReset {
96+
if ln, err = w.writePadding(); err != nil {
97+
break
98+
}
99+
}
100+
// Write a new line and a padding in case of line overflow:
101+
if ln || w.currentLine+length > w.width {
102+
if err = w.ln(); err != nil {
103+
break
104+
}
105+
if token == " " {
106+
// Do not write space token that caused line break.
107+
continue
108+
}
109+
if _, err = w.writePadding(); err != nil {
110+
break
111+
}
112+
}
113+
err = w.write(token)
114+
}
115+
return w.written, err
116+
}

0 commit comments

Comments
 (0)