Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions internal/command/e2etest/pluggable_state_store_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -198,3 +198,89 @@ resource "terraform_data" "my-data" {
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.
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

// 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()
hashiDir := "cache/registry.terraform.io/hashicorp/"
if err := os.MkdirAll(tf.Path(hashiDir, "simple6/0.0.1/", platform), os.ModePerm); err != nil {
t.Fatal(err)
}
if err := os.Rename(simple6ProviderExe, tf.Path(hashiDir, "simple6/0.0.1/", 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")
}

//// Providers: `terraform providers`
stdout, stderr, err := tf.Run("providers", "-no-color")
if err != nil {
t.Fatalf("unexpected error: %s\nstderr:\n%s", err, stderr)
}

expectedMsgs := []string{
"├── provider[registry.terraform.io/hashicorp/simple6]",
"└── provider[terraform.io/builtin/terraform]",
}
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{
`{"format_version":"1.0","provider_schemas":{`, // opening of JSON
`"registry.terraform.io/hashicorp/simple6":{`, // provider 1
`"terraform.io/builtin/terraform":{`, // provider 2
}
for _, msg := range expectedMsgs {
if !strings.Contains(stdout, msg) {
t.Errorf("unexpected output, expected %q, but got:\n%s", msg, stdout)
}
}
}
82 changes: 81 additions & 1 deletion internal/command/providers_schema_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,25 @@
package command

import (
"bytes"
"encoding/json"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"strings"
"testing"

"github.com/google/go-cmp/cmp"
"github.com/hashicorp/cli"
"github.com/zclconf/go-cty/cty"

"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/configs/configschema"
"github.com/hashicorp/terraform/internal/providers"
testing_provider "github.com/hashicorp/terraform/internal/providers/testing"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
)

func TestProvidersSchema_error(t *testing.T) {
Expand All @@ -37,7 +42,7 @@ func TestProvidersSchema_error(t *testing.T) {

func TestProvidersSchema_output(t *testing.T) {
fixtureDir := "testdata/providers-schema"
testDirs, err := ioutil.ReadDir(fixtureDir)
testDirs, err := os.ReadDir(fixtureDir)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -103,6 +108,81 @@ func TestProvidersSchema_output(t *testing.T) {
}
}

func TestProvidersSchema_output_withStateStore(t *testing.T) {
// State with a 'baz' provider not in the config
originalState := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "baz_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("baz"),
Module: addrs.RootModule,
},
)
})

// Create a temporary working directory that is empty
td := t.TempDir()
testCopyDir(t, testFixturePath("state-store-unchanged"), td)
t.Chdir(td)

// Get bytes describing the state
var stateBuf bytes.Buffer
if err := statefile.Write(statefile.New(originalState, "", 1), &stateBuf); err != nil {
t.Fatalf("error during test setup: %s", err)
}

// Create a mock that contains a persisted "default" state that uses the bytes from above.
mockProvider := mockPluggableStateStorageProvider()
mockProvider.MockStates = map[string]interface{}{
"default": stateBuf.Bytes(),
}
mockProviderAddressTest := addrs.NewDefaultProvider("test")

// Mock for the provider in the state
mockProviderAddressBaz := addrs.NewDefaultProvider("baz")

ui := new(cli.MockUi)
c := &ProvidersSchemaCommand{
Meta: Meta{
Ui: ui,
AllowExperimentalFeatures: true,
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
mockProviderAddressTest: providers.FactoryFixed(mockProvider),
mockProviderAddressBaz: providers.FactoryFixed(mockProvider),
},
},
},
}

args := []string{"-json"}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}

wantOutput := []string{
`{"format_version":"1.0","provider_schemas":{`, // Opening of JSON
`"registry.terraform.io/hashicorp/baz":{`, // provider from state
`"registry.terraform.io/hashicorp/test":{`, // provider from config
}

output := ui.OutputWriter.String()
for _, want := range wantOutput {
if !strings.Contains(output, want) {
t.Errorf("output missing %s:\n%s", want, output)
}
}

}

type providerSchemas struct {
FormatVersion string `json:"format_version"`
Schemas map[string]providerSchema `json:"provider_schemas"`
Expand Down
76 changes: 76 additions & 0 deletions internal/command/providers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@
package command

import (
"bytes"
"os"
"strings"
"testing"

"github.com/hashicorp/cli"
"github.com/hashicorp/terraform/internal/addrs"
"github.com/hashicorp/terraform/internal/providers"
"github.com/hashicorp/terraform/internal/states"
"github.com/hashicorp/terraform/internal/states/statefile"
)

func TestProviders(t *testing.T) {
Expand Down Expand Up @@ -203,3 +208,74 @@ func TestProviders_tests(t *testing.T) {
}
}
}

func TestProviders_state_withStateStore(t *testing.T) {
// State with a 'baz' provider not in the config
originalState := states.BuildState(func(s *states.SyncState) {
s.SetResourceInstanceCurrent(
addrs.Resource{
Mode: addrs.ManagedResourceMode,
Type: "baz_instance",
Name: "foo",
}.Instance(addrs.NoKey).Absolute(addrs.RootModuleInstance),
&states.ResourceInstanceObjectSrc{
AttrsJSON: []byte(`{"id":"bar"}`),
Status: states.ObjectReady,
},
addrs.AbsProviderConfig{
Provider: addrs.NewDefaultProvider("baz"),
Module: addrs.RootModule,
},
)
})

// Create a temporary working directory that is empty
td := t.TempDir()
testCopyDir(t, testFixturePath("state-store-unchanged"), td)
t.Chdir(td)

// Get bytes describing the state
var stateBuf bytes.Buffer
if err := statefile.Write(statefile.New(originalState, "", 1), &stateBuf); err != nil {
t.Fatalf("error during test setup: %s", err)
}

// Create a mock that contains a persisted "default" state that uses the bytes from above.
mockProvider := mockPluggableStateStorageProvider()
mockProvider.MockStates = map[string]interface{}{
"default": stateBuf.Bytes(),
}
mockProviderAddress := addrs.NewDefaultProvider("test")

ui := new(cli.MockUi)
c := &ProvidersCommand{
Meta: Meta{
Ui: ui,
AllowExperimentalFeatures: true,
testingOverrides: &testingOverrides{
Providers: map[addrs.Provider]providers.Factory{
mockProviderAddress: providers.FactoryFixed(mockProvider),
},
},
},
}

args := []string{}
if code := c.Run(args); code != 0 {
t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String())
}

wantOutput := []string{
"Providers required by configuration:",
"└── provider[registry.terraform.io/hashicorp/test] 1.2.3",
"Providers required by state:",
"provider[registry.terraform.io/hashicorp/baz]",
}

output := ui.OutputWriter.String()
for _, want := range wantOutput {
if !strings.Contains(output, want) {
t.Errorf("output missing %s:\n%s", want, output)
}
}
}