-
Notifications
You must be signed in to change notification settings - Fork 10.1k
PSS: Add tests showing state subcommands being used with PSS
#37891
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0548a42
4a3ed2b
1670449
2874ba3
fb446c0
db2b662
ac088bf
85b4a83
cebf4a5
38abe77
6567d88
deda57e
a6b6c1a
ea418bc
8248ba0
bd6f62e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -11,10 +11,12 @@ import ( | |
| "strings" | ||
| "testing" | ||
|
|
||
| "github.com/google/go-cmp/cmp" | ||
| "github.com/hashicorp/terraform/internal/e2e" | ||
| "github.com/hashicorp/terraform/internal/getproviders" | ||
| ) | ||
|
|
||
| // 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 | ||
|
|
@@ -119,3 +121,79 @@ func TestPrimary_stateStore_workspaceCmd(t *testing.T) { | |
| 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 directory .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 { | ||
|
Comment on lines
+155
to
+158
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nitpick: It would be nice to factor out the path as a variable as it's quite a long one and it's being repeated here. |
||
| 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) | ||
| } | ||
| } | ||
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
|
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Note: the test will still need to build the appropriate binary and put it in a folder here that's named appropriately for the platform that is running the test.
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Nit: I thought that |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| "version": 3, | ||
| "terraform_version": "1.15.0", | ||
| "state_store": { | ||
| "type": "simple6_fs", | ||
| "provider": { | ||
| "version": "0.0.1", | ||
| "source": "registry.terraform.io/hashicorp/simple6", | ||
| "config": {} | ||
| }, | ||
| "config": { | ||
| "workspace_dir": "states" | ||
| }, | ||
| "hash": 3942813381 | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| terraform { | ||
| required_providers { | ||
| simple6 = { | ||
| source = "registry.terraform.io/hashicorp/simple6" | ||
| } | ||
| } | ||
|
|
||
| state_store "simple6_fs" { | ||
| provider "simple6" {} | ||
|
|
||
| workspace_dir = "states" | ||
| } | ||
| } | ||
|
|
||
| variable "name" { | ||
| default = "world" | ||
| } | ||
|
|
||
| resource "terraform_data" "my-data" { | ||
| input = "hello ${var.name}" | ||
| } | ||
|
|
||
| output "greeting" { | ||
| value = resource.terraform_data.my-data.output | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| { | ||
| "version": 4, | ||
| "terraform_version": "1.15.0", | ||
| "serial": 1, | ||
| "lineage": "9e13d881-e480-7a63-d47a-b4f5224e6743", | ||
| "outputs": { | ||
| "greeting": { | ||
| "value": "hello world", | ||
| "type": "string" | ||
| } | ||
| }, | ||
| "resources": [ | ||
| { | ||
| "mode": "managed", | ||
| "type": "terraform_data", | ||
| "name": "my-data", | ||
| "provider": "provider[\"terraform.io/builtin/terraform\"]", | ||
| "instances": [ | ||
| { | ||
| "schema_version": 0, | ||
| "attributes": { | ||
| "id": "d71fb368-2ba1-fb4c-5bd9-6a2b7f05d60c", | ||
| "input": { | ||
| "value": "hello world", | ||
| "type": "string" | ||
| }, | ||
| "output": { | ||
| "value": "hello world", | ||
| "type": "string" | ||
| }, | ||
| "triggers_replace": null | ||
| }, | ||
| "sensitive_attributes": [], | ||
| "identity_schema_version": 0 | ||
| } | ||
| ] | ||
| } | ||
| ], | ||
| "check_results": null | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -3238,7 +3238,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { | |
| testCopyDir(t, testFixturePath("init-with-state-store"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
| // The test fixture config has no version constraints, so the latest version will | ||
|
|
@@ -3325,7 +3325,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { | |
| testCopyDir(t, testFixturePath("init-with-state-store"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
| "hashicorp/test": {"1.0.0"}, | ||
|
|
@@ -3374,7 +3374,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { | |
| testCopyDir(t, testFixturePath("init-with-state-store"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
| "hashicorp/test": {"1.0.0"}, | ||
|
|
@@ -3429,7 +3429,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { | |
| customWorkspace := "my-custom-workspace" | ||
| t.Setenv(WorkspaceNameEnvVar, customWorkspace) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
| "hashicorp/test": {"1.0.0"}, | ||
|
|
@@ -3492,7 +3492,7 @@ func TestInit_stateStore_newWorkingDir(t *testing.T) { | |
| testCopyDir(t, testFixturePath("init-with-state-store"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProvider.GetStatesResponse = &providers.GetStatesResponse{ | ||
| States: []string{ | ||
| "foobar1", | ||
|
|
@@ -3583,7 +3583,7 @@ func TestInit_stateStore_configUnchanged(t *testing.T) { | |
| testCopyDir(t, testFixturePath("state-store-unchanged"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| // If the working directory was previously initialized successfully then at least | ||
| // one workspace is guaranteed to exist when a user is re-running init with no config | ||
| // changes since last init. So this test says `default` exists. | ||
|
|
@@ -3670,11 +3670,11 @@ func TestInit_stateStore_configChanges(t *testing.T) { | |
| testCopyDir(t, testFixturePath("state-store-changed/store-config"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
|
|
||
| // The previous init implied by this test scenario would have created this. | ||
| mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} | ||
| mockProvider.MockStates = map[string]interface{}{"default": true} | ||
| mockProvider.MockStates = map[string]interface{}{"default": []byte(`{"version": 4,"terraform_version":"1.15.0","serial": 1,"lineage": "","outputs": {},"resources": [],"checks":[]}`)} | ||
|
|
||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
|
|
@@ -3758,7 +3758,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { | |
| testCopyDir(t, testFixturePath("state-store-changed/store-config"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. | ||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
|
|
@@ -3808,7 +3808,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { | |
| testCopyDir(t, testFixturePath("state-store-changed/provider-config"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. | ||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
|
|
@@ -3857,7 +3857,7 @@ func TestInit_stateStore_configChanges(t *testing.T) { | |
| testCopyDir(t, testFixturePath("state-store-changed/state-store-type"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| storeName := "test_store" | ||
| otherStoreName := "test_otherstore" | ||
| // Make the provider report that it contains a 2nd storage implementation with the above name | ||
|
|
@@ -3909,11 +3909,11 @@ func TestInit_stateStore_configChanges(t *testing.T) { | |
| testCopyDir(t, testFixturePath("state-store-changed/provider-used"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProvider.GetStatesResponse = &providers.GetStatesResponse{States: []string{"default"}} // The previous init implied by this test scenario would have created the default workspace. | ||
|
|
||
| // Make a mock that implies its name is test2 based on returned schemas | ||
| mockProvider2 := mockPluggableStateStorageProvider() | ||
| mockProvider2 := mockPluggableStateStorageProvider(t) | ||
| mockProvider2.GetProviderSchemaResponse.StateStores["test2_store"] = mockProvider.GetProviderSchemaResponse.StateStores["test_store"] | ||
| delete(mockProvider2.GetProviderSchemaResponse.StateStores, "test_store") | ||
|
|
||
|
|
@@ -3974,7 +3974,7 @@ func TestInit_stateStore_providerUpgrade(t *testing.T) { | |
| testCopyDir(t, testFixturePath("state-store-changed/provider-upgraded"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| mockProviderAddress := addrs.NewDefaultProvider("test") | ||
| providerSource, close := newMockProviderSource(t, map[string][]string{ | ||
| "hashicorp/test": {"1.2.3", "9.9.9"}, // 1.2.3 is the version used in the backend state file, 9.9.9 is the version being upgraded to | ||
|
|
@@ -4023,7 +4023,7 @@ func TestInit_stateStore_unset(t *testing.T) { | |
| testCopyDir(t, testFixturePath("init-state-store"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| storeName := "test_store" | ||
| otherStoreName := "test_otherstore" | ||
| // Make the provider report that it contains a 2nd storage implementation with the above name | ||
|
|
@@ -4121,7 +4121,7 @@ func TestInit_stateStore_unset_withoutProviderRequirements(t *testing.T) { | |
| testCopyDir(t, testFixturePath("init-state-store"), td) | ||
| t.Chdir(td) | ||
|
|
||
| mockProvider := mockPluggableStateStorageProvider() | ||
| mockProvider := mockPluggableStateStorageProvider(t) | ||
| storeName := "test_store" | ||
| otherStoreName := "test_otherstore" | ||
| // Make the provider report that it contains a 2nd storage implementation with the above name | ||
|
|
@@ -4355,7 +4355,7 @@ func expectedPackageInstallPath(name, version string, exe bool) string { | |
| )) | ||
| } | ||
|
|
||
| func mockPluggableStateStorageProvider() *testing_provider.MockProvider { | ||
| func mockPluggableStateStorageProvider(t *testing.T) *testing_provider.MockProvider { | ||
| // Create a mock provider to use for PSS | ||
| // Get mock provider factory to be used during init | ||
| // | ||
|
|
@@ -4371,8 +4371,17 @@ func mockPluggableStateStorageProvider() *testing_provider.MockProvider { | |
| }, | ||
| }, | ||
| }, | ||
| DataSources: map[string]providers.Schema{}, | ||
| ResourceTypes: map[string]providers.Schema{}, | ||
| DataSources: map[string]providers.Schema{}, | ||
| ResourceTypes: map[string]providers.Schema{ | ||
| "test_instance": { | ||
| Body: &configschema.Block{ | ||
| Attributes: map[string]*configschema.Attribute{ | ||
| "input": {Type: cty.String, Optional: true}, | ||
| "id": {Type: cty.String, Computed: true}, | ||
| }, | ||
| }, | ||
| }, | ||
| }, | ||
| ListResourceTypes: map[string]providers.Schema{}, | ||
| StateStores: map[string]providers.Schema{ | ||
| pssName: { | ||
|
|
@@ -4412,8 +4421,14 @@ func mockPluggableStateStorageProvider() *testing_provider.MockProvider { | |
| mock.ReadStateBytesFn = func(req providers.ReadStateBytesRequest) providers.ReadStateBytesResponse { | ||
| state := []byte{} | ||
| if v, exist := mock.MockStates[req.StateId]; exist { | ||
| if s, ok := v.([]byte); ok { | ||
| s, ok := v.([]byte) | ||
| if ok { | ||
| state = s | ||
| } else { | ||
| // Test setup is incorrect if this happens | ||
| t.Logf("Warning: The mock provider is set up to return state bytes from the MockStates map, but the mock encountered a value that wasn't a slice of bytes: %#v", | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm afraid a |
||
| mock.MockStates, | ||
| ) | ||
| } | ||
| } | ||
| return providers.ReadStateBytesResponse{ | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why are we setting this to true? I feel like that defeats the original purpose of the flag/variable, which is to avoid running E2E tests by default during
go test ./...