Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions internal/getproviders/reattach/reattach.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package reattach

import (
"encoding/json"
"fmt"
"net"
"os"

"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/internal/addrs"
)

const TF_REATTACH_PROVIDERS = "TF_REATTACH_PROVIDERS"

// ParseReattachProviders parses information used for reattaching to unmanaged providers out of a
// JSON-encoded environment variable (TF_REATTACH_PROVIDERS).
func ParseReattachProviders() (map[addrs.Provider]*plugin.ReattachConfig, error) {
in := os.Getenv(TF_REATTACH_PROVIDERS)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't it be easier to test things and reuse the function if we continued passing the value in as an argument, rather than read the environment variable?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah sure, we can avoid use of t.Setenv in tests that way. My rationalisation was that moving the call to os.Getenv into this package meant that the "TF_REATTACH_PROVIDERS" string wasn't floating around the codebase, but I can address that by making calling code use the new constant.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've refactored the function signature to require the new parameters in 12f8da6

unmanagedProviders := map[addrs.Provider]*plugin.ReattachConfig{}
if in != "" {
type reattachConfig struct {
Protocol string
ProtocolVersion int
Addr struct {
Network string
String string
}
Pid int
Test bool
}
var m map[string]reattachConfig
err := json.Unmarshal([]byte(in), &m)
if err != nil {
return unmanagedProviders, fmt.Errorf("Invalid format for %s: %w", TF_REATTACH_PROVIDERS, err)
}
for p, c := range m {
a, diags := addrs.ParseProviderSourceString(p)
if diags.HasErrors() {
return unmanagedProviders, fmt.Errorf("Error parsing %q as a provider address: %w", a, diags.Err())
}
var addr net.Addr
switch c.Addr.Network {
case "unix":
addr, err = net.ResolveUnixAddr("unix", c.Addr.String)
if err != nil {
return unmanagedProviders, fmt.Errorf("Invalid unix socket path %q for %q: %w", c.Addr.String, p, err)
}
case "tcp":
addr, err = net.ResolveTCPAddr("tcp", c.Addr.String)
if err != nil {
return unmanagedProviders, fmt.Errorf("Invalid TCP address %q for %q: %w", c.Addr.String, p, err)
}
default:
return unmanagedProviders, fmt.Errorf("Unknown address type %q for %q", c.Addr.Network, p)
}
unmanagedProviders[a] = &plugin.ReattachConfig{
Protocol: plugin.Protocol(c.Protocol),
ProtocolVersion: c.ProtocolVersion,
Pid: c.Pid,
Test: c.Test,
Addr: addr,
}
}
}
return unmanagedProviders, nil
}

// IsProviderReattached determines if a given provider is being supplied to Terraform via the TF_REATTACH_PROVIDERS
// environment variable.
func IsProviderReattached(provider addrs.Provider) (bool, error) {
providers, err := ParseReattachProviders()
if err != nil {
return false, err
}

_, ok := providers[provider]
return ok, nil
}
293 changes: 293 additions & 0 deletions internal/getproviders/reattach/reattach_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1

package reattach

import (
"net"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-plugin"
tfaddr "github.com/hashicorp/terraform-registry-address"
"github.com/hashicorp/terraform/internal/addrs"
)

