Skip to content

Commit 420f92d

Browse files
mook-asjandubois
authored andcommitted
nerdctl-stub: Better support foreign flags
This fixes `nerdctl run ... -flag` to not warn on flags that are not to be parsed by nerdctl. Signed-off-by: Mark Yen <mark.yen@suse.com>
1 parent 0f91401 commit 420f92d

File tree

4 files changed

+82
-11
lines changed

4 files changed

+82
-11
lines changed

src/go/nerdctl-stub/generate/main_linux.go

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ type helpData struct {
3232
// (`--version`) or the short option (`-v`), and the value is whether the
3333
// option takes an argument.
3434
Options map[string]bool
35-
// Whether this command has positional arguments.
36-
hasPositionalArguments bool
35+
// If set, this command can have subcommands; this alters argument parsing.
36+
canHaveSubcommands bool
37+
// If set, this command can pass flags to foreign commands, as in `nerdctl run`.
38+
// This alters argument parsing by letting us ignore unknown flags.
39+
HasForeignFlags bool
3740
// mergedOptions includes local options plus inherited options.
3841
mergedOptions map[string]struct{}
3942
}
@@ -103,14 +106,19 @@ func buildSubcommand(args []string, parentData helpData, writer io.Writer) error
103106
subcommands := parseHelp(help, parentData)
104107

