DockerCLI/cli-plugins/manager/hooks.go

128 lines
3.2 KiB
Go

package manager
import (
"encoding/json"
"strings"
"github.com/docker/cli/cli-plugins/hooks"
"github.com/docker/cli/cli/command"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
// HookPluginData is the type representing the information
// that plugins declaring support for hooks get passed when
// being invoked following a CLI command execution.
type HookPluginData struct {
RootCmd string
Flags map[string]string
}
// RunPluginHooks calls the hook subcommand for all present
// CLI plugins that declare support for hooks in their metadata
// and parses/prints their responses.
func RunPluginHooks(dockerCli command.Cli, rootCmd, subCommand *cobra.Command, plugin string, args []string) error {
subCmdName := subCommand.Name()
if plugin != "" {
subCmdName = plugin
}
var flags map[string]string
if plugin == "" {
flags = getCommandFlags(subCommand)
} else {
flags = getNaiveFlags(args)
}
nextSteps := invokeAndCollectHooks(dockerCli, rootCmd, subCommand, subCmdName, flags)
hooks.PrintNextSteps(dockerCli.Err(), nextSteps)
return nil
}
func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, hookCmdName string, flags map[string]string) []string {
pluginsCfg := dockerCli.ConfigFile().Plugins
if pluginsCfg == nil {
return nil
}
nextSteps := make([]string, 0, len(pluginsCfg))
for pluginName, cfg := range pluginsCfg {
if !registersHook(cfg, hookCmdName) {
continue
}
p, err := GetPlugin(pluginName, dockerCli, rootCmd)
if err != nil {
continue
}
hookReturn, err := p.RunHook(hookCmdName, flags)
if err != nil {
// skip misbehaving plugins, but don't halt execution
continue
}
var hookMessageData hooks.HookMessage
err = json.Unmarshal(hookReturn, &hookMessageData)
if err != nil {
continue
}
// currently the only hook type
if hookMessageData.Type != hooks.NextSteps {
continue
}
processedHook, err := hooks.ParseTemplate(hookMessageData.Template, subCmd)
if err != nil {
continue
}
nextSteps = append(nextSteps, processedHook)
}
return nextSteps
}
func registersHook(pluginCfg map[string]string, subCmdName string) bool {
hookCmdStr, ok := pluginCfg["hooks"]
if !ok {
return false
}
commands := strings.Split(hookCmdStr, ",")
for _, hookCmd := range commands {
if hookCmd == subCmdName {
return true
}
}
return false
}
func getCommandFlags(cmd *cobra.Command) map[string]string {
flags := make(map[string]string)
cmd.Flags().Visit(func(f *pflag.Flag) {
var fValue string
if f.Value.Type() == "bool" {
fValue = f.Value.String()
}
flags[f.Name] = fValue
})
return flags
}
// getNaiveFlags string-matches argv and parses them into a map.
// This is used when calling hooks after a plugin command, since
// in this case we can't rely on the cobra command tree to parse
// flags in this case. In this case, no values are ever passed,
// since we don't have enough information to process them.
func getNaiveFlags(args []string) map[string]string {
flags := make(map[string]string)
for _, arg := range args {
if strings.HasPrefix(arg, "--") {
flags[arg[2:]] = ""
continue
}
if strings.HasPrefix(arg, "-") {
flags[arg[1:]] = ""
}
}
return flags
}