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
This commit is contained in:
Sarah French 2025-09-19 12:42:13 +01:00 committed by GitHub
parent 15a6cd208b
commit 0dfa115d7d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 389 additions and 56 deletions

View file

@ -0,0 +1,99 @@
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package reattach
import (
"encoding/json"
"fmt"
"net"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/internal/addrs"
)
// TF_REATTACH_PROVIDERS is JSON string, containing a map of provider source to reattachment config.
//
// E.g this corresponds to a provider with source 'registry.terraform.io/hashicorp/foobar':
/*
{
"foobar": {
"Protocol": "grpc",
"ProtocolVersion": 6,
"Pid": 12345,
"Test": true,
"Addr": {
"Network": "unix",
"String":"/var/folders/xx/abcde12345/T/plugin12345"
}
}
*/
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).
//
// Calling code is expected to pass in the value of os.Getenv("TF_REATTACH_PROVIDERS")
func ParseReattachProviders(in string) (map[addrs.Provider]*plugin.ReattachConfig, error) {
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.
//
// Calling code is expected to pass in a provider address and the value of os.Getenv("TF_REATTACH_PROVIDERS")
func IsProviderReattached(provider addrs.Provider, in string) (bool, error) {
providers, err := ParseReattachProviders(in)
if err != nil {
return false, err
}
_, ok := providers[provider]
return ok, nil
}

View file

@ -0,0 +1,288 @@
// 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) {
output, err := ParseReattachProviders(tc.reattachProviders)
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) {
output, err := IsProviderReattached(tc.provider, tc.reattachProviders)
if err != nil {
t.Fatal(err)
}
if output != tc.expectedOutput {
t.Fatalf("expected returned value to be %v, got %v", tc.expectedOutput, output)
}
})
}
}

58
main.go
View file

@ -5,10 +5,8 @@ package main
import (
"context"
"encoding/json"
"fmt"
"log"
"net"
"os"
"path/filepath"
"runtime"
@ -18,10 +16,10 @@ import (
"github.com/hashicorp/cli"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform-svchost/disco"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/command/cliconfig"
"github.com/hashicorp/terraform/internal/command/format"
"github.com/hashicorp/terraform/internal/didyoumean"
"github.com/hashicorp/terraform/internal/getproviders/reattach"
"github.com/hashicorp/terraform/internal/httpclient"
"github.com/hashicorp/terraform/internal/logging"
"github.com/hashicorp/terraform/internal/terminal"
@ -204,7 +202,7 @@ func realMain() int {
// The user can declare that certain providers are being managed on
// Terraform's behalf using this environment variable. This is used
// primarily by the SDK's acceptance testing framework.
unmanagedProviders, err := parseReattachProviders(os.Getenv("TF_REATTACH_PROVIDERS"))
unmanagedProviders, err := reattach.ParseReattachProviders(os.Getenv(reattach.TF_REATTACH_PROVIDERS))
if err != nil {
Ui.Error(err.Error())
return 1
@ -402,58 +400,6 @@ func mergeEnvArgs(envName string, cmd string, args []string) ([]string, error) {
return newArgs, nil
}
// parse information on reattaching to unmanaged providers out of a
// JSON-encoded environment variable.
func parseReattachProviders(in string) (map[addrs.Provider]*plugin.ReattachConfig, error) {
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 TF_REATTACH_PROVIDERS: %w", 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
}
func extractChdirOption(args []string) (string, []string, error) {
if len(args) == 0 {
return "", args, nil