Skip to content

Commit 36ee3aa

Browse files
authored
plugin: Serve via tf5server/tf6server rather than go-plugin directly (#857)
Providers will automatically receive upstream enhancements to provider servers, including protocol logging. Lower level dependencies in this SDK, such as gRPC and go-plugin versions, can instead be managed upstream, which will ensure consistency across the SDKs.
1 parent 4d65183 commit 36ee3aa

File tree

5 files changed

+162
-96
lines changed

5 files changed

+162
-96
lines changed

.changelog/857.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
```release-note:note
2+
plugin: The `Debug` function has been deprecated in preference of setting the `Debug` field in the `ServeOpts` passed into the `Serve` function.
3+
```
4+
5+
```release-note:enhancement
6+
plugin: Increased maximum gRPC send and receive message size limit to 256MB
7+
```
8+
9+
```release-note:enhancement
10+
plugin: Added support for writing protocol data to disk by setting `TF_LOG_SDK_PROTO_DATA_DIR` environment variable
11+
```

.github/dependabot.yml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,8 @@ updates:
55
schedule:
66
interval: "daily"
77
open-pull-requests-limit: 20
8+
ignore:
9+
# go-hclog should only be updated via terraform-plugin-log
10+
- dependency-name: "github.com/hashicorp/go-hclog"
11+
# go-plugin should only be updated via terraform-plugin-go
12+
- dependency-name: "github.com/hashicorp/go-plugin"

go.mod

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,4 @@ require (
3434
golang.org/x/tools v0.0.0-20200713011307-fd294ab11aed
3535
google.golang.org/appengine v1.6.6 // indirect
3636
google.golang.org/genproto v0.0.0-20200711021454-869866162049 // indirect
37-
google.golang.org/grpc v1.44.0
3837
)

plugin/debug.go

Lines changed: 14 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,7 @@ package plugin
22

33
import (
44
"context"
5-
"encoding/json"
65
"errors"
7-
"fmt"
8-
"os"
9-
"os/signal"
10-
"runtime"
11-
"strings"
126
"time"
137

148
"github.com/hashicorp/go-plugin"
@@ -37,6 +31,10 @@ func DebugServe(ctx context.Context, opts *ServeOpts) (ReattachConfig, <-chan st
3731
reattachCh := make(chan *plugin.ReattachConfig)
3832
closeCh := make(chan struct{})
3933

34+
if opts == nil {
35+
return ReattachConfig{}, closeCh, errors.New("ServeOpts must be passed in with at least GRPCProviderFunc, GRPCProviderV6Func, or ProviderFunc")
36+
}
37+
4038
opts.TestConfig = &plugin.ServeTestConfig{
4139
Context: ctx,
4240
ReattachConfigCh: reattachCh,
@@ -71,48 +69,20 @@ func DebugServe(ctx context.Context, opts *ServeOpts) (ReattachConfig, <-chan st
7169
// Debug starts a debug server and controls its lifecycle, printing the
7270
// information needed for Terraform to connect to the provider to stdout.
7371
// os.Interrupt will be captured and used to stop the server.
72+
//
73+
// Deprecated: Use the Serve function with the ServeOpts Debug field instead.
7474
func Debug(ctx context.Context, providerAddr string, opts *ServeOpts) error {
75-
ctx, cancel := context.WithCancel(ctx)
76-
// Ctrl-C will stop the server
77-
sigCh := make(chan os.Signal, 1)
78-
signal.Notify(sigCh, os.Interrupt)
79-
defer func() {
80-
signal.Stop(sigCh)
81-
cancel()
82-
}()
83-
config, closeCh, err := DebugServe(ctx, opts)
84-
if err != nil {
85-
return fmt.Errorf("Error launching debug server: %w", err)
86-
}
87-
go func() {
88-
select {
89-
case <-sigCh:
90-
cancel()
91-
case <-ctx.Done():
92-
}
93-
}()
94-
reattachBytes, err := json.Marshal(map[string]ReattachConfig{
95-
providerAddr: config,
96-
})
97-
if err != nil {
98-
return fmt.Errorf("Error building reattach string: %w", err)
75+
if opts == nil {
76+
return errors.New("ServeOpts must be passed in with at least GRPCProviderFunc, GRPCProviderV6Func, or ProviderFunc")
9977
}
10078

101-
reattachStr := string(reattachBytes)
102-
103-
fmt.Printf("Provider started, to attach Terraform set the TF_REATTACH_PROVIDERS env var:\n\n")
104-
switch runtime.GOOS {
105-
case "windows":
106-
fmt.Printf("\tCommand Prompt:\tset \"TF_REATTACH_PROVIDERS=%s\"\n", reattachStr)
107-
fmt.Printf("\tPowerShell:\t$env:TF_REATTACH_PROVIDERS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `''`))
108-
case "linux", "darwin":
109-
fmt.Printf("\tTF_REATTACH_PROVIDERS='%s'\n", strings.ReplaceAll(reattachStr, `'`, `'"'"'`))
110-
default:
111-
fmt.Println(reattachStr)
79+
opts.Debug = true
80+
81+
if opts.ProviderAddr == "" {
82+
opts.ProviderAddr = providerAddr
11283
}
113-
fmt.Println("")
11484

115-
// wait for the server to be done
116-
<-closeCh
85+
Serve(opts)
86+
11787
return nil
11888
}

plugin/serve.go

Lines changed: 132 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
package plugin
22

33
import (
4+
"errors"
45
"log"
56

67
hclog "github.com/hashicorp/go-hclog"
78
"github.com/hashicorp/go-plugin"
89
testing "github.com/mitchellh/go-testing-interface"
9-
"google.golang.org/grpc"
1010

1111
"github.com/hashicorp/terraform-plugin-go/tfprotov5"
1212
"github.com/hashicorp/terraform-plugin-go/tfprotov5/tf5server"
@@ -18,10 +18,16 @@ import (
1818
const (
1919
// The constants below are the names of the plugins that can be dispensed
2020
// from the plugin server.
21+
//
22+
// Deprecated: This is no longer used, but left for backwards compatibility
23+
// since it is exported. It will be removed in the next major version.
2124
ProviderPluginName = "provider"
2225
)
2326

2427
// Handshake is the HandshakeConfig used to configure clients and servers.
28+
//
29+
// Deprecated: This is no longer used, but left for backwards compatibility
30+
// since it is exported. It will be removed in the next major version.
2531
var Handshake = plugin.HandshakeConfig{
2632
// The magic cookie values should NEVER be changed.
2733
MagicCookieKey: "TF_PLUGIN_MAGIC_COOKIE",
@@ -45,11 +51,20 @@ type ServeOpts struct {
4551
// Logger is the logger that go-plugin will use.
4652
Logger hclog.Logger
4753

54+
// Debug starts a debug server and controls its lifecycle, printing the
55+
// information needed for Terraform to connect to the provider to stdout.
56+
// os.Interrupt will be captured and used to stop the server.
57+
//
58+
// This option cannot be combined with TestConfig.
59+
Debug bool
60+
4861
// TestConfig should only be set when the provider is being tested; it
4962
// will opt out of go-plugin's lifecycle management and other features,
5063
// and will use the supplied configuration options to control the
5164
// plugin's lifecycle and communicate connection information. See the
5265
// go-plugin GoDoc for more information.
66+
//
67+
// This option cannot be combined with Debug.
5368
TestConfig *plugin.ServeTestConfig
5469

5570
// Set NoLogOutputOverride to not override the log output with an hclog
@@ -69,6 +84,11 @@ type ServeOpts struct {
6984
// Serve serves a plugin. This function never returns and should be the final
7085
// function called in the main function of the plugin.
7186
func Serve(opts *ServeOpts) {
87+
if opts.Debug && opts.TestConfig != nil {
88+
log.Printf("[ERROR] Error starting provider: cannot set both Debug and TestConfig")
89+
return
90+
}
91+
7292
if !opts.NoLogOutputOverride {
7393
// In order to allow go-plugin to correctly pass log-levels through to
7494
// terraform, we need to use an hclog.Logger with JSON output. We can
@@ -84,65 +104,126 @@ func Serve(opts *ServeOpts) {
84104
log.SetOutput(logger.StandardWriter(&hclog.StandardLoggerOptions{InferLevels: true}))
85105
}
86106

87-
// since the plugins may not yet be aware of the new protocol, we
88-
// automatically wrap the plugins in the grpc shims.
89-
if opts.GRPCProviderFunc == nil && opts.ProviderFunc != nil {
107+
if opts.ProviderAddr == "" {
108+
opts.ProviderAddr = "provider"
109+
}
110+
111+
var err error
112+
113+
switch {
114+
case opts.ProviderFunc != nil && opts.GRPCProviderFunc == nil:
90115
opts.GRPCProviderFunc = func() tfprotov5.ProviderServer {
91116
return schema.NewGRPCProviderServer(opts.ProviderFunc())
92117
}
118+
err = tf5serverServe(opts)
119+
case opts.GRPCProviderFunc != nil:
120+
err = tf5serverServe(opts)
121+
case opts.GRPCProviderV6Func != nil:
122+
err = tf6serverServe(opts)
123+
default:
124+
err = errors.New("no provider server defined in ServeOpts")
93125
}
94126

95-
serveConfig := plugin.ServeConfig{
96-
HandshakeConfig: Handshake,
97-
GRPCServer: func(opts []grpc.ServerOption) *grpc.Server {
98-
return grpc.NewServer(opts...)
99-
},
100-
Logger: opts.Logger,
101-
Test: opts.TestConfig,
127+
if err != nil {
128+
log.Printf("[ERROR] Error starting provider: %s", err)
102129
}
130+
}
103131

104-
// assume we have either a v5 or a v6 provider
105-
if opts.GRPCProviderFunc != nil {
106-
provider := opts.GRPCProviderFunc()
107-
addr := opts.ProviderAddr
108-
if addr == "" {
109-
addr = "provider"
110-
}
111-
serveConfig.VersionedPlugins = map[int]plugin.PluginSet{
112-
5: {
113-
ProviderPluginName: &tf5server.GRPCProviderPlugin{
114-
GRPCProvider: func() tfprotov5.ProviderServer {
115-
return provider
116-
},
117-
Name: addr,
118-
},
119-
},
120-
}
121-
if opts.UseTFLogSink != nil {
122-
serveConfig.VersionedPlugins[5][ProviderPluginName].(*tf5server.GRPCProviderPlugin).Opts = append(serveConfig.VersionedPlugins[5][ProviderPluginName].(*tf5server.GRPCProviderPlugin).Opts, tf5server.WithLoggingSink(opts.UseTFLogSink))
123-
}
132+
func tf5serverServe(opts *ServeOpts) error {
133+
var tf5serveOpts []tf5server.ServeOpt
124134

125-
} else if opts.GRPCProviderV6Func != nil {
126-
provider := opts.GRPCProviderV6Func()
127-
addr := opts.ProviderAddr
128-
if addr == "" {
129-
addr = "provider"
130-
}
131-
serveConfig.VersionedPlugins = map[int]plugin.PluginSet{
132-
6: {
133-
ProviderPluginName: &tf6server.GRPCProviderPlugin{
134-
GRPCProvider: func() tfprotov6.ProviderServer {
135-
return provider
136-
},
137-
Name: addr,
138-
},
139-
},
140-
}
141-
if opts.UseTFLogSink != nil {
142-
serveConfig.VersionedPlugins[6][ProviderPluginName].(*tf6server.GRPCProviderPlugin).Opts = append(serveConfig.VersionedPlugins[6][ProviderPluginName].(*tf6server.GRPCProviderPlugin).Opts, tf6server.WithLoggingSink(opts.UseTFLogSink))
143-
}
135+
if opts.Debug {
136+
tf5serveOpts = append(tf5serveOpts, tf5server.WithManagedDebug())
137+
}
138+
139+
if opts.Logger != nil {
140+
tf5serveOpts = append(tf5serveOpts, tf5server.WithGoPluginLogger(opts.Logger))
141+
}
142+
143+
if opts.TestConfig != nil {
144+
// Convert send-only channels to bi-directional channels to appease
145+
// the compiler. WithDebug is errantly defined to require
146+
// bi-directional when send-only is actually needed, which may be
147+
// fixed in the future so the opts.TestConfig channels can be passed
148+
// through directly.
149+
closeCh := make(chan struct{})
150+
reattachConfigCh := make(chan *plugin.ReattachConfig)
151+
152+
go func() {
153+
// Always forward close channel receive, since its signaling that
154+
// the channel is closed.
155+
val := <-closeCh
156+
opts.TestConfig.CloseCh <- val
157+
}()
158+
159+
go func() {
160+
val, ok := <-reattachConfigCh
161+
162+
if ok {
163+
opts.TestConfig.ReattachConfigCh <- val
164+
}
165+
}()
166+
167+
tf5serveOpts = append(tf5serveOpts, tf5server.WithDebug(
168+
opts.TestConfig.Context,
169+
reattachConfigCh,
170+
closeCh),
171+
)
172+
}
173+
174+
if opts.UseTFLogSink != nil {
175+
tf5serveOpts = append(tf5serveOpts, tf5server.WithLoggingSink(opts.UseTFLogSink))
176+
}
177+
178+
return tf5server.Serve(opts.ProviderAddr, opts.GRPCProviderFunc, tf5serveOpts...)
179+
}
180+
181+
func tf6serverServe(opts *ServeOpts) error {
182+
var tf6serveOpts []tf6server.ServeOpt
183+
184+
if opts.Debug {
185+
tf6serveOpts = append(tf6serveOpts, tf6server.WithManagedDebug())
186+
}
187+
188+
if opts.Logger != nil {
189+
tf6serveOpts = append(tf6serveOpts, tf6server.WithGoPluginLogger(opts.Logger))
190+
}
191+
192+
if opts.TestConfig != nil {
193+
// Convert send-only channels to bi-directional channels to appease
194+
// the compiler. WithDebug is errantly defined to require
195+
// bi-directional when send-only is actually needed, which may be
196+
// fixed in the future so the opts.TestConfig channels can be passed
197+
// through directly.
198+
closeCh := make(chan struct{})
199+
reattachConfigCh := make(chan *plugin.ReattachConfig)
200+
201+
go func() {
202+
val, ok := <-closeCh
203+
204+
if ok {
205+
opts.TestConfig.CloseCh <- val
206+
}
207+
}()
208+
209+
go func() {
210+
val, ok := <-reattachConfigCh
211+
212+
if ok {
213+
opts.TestConfig.ReattachConfigCh <- val
214+
}
215+
}()
216+
217+
tf6serveOpts = append(tf6serveOpts, tf6server.WithDebug(
218+
opts.TestConfig.Context,
219+
reattachConfigCh,
220+
closeCh),
221+
)
222+
}
144223

224+
if opts.UseTFLogSink != nil {
225+
tf6serveOpts = append(tf6serveOpts, tf6server.WithLoggingSink(opts.UseTFLogSink))
145226
}
146227

147-
plugin.Serve(&serveConfig)
228+
return tf6server.Serve(opts.ProviderAddr, opts.GRPCProviderV6Func, tf6serveOpts...)
148229
}

0 commit comments

Comments
 (0)