github.com/khulnasoft/cli@v0.0.0-20240402070845-01bcad7beefa/cli-plugins/manager/hooks.go (about) 1 package manager 2 3 import ( 4 "encoding/json" 5 "strings" 6 7 "github.com/khulnasoft/cli/cli-plugins/hooks" 8 "github.com/khulnasoft/cli/cli/command" 9 "github.com/spf13/cobra" 10 "github.com/spf13/pflag" 11 ) 12 13 // HookPluginData is the type representing the information 14 // that plugins declaring support for hooks get passed when 15 // being invoked following a CLI command execution. 16 type HookPluginData struct { 17 RootCmd string 18 Flags map[string]string 19 } 20 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) 36 37 hooks.PrintNextSteps(dockerCli.Err(), nextSteps) 38 return nil 39 } 40 41 func invokeAndCollectHooks(dockerCli command.Cli, rootCmd, subCmd *cobra.Command, hookCmdName string, flags map[string]string) []string { 42 pluginsCfg := dockerCli.ConfigFile().Plugins 43 if pluginsCfg == nil { 44 return nil 45 } 46 47 nextSteps := make([]string, 0, len(pluginsCfg)) 48 for pluginName, cfg := range pluginsCfg { 49 if !registersHook(cfg, hookCmdName) { 50 continue 51 } 52 53 p, err := GetPlugin(pluginName, dockerCli, rootCmd) 54 if err != nil { 55 continue 56 } 57 58 hookReturn, err := p.RunHook(hookCmdName, flags) 59 if err != nil { 60 // skip misbehaving plugins, but don't halt execution 61 continue 62 } 63 64 var hookMessageData hooks.HookMessage 65 err = json.Unmarshal(hookReturn, &hookMessageData) 66 if err != nil { 67 continue 68 } 69 70 // currently the only hook type 71 if hookMessageData.Type != hooks.NextSteps { 72 continue 73 } 74 75 processedHook, err := hooks.ParseTemplate(hookMessageData.Template, subCmd) 76 if err != nil { 77 continue 78 } 79 nextSteps = append(nextSteps, processedHook) 80 } 81 return nextSteps 82 } 83 84 func registersHook(pluginCfg map[string]string, subCmdName string) bool { 85 hookCmdStr, ok := pluginCfg["hooks"] 86 if !ok { 87 return false 88 } 89 commands := strings.Split(hookCmdStr, ",") 90 for _, hookCmd := range commands { 91 if hookCmd == subCmdName { 92 return true 93 } 94 } 95 return false 96 } 97 98 func getCommandFlags(cmd *cobra.Command) map[string]string { 99 flags := make(map[string]string) 100 cmd.Flags().Visit(func(f *pflag.Flag) { 101 var fValue string 102 if f.Value.Type() == "bool" { 103 fValue = f.Value.String() 104 } 105 flags[f.Name] = fValue 106 }) 107 return flags 108 } 109 110 // getNaiveFlags string-matches argv and parses them into a map. 111 // This is used when calling hooks after a plugin command, since 112 // in this case we can't rely on the cobra command tree to parse 113 // flags in this case. In this case, no values are ever passed, 114 // since we don't have enough information to process them. 115 func getNaiveFlags(args []string) map[string]string { 116 flags := make(map[string]string) 117 for _, arg := range args { 118 if strings.HasPrefix(arg, "--") { 119 flags[arg[2:]] = "" 120 continue 121 } 122 if strings.HasPrefix(arg, "-") { 123 flags[arg[1:]] = "" 124 } 125 } 126 return flags 127 }