func Test_parseReattachProviders(t *testing.T) {
cases := map[string]struct {
reattachProviders string
expectedOutput map[addrs.Provider]*plugin.ReattachConfig
expectErr bool
}{
"simple parse - 1 provider": {
reattachProviders: `{
"test": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String":"/var/folders/xx/abcde12345/T/plugin12345"
}
}
}`,
expectedOutput: map[addrs.Provider]*plugin.ReattachConfig{
tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test"): func() *plugin.ReattachConfig {
addr, err := net.ResolveUnixAddr("unix", "/var/folders/xx/abcde12345/T/plugin12345")
if err != nil {
t.Fatal(err)
}
return &plugin.ReattachConfig{
Protocol: plugin.Protocol("grpc"),
ProtocolVersion: 6,
Pid: 12345,
Test: true,
Addr: addr,
}
}(),
},
},
"complex parse - 2 providers via different protocols etc": {
reattachProviders: `{
"test-grpc": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String": "/var/folders/xx/abcde12345/T/plugin12345"
}
},
"test-netrpc": {
"Protocol": "netrpc",
"ProtocolVersion": 5,
"Pid": 6789,
"Test": false,
"Addr": {
"Network": "tcp",
"String":"127.0.0.1:1337"
}
}
}`,
expectedOutput: map[addrs.Provider]*plugin.ReattachConfig{
//test-grpc
tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test-grpc"): func() *plugin.ReattachConfig {
addr, err := net.ResolveUnixAddr("unix", "/var/folders/xx/abcde12345/T/plugin12345")
if err != nil {
t.Fatal(err)
}
return &plugin.ReattachConfig{
Protocol: plugin.Protocol("grpc"),
ProtocolVersion: 6,
Pid: 12345,
Test: true,
Addr: addr,
}
}(),
//test-netrpc
tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test-netrpc"): func() *plugin.ReattachConfig {
addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:1337")
if err != nil {
t.Fatal(err)
}
return &plugin.ReattachConfig{
Protocol: plugin.Protocol("netrpc"),
ProtocolVersion: 5,
Pid: 6789,
Test: false,
Addr: addr,
}
}(),
},
},
"can specify the providers host and namespace": {
// The key here has host and namespace data, vs. just "test"
reattachProviders: `{
"example.com/my-org/test": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String":"/var/folders/xx/abcde12345/T/plugin12345"
}
}
}`,
expectedOutput: map[addrs.Provider]*plugin.ReattachConfig{
tfaddr.NewProvider("example.com", "my-org", "test"): func() *plugin.ReattachConfig {
addr, err := net.ResolveUnixAddr("unix", "/var/folders/xx/abcde12345/T/plugin12345")
if err != nil {
t.Fatal(err)
}
return &plugin.ReattachConfig{
Protocol: plugin.Protocol("grpc"),
ProtocolVersion: 6,
Pid: 12345,
Test: true,
Addr: addr,
}
}(),
},
},
"error - bad JSON": {
// Missing closing brace
reattachProviders: `{
"test": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String":"/var/folders/xx/abcde12345/T/plugin12345"
}
}
`,
expectErr: true,
},
"error - bad provider address": {
reattachProviders: `{
"bad provider addr": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String":"/var/folders/xx/abcde12345/T/plugin12345"
}
}
}`,
expectErr: true,
},
"error - unrecognized protocol": {
reattachProviders: `{
"test": {
"Protocol": "carrier-pigeon",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "pigeon",
"String":"fly home little pigeon"
}
}
}`,
expectErr: true,
},
"error - unrecognized network": {
reattachProviders: `{
"test": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "linkedin",
"String":"http://www.linkedin.com/"
}
}
}`,
expectErr: true,
},
"error - bad tcp address": {
// Addr.String has no port at the end
reattachProviders: `{
"test": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "tcp",
"String":"127.0.0.1"
}
}
}`,
expectErr: true,
},
}

for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {
t.Setenv("TF_REATTACH_PROVIDERS", tc.reattachProviders)

output, err := ParseReattachProviders()
if err != nil {
if !tc.expectErr {
t.Fatal(err)
}
// an expected error occurred
return
}
if err == nil && tc.expectErr {
t.Fatal("expected error but there was none")
}
if diff := cmp.Diff(output, tc.expectedOutput); diff != "" {
t.Fatalf("expected diff:\n%s", diff)
}
})
}
}

func Test_isProviderReattached(t *testing.T) {
cases := map[string]struct {
provider addrs.Provider
reattachProviders string
expectedOutput bool
}{
"identifies when a matching provider is present in TF_REATTACH_PROVIDERS": {
// Note that the source in the TF_REATTACH_PROVIDERS value is just the provider name.
// It'll be assumed to be under the default registry host and in the 'hashicorp' namespace.
reattachProviders: `{
"test": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String":"/var/folders/xx/abcde12345/T/plugin12345"
}
}
}`,
provider: tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "hashicorp", "test"),
expectedOutput: true,
},
"identifies when a provider doesn't have a match in TF_REATTACH_PROVIDERS": {
// Note the mismatch on namespace
reattachProviders: `{
"hashicorp/test": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String":"/var/folders/xx/abcde12345/T/plugin12345"
}
}
}`,
provider: tfaddr.NewProvider(tfaddr.DefaultProviderRegistryHost, "dadgarcorp", "test"),
expectedOutput: false,
},
}

for tn, tc := range cases {
t.Run(tn, func(t *testing.T) {

t.Setenv("TF_REATTACH_PROVIDERS", tc.reattachProviders)

output, err := IsProviderReattached(tc.provider)
if err != nil {
t.Fatal(err)
}
if output != tc.expectedOutput {
t.Fatalf("expected returned value to be %v, got %v", tc.expectedOutput, output)
}
})
}
}
Loading