-
Notifications
You must be signed in to change notification settings - Fork 10.1k
PSS : Add fs and inmem state storage implementations to the builtin simplev6 provider, update grpcwrap package, use PSS implementation in E2E test
#37790
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
60e8997
306d896
9f104fa
b79de8d
f54f95b
a3ffebd
c4e0274
759df39
c6cea63
320fa96
992eb53
eeaaac8
099b206
40b9930
4705e52
bd0fa59
5f0581f
b986709
2146307
0cc10c5
9979fe1
316d5b5
9088fe5
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 |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| // Copyright (c) HashiCorp, Inc. | ||
| // SPDX-License-Identifier: BUSL-1.1 | ||
|
|
||
| package pluggable | ||
|
|
||
| const ( | ||
| // DefaultStateStoreChunkSize is the default chunk size proposed | ||
| // to the provider. | ||
| // This can be tweaked but should provide reasonable performance | ||
| // trade-offs for average network conditions and state file sizes. | ||
| DefaultStateStoreChunkSize int64 = 8 << 20 // 8 MB | ||
|
|
||
| // MaxStateStoreChunkSize is the highest chunk size provider may choose | ||
| // which we still consider reasonable/safe. | ||
| // This reflects terraform-plugin-go's max. RPC message size of 256MB | ||
| // and leaves plenty of space for other variable data like diagnostics. | ||
| MaxStateStoreChunkSize int64 = 128 << 20 // 128 MB | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -4,6 +4,7 @@ | |
| package e2etest | ||
|
|
||
| import ( | ||
| "os" | ||
| "path/filepath" | ||
| "reflect" | ||
| "sort" | ||
|
|
@@ -12,7 +13,9 @@ import ( | |
|
|
||
| "github.com/davecgh/go-spew/spew" | ||
| "github.com/hashicorp/terraform/internal/e2e" | ||
| "github.com/hashicorp/terraform/internal/getproviders" | ||
| "github.com/hashicorp/terraform/internal/plans" | ||
| "github.com/hashicorp/terraform/internal/states/statefile" | ||
| "github.com/zclconf/go-cty/cty" | ||
| ) | ||
|
|
||
|
|
@@ -230,3 +233,149 @@ func TestPrimaryChdirOption(t *testing.T) { | |
| t.Errorf("incorrect destroy tally; want 0 destroyed:\n%s", stdout) | ||
| } | ||
| } | ||
|
|
||
| // Requires TF_TEST_EXPERIMENTS to be set in the environment | ||
| func TestPrimary_stateStore(t *testing.T) { | ||
| if v := os.Getenv("TF_TEST_EXPERIMENTS"); v == "" { | ||
| t.Skip("can't run without enabling experiments in the executable terraform binary, enable with TF_TEST_EXPERIMENTS=1") | ||
| } | ||
|
Comment on lines
+239
to
+241
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. What is the rationale behind introducing this variable if we always set it to true in CI? Is there a particular reason we cannot or should not always run these tests and just gate them the same way as we gate other E2E tests (i.e. via One might say "but we do not need network access here" but I assume to build Terraform we'll always need network to pull all the dependencies or to download the ready binary from somewhere. 🤷🏻 |
||
|
|
||
| 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.Parallel() | ||
|
|
||
| tf := e2e.NewBinary(t, terraformBin, "testdata/full-workflow-with-state-store-fs") | ||
|
|
||
| // 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 | ||
| stdout, stderr, err := tf.Run("init", "-enable-pluggable-state-storage-experiment=true", "-plugin-dir=cache", "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
|
|
||
| if !strings.Contains(stdout, "Terraform created an empty state file for the default workspace") { | ||
| t.Errorf("notice about creating the default workspace is missing from init output:\n%s", stdout) | ||
| } | ||
|
|
||
| //// PLAN | ||
| // No separate plan step; this test lets the apply make a plan. | ||
|
|
||
| //// APPLY | ||
| stdout, stderr, err = tf.Run("apply", "-auto-approve", "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
|
|
||
| if !strings.Contains(stdout, "Resources: 1 added, 0 changed, 0 destroyed") { | ||
| t.Errorf("incorrect apply tally; want 1 added:\n%s", stdout) | ||
| } | ||
|
|
||
| // Check the statefile saved by the fs state store. | ||
| path := "terraform.tfstate.d/default/terraform.tfstate" | ||
| f, err := tf.OpenFile(path) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error opening state file %s: %s\nstderr:\n%s", path, err, stderr) | ||
| } | ||
| defer f.Close() | ||
|
|
||
| stateFile, err := statefile.Read(f) | ||
| if err != nil { | ||
| t.Fatalf("unexpected error reading statefile %s: %s\nstderr:\n%s", path, err, stderr) | ||
| } | ||
|
|
||
| r := stateFile.State.RootModule().Resources | ||
| if len(r) != 1 { | ||
| t.Fatalf("expected state to include one resource, but got %d", len(r)) | ||
| } | ||
| if _, ok := r["terraform_data.my-data"]; !ok { | ||
| t.Fatalf("expected state to include terraform_data.my-data but it's missing") | ||
| } | ||
| } | ||
|
|
||
| // Requires TF_TEST_EXPERIMENTS to be set in the environment | ||
| func TestPrimary_stateStore_inMem(t *testing.T) { | ||
| if v := os.Getenv("TF_TEST_EXPERIMENTS"); v == "" { | ||
| t.Skip("can't run without enabling experiments in the executable terraform binary, enable with TF_TEST_EXPERIMENTS=1") | ||
| } | ||
|
|
||
| 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.Parallel() | ||
|
|
||
| tf := e2e.NewBinary(t, terraformBin, "testdata/full-workflow-with-state-store-inmem") | ||
|
|
||
| // 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 | ||
| // | ||
| // Note - the inmem PSS implementation means that the default workspace state created during init | ||
| // is lost as soon as the command completes. | ||
| stdout, stderr, err := tf.Run("init", "-enable-pluggable-state-storage-experiment=true", "-plugin-dir=cache", "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
|
|
||
| if !strings.Contains(stdout, "Terraform created an empty state file for the default workspace") { | ||
| t.Errorf("notice about creating the default workspace is missing from init output:\n%s", stdout) | ||
| } | ||
|
|
||
| //// PLAN | ||
| // No separate plan step; this test lets the apply make a plan. | ||
|
|
||
| //// APPLY | ||
| // | ||
| // Note - the inmem PSS implementation means that writing to the default workspace during apply | ||
| // is creating the default state file for the first time. | ||
| stdout, stderr, err = tf.Run("apply", "-auto-approve", "-no-color") | ||
| if err != nil { | ||
| t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) | ||
| } | ||
|
|
||
| if !strings.Contains(stdout, "Resources: 1 added, 0 changed, 0 destroyed") { | ||
| t.Errorf("incorrect apply tally; want 1 added:\n%s", stdout) | ||
| } | ||
|
|
||
| // We cannot inspect state or perform a destroy here, as the state isn't persisted between steps | ||
| // when we use the simple6_inmem state store. | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| terraform { | ||
| required_providers { | ||
| simple6 = { | ||
| source = "registry.terraform.io/hashicorp/simple6" | ||
| } | ||
| } | ||
|
|
||
| state_store "simple6_fs" { | ||
| provider "simple6" {} | ||
| } | ||
| } | ||
|
|
||
| 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,23 @@ | ||
| terraform { | ||
| required_providers { | ||
| simple6 = { | ||
| source = "registry.terraform.io/hashicorp/simple6" | ||
| } | ||
| } | ||
|
|
||
| state_store "simple6_inmem" { | ||
| provider "simple6" {} | ||
| } | ||
| } | ||
|
|
||
| variable "name" { | ||
| default = "world" | ||
| } | ||
|
|
||
| resource "terraform_data" "my-data" { | ||
| input = "hello ${var.name}" | ||
| } | ||
|
|
||
| output "greeting" { | ||
| value = resource.terraform_data.my-data.output | ||
| } |
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.
I realised that we never sent the default chunk size data from Core to the provider at the start of chunk size negotiation 😬