356 lines
9.1 KiB
Go
356 lines
9.1 KiB
Go
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(terraformBin, fixturePath)
|
|
defer tf.Close()
|
|
|
|
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(terraformBin, fixturePath)
|
|
defer tf.Close()
|
|
|
|
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
|
|
}
|