Skip to content

Commit d8fc76e

Browse files
authored
Merge pull request #5030 from laurazard/hooks-plugin-name
hooks: include plugin name in hook data
2 parents 118d6ba + 9d8320d commit d8fc76e

File tree

3 files changed

+137
-31
lines changed

3 files changed

+137
-31
lines changed

cli-plugins/manager/hooks.go

Lines changed: 61 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -14,39 +14,50 @@ import (
1414
// that plugins declaring support for hooks get passed when
1515
// being invoked following a CLI command execution.
1616
type HookPluginData struct {
17+
// RootCmd is a string representing the matching hook configuration
18+
// which is currently being invoked. If a hook for `docker context` is
19+
// configured and the user executes `docker context ls`, the plugin will
20+
// be invoked with `context`.
1721
RootCmd string
1822
Flags map[string]string
1923
}
2024

21-
// RunPluginHooks calls the hook subcommand for all present
22-
// CLI plugins that declare support for hooks in their metadata
23-
// and parses/prints their responses.
24-
func RunPluginHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, plugin string, args []string) error {
25-
subCmdName := subCommand.Name()
26-
if plugin != "" {
27-
subCmdName = plugin
28-
}
29-
var flags map[string]string
30-
if plugin == "" {
31-
flags = getCommandFlags(subCommand)
32-
} else {
33-
flags = getNaiveFlags(args)
34-
}
35-
nextSteps := invokeAndCollectHooks(dockerCli, rootCmd, subCommand, subCmdName, flags)
25+
// RunCLICommandHooks is the entrypoint into the hooks execution flow after
26+
// a main CLI command was executed. It calls the hook subcommand for all
27+
// present CLI plugins that declare support for hooks in their metadata and
28+
// parses/prints their responses.
29+
func RunCLICommandHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command) {
30+
commandName := strings.TrimPrefix(subCommand.CommandPath(), rootCmd.Name()+" ")
31+
flags := getCommandFlags(subCommand)
32+
33+
runHooks(dockerCli, rootCmd, subCommand, commandName, flags)
34+
}
35+
36+
// RunPluginHooks is the entrypoint for the hooks execution flow
37+
// after a plugin command was just executed by the CLI.
38+
func RunPluginHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, args []string) {
39+
commandName := strings.Join(args, " ")
40+
flags := getNaiveFlags(args)
41+
42+
runHooks(dockerCli, rootCmd, subCommand, commandName, flags)
43+
}
44+
45+
func runHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, invokedCommand string, flags map[string]string) {
46+
nextSteps := invokeAndCollectHooks(dockerCli, rootCmd, subCommand, invokedCommand, flags)
3647

3748
hooks.PrintNextSteps(dockerCli.Err(), nextSteps)
38-
return nil
3949
}
4050

