opentofu/internal/command/e2etest/unmanaged_test.go
2023-05-02 15:33:06 +00:00

357 lines
9.2 KiB
Go

// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: MPL-2.0
package e2etest
import (
"context"
"encoding/json"
"io/ioutil"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/terraform/internal/e2e"
"github.com/hashicorp/terraform/internal/grpcwrap"
tfplugin5 "github.com/hashicorp/terraform/internal/plugin"
tfplugin "github.com/hashicorp/terraform/internal/plugin6"
simple5 "github.com/hashicorp/terraform/internal/provider-simple"
simple "github.com/hashicorp/terraform/internal/provider-simple-v6"
proto5 "github.com/hashicorp/terraform/internal/tfplugin5"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
)
// The tests in this file are for the "unmanaged provider workflow", which
// includes variants of the following sequence, with different details:
// terraform init
// terraform plan
// terraform apply
//
// These tests are run against an in-process server, and checked to make sure
// they're not trying to control the lifecycle of the binary. They are not
// checked for correctness of the operations themselves.
type reattachConfig struct {
Protocol string
ProtocolVersion int
Pid int
Test bool
Addr reattachConfigAddr
}
type reattachConfigAddr struct {
Network string
String string
}
type providerServer struct {
sync.Mutex
proto.ProviderServer
planResourceChangeCalled bool
applyResourceChangeCalled bool
}
func (p *providerServer) PlanResourceChange(ctx context.Context, req *proto.PlanResourceChange_Request) (*proto.PlanResourceChange_Response, error) {
p.Lock()
defer p.Unlock()
p.planResourceChangeCalled = true
return p.ProviderServer.PlanResourceChange(ctx, req)
}
func (p *providerServer) ApplyResourceChange(ctx context.Context, req *proto.ApplyResourceChange_Request) (*proto.ApplyResourceChange_Response, error) {
p.Lock()
defer p.Unlock()
p.applyResourceChangeCalled = true
return p.ProviderServer.ApplyResourceChange(ctx, req)
}
func (p *providerServer) PlanResourceChangeCalled() bool {
p.Lock()
defer p.Unlock()
return p.planResourceChangeCalled
}
func (p *providerServer) ResetPlanResourceChangeCalled() {
p.Lock()
defer p.Unlock()
p.planResourceChangeCalled = false
}
func (p *providerServer) ApplyResourceChangeCalled() bool {
p.Lock()
defer p.Unlock()
return p.applyResourceChangeCalled
}
func (p *providerServer) ResetApplyResourceChangeCalled() {
p.Lock()
defer p.Unlock()
p.applyResourceChangeCalled = false
}
type providerServer5 struct {
sync.Mutex
proto5.ProviderServer
planResourceChangeCalled bool
applyResourceChangeCalled bool
}
func (p *providerServer5) PlanResourceChange(ctx context.Context, req *proto5.PlanResourceChange_Request) (*proto5.PlanResourceChange_Response, error) {
p.Lock()
defer p.Unlock()
p.planResourceChangeCalled = true
return p.ProviderServer.PlanResourceChange(ctx, req)
}
func (p *providerServer5) ApplyResourceChange(ctx context.Context, req *proto5.ApplyResourceChange_Request) (*proto5.ApplyResourceChange_Response, error) {
p.Lock()
defer p.Unlock()
p.applyResourceChangeCalled = true
return p.ProviderServer.ApplyResourceChange(ctx, req)
}
func (p *providerServer5) PlanResourceChangeCalled() bool {
p.Lock()
defer p.Unlock()
return p.planResourceChangeCalled
}
func (p *providerServer5) ResetPlanResourceChangeCalled() {
p.Lock()
defer p.Unlock()
p.planResourceChangeCalled = false
}
func (p *providerServer5) ApplyResourceChangeCalled() bool {
p.Lock()
defer p.Unlock()
return p.applyResourceChangeCalled
}
func (p *providerServer5) ResetApplyResourceChangeCalled() {
p.Lock()
defer p.Unlock()
p.applyResourceChangeCalled = false
}
func TestUnmanagedSeparatePlan(t *testing.T) {
t.Parallel()
fixturePath := filepath.Join("testdata", "test-provider")
tf := e2e.NewBinary(t, terraformBin, fixturePath)
reattachCh := make(chan *plugin.ReattachConfig)
closeCh := make(chan struct{})
provider := &providerServer{
ProviderServer: grpcwrap.Provider6(simple.Provider()),
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go plugin.Serve(&plugin.ServeConfig{
Logger: hclog.New(&hclog.LoggerOptions{
Name: "plugintest",
Level: hclog.Trace,
Output: ioutil.Discard,
}),
Test: &plugin.ServeTestConfig{
Context: ctx,
ReattachConfigCh: reattachCh,
CloseCh: closeCh,
},
GRPCServer: plugin.DefaultGRPCServer,
VersionedPlugins: map[int]plugin.PluginSet{
6: {
"provider": &tfplugin.GRPCProviderPlugin{
GRPCProvider: func() proto.ProviderServer {
return provider
},
},
},
},
})
config := <-reattachCh
if config == nil {
t.Fatalf("no reattach config received")
}
reattachStr, err := json.Marshal(map[string]reattachConfig{
"hashicorp/test": {
Protocol: string(config.Protocol),
ProtocolVersion: 6,
Pid: config.Pid,
Test: true,
Addr: reattachConfigAddr{
Network: config.Addr.Network(),
String: config.Addr.String(),
},
},
})
if err != nil {
t.Fatal(err)
}
tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr))
//// INIT
stdout, stderr, err := tf.Run("init")
if err != nil {
t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
}
// Make sure we didn't download the binary
if strings.Contains(stdout, "Installing hashicorp/test v") {
t.Errorf("test provider download message is present in init output:\n%s", stdout)
}
if tf.FileExists(filepath.Join(".terraform", "plugins", "registry.terraform.io", "hashicorp", "test")) {
t.Errorf("test provider binary found in .terraform dir")
}
//// PLAN
_, stderr, err = tf.Run("plan", "-out=tfplan")
if err != nil {
t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
}
if !provider.PlanResourceChangeCalled() {
t.Error("PlanResourceChange not called on un-managed provider")
}
//// APPLY
_, stderr, err = tf.Run("apply", "tfplan")
if err != nil {
t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
}
if !provider.ApplyResourceChangeCalled() {
t.Error("ApplyResourceChange not called on un-managed provider")
}
provider.ResetApplyResourceChangeCalled()
//// DESTROY
_, stderr, err = tf.Run("destroy", "-auto-approve")
if err != nil {
t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr)
}
if !provider.ApplyResourceChangeCalled() {
t.Error("ApplyResourceChange (destroy) not called on in-process provider")
}
cancel()
<-closeCh
}
func TestUnmanagedSeparatePlan_proto5(t *testing.T) {
t.Parallel()
fixturePath := filepath.Join("testdata", "test-provider")
tf := e2e.NewBinary(t, terraformBin, fixturePath)
reattachCh := make(chan *plugin.ReattachConfig)
closeCh := make(chan struct{})
provider := &providerServer5{
ProviderServer: grpcwrap.Provider(simple5.Provider()),
}
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go plugin.Serve(&plugin.ServeConfig{
Logger: hclog.New(&hclog.LoggerOptions{
Name: "plugintest",
Level: hclog.Trace,
Output: ioutil.Discard,
}),
Test: &plugin.ServeTestConfig{
Context: ctx,
ReattachConfigCh: reattachCh,
CloseCh: closeCh,
},
GRPCServer: plugin.DefaultGRPCServer,
VersionedPlugins: map[int]plugin.PluginSet{
5: {
"provider": &tfplugin5.GRPCProviderPlugin{
GRPCProvider: func() proto5.ProviderServer {
return provider
},
},
},
},
})
config := <-reattachCh
if config == nil {
t.Fatalf("no reattach config received")
}
reattachStr, err := json.Marshal(map[string]reattachConfig{
"hashicorp/test": {
Protocol: string(config.Protocol),
ProtocolVersion: 5,
Pid: config.Pid,
Test: true,
Addr: reattachConfigAddr{
Network: config.Addr.Network(),
String: config.Addr.String(),
},
},
})
if err != nil {
t.Fatal(err)
}
tf.AddEnv("TF_REATTACH_PROVIDERS=" + string(reattachStr))
//// INIT
stdout, stderr, err := tf.Run("init")
if err != nil {
t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr)
}
// Make sure we didn't download the binary
if strings.Contains(stdout, "Installing hashicorp/test v") {
t.Errorf("test provider download message is present in init output:\n%s", stdout)
}
if tf.FileExists(filepath.Join(".terraform", "plugins", "registry.terraform.io", "hashicorp", "test")) {
t.Errorf("test provider binary found in .terraform dir")
}
//// PLAN
_, stderr, err = tf.Run("plan", "-out=tfplan")
if err != nil {
t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr)
}
if !provider.PlanResourceChangeCalled() {
t.Error("PlanResourceChange not called on un-managed provider")
}
//// APPLY
_, stderr, err = tf.Run("apply", "tfplan")
if err != nil {
t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr)
}
if !provider.ApplyResourceChangeCalled() {
t.Error("ApplyResourceChange not called on un-managed provider")
}
provider.ResetApplyResourceChangeCalled()
//// DESTROY
_, stderr, err = tf.Run("destroy", "-auto-approve")
if err != nil {
t.Fatalf("unexpected destroy error: %s\nstderr:\n%s", err, stderr)
}
if !provider.ApplyResourceChangeCalled() {
t.Error("ApplyResourceChange (destroy) not called on in-process provider")
}
cancel()
<-closeCh
}