internal/config/plugin/client.go (129 lines of code) (raw):

package plugin import ( "context" "fmt" "io" "os/exec" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" "github.com/Azure/k6ctl/internal/config" "github.com/Azure/k6ctl/internal/target" ) func remoteNamespacedConfigProvider( namespace string, name string, impl Interface, ) config.Provider { return config.Provide[ResolveRequest]( fmt.Sprintf("%s/%s", namespace, name), func( ctx context.Context, target target.Target, userInput map[string]any, ) (ResolveRequest, error) { kubeconfig, _ := target.GetKubeconfig() rv := ResolveRequest{ Name: name, TargetKubeconfig: kubeconfig, UserInput: userInput, } deadline, hasDeadline := ctx.Deadline() if hasDeadline { rv.ContextDeadlineInUnixNano = deadline.UnixNano() } return rv, nil }, func(ctx context.Context, target target.Target, params ResolveRequest) (string, error) { return impl.Resolve(params) }, ) } // ClientBinarySettings defines a namespaced plugin binary. // The binary is expected to be used as a remote plugin. type ClientBinarySettings struct { // Namespace - the namespace of the plugin Namespace string // Path - the absolute path to the plugin binary Path string // Args - optional arguments to pass to the plugin binary Args []string } func (s ClientBinarySettings) validate() error { if s.Namespace == "" { return fmt.Errorf("namespace is required") } if s.Path == "" { return fmt.Errorf("path is required") } return nil } func registerFromClientBinary( ctx context.Context, reg config.ProviderRegistry, settings ClientBinarySettings, ) (func(), error) { var stop func() errOut := func(err error) (func(), error) { if stop != nil { stop() } return func() {}, err } if err := settings.validate(); err != nil { return errOut(err) } // TODO: move to ui package logger := hclog.New(&hclog.LoggerOptions{ Name: "k6ctl", Level: hclog.Error, Output: io.Discard, }) pluginClient := plugin.NewClient( &plugin.ClientConfig{ // TODO: move to ui package // Stderr: os.Stderr, HandshakeConfig: handshakeConfig, Plugins: map[string]plugin.Plugin{ pluginName: &Plugin{}, }, // TODO: validate command for security concern Cmd: exec.CommandContext(ctx, settings.Path), // #nosec G204 - expected usage AllowedProtocols: []plugin.Protocol{plugin.ProtocolNetRPC}, Logger: logger, }, ) cc, err := pluginClient.Client() if err != nil { return errOut(err) } stop = func() { pluginClient.Kill() } raw, err := cc.Dispense(pluginName) if err != nil { return errOut(err) } p := raw.(Interface) supportedProviderNames, err := p.GetNames() if err != nil { return errOut(err) } for _, name := range supportedProviderNames { reg.Register(remoteNamespacedConfigProvider(settings.Namespace, name, p)) } return stop, nil } // RegisterFromClientBinaries registers the given client binaries as remote plugins. func RegisterFromClientBinaries( ctx context.Context, reg config.ProviderRegistry, settingsList []ClientBinarySettings, ) (func(), error) { var stopFuncs []func() for _, settings := range settingsList { stop, err := registerFromClientBinary(ctx, reg, settings) if err != nil { for _, stop := range stopFuncs { stop() } return func() {}, err } stopFuncs = append(stopFuncs, stop) } stop := func() { for _, stop := range stopFuncs { stop() } } return stop, nil }