41-
func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, hookCmdName string, flags map[string]string) []string {
51+
func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, subCmdStr string, flags map[string]string) []string {
4252
pluginsCfg := dockerCli.ConfigFile().Plugins
4353
if pluginsCfg == nil {
4454
return nil
4555
}
4656

4757
nextSteps := make([]string, 0, len(pluginsCfg))
4858
for pluginName, cfg := range pluginsCfg {
49-
if !registersHook(cfg, hookCmdName) {
59+
match, ok := pluginMatch(cfg, subCmdStr)
60+
if !ok {
5061
continue
5162
}
5263

@@ -55,7 +66,7 @@ func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command
5566
continue
5667
}
5768

58-
hookReturn, err := p.RunHook(hookCmdName, flags)
69+
hookReturn, err := p.RunHook(match, flags)
5970
if err != nil {
6071
// skip misbehaving plugins, but don't halt execution
6172
continue
@@ -81,18 +92,41 @@ func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command
8192
return nextSteps
8293
}
8394

84-
func registersHook(pluginCfg map[string]string, subCmdName string) bool {
85-
hookCmdStr, ok := pluginCfg["hooks"]
86-
if !ok {
87-
return false
95+
// pluginMatch takes a plugin configuration and a string representing the
96+
// command being executed (such as 'image ls' – the root 'docker' is omitted)
97+
// and, if the configuration includes a hook for the invoked command, returns
98+
// the configured hook string.
99+
func pluginMatch(pluginCfg map[string]string, subCmd string) (string, bool) {
100+
configuredPluginHooks, ok := pluginCfg["hooks"]
101+
if !ok || configuredPluginHooks == "" {
102+
return "", false
88103
}
89-
commands := strings.Split(hookCmdStr, ",")
104+
105+
commands := strings.Split(configuredPluginHooks, ",")
90106
for _, hookCmd := range commands {
91-
if hookCmd == subCmdName {
92-
return true
107+
if hookMatch(hookCmd, subCmd) {
108+
return hookCmd, true
93109
}
94110
}
95-
return false
111+
112+
return "", false
113+
}
114+
115+
func hookMatch(hookCmd, subCmd string) bool {
116+
hookCmdTokens := strings.Split(hookCmd, " ")
117+
subCmdTokens := strings.Split(subCmd, " ")
118+
119+
if len(hookCmdTokens) > len(subCmdTokens) {
120+
return false
121+
}
122+
123+
for i, v := range hookCmdTokens {
124+
if v != subCmdTokens[i] {
125+
return false
126+
}
127+
}
128+
129+
return true
96130
}
97131

98132
func getCommandFlags(cmd *cobra.Command) map[string]string {

cli-plugins/manager/hooks_test.go

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,3 +36,75 @@ func TestGetNaiveFlags(t *testing.T) {
3636
assert.DeepEqual(t, getNaiveFlags(tc.args), tc.expectedFlags)
3737
}
3838
}
39+
40+
func TestPluginMatch(t *testing.T) {
41+
testCases := []struct {
42+
commandString string
43+
pluginConfig map[string]string
44+
expectedMatch string
45+
expectedOk bool
46+
}{
47+
{
48+
commandString: "image ls",
49+
pluginConfig: map[string]string{
50+
"hooks": "image",
51+
},
52+
expectedMatch: "image",
53+
expectedOk: true,
54+
},
55+
{
56+
commandString: "context ls",
57+
pluginConfig: map[string]string{
58+
"hooks": "build",
59+
},
60+
expectedMatch: "",
61+
expectedOk: false,
62+
},
63+
{
64+
commandString: "context ls",
65+
pluginConfig: map[string]string{
66+
"hooks": "context ls",
67+
},
68+
expectedMatch: "context ls",
69+
expectedOk: true,
70+
},
71+
{
72+
commandString: "image ls",
73+
pluginConfig: map[string]string{
74+
"hooks": "image ls,image",
75+
},
76+
expectedMatch: "image ls",
77+
expectedOk: true,
78+
},
79+
{
80+
commandString: "image ls",
81+
pluginConfig: map[string]string{
82+
"hooks": "",
83+
},
84+
expectedMatch: "",
85+
expectedOk: false,
86+
},
87+
{
88+
commandString: "image inspect",
89+
pluginConfig: map[string]string{
90+
"hooks": "image i",
91+
},
92+
expectedMatch: "",
93+
expectedOk: false,
94+
},
95+
{
96+
commandString: "image inspect",
97+
pluginConfig: map[string]string{
98+
"hooks": "image",
99+
},
100+
expectedMatch: "image",
101+
expectedOk: true,
102+
},
103+
}
104+
105+
for _, tc := range testCases {
106+
match, ok := pluginMatch(tc.pluginConfig, tc.commandString)
107+
assert.Equal(t, ok, tc.expectedOk)
108+
assert.Equal(t, match, tc.expectedMatch)
109+
}
110+
}

cmd/docker/docker.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -336,7 +336,7 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
336336
err := tryPluginRun(dockerCli, cmd, args[0], envs)
337337
if err == nil {
338338
if dockerCli.HooksEnabled() && dockerCli.Out().IsTerminal() && ccmd != nil {
339-
_ = pluginmanager.RunPluginHooks(dockerCli, cmd, ccmd, args[0], args)
339+
pluginmanager.RunPluginHooks(dockerCli, cmd, ccmd, args)
340340
}
341341
return nil
342342
}
@@ -354,10 +354,10 @@ func runDocker(ctx context.Context, dockerCli *command.DockerCli) error {
354354
cmd.SetArgs(args)
355355
err = cmd.Execute()
356356

357-
// If the command is being executed in an interactive terminal,
358-
// run the plugin hooks (but don't throw an error if something misbehaves)
357+
// If the command is being executed in an interactive terminal
358+
// and hook are enabled, run the plugin hooks.
359359
if dockerCli.HooksEnabled() && dockerCli.Out().IsTerminal() && subCommand != nil {
360-
_ = pluginmanager.RunPluginHooks(dockerCli, cmd, subCommand, "", args)
360+
pluginmanager.RunCLICommandHooks(dockerCli, cmd, subCommand)
361361
}
362362

363363
return err

0 commit comments

Comments
 (0)