From 998dc6a4b0efc97e5a24d4cc747289b87c9cfb84 Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 24 Jun 2021 19:24:03 -0400 Subject: [PATCH 1/2] First pass at reattaching plugins working This allows for debugging plugins! --- main.go | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 118 insertions(+) diff --git a/main.go b/main.go index 1f260ac..03e46d7 100644 --- a/main.go +++ b/main.go @@ -1,7 +1,14 @@ package sdk import ( + "context" + "encoding/json" + "fmt" "os" + "os/signal" + "runtime" + "strings" + "time" "github.com/fatih/color" "github.com/hashicorp/go-argmapper" @@ -80,9 +87,113 @@ func Main(opts ...Option) { ), GRPCServer: plugin.DefaultGRPCServer, Logger: log, + Test: c.TestConfig, }) } +// DebugServe starts a plugin server in debug mode; this should only be used +// when the provider will manage its own lifecycle. It is not recommended for +// normal usage; Serve is the correct function for that. +func DebugServe(ctx context.Context, opts ...Option) (ReattachConfig, <-chan struct{}, error) { + reattachCh := make(chan *plugin.ReattachConfig) + closeCh := make(chan struct{}) + + opts = append(opts, func(c *config) { + c.TestConfig = &plugin.ServeTestConfig{ + Context: ctx, + ReattachConfigCh: reattachCh, + CloseCh: closeCh, + }}) + + go Main(opts...) + + var config *plugin.ReattachConfig + select { + case config = <-reattachCh: + case <-time.After(10 * time.Second): // TODO: evaluate this time + return ReattachConfig{}, closeCh, fmt.Errorf("timeout waiting on reattach config") + } + + if config == nil { + return ReattachConfig{}, closeCh, fmt.Errorf("nil reattach config received") + } + + return ReattachConfig{ + Protocol: string(config.Protocol), + Pid: config.Pid, + Test: config.Test, + Addr: ReattachConfigAddr{ + Network: config.Addr.Network(), + String: config.Addr.String(), + }, + }, closeCh, nil +} + +// ReattachConfig holds the information Waypoint needs to be able to attach +// itself to a provider process, so it can drive the process. +type ReattachConfig struct { + Protocol string + Pid int + Test bool + Addr ReattachConfigAddr +} + +// ReattachConfigAddr is a JSON-encoding friendly version of net.Addr. +type ReattachConfigAddr struct { + Network string + String string +} + +// Debug starts a debug server and controls its lifecycle, printing the +// information needed for Waypoint to connect to the provider to stdout. +// os.Interrupt will be captured and used to stop the server. +func Debug(ctx context.Context, providerAddr string, opts ...Option) error { + ctx, cancel := context.WithCancel(ctx) + // Ctrl-C will stop the server + sigCh := make(chan os.Signal, 1) + signal.Notify(sigCh, os.Interrupt) + defer func() { + signal.Stop(sigCh) + cancel() + }() + config, closeCh, err := DebugServe(ctx, opts...) + if err != nil { + return fmt.Errorf("Error launching debug server: %w", err) + } + go func() { + select { + case <-sigCh: + cancel() + case <-ctx.Done(): + } + }() + reattachBytes, err := json.Marshal(map[string]ReattachConfig{ + providerAddr: config, + }) + if err != nil { + return fmt.Errorf("Error building reattach string: %w", err) + } + + reattachStr := string(reattachBytes) + + fmt.Printf("Provider started, to attach Waypoint set the WP_REATTACH_PROVIDERS env var:\n\n") + switch runtime.GOOS { + case "windows": + fmt.Printf("\tCommand Prompt:\tset \"WP_REATTACH_PROVIDERS=%s\"\n", reattachStr) + fmt.Printf("\tPowerShell:\t$env:WP_REATTACH_PROVIDERS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `''`)) + case "linux", "darwin": + fmt.Printf("\tWP_REATTACH_PROVIDERS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `'"'"'`)) + default: + fmt.Println(reattachStr) + } + fmt.Println("") + + // wait for the server to be done + <-closeCh + return nil +} + + // config is the configuration for Main. This can only be modified using // Option implementations. type config struct { @@ -91,6 +202,13 @@ type config struct { // Mappers is the list of mapper functions. Mappers []interface{} + + // TestConfig should only be set when the provider is being tested; it + // will opt out of go-plugin's lifecycle management and other features, + // and will use the supplied configuration options to control the + // plugin's lifecycle and communicate connection information. See the + // go-plugin GoDoc for more information. + TestConfig *plugin.ServeTestConfig } // Option modifies config. Zero or more can be passed to Main. From 84dc1f1dcead1356db1884dc83c1018fb239662d Mon Sep 17 00:00:00 2001 From: Izaak Lauer <8404559+izaaklauer@users.noreply.github.com> Date: Thu, 24 Jun 2021 20:36:30 -0400 Subject: [PATCH 2/2] Polished debug reattach path --- go.mod | 2 +- go.sum | 5 +- main.go | 160 +++++++++++++++++++++++++------------------------ tools/tools.go | 1 - 4 files changed, 84 insertions(+), 84 deletions(-) diff --git a/go.mod b/go.mod index 05f9098..1605625 100644 --- a/go.mod +++ b/go.mod @@ -13,7 +13,7 @@ require ( github.com/hashicorp/go-argmapper v0.2.0 github.com/hashicorp/go-hclog v0.14.1 github.com/hashicorp/go-multierror v1.1.0 - github.com/hashicorp/go-plugin v1.3.0 + github.com/hashicorp/go-plugin v1.4.2 github.com/hashicorp/hcl/v2 v2.6.0 github.com/iancoleman/strcase v0.1.2 github.com/kr/pretty v0.2.1 // indirect diff --git a/go.sum b/go.sum index 32dd25e..3d24beb 100644 --- a/go.sum +++ b/go.sum @@ -71,15 +71,14 @@ github.com/hashicorp/errwrap v1.0.0 h1:hLrqtEDnRye3+sgx6z4qVLNuviH3MR5aQ0ykNJa/U github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/go-argmapper v0.2.0 h1:hODvyLdq7akV0n6SbOP47VXZjAX1QrUvAveCA6qXSfQ= github.com/hashicorp/go-argmapper v0.2.0/go.mod h1:WA3PocIo+40wf4ko3dRdL3DEgxIQB4qaqp+jVccLV1I= -github.com/hashicorp/go-hclog v0.0.0-20180709165350-ff2cf002a8dd/go.mod h1:9bjs9uLqI8l75knNv3lV1kA55veR+WUPSiKIWcQHudI= github.com/hashicorp/go-hclog v0.9.2/go.mod h1:5CU+agLiy3J7N7QjHK5d05KxGsuXiQLrjA0H7acj2lQ= github.com/hashicorp/go-hclog v0.14.0/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-hclog v0.14.1 h1:nQcJDQwIAGnmoUWp8ubocEX40cCml/17YkF6csQLReU= github.com/hashicorp/go-hclog v0.14.1/go.mod h1:whpDNt7SSdeAju8AWKIWsul05p54N/39EeqMAyrmvFQ= github.com/hashicorp/go-multierror v1.1.0 h1:B9UzwGQJehnUY1yNrnwREHc3fGbC2xefo8g4TbElacI= github.com/hashicorp/go-multierror v1.1.0/go.mod h1:spPvp8C1qA32ftKqdAHm4hHTbPw+vmowP0z+KUhOZdA= -github.com/hashicorp/go-plugin v1.3.0 h1:4d/wJojzvHV1I4i/rrjVaeuyxWrLzDE1mDCyDy8fXS8= -github.com/hashicorp/go-plugin v1.3.0/go.mod h1:F9eH4LrE/ZsRdbwhfjs9k9HoDUwAHnYtXdgmf1AVNs0= +github.com/hashicorp/go-plugin v1.4.2 h1:yFvG3ufXXpqiMiZx9HLcaK3XbIqQ1WJFR/F1a2CuVw0= +github.com/hashicorp/go-plugin v1.4.2/go.mod h1:5fGEH17QVwTTcR0zV7yhDPLLmFX9YSZ38b18Udy6vYQ= github.com/hashicorp/hcl/v2 v2.6.0 h1:3krZOfGY6SziUXa6H9PJU6TyohHn7I+ARYnhbeNBz+o= github.com/hashicorp/hcl/v2 v2.6.0/go.mod h1:bQTN5mpo+jewjJgh8jr0JUguIi7qPHUF6yIfAEN3jqY= github.com/hashicorp/yamux v0.0.0-20180604194846-3520598351bb h1:b5rjCoWHc7eqmAS4/qyk21ZsHyb6Mxv/jykxvNTkU4M= diff --git a/main.go b/main.go index 03e46d7..52e0c33 100644 --- a/main.go +++ b/main.go @@ -4,17 +4,17 @@ import ( "context" "encoding/json" "fmt" + "github.com/fatih/color" + "github.com/mattn/go-colorable" "os" "os/signal" "runtime" "strings" "time" - "github.com/fatih/color" "github.com/hashicorp/go-argmapper" "github.com/hashicorp/go-hclog" "github.com/hashicorp/go-plugin" - "github.com/mattn/go-colorable" "github.com/hashicorp/waypoint-plugin-sdk/internal-shared/protomappers" sdkplugin "github.com/hashicorp/waypoint-plugin-sdk/internal/plugin" @@ -87,12 +87,60 @@ func Main(opts ...Option) { ), GRPCServer: plugin.DefaultGRPCServer, Logger: log, - Test: c.TestConfig, + Test: c.TestConfig, }) } +// config is the configuration for Main. This can only be modified using +// Option implementations. +type config struct { + // Components is the list of components to serve from the plugin. + Components []interface{} + + // Mappers is the list of mapper functions. + Mappers []interface{} + + // TestConfig should only be set when the plugin is being tested; it + // will opt out of go-plugin's lifecycle management and other features, + // and will use the supplied configuration options to control the + // plugin's lifecycle and communicate connection information. See the + // go-plugin GoDoc for more information. + TestConfig *plugin.ServeTestConfig +} + +// Option modifies config. Zero or more can be passed to Main. +type Option func(*config) + +// WithComponents specifies a list of components to serve from the plugin +// binary. This will append to the list of components to serve. You can +// currently only serve at most one of each type of plugin. +func WithComponents(cs ...interface{}) Option { + return func(c *config) { c.Components = append(c.Components, cs...) } +} + +// WithMappers specifies a list of mappers to apply to the plugin. +// +// Mappers are functions that take zero or more arguments and return +// one result (optionally with an error). These can be used to convert argument +// types as needed for your plugin functions. For example, you can convert a +// proto type to a richer Go struct. +// +// Mappers must take zero or more arguments and return exactly one or two +// values where the second return type must be an error. Example: +// +// func() *Value +// func() (*Value, error) +// -- the above with any arguments +// +// This will append the mappers to the list of available mappers. A set of +// default mappers is always included to convert from SDK proto types to +// richer Go structs. +func WithMappers(ms ...interface{}) Option { + return func(c *config) { c.Mappers = append(c.Mappers, ms...) } +} + // DebugServe starts a plugin server in debug mode; this should only be used -// when the provider will manage its own lifecycle. It is not recommended for +// when the plugin will manage its own lifecycle. It is not recommended for // normal usage; Serve is the correct function for that. func DebugServe(ctx context.Context, opts ...Option) (ReattachConfig, <-chan struct{}, error) { reattachCh := make(chan *plugin.ReattachConfig) @@ -100,17 +148,18 @@ func DebugServe(ctx context.Context, opts ...Option) (ReattachConfig, <-chan str opts = append(opts, func(c *config) { c.TestConfig = &plugin.ServeTestConfig{ - Context: ctx, - ReattachConfigCh: reattachCh, - CloseCh: closeCh, - }}) + Context: ctx, + ReattachConfigCh: reattachCh, + CloseCh: closeCh, + } + }) go Main(opts...) var config *plugin.ReattachConfig select { case config = <-reattachCh: - case <-time.After(10 * time.Second): // TODO: evaluate this time + case <-time.After(2 * time.Second): return ReattachConfig{}, closeCh, fmt.Errorf("timeout waiting on reattach config") } @@ -119,9 +168,10 @@ func DebugServe(ctx context.Context, opts ...Option) (ReattachConfig, <-chan str } return ReattachConfig{ - Protocol: string(config.Protocol), - Pid: config.Pid, - Test: config.Test, + Protocol: string(config.Protocol), + ProtocolVersion: config.ProtocolVersion, + Pid: config.Pid, + Test: config.Test, Addr: ReattachConfigAddr{ Network: config.Addr.Network(), String: config.Addr.String(), @@ -129,25 +179,10 @@ func DebugServe(ctx context.Context, opts ...Option) (ReattachConfig, <-chan str }, closeCh, nil } -// ReattachConfig holds the information Waypoint needs to be able to attach -// itself to a provider process, so it can drive the process. -type ReattachConfig struct { - Protocol string - Pid int - Test bool - Addr ReattachConfigAddr -} - -// ReattachConfigAddr is a JSON-encoding friendly version of net.Addr. -type ReattachConfigAddr struct { - Network string - String string -} - // Debug starts a debug server and controls its lifecycle, printing the -// information needed for Waypoint to connect to the provider to stdout. +// information needed for Waypoint to connect to the plugin to stdout. // os.Interrupt will be captured and used to stop the server. -func Debug(ctx context.Context, providerAddr string, opts ...Option) error { +func Debug(ctx context.Context, pluginAddr string, opts ...Option) error { ctx, cancel := context.WithCancel(ctx) // Ctrl-C will stop the server sigCh := make(chan os.Signal, 1) @@ -168,7 +203,7 @@ func Debug(ctx context.Context, providerAddr string, opts ...Option) error { } }() reattachBytes, err := json.Marshal(map[string]ReattachConfig{ - providerAddr: config, + pluginAddr: config, }) if err != nil { return fmt.Errorf("Error building reattach string: %w", err) @@ -176,13 +211,13 @@ func Debug(ctx context.Context, providerAddr string, opts ...Option) error { reattachStr := string(reattachBytes) - fmt.Printf("Provider started, to attach Waypoint set the WP_REATTACH_PROVIDERS env var:\n\n") + fmt.Printf("Plugin started, to attach Waypoint set the WP_REATTACH_PLUGINS env var:\n\n") switch runtime.GOOS { case "windows": - fmt.Printf("\tCommand Prompt:\tset \"WP_REATTACH_PROVIDERS=%s\"\n", reattachStr) - fmt.Printf("\tPowerShell:\t$env:WP_REATTACH_PROVIDERS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `''`)) + fmt.Printf("\tCommand Prompt:\tset \"WP_REATTACH_PLUGINS=%s\"\n", reattachStr) + fmt.Printf("\tPowerShell:\t$env:WP_REATTACH_PLUGINS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `''`)) case "linux", "darwin": - fmt.Printf("\tWP_REATTACH_PROVIDERS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `'"'"'`)) + fmt.Printf("\tWP_REATTACH_PLUGINS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `'"'"'`)) default: fmt.Println(reattachStr) } @@ -193,51 +228,18 @@ func Debug(ctx context.Context, providerAddr string, opts ...Option) error { return nil } - -// config is the configuration for Main. This can only be modified using -// Option implementations. -type config struct { - // Components is the list of components to serve from the plugin. - Components []interface{} - - // Mappers is the list of mapper functions. - Mappers []interface{} - - // TestConfig should only be set when the provider is being tested; it - // will opt out of go-plugin's lifecycle management and other features, - // and will use the supplied configuration options to control the - // plugin's lifecycle and communicate connection information. See the - // go-plugin GoDoc for more information. - TestConfig *plugin.ServeTestConfig -} - -// Option modifies config. Zero or more can be passed to Main. -type Option func(*config) - -// WithComponents specifies a list of components to serve from the plugin -// binary. This will append to the list of components to serve. You can -// currently only serve at most one of each type of plugin. -func WithComponents(cs ...interface{}) Option { - return func(c *config) { c.Components = append(c.Components, cs...) } +// ReattachConfig holds the information Waypoint needs to be able to attach +// itself to a plugin process, so it can drive the process. +type ReattachConfig struct { + Protocol string + ProtocolVersion int + Pid int + Test bool + Addr ReattachConfigAddr } -// WithMappers specifies a list of mappers to apply to the plugin. -// -// Mappers are functions that take zero or more arguments and return -// one result (optionally with an error). These can be used to convert argument -// types as needed for your plugin functions. For example, you can convert a -// proto type to a richer Go struct. -// -// Mappers must take zero or more arguments and return exactly one or two -// values where the second return type must be an error. Example: -// -// func() *Value -// func() (*Value, error) -// -- the above with any arguments -// -// This will append the mappers to the list of available mappers. A set of -// default mappers is always included to convert from SDK proto types to -// richer Go structs. -func WithMappers(ms ...interface{}) Option { - return func(c *config) { c.Mappers = append(c.Mappers, ms...) } +// ReattachConfigAddr is a JSON-encoding friendly version of net.Addr. +type ReattachConfigAddr struct { + Network string + String string } diff --git a/tools/tools.go b/tools/tools.go index 1fd7570..bf208de 100644 --- a/tools/tools.go +++ b/tools/tools.go @@ -12,4 +12,3 @@ import _ "github.com/golang/protobuf/proto" //go:generate go install github.com/golang/protobuf/protoc-gen-go import _ "github.com/golang/protobuf/protoc-gen-go" -