Skip to content

Commit 0dfa115

Browse files
authored
First step to enable use of TF_REATTACH_PROVIDERS with PSS (#37634)
* Add test coverage for `parseReattachProviders` * Add unhappy path test cases to parseReattachProviders test * Add `isProviderReattached` function, to help identify when a reattached provider is being used for PSS * Move reattach config-related code into its own package * Make calling code not need to know what the ENV is for reattach providers * Refactor IsProviderReattached to use pre-existing logic * Add test case, make spell check happy with US English * Add headers * Make calling code responsible for reading in the related ENV * Add godoc comment to TF_REATTACH_PROVIDERS const * Update internal/getproviders/reattach/reattach.go
1 parent 15a6cd2 commit 0dfa115

File tree

3 files changed

+389
-56
lines changed

3 files changed

+389
-56
lines changed
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package reattach
5+
6+
import (
7+
"encoding/json"
8+
"fmt"
9+
"net"
10+
11+
"github.com/hashicorp/go-plugin"
12+
"github.com/hashicorp/terraform/internal/addrs"
13+
)
14+
15+
// TF_REATTACH_PROVIDERS is JSON string, containing a map of provider source to reattachment config.
16+
//
17+
// E.g this corresponds to a provider with source 'registry.terraform.io/hashicorp/foobar':
18+
/*
19+
{
20+
"foobar": {
21+
"Protocol": "grpc",
22+
"ProtocolVersion": 6,
23+
"Pid": 12345,
24+
"Test": true,
25+
"Addr": {
26+
"Network": "unix",
27+
"String":"/var/folders/xx/abcde12345/T/plugin12345"
28+
}
29+
}
30+
*/
31+
const TF_REATTACH_PROVIDERS = "TF_REATTACH_PROVIDERS"
32+
33+
// ParseReattachProviders parses information used for reattaching to unmanaged providers out of a
34+
// JSON-encoded environment variable (TF_REATTACH_PROVIDERS).
35+
//
36+
// Calling code is expected to pass in the value of os.Getenv("TF_REATTACH_PROVIDERS")
37+
func ParseReattachProviders(in string) (map[addrs.Provider]*plugin.ReattachConfig, error) {
38+
unmanagedProviders := map[addrs.Provider]*plugin.ReattachConfig{}
39+
if in != "" {
40+
type reattachConfig struct {
41+
Protocol string
42+
ProtocolVersion int
43+
Addr struct {
44+
Network string
45+
String string
46+
}
47+
Pid int
48+
Test bool
49+
}
50+
var m map[string]reattachConfig
51+
err := json.Unmarshal([]byte(in), &m)
52+
if err != nil {
53+
return unmanagedProviders, fmt.Errorf("Invalid format for %s: %w", TF_REATTACH_PROVIDERS, err)
54+
}
55+
for p, c := range m {
56+
a, diags := addrs.ParseProviderSourceString(p)
57+
if diags.HasErrors() {
58+
return unmanagedProviders, fmt.Errorf("Error parsing %q as a provider address: %w", a, diags.Err())
59+
}
60+
var addr net.Addr
61+
switch c.Addr.Network {
62+
case "unix":
63+
addr, err = net.ResolveUnixAddr("unix", c.Addr.String)
64+
if err != nil {
65+
return unmanagedProviders, fmt.Errorf("Invalid unix socket path %q for %q: %w", c.Addr.String, p, err)
66+
}
67+
case "tcp":
68+
addr, err = net.ResolveTCPAddr("tcp", c.Addr.String)
69+
if err != nil {
70+
return unmanagedProviders, fmt.Errorf("Invalid TCP address %q for %q: %w", c.Addr.String, p, err)
71+
}
72+
default:
73+
return unmanagedProviders, fmt.Errorf("Unknown address type %q for %q", c.Addr.Network, p)
74+
}
75+
unmanagedProviders[a] = &plugin.ReattachConfig{
76+
Protocol: plugin.Protocol(c.Protocol),
77+
ProtocolVersion: c.ProtocolVersion,
78+
Pid: c.Pid,
79+
Test: c.Test,
80+
Addr: addr,
81+
}
82+
}
83+
}
84+
return unmanagedProviders, nil
85+
}
86+
87+
// IsProviderReattached determines if a given provider is being supplied to Terraform via the TF_REATTACH_PROVIDERS
88+
// environment variable.
89+
//
90+
// Calling code is expected to pass in a provider address and the value of os.Getenv("TF_REATTACH_PROVIDERS")
91+
func IsProviderReattached(provider addrs.Provider, in string) (bool, error) {
92+
providers, err := ParseReattachProviders(in)
93+
if err != nil {
94+
return false, err
95+
}
96+
97+
_, ok := providers[provider]
98+
return ok, nil
99+
}
Lines changed: 288 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,288 @@
1+
// Copyright (c) HashiCorp, Inc.
2+
// SPDX-License-Identifier: BUSL-1.1
3+
4+
package reattach
5+
6+
import (
7+
"net"
8+
"testing"
9+
10+
"github.com/google/go-cmp/cmp"
11+
"github.com/hashicorp/go-plugin"
12+
tfaddr "github.com/hashicorp/terraform-registry-address"
13+
"github.com/hashicorp/terraform/internal/addrs"
14+
)
15+
16+
func Test_parseReattachProviders(t *testing.T) {
17+
cases := map[string]struct {
18+
reattachProviders string
19+
expectedOutput map[addrs.Provider]*plugin.ReattachConfig
20+
expectErr bool
21+
}{
22+
"simple parse - 1 provider": {
23+
reattachProviders: `{
24+
"test": {
25+
"Protocol": "grpc",
26+
"ProtocolVersion": 6,
27+
"Pid": 12345,
28+
"Test": true,
29+
"Addr": {
30+
"Network": "unix",
31+
"String":"/var/folders/xx/abcde12345/T/plugin12345"
32+
}
33+
}
34+
}`,
35+
expectedOutput: map[addrs.Provider]*plugin.ReattachConfig{
36+
tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test"): func() *plugin.ReattachConfig {
37+
addr, err := net.ResolveUnixAddr("unix", "/var/folders/xx/abcde12345/T/plugin12345")
38+
if err != nil {
39+
t.Fatal(err)
40+
}
41+
return &plugin.ReattachConfig{
42+
Protocol: plugin.Protocol("grpc"),
43+
ProtocolVersion: 6,
44+
Pid: 12345,
45+
Test: true,
46+
Addr: addr,
47+
}
48+
}(),
49+
},
50+
},
51+
"complex parse - 2 providers via different protocols etc": {
52+
reattachProviders: `{
53+
"test-grpc": {
54+
"Protocol": "grpc",
55+
"ProtocolVersion": 6,
56+
"Pid": 12345,
57+
"Test": true,
58+
"Addr": {
59+
"Network": "unix",
60+
"String": "/var/folders/xx/abcde12345/T/plugin12345"
61+
}
62+
},
63+
"test-netrpc": {
64+
"Protocol": "netrpc",
65+
"ProtocolVersion": 5,
66+
"Pid": 6789,
67+
"Test": false,
68+
"Addr": {
69+
"Network": "tcp",
70+
"String":"127.0.0.1:1337"
71+
}
72+
}
73+
}`,
74+
expectedOutput: map[addrs.Provider]*plugin.ReattachConfig{
75+
//test-grpc
76+
tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test-grpc"): func() *plugin.ReattachConfig {
77+
addr, err := net.ResolveUnixAddr("unix", "/var/folders/xx/abcde12345/T/plugin12345")
78+
if err != nil {
79+
t.Fatal(err)
80+
}
81+
return &plugin.ReattachConfig{
82+
Protocol: plugin.Protocol("grpc"),
83+
ProtocolVersion: 6,
84+
Pid: 12345,
85+
Test: true,
86+
Addr: addr,
87+
}
88+
}(),
89+
//test-netrpc
90+
tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test-netrpc"): func() *plugin.ReattachConfig {
91+
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:1337")
92+
if err != nil {
93+
t.Fatal(err)
94+
}
95+
return &plugin.ReattachConfig{
96+
Protocol: plugin.Protocol("netrpc"),
97+
ProtocolVersion: 5,
98+
Pid: 6789,
99+
Test: false,
100+
Addr: addr,
101+
}
102+
}(),
103+
},
104+
},
105+
"can specify the providers host and namespace": {
106+
// The key here has host and namespace data, vs. just "test"
107+
reattachProviders: `{
108+
"example.com/my-org/test": {
109+
"Protocol": "grpc",
110+
"ProtocolVersion": 6,
111+
"Pid": 12345,
112+
"Test": true,
113+
"Addr": {
114+
"Network": "unix",
115+
"String":"/var/folders/xx/abcde12345/T/plugin12345"
116+
}
117+
}
118+
}`,
119+
expectedOutput: map[addrs.Provider]*plugin.ReattachConfig{
120+
tfaddr.NewProvider("example.com", "my-org", "test"): func() *plugin.ReattachConfig {
121+
addr, err := net.ResolveUnixAddr("unix", "/var/folders/xx/abcde12345/T/plugin12345")
122+
if err != nil {
123+
t.Fatal(err)
124+
}
125+
return &plugin.ReattachConfig{
126+
Protocol: plugin.Protocol("grpc"),
127+
ProtocolVersion: 6,
128+
Pid: 12345,
129+
Test: true,
130+
Addr: addr,
131+
}
132+
}(),
133+
},
134+
},
135+
"error - bad JSON": {
136+
// Missing closing brace
137+
reattachProviders: `{
138+
"test": {
139+
"Protocol": "grpc",
140+
"ProtocolVersion": 6,
141+
"Pid": 12345,
142+
"Test": true,
143+
"Addr": {
144+
"Network": "unix",
145+
"String":"/var/folders/xx/abcde12345/T/plugin12345"
146+
}
147+
}
148+
`,
149+
expectErr: true,
150+
},
151+
"error - bad provider address": {
152+
reattachProviders: `{
153+
"bad provider addr": {
154+
"Protocol": "grpc",
155+
"ProtocolVersion": 6,
156+
"Pid": 12345,
157+
"Test": true,
158+
"Addr": {
159+
"Network": "unix",
160+
"String":"/var/folders/xx/abcde12345/T/plugin12345"
161+
}
162+
}
163+
}`,
164+
expectErr: true,
165+
},
166+
"error - unrecognized protocol": {
167+
reattachProviders: `{
168+
"test": {
169+
"Protocol": "carrier-pigeon",
170+
"ProtocolVersion": 6,
171+
"Pid": 12345,
172+
"Test": true,
173+
"Addr": {
174+
"Network": "pigeon",
175+
"String":"fly home little pigeon"
176+
}
177+
}
178+
}`,
179+
expectErr: true,
180+
},
181+
"error - unrecognized network": {
182+
reattachProviders: `{
183+
"test": {
184+
"Protocol": "grpc",
185+
"ProtocolVersion": 6,
186+
"Pid": 12345,
187+
"Test": true,
188+
"Addr": {
189+
"Network": "linkedin",
190+
"String":"http://www.linkedin.com/"
191+
}
192+
}
193+
}`,
194+
expectErr: true,
195+
},
196+
"error - bad tcp address": {
197+
// Addr.String has no port at the end
198+
reattachProviders: `{
199+
"test": {
200+
"Protocol": "grpc",
201+
"ProtocolVersion": 6,
202+
"Pid": 12345,
203+
"Test": true,
204+
"Addr": {
205+
"Network": "tcp",
206+
"String":"127.0.0.1"
207+
}
208+
}
209+
}`,
210+
expectErr: true,
211+
},
212+
}
213+
214+
for tn, tc := range cases {
215+
t.Run(tn, func(t *testing.T) {
216+
output, err := ParseReattachProviders(tc.reattachProviders)
217+
if err != nil {
218+
if !tc.expectErr {
219+
t.Fatal(err)
220+
}
221+
// an expected error occurred
222+
return
223+
}
224+
if err == nil && tc.expectErr {
225+
t.Fatal("expected error but there was none")
226+
}
227+
if diff := cmp.Diff(output, tc.expectedOutput); diff != "" {
228+
t.Fatalf("expected diff:\n%s", diff)
229+
}
230+
})
231+
}
232+
}
233+
234+
func Test_isProviderReattached(t *testing.T) {
235+
cases := map[string]struct {
236+
provider addrs.Provider
237+
reattachProviders string
238+
expectedOutput bool
239+
}{
240+
"identifies when a matching provider is present in TF_REATTACH_PROVIDERS": {
241+
// Note that the source in the TF_REATTACH_PROVIDERS value is just the provider name.
242+
// It'll be assumed to be under the default registry host and in the 'hashicorp' namespace.
243+
reattachProviders: `{
244+
"test": {
245+
"Protocol": "grpc",
246+
"ProtocolVersion": 6,
247+
"Pid": 12345,
248+
"Test": true,
249+
"Addr": {
250+
"Network": "unix",
251+
"String":"/var/folders/xx/abcde12345/T/plugin12345"
252+
}
253+
}
254+
}`,
255+
provider: tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test"),
256+
expectedOutput: true,
257+
},
258+
"identifies when a provider doesn't have a match in TF_REATTACH_PROVIDERS": {
259+
// Note the mismatch on namespace
260+
reattachProviders: `{
261+
"hashicorp/test": {
262+
"Protocol": "grpc",
263+
"ProtocolVersion": 6,
264+
"Pid": 12345,
265+
"Test": true,
266+
"Addr": {
267+
"Network": "unix",
268+
"String":"/var/folders/xx/abcde12345/T/plugin12345"
269+
}
270+
}
271+
}`,
272+
provider: tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "dadgarcorp", "test"),
273+
expectedOutput: false,
274+
},
275+
}
276+
277+
for tn, tc := range cases {
278+
t.Run(tn, func(t *testing.T) {
279+
output, err := IsProviderReattached(tc.provider, tc.reattachProviders)
280+
if err != nil {
281+
t.Fatal(err)
282+
}
283+
if output != tc.expectedOutput {
284+
t.Fatalf("expected returned value to be %v, got %v", tc.expectedOutput, output)
285+
}
286+
})
287+
}
288+
}

0 commit comments

Comments
 (0)