blob: f81f51957dfa8f03b69b858af3f63b0fc72f2365 [file] [edit]
// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package e2etest
import (
"context"
"path/filepath"
"strings"
"sync"
"testing"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/e2e"
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
listResourceCalled bool
readStateBytesCalled bool
writeStateBytesCalled 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) WriteStateBytes(server proto.Provider_WriteStateBytesServer) error {
p.Lock()
defer p.Unlock()
p.writeStateBytesCalled = true
return p.ProviderServer.WriteStateBytes(server)
}
func (p *providerServer) ReadStateBytes(req *proto.ReadStateBytes_Request, server proto.Provider_ReadStateBytesServer) error {
p.Lock()
defer p.Unlock()
p.readStateBytesCalled = true
return p.ProviderServer.ReadStateBytes(req, server)
}
func (p *providerServer) ListResource(req *proto.ListResource_Request, res proto.Provider_ListResourceServer) error {
p.Lock()
defer p.Unlock()
p.listResourceCalled = true
return p.ProviderServer.ListResource(req, res)
}
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
}
func (p *providerServer) ListResourceCalled() bool {
p.Lock()
defer p.Unlock()
return p.listResourceCalled
}
func (p *providerServer) ReadStateBytesCalled() bool {
p.Lock()
defer p.Unlock()
return p.readStateBytesCalled
}
func (p *providerServer) ResetReadStateBytesCalled() {
p.Lock()
defer p.Unlock()
p.readStateBytesCalled = false
}
func (p *providerServer) WriteStateBytesCalled() bool {
p.Lock()
defer p.Unlock()
return p.writeStateBytesCalled
}
func (p *providerServer) ResetWriteStateBytesCalled() {
p.Lock()
defer p.Unlock()
p.writeStateBytesCalled = false
}
type providerServer5 struct {
sync.Mutex
proto5.ProviderServer
planResourceChangeCalled bool
applyResourceChangeCalled bool
listResourceCalled 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) ListResource(req *proto5.ListResource_Request, res proto5.Provider_ListResourceServer) error {
p.Lock()
defer p.Unlock()
p.listResourceCalled = true
return p.ProviderServer.ListResource(req, res)
}
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 (p *providerServer5) ListResourceCalled() bool {
p.Lock()
defer p.Unlock()
return p.listResourceCalled
}
func TestUnmanagedSeparatePlan(t *testing.T) {
t.Parallel()
fixturePath := filepath.Join("testdata", "test-provider")
tf := e2e.NewBinary(t, terraformBin, fixturePath)
reattachStr, provider := reattachedProviderForTest(t, addrs.NewDefaultProvider("test"), 6)
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")
}
}
func TestUnmanagedSeparatePlan_proto5(t *testing.T) {
t.Parallel()
fixturePath := filepath.Join("testdata", "test-provider")
tf := e2e.NewBinary(t, terraformBin, fixturePath)
reattachStr, provider := reattachedProviderForTest(t, addrs.NewDefaultProvider("test"), 5) // protocol 5
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")
}
}