terraform/internal/command/e2etest/pluggable_state_store_test.go
Sarah French ee3190aab4
fix: Add check that we're able to run the e2e.GoBuild method in TestPrimary_stateStore_unmanaged_separatePlan (#38190)
This change should hopefully address E2E failures that are happening on main. It's hard to test that as the failures are platform specific and neither my laptop nor the runner for tests on PRs matches the failing platforms.
2026-02-18 14:42:41 +01:00

692 lines
26 KiB
Go

// Copyright IBM Corp. 2014, 2026
// SPDX-License-Identifier: BUSL-1.1
package e2etest
import (
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"os"
"path"
"path/filepath"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
"github.com/hashicorp/go-hclog"
"github.com/hashicorp/go-plugin"
"github.com/hashicorp/go-version"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/e2e"
"github.com/hashicorp/terraform/internal/getproviders"
"github.com/hashicorp/terraform/internal/grpcwrap"
tfplugin "github.com/hashicorp/terraform/internal/plugin6"
simple "github.com/hashicorp/terraform/internal/provider-simple-v6"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
proto "github.com/hashicorp/terraform/internal/tfplugin6"
)
// Test that users can do the full init-plan-apply workflow with pluggable state storage
// when the state storage provider is reattached/unmanaged by Terraform.
// As well as ensuring that the state store can be initialised ok, this tests that
// the state store's details can be stored in the plan file despite the fact it's reattached.
func TestPrimary_stateStore_unmanaged_separatePlan(t *testing.T) {
if !canRunGoBuild {
// We're running in a separate-build-then-run context, so we can't
// currently execute this test which depends on being able to build
// new executable at runtime.
//
// (See the comment on canRunGoBuild's declaration for more information.)
t.Skip("can't run without building a new provider executable")
}
fixturePath := filepath.Join("testdata", "full-workflow-with-state-store-fs")
t.Setenv(e2e.TestExperimentFlag, "true")
terraformBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform")
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: io.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/simple6": {
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))
// Required for the local state files to be written to the temp directory,
// instead of the e2e directory in the repo.
t.Chdir(tf.WorkDir())
//// INIT
t.Setenv("TF_ENABLE_PLUGGABLE_STATE_STORAGE", "1")
stdout, stderr, err := tf.Run("init")
if err != nil {
t.Fatalf("unexpected init error: %s\nstderr:\n%s\nstdout:\n%s", err, stderr, stdout)
}
if !provider.ReadStateBytesCalled() {
t.Error("ReadStateBytes not called on un-managed provider")
}
if !provider.WriteStateBytesCalled() {
t.Error("WriteStateBytes not called on un-managed provider")
}
provider.ResetReadStateBytesCalled()
provider.ResetWriteStateBytesCalled()
// Make sure we didn't download the binary
if strings.Contains(stdout, "Installing hashicorp/simple6 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", "simple6")) {
t.Errorf("test provider binary found in .terraform dir")
}
//// PLAN
stdout, stderr, err = tf.Run("plan", "-out=tfplan")
if err != nil {
t.Fatalf("unexpected plan error: %s\nstderr:\n%s\nstdout:\n%s", err, stderr, stdout)
}
if !provider.ReadStateBytesCalled() {
t.Error("ReadStateBytes not called on un-managed provider")
}
if provider.WriteStateBytesCalled() {
t.Error("WriteStateBytes should not be called on un-managed provider during plan")
}
provider.ResetReadStateBytesCalled()
provider.ResetWriteStateBytesCalled()
//// APPLY
stdout, stderr, err = tf.Run("apply", "tfplan")
if err != nil {
t.Fatalf("unexpected apply error: %s\nstderr:\n%s\nstdout:\n%s", err, stderr, stdout)
}
if !provider.ReadStateBytesCalled() {
t.Error("ReadStateBytes not called on un-managed provider")
}
if !provider.WriteStateBytesCalled() {
t.Error("WriteStateBytes not called on un-managed provider")
}
provider.ResetReadStateBytesCalled()
provider.ResetWriteStateBytesCalled()
// Check the apply process has made a state file as expected.
stateFilePath := filepath.Join("states", "default", "terraform.tfstate")
if !tf.FileExists(stateFilePath) {
t.Fatalf("state file not found at expected path: %s", filepath.Join(tf.WorkDir(), stateFilePath))
}
//// DESTROY
stdout, stderr, err = tf.Run("destroy", "-auto-approve")
if err != nil {
t.Fatalf("unexpected destroy error: %s\nstderr:\n%s\nstdout:\n%s", err, stderr, stdout)
}
cancel()
<-closeCh
}
// Tests using `terraform workspace` commands in combination with pluggable state storage.
func TestPrimary_stateStore_workspaceCmd(t *testing.T) {
if !canRunGoBuild {
// We're running in a separate-build-then-run context, so we can't
// currently execute this test which depends on being able to build
// new executable at runtime.
//
// (See the comment on canRunGoBuild's declaration for more information.)
t.Skip("can't run without building a new provider executable")
}
t.Setenv(e2e.TestExperimentFlag, "true")
terraformBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform")
fixturePath := filepath.Join("testdata", "full-workflow-with-state-store-fs")
tf := e2e.NewBinary(t, terraformBin, fixturePath)
workspaceDirName := "states" // See workspace_dir value in the configuration
// In order to test integration with PSS we need a provider plugin implementing a state store.
// Here will build the simple6 (built with protocol v6) provider, which implements PSS.
simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6")
simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider)
// Move the provider binaries into a directory that we will point terraform
// to using the -plugin-dir cli flag.
platform := getproviders.CurrentPlatform.String()
fsMirrorPath := "cache/registry.terraform.io/hashicorp/simple6/0.0.1/"
if err := os.MkdirAll(tf.Path(fsMirrorPath, platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(fsMirrorPath, platform, "terraform-provider-simple6")); err != nil {
t.Fatal(err)
}
//// Init
_, stderr, err := tf.Run("init", "-enable-pluggable-state-storage-experiment=true", "-plugin-dir=cache", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, "default", "terraform.tfstate"))
if err != nil {
t.Fatalf("failed to open default workspace's state file: %s", err)
}
if fi.Size() == 0 {
t.Fatal("default workspace's state file should not have size 0 bytes")
}
//// Create Workspace: terraform workspace new
newWorkspace := "foobar"
stdout, stderr, err := tf.Run("workspace", "new", newWorkspace, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg := fmt.Sprintf("Created and switched to workspace %q!", newWorkspace)
if !strings.Contains(stdout, expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
fi, err = os.Stat(path.Join(tf.WorkDir(), workspaceDirName, newWorkspace, "terraform.tfstate"))
if err != nil {
t.Fatalf("failed to open %s workspace's state file: %s", newWorkspace, err)
}
if fi.Size() == 0 {
t.Fatalf("%s workspace's state file should not have size 0 bytes", newWorkspace)
}
//// List Workspaces: : terraform workspace list
stdout, stderr, err = tf.Run("workspace", "list", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
if !strings.Contains(stdout, newWorkspace) {
t.Errorf("unexpected output, expected the new %q workspace to be listed present, but it's missing. Got:\n%s", newWorkspace, stdout)
}
//// Select Workspace: terraform workspace select
selectedWorkspace := "default"
stdout, stderr, err = tf.Run("workspace", "select", selectedWorkspace, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = fmt.Sprintf("Switched to workspace %q.", selectedWorkspace)
if !strings.Contains(stdout, expectedMsg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
//// Show Workspace: terraform workspace show
stdout, stderr, err = tf.Run("workspace", "show", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = fmt.Sprintf("%s\n", selectedWorkspace)
if stdout != expectedMsg {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
//// Delete Workspace: terraform workspace delete
stdout, stderr, err = tf.Run("workspace", "delete", newWorkspace, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = fmt.Sprintf("Deleted workspace %q!\n", newWorkspace)
if stdout != expectedMsg {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
}
// Tests using `terraform state` subcommands in combination with pluggable state storage:
// > `terraform state show`
// > `terraform state list`
func TestPrimary_stateStore_stateCmds(t *testing.T) {
if !canRunGoBuild {
// We're running in a separate-build-then-run context, so we can't
// currently execute this test which depends on being able to build
// new executable at runtime.
//
// (See the comment on canRunGoBuild's declaration for more information.)
t.Skip("can't run without building a new provider executable")
}
t.Setenv(e2e.TestExperimentFlag, "true")
tfBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform")
fixturePath := filepath.Join("testdata", "initialized-directory-with-state-store-fs")
tf := e2e.NewBinary(t, tfBin, fixturePath)
workspaceDirName := "states" // see test fixture value for workspace_dir
// In order to test integration with PSS we need a provider plugin implementing a state store.
// Here will build the simple6 (built with protocol v6) provider, which implements PSS.
simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6")
simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider)
// Move the provider binaries into the correct .terraform/providers/ directory
// that will contain provider binaries in an initialized working directory.
platform := getproviders.CurrentPlatform.String()
providerCachePath := ".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/"
if err := os.MkdirAll(tf.Path(providerCachePath, platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(providerCachePath, platform, "terraform-provider-simple6")); err != nil {
t.Fatal(err)
}
// Assert that the test starts with the default state present from test fixtures
defaultStateId := "default"
fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, defaultStateId, "terraform.tfstate"))
if err != nil {
t.Fatalf("failed to open default workspace's state file: %s", err)
}
if fi.Size() == 0 {
t.Fatal("default workspace's state file should not have size 0 bytes")
}
//// List State: terraform state list
expectedResourceAddr := "terraform_data.my-data"
stdout, stderr, err := tf.Run("state", "list", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg := expectedResourceAddr + "\n" // This is the only resource instance in the test fixture state
if stdout != expectedMsg {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
//// Show State: terraform state show
stdout, stderr, err = tf.Run("state", "show", expectedResourceAddr, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
// show displays the state for the specified resource
expectedMsg = `# terraform_data.my-data:
resource "terraform_data" "my-data" {
id = "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c"
input = "hello world"
output = "hello world"
}
`
if diff := cmp.Diff(stdout, expectedMsg); diff != "" {
t.Errorf("wrong result, diff:\n%s", diff)
}
}
// Tests using the `terraform output` command in combination with pluggable state storage:
// > `terraform output`
// > `terraform output <name>`
func TestPrimary_stateStore_outputCmd(t *testing.T) {
if !canRunGoBuild {
// We're running in a separate-build-then-run context, so we can't
// currently execute this test which depends on being able to build
// new executable at runtime.
//
// (See the comment on canRunGoBuild's declaration for more information.)
t.Skip("can't run without building a new provider executable")
}
t.Setenv(e2e.TestExperimentFlag, "true")
tfBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform")
fixturePath := filepath.Join("testdata", "initialized-directory-with-state-store-fs")
tf := e2e.NewBinary(t, tfBin, fixturePath)
workspaceDirName := "states" // see test fixture value for workspace_dir
// In order to test integration with PSS we need a provider plugin implementing a state store.
// Here will build the simple6 (built with protocol v6) provider, which implements PSS.
simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6")
simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider)
// Move the provider binaries into the correct .terraform/providers/ directory
// that will contain provider binaries in an initialized working directory.
platform := getproviders.CurrentPlatform.String()
if err := os.MkdirAll(tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil {
t.Fatal(err)
}
// Assert that the test starts with the default state present from test fixtures
defaultStateId := "default"
fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, defaultStateId, "terraform.tfstate"))
if err != nil {
t.Fatalf("failed to open default workspace's state file: %s", err)
}
if fi.Size() == 0 {
t.Fatal("default workspace's state file should not have size 0 bytes")
}
//// List all outputs: terraform output
stdout, stderr, err := tf.Run("output", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg := "greeting = \"hello world\"\n" // See the test fixture files
if stdout != expectedMsg {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
//// View a specific output: terraform output <name>
outputName := "greeting"
stdout, stderr, err = tf.Run("output", outputName, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = "\"hello world\"\n" // Only the value is outputted, no name present
if stdout != expectedMsg {
t.Errorf("unexpected output, expected %q, but got:\n%s", expectedMsg, stdout)
}
}
// Tests using the `terraform show` command in combination with pluggable state storage
// > `terraform show`
// > `terraform show <path-to-state-file>`
// > `terraform show <path-to-plan-file>`
func TestPrimary_stateStore_showCmd(t *testing.T) {
if !canRunGoBuild {
// We're running in a separate-build-then-run context, so we can't
// currently execute this test which depends on being able to build
// new executable at runtime.
//
// (See the comment on canRunGoBuild's declaration for more information.)
t.Skip("can't run without building a new provider executable")
}
t.Setenv(e2e.TestExperimentFlag, "true")
tfBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform")
fixturePath := filepath.Join("testdata", "initialized-directory-with-state-store-fs")
tf := e2e.NewBinary(t, tfBin, fixturePath)
workspaceDirName := "states" // see test fixture value for workspace_dir
// In order to test integration with PSS we need a provider plugin implementing a state store.
// Here will build the simple6 (built with protocol v6) provider, which implements PSS.
simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6")
simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider)
// Move the provider binaries into the correct .terraform/providers/ directory
// that will contain provider binaries in an initialized working directory.
platform := getproviders.CurrentPlatform.String()
if err := os.MkdirAll(tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(".terraform/providers/registry.terraform.io/hashicorp/simple6/0.0.1/", platform, "terraform-provider-simple6")); err != nil {
t.Fatal(err)
}
// Assert that the test starts with the default state present from test fixtures
defaultStateId := "default"
fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, defaultStateId, "terraform.tfstate"))
if err != nil {
t.Fatalf("failed to open default workspace's state file: %s", err)
}
if fi.Size() == 0 {
t.Fatal("default workspace's state file should not have size 0 bytes")
}
//// Show state: terraform state
stdout, stderr, err := tf.Run("show", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg := `# terraform_data.my-data:
resource "terraform_data" "my-data" {
id = "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c"
input = "hello world"
output = "hello world"
}
Outputs:
greeting = "hello world"
` // See the test fixture folder's state file
if diff := cmp.Diff(stdout, expectedMsg); diff != "" {
t.Errorf("wrong result, diff:\n%s", diff)
}
//// Show state: terraform show <path to state file>
path := fmt.Sprintf("./%s/%s/terraform.tfstate", workspaceDirName, defaultStateId)
stdout, stderr, err = tf.Run("show", path, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
if diff := cmp.Diff(stdout, expectedMsg); diff != "" {
t.Errorf("wrong result, diff:\n%s", diff)
}
//// Show state: terraform show <path to plan file>
// 1. Create a plan file via plan command
newOutput := `output "replacement" {
value = resource.terraform_data.my-data.output
}`
if err := os.WriteFile(filepath.Join(tf.WorkDir(), "outputs.tf"), []byte(newOutput), 0644); err != nil {
t.Fatalf("err: %s", err)
}
planFile := "tfplan"
stdout, stderr, err = tf.Run("plan", fmt.Sprintf("-out=%s", planFile), "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = "Changes to Outputs"
if !strings.Contains(stdout, expectedMsg) {
t.Errorf("wrong result, expected the plan command to create a plan file but that hasn't happened, got:\n%s",
stdout,
)
}
// 2. Inspect plan file
stdout, stderr, err = tf.Run("show", planFile, "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsg = `
Changes to Outputs:
- greeting = "hello world" -> null
+ replacement = "hello world"
You can apply this plan to save these new output values to the Terraform
state, without changing any real infrastructure.
`
if diff := cmp.Diff(stdout, expectedMsg); diff != "" {
t.Errorf("wrong result, diff:\n%s", diff)
}
}
// Tests using the `terraform provider` subcommands in combination with pluggable state storage:
// > `terraform providers`
// > `terraform providers schema`
//
// Commands `terraform providers locks` and `terraform providers mirror` aren't tested as they
// don't interact with the backend.
//
// The test `TestProvidersSchema` has test coverage showing that state store schemas are present
// in the command's outputs. _This_ test is intended to assert that the command is able to read and use
// state via a state store ok, and is able to detect providers required only by the state.
func TestPrimary_stateStore_providerCmds(t *testing.T) {
if !canRunGoBuild {
// We're running in a separate-build-then-run context, so we can't
// currently execute this test which depends on being able to build
// new executable at runtime.
//
// (See the comment on canRunGoBuild's declaration for more information.)
t.Skip("can't run without building a new provider executable")
}
t.Setenv(e2e.TestExperimentFlag, "true")
terraformBin := e2e.GoBuild("github.com/hashicorp/terraform", "terraform")
fixturePath := filepath.Join("testdata", "full-workflow-with-state-store-fs")
tf := e2e.NewBinary(t, terraformBin, fixturePath)
workspaceDirName := "states" // See workspace_dir value in the configuration
// Add a state file describing a resource from the simple (v5) provider, so
// we can test that the state is read and used to get all the provider schemas
fakeState := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "simple_resource",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("simple"),
Module: addrs.RootModule,
},
)
})
fakeStateFile := &statefile.File{
Lineage: "boop",
Serial: 4,
TerraformVersion: version.Must(version.NewVersion("1.0.0")),
State: fakeState,
}
var fakeStateBuf bytes.Buffer
err := statefile.WriteForTest(fakeStateFile, &fakeStateBuf)
if err != nil {
t.Error(err)
}
fakeStateBytes := fakeStateBuf.Bytes()
if err := os.MkdirAll(tf.Path(workspaceDirName, "default"), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.WriteFile(tf.Path(workspaceDirName, "default", "terraform.tfstate"), fakeStateBytes, 0644); err != nil {
t.Fatal(err)
}
// In order to test integration with PSS we need a provider plugin implementing a state store.
// Here will build the simple6 (built with protocol v6) provider, which will be used for PSS.
// The simple (v5) provider is also built, as that provider will be present in the state and therefore
// needed for creating the schema output.
simple6Provider := filepath.Join(tf.WorkDir(), "terraform-provider-simple6")
simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider)
simpleProvider := filepath.Join(tf.WorkDir(), "terraform-provider-simple")
simpleProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple/main", simpleProvider)
// Move the provider binaries into a directory that we will point terraform
// to using the -plugin-dir cli flag.
platform := getproviders.CurrentPlatform.String()
fsMirrorPathV6 := "cache/registry.terraform.io/hashicorp/simple6/0.0.1/"
if err := os.MkdirAll(tf.Path(fsMirrorPathV6, platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(fsMirrorPathV6, platform, "terraform-provider-simple6")); err != nil {
t.Fatal(err)
}
fsMirrorPathV5 := "cache/registry.terraform.io/hashicorp/simple/0.0.1/"
if err := os.MkdirAll(tf.Path(fsMirrorPathV5, platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simpleProviderExe, tf.Path(fsMirrorPathV5, platform, "terraform-provider-simple")); err != nil {
t.Fatal(err)
}
//// Init
_, stderr, err := tf.Run("init", "-enable-pluggable-state-storage-experiment=true", "-plugin-dir=cache", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
fi, err := os.Stat(path.Join(tf.WorkDir(), workspaceDirName, "default", "terraform.tfstate"))
if err != nil {
t.Fatalf("failed to open default workspace's state file: %s", err)
}
if fi.Size() == 0 {
t.Fatal("default workspace's state file should not have size 0 bytes")
}
//// Providers: `terraform providers`
stdout, stderr, err := tf.Run("providers", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
// We expect the command to be able to use the state store to
// detect providers that come from only the state.
expectedMsgs := []string{
"Providers required by configuration:",
"provider[registry.terraform.io/hashicorp/simple6]",
"provider[terraform.io/builtin/terraform]",
"Providers required by state:",
"provider[registry.terraform.io/hashicorp/simple]",
}
for _, msg := range expectedMsgs {
if !strings.Contains(stdout, msg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", msg, stdout)
}
}
//// Provider schemas: `terraform providers schema`
stdout, stderr, err = tf.Run("providers", "schema", "-json", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}
expectedMsgs = []string{
`"registry.terraform.io/hashicorp/simple6"`, // provider used for PSS
`"terraform.io/builtin/terraform"`, // provider used for resources
`"registry.terraform.io/hashicorp/simple"`, // provider present only in the state
}
for _, msg := range expectedMsgs {
if !strings.Contains(stdout, msg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", msg, stdout)
}
}
// More thorough checking of the JSON output is in `TestProvidersSchema`.
// This test just asserts that `terraform providers schema` can read state
// via the state store, and therefore detects all 3 providers needed for the output.
}