44package e2etest
55
66import (
7+ "os"
78 "path/filepath"
89 "reflect"
910 "sort"
@@ -12,7 +13,9 @@ import (
1213
1314 "github.com/davecgh/go-spew/spew"
1415 "github.com/hashicorp/terraform/internal/e2e"
16+ "github.com/hashicorp/terraform/internal/getproviders"
1517 "github.com/hashicorp/terraform/internal/plans"
18+ "github.com/hashicorp/terraform/internal/states/statefile"
1619 "github.com/zclconf/go-cty/cty"
1720)
1821
@@ -230,3 +233,146 @@ func TestPrimaryChdirOption(t *testing.T) {
230233 t .Errorf ("incorrect destroy tally; want 0 destroyed:\n %s" , stdout )
231234 }
232235}
236+
237+ func TestPrimary_stateStore (t * testing.T ) {
238+
239+ if ! canRunGoBuild {
240+ // We're running in a separate-build-then-run context, so we can't
241+ // currently execute this test which depends on being able to build
242+ // new executable at runtime.
243+ //
244+ // (See the comment on canRunGoBuild's declaration for more information.)
245+ t .Skip ("can't run without building a new provider executable" )
246+ }
247+
248+ t .Setenv (e2e .TestExperimentFlag , "true" )
249+ terraformBin := e2e .GoBuild ("github.com/hashicorp/terraform" , "terraform" )
250+
251+ fixturePath := filepath .Join ("testdata" , "full-workflow-with-state-store-fs" )
252+ tf := e2e .NewBinary (t , terraformBin , fixturePath )
253+
254+ // In order to test integration with PSS we need a provider plugin implementing a state store.
255+ // Here will build the simple6 (built with protocol v6) provider, which implements PSS.
256+ simple6Provider := filepath .Join (tf .WorkDir (), "terraform-provider-simple6" )
257+ simple6ProviderExe := e2e .GoBuild ("github.com/hashicorp/terraform/internal/provider-simple-v6/main" , simple6Provider )
258+
259+ // Move the provider binaries into a directory that we will point terraform
260+ // to using the -plugin-dir cli flag.
261+ platform := getproviders .CurrentPlatform .String ()
262+ hashiDir := "cache/registry.terraform.io/hashicorp/"
263+ if err := os .MkdirAll (tf .Path (hashiDir , "simple6/0.0.1/" , platform ), os .ModePerm ); err != nil {
264+ t .Fatal (err )
265+ }
266+ if err := os .Rename (simple6ProviderExe , tf .Path (hashiDir , "simple6/0.0.1/" , platform , "terraform-provider-simple6" )); err != nil {
267+ t .Fatal (err )
268+ }
269+
270+ //// INIT
271+ stdout , stderr , err := tf .Run ("init" , "-enable-pluggable-state-storage-experiment=true" , "-plugin-dir=cache" , "-no-color" )
272+ if err != nil {
273+ t .Fatalf ("unexpected init error: %s\n stderr:\n %s" , err , stderr )
274+ }
275+
276+ if ! strings .Contains (stdout , "Terraform created an empty state file for the default workspace" ) {
277+ t .Errorf ("notice about creating the default workspace is missing from init output:\n %s" , stdout )
278+ }
279+
280+ //// PLAN
281+ // No separate plan step; this test lets the apply make a plan.
282+
283+ //// APPLY
284+ stdout , stderr , err = tf .Run ("apply" , "-auto-approve" , "-no-color" )
285+ if err != nil {
286+ t .Fatalf ("unexpected apply error: %s\n stderr:\n %s" , err , stderr )
287+ }
288+
289+ if ! strings .Contains (stdout , "Resources: 1 added, 0 changed, 0 destroyed" ) {
290+ t .Errorf ("incorrect apply tally; want 1 added:\n %s" , stdout )
291+ }
292+
293+ // Check the statefile saved by the fs state store.
294+ path := "terraform.tfstate.d/default/terraform.tfstate"
295+ f , err := tf .OpenFile (path )
296+ if err != nil {
297+ t .Fatalf ("unexpected error opening state file %s: %s\n stderr:\n %s" , path , err , stderr )
298+ }
299+ defer f .Close ()
300+
301+ stateFile , err := statefile .Read (f )
302+ if err != nil {
303+ t .Fatalf ("unexpected error reading statefile %s: %s\n stderr:\n %s" , path , err , stderr )
304+ }
305+
306+ r := stateFile .State .RootModule ().Resources
307+ if len (r ) != 1 {
308+ t .Fatalf ("expected state to include one resource, but got %d" , len (r ))
309+ }
310+ if _ , ok := r ["terraform_data.my-data" ]; ! ok {
311+ t .Fatalf ("expected state to include terraform_data.my-data but it's missing" )
312+ }
313+ }
314+
315+ func TestPrimary_stateStore_inMem (t * testing.T ) {
316+ if ! canRunGoBuild {
317+ // We're running in a separate-build-then-run context, so we can't
318+ // currently execute this test which depends on being able to build
319+ // new executable at runtime.
320+ //
321+ // (See the comment on canRunGoBuild's declaration for more information.)
322+ t .Skip ("can't run without building a new provider executable" )
323+ }
324+
325+ t .Setenv (e2e .TestExperimentFlag , "true" )
326+ terraformBin := e2e .GoBuild ("github.com/hashicorp/terraform" , "terraform" )
327+
328+ fixturePath := filepath .Join ("testdata" , "full-workflow-with-state-store-inmem" )
329+ tf := e2e .NewBinary (t , terraformBin , fixturePath )
330+
331+ // In order to test integration with PSS we need a provider plugin implementing a state store.
332+ // Here will build the simple6 (built with protocol v6) provider, which implements PSS.
333+ simple6Provider := filepath .Join (tf .WorkDir (), "terraform-provider-simple6" )
334+ simple6ProviderExe := e2e .GoBuild ("github.com/hashicorp/terraform/internal/provider-simple-v6/main" , simple6Provider )
335+
336+ // Move the provider binaries into a directory that we will point terraform
337+ // to using the -plugin-dir cli flag.
338+ platform := getproviders .CurrentPlatform .String ()
339+ hashiDir := "cache/registry.terraform.io/hashicorp/"
340+ if err := os .MkdirAll (tf .Path (hashiDir , "simple6/0.0.1/" , platform ), os .ModePerm ); err != nil {
341+ t .Fatal (err )
342+ }
343+ if err := os .Rename (simple6ProviderExe , tf .Path (hashiDir , "simple6/0.0.1/" , platform , "terraform-provider-simple6" )); err != nil {
344+ t .Fatal (err )
345+ }
346+
347+ //// INIT
348+ //
349+ // Note - the inmem PSS implementation means that the default workspace state created during init
350+ // is lost as soon as the command completes.
351+ stdout , stderr , err := tf .Run ("init" , "-enable-pluggable-state-storage-experiment=true" , "-plugin-dir=cache" , "-no-color" )
352+ if err != nil {
353+ t .Fatalf ("unexpected init error: %s\n stderr:\n %s" , err , stderr )
354+ }
355+
356+ if ! strings .Contains (stdout , "Terraform created an empty state file for the default workspace" ) {
357+ t .Errorf ("notice about creating the default workspace is missing from init output:\n %s" , stdout )
358+ }
359+
360+ //// PLAN
361+ // No separate plan step; this test lets the apply make a plan.
362+
363+ //// APPLY
364+ //
365+ // Note - the inmem PSS implementation means that writing to the default workspace during apply
366+ // is creating the default state file for the first time.
367+ stdout , stderr , err = tf .Run ("apply" , "-auto-approve" , "-no-color" )
368+ if err != nil {
369+ t .Fatalf ("unexpected apply error: %s\n stderr:\n %s" , err , stderr )
370+ }
371+
372+ if ! strings .Contains (stdout , "Resources: 1 added, 0 changed, 0 destroyed" ) {
373+ t .Errorf ("incorrect apply tally; want 1 added:\n %s" , stdout )
374+ }
375+
376+ // We cannot inspect state or perform a destroy here, as the state isn't persisted between steps
377+ // when we use the simple6_inmem state store.
378+ }
0 commit comments