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  }