105108
fields := logrus.Fields{"args": args}
106-
if subcommands.hasPositionalArguments {
107-
fields["positional"] = true
109+
if subcommands.HasForeignFlags {
110+
fields["type"] = "arguments"
111+
} else if !subcommands.canHaveSubcommands {
112+
fields["type"] = "positional"
108113
}
109114
logrus.WithFields(fields).Trace("building subcommand")
110115

111-
if subcommands.hasPositionalArguments && len(subcommands.Commands) > 0 {
116+
if !subcommands.canHaveSubcommands && len(subcommands.Commands) > 0 {
112117
return fmt.Errorf("Invalid command %v: has positional arguments, but also subcommands %+v", args, subcommands.Commands)
113118
}
119+
if subcommands.canHaveSubcommands && subcommands.HasForeignFlags {
120+
return fmt.Errorf("Invalid command %v: has subcommands and foreign flags", args)
121+
}
114122

115123
err = emitCommand(args, subcommands, writer)
116124
if err != nil {
@@ -178,8 +186,10 @@ func parseHelp(help string, parentData helpData) helpData {
178186
newArgs = strings.TrimSpace(newArgs)
179187
// Unlike everything else, `nerdctl compose` has a usage string of
180188
// `nerdctl compose [flags] COMMAND` so we need to ignore that.
181-
if newArgs != "" && newArgs != "COMMAND" {
182-
result.hasPositionalArguments = true
189+
if newArgs == "" || newArgs == "COMMAND" {
190+
result.canHaveSubcommands = true
191+
} else if strings.Contains(newArgs, "COMMAND") && strings.Contains(newArgs, "...") {
192+
result.HasForeignFlags = true
183193
}
184194
} else {
185195
state = STATE_OTHER
@@ -245,6 +255,9 @@ const commandTemplate = `
245255
{{- printf "%q" $k -}}: {{ if $v -}} ignoredArgHandler {{- else -}} nil {{- end -}},
246256
{{ end }}
247257
},
258+
{{- if .Data.HasForeignFlags }}
259+
hasForeignFlags: true,
260+
{{- end }}
248261
},
249262
`
250263

src/go/nerdctl-stub/nerdctl_commands_generated.go

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/go/nerdctl-stub/parse_args.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,10 @@ type commandDefinition struct {
4747
// options for this (sub) command. If the handler is null, the option does
4848
// not take arguments.
4949
options map[string]argHandler
50+
// if set, this command can include foreign flags that should not be parsed.
51+
// This should be set for things like `nerdctl run` where flags can be passed
52+
// to the command to be run.
53+
hasForeignFlags bool
5054
// handler for any positional arguments and subcommands. This should not
5155
// include the name of the subcommand itself. If this is not given, all
5256
// subcommands are searched for, and positional arguments are ignored.
@@ -138,8 +142,14 @@ func (c commandDefinition) parse(args []string) (*parsedArgs, error) {
138142
// - At each command level, short options (-x) at that level can be parsed.
139143
// - At each command level, long options from the current or any previous level can be parsed.
140144
// - If a command contains positional arguments, it may not contain any subcommands.
141-
// (We check this in ./generate to make sure this stays true.)
145+
// (We check this in `./generate` to make sure this stays true.)
142146
// - Positional arguments can be intermixed with (both long and short) options.
147+
// - If a command can have foreign flags (e.g. `nerdctl run`), we stop parsing
148+
// options on first positional argument. This means we parse the flag in
149+
// `nerdctl run --env foo=bar image sh -c ...` but not the `--env` flag in
150+
// `nerdctl run image --env foo=bar sh -c ...`.
151+
// - Having foreign flags is mutually exclusive with having subcommands; this
152+
// is also checked in `./generate`.
143153
// - `--` stops parsing of options.
144154
var result parsedArgs
145155
var positionalArgs []string
@@ -196,9 +206,17 @@ func (c commandDefinition) parse(args []string) (*parsedArgs, error) {
196206
}
197207
break
198208
} else {
199-
// This command doesn't have subcommands; everything is positional arguments.
200-
// We still have to parse other arguments for flags, though.
201-
positionalArgs = append(positionalArgs, arg)
209+
if c.hasForeignFlags {
210+
// If we have foreign flags, assume the rest of the arguments starting
211+
// from the first positional argument is foreign.
212+
positionalArgs = append(positionalArgs, args[argIndex:]...)
213+
break
214+
} else {
215+
// This command doesn't have subcommands, nor foreign arguments.
216+
// Everything is positional arguments; we still have to parse other
217+
// arguments for flags, though.
218+
positionalArgs = append(positionalArgs, arg)
219+
}
202220
}
203221
}
204222
// At this point, `result` is filled with options, and `positionalArgs`

src/go/nerdctl-stub/parse_args_test.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -258,4 +258,36 @@ func TestParse(t *testing.T) {
258258
assert.Equal(t, []string{"subcommand", "--foo", "FOO", "--bar", "BAR"}, result.args)
259259
}
260260
})
261+
t.Run("subcommand with foreign flags", func(t *testing.T) {
262+
t.Parallel()
263+
var seenArgs []string
264+
localCommands := make(map[string]commandDefinition)
265+
localCommands[""] = commandDefinition{
266+
commands: &localCommands,
267+
subcommands: map[string]struct{}{
268+
"subcommand": {},
269+
},
270+
options: map[string]argHandler{
271+
"--foo": ignoredArgHandler,
272+
},
273+
}
274+
localCommands["subcommand"] = commandDefinition{
275+
commandPath: "subcommand",
276+
commands: &localCommands,
277+
options: map[string]argHandler{
278+
"--bar": ignoredArgHandler,
279+
},
280+
hasForeignFlags: true,
281+
handler: func(cd *commandDefinition, s []string, argHandlers argHandlersType) (*parsedArgs, error) {
282+
seenArgs = s
283+
return &parsedArgs{}, nil
284+
},
285+
}
286+
result, err := localCommands[""].parse([]string{"subcommand", "--foo", "FOO", "qq", "--bar", "BAR", "zz"})
287+
if assert.NoError(t, err) {
288+
assert.Equal(t, []string{"qq", "--bar", "BAR", "zz"}, seenArgs)
289+
// Because we have a custom handler, they don't show up in result.args
290+
assert.Equal(t, []string{"subcommand", "--foo", "FOO"}, result.args)
291+
}
292+
})
261293
}

0 commit comments

Comments
 (0)