| // Copyright IBM Corp. 2014, 2026 | |
| // SPDX-License-Identifier: BUSL-1.1 | |
| package e2etest | |
| import ( | |
| "bytes" | |
| "context" | |
| "encoding/json" | |
| "fmt" | |
| "io" | |
| "os" | |
| "path/filepath" | |
| "slices" | |
| "strings" | |
| "testing" | |
| "github.com/google/go-cmp/cmp" | |
| "github.com/hashicorp/go-hclog" | |
| "github.com/hashicorp/go-plugin" | |
| "github.com/hashicorp/terraform/internal/addrs" | |
| "github.com/hashicorp/terraform/internal/e2e" | |
| "github.com/hashicorp/terraform/internal/getproviders" | |
| "github.com/hashicorp/terraform/internal/getproviders/providerreqs" | |
| "github.com/hashicorp/terraform/internal/grpcwrap" | |
| tfplugin "github.com/hashicorp/terraform/internal/plugin6" | |
| simple "github.com/hashicorp/terraform/internal/provider-simple-v6" | |
| proto "github.com/hashicorp/terraform/internal/tfplugin6" | |
| ) | |
| // TestProviderProtocols verifies that Terraform can execute provider plugins | |
| // with both supported protocol versions. | |
| func TestProviderProtocols(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.Parallel() | |
| tf := e2e.NewBinary(t, terraformBin, "testdata/provider-plugin") | |
| // In order to do a decent end-to-end test for this case we will need a real | |
| // enough provider plugin to try to run and make sure we are able to | |
| // actually run it. Here will build the simple and simple6 (built with | |
| // protocol v6) providers. | |
| 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() | |
| 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) | |
| } | |
| if err := os.MkdirAll(tf.Path(hashiDir, "simple/0.0.1/", platform), os.ModePerm); err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.Rename(simpleProviderExe, tf.Path(hashiDir, "simple/0.0.1/", platform, "terraform-provider-simple")); err != nil { | |
| t.Fatal(err) | |
| } | |
| //// INIT | |
| _, stderr, err := tf.Run("init", "-plugin-dir=cache") | |
| if err != nil { | |
| t.Fatalf("unexpected init error: %s\nstderr:\n%s", err, stderr) | |
| } | |
| //// PLAN | |
| _, stderr, err = tf.Run("plan", "-out=tfplan") | |
| if err != nil { | |
| t.Fatalf("unexpected plan error: %s\nstderr:\n%s", err, stderr) | |
| } | |
| //// APPLY | |
| stdout, stderr, err := tf.Run("apply", "tfplan") | |
| if err != nil { | |
| t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) | |
| } | |
| if !strings.Contains(stdout, "Apply complete! Resources: 2 added, 0 changed, 0 destroyed.") { | |
| t.Fatalf("wrong output:\nstdout:%s\nstderr%s", stdout, stderr) | |
| } | |
| /// DESTROY | |
| stdout, stderr, err = tf.Run("destroy", "-auto-approve") | |
| if err != nil { | |
| t.Fatalf("unexpected apply error: %s\nstderr:\n%s", err, stderr) | |
| } | |
| if !strings.Contains(stdout, "Resources: 2 destroyed") { | |
| t.Fatalf("wrong destroy output\nstdout:%s\nstderr:%s", stdout, stderr) | |
| } | |
| } | |
| // TestProviderInstall_dev_override verifies provider plugin installation behaviour | |
| // when a dev_override is in use. | |
| func TestProviderInstall_dev_override(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 := "testdata/provider-plugin" // Reused | |
| // In temp dir create a plugin cache to be used in the test cases. | |
| // The cache is supplied to commands using the -plugin-dir init flag. | |
| // There are 4 providers total: | |
| // - simple provider with versions 1.0.0 and 2.0.0 available | |
| // - simple6 provider with versions 1.0.0 and 2.0.0 available | |
| td := t.TempDir() | |
| providerVersionOld := "1.0.0" | |
| providerVersionNew := "2.0.0" | |
| platform := getproviders.CurrentPlatform.String() | |
| absolutePathToCache := filepath.Join(td, "cache") | |
| simple5Provider := filepath.Join(td, "terraform-provider-simple") | |
| simple5ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple/main", simple5Provider) | |
| for _, v := range []string{providerVersionOld, providerVersionNew} { | |
| dir := filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple", v, platform) | |
| if err := os.MkdirAll(dir, os.ModePerm); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Create an executable copy of the simple5ProviderExe file per version in the cache dir | |
| data, err := os.ReadFile(simple5ProviderExe) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.WriteFile(filepath.Join(dir, "terraform-provider-simple"), data, 0755); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| simple6Provider := filepath.Join(td, "terraform-provider-simple6") | |
| simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider) | |
| for _, v := range []string{providerVersionOld, providerVersionNew} { | |
| dir := filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple6", v, platform) | |
| if err := os.MkdirAll(dir, os.ModePerm); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Create an executable copy of the simple6ProviderExe file per version in the cache dir | |
| data, err := os.ReadFile(simple6ProviderExe) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.WriteFile(filepath.Join(dir, "terraform-provider-simple6"), data, 0755); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| // Get hashes of 3 of the 4 providers | |
| // These are used when creating or asserting against lock files. | |
| var simple5v1_0_0Hash providerreqs.Hash | |
| var simple6v1_0_0Hash providerreqs.Hash | |
| var simple5v2_0_0Hash providerreqs.Hash | |
| var err error | |
| loc := getproviders.PackageLocalDir(filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple", providerVersionOld, platform)) | |
| if simple5v1_0_0Hash, err = getproviders.PackageHash(loc); err != nil { | |
| t.Fatal(err) | |
| } | |
| loc = getproviders.PackageLocalDir(filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple", providerVersionNew, platform)) | |
| if simple5v2_0_0Hash, err = getproviders.PackageHash(loc); err != nil { | |
| t.Fatal(err) | |
| } | |
| loc = getproviders.PackageLocalDir(filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple6", providerVersionOld, platform)) | |
| if simple6v1_0_0Hash, err = getproviders.PackageHash(loc); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Set up a reused CLI configuration file that sets simple6 as a dev_override, | |
| // Tests will use this via the TF_CLI_CONFIG_FILE environment variable. | |
| cliCfg := fmt.Sprintf(`provider_installation { | |
| dev_overrides { | |
| "hashicorp/simple6" = "%s" | |
| } | |
| # For all other providers, install them directly from their origin provider | |
| # registries as normal. If you omit this, Terraform will _only_ use | |
| # the dev_overrides block, and so no other providers will be available. | |
| direct {} | |
| } | |
| `, filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple6", providerVersionOld, platform)) | |
| cliConfigFilePath := filepath.Join(td, "dev_override.tfrc") | |
| if err := os.WriteFile(cliConfigFilePath, []byte(cliCfg), 0644); err != nil { | |
| t.Fatalf("err: %s", err) | |
| } | |
| t.Run("dev_override not installed during init when provider not present in dependency lock file", func(t *testing.T) { | |
| t.Parallel() | |
| tf := e2e.NewBinary(t, terraformBin, fixturePath) | |
| // There is no lock file present at start | |
| lockFile := filepath.Join(tf.WorkDir(), ".terraform.lock.hcl") | |
| _, err := os.Stat(lockFile) | |
| if err == nil { | |
| t.Fatal("expected error due to file not existing, got no error") | |
| } | |
| if !os.IsNotExist(err) { | |
| t.Fatalf("expected error due to file not existing, got different error: %s", err) | |
| } | |
| // The simple6 provider is a dev_override | |
| tf.AddEnv("TF_CLI_CONFIG_FILE=" + cliConfigFilePath) | |
| // The init process should succeed. | |
| stdout, stderr, err := tf.Run( | |
| "init", | |
| "-no-color", | |
| fmt.Sprintf("-plugin-dir=%s", absolutePathToCache), | |
| ) | |
| if err != nil { | |
| t.Fatalf("unexpected error: %s\nstdout: %s\nstderr: %s", err, stdout, stderr) | |
| } | |
| if got, want := stdout, "Warning: Provider development overrides are in effect"; !strings.Contains(got, want) { | |
| t.Fatalf("stdout doesn't include the warning about overridden providers\nwant: %s\n%s", want, got) | |
| } | |
| // Lockfile is created | |
| // simple provider is installed using the latest, 2.0.0 version, | |
| // but the dev_override simple6 provider is not added to the lockfile. | |
| buf, err := os.ReadFile(lockFile) | |
| if err != nil { | |
| t.Fatalf("unexpected error accessing lock file: %s", err) | |
| } | |
| buf = bytes.TrimSpace(buf) | |
| expectedLockFileContent := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "2.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, | |
| simple5v2_0_0Hash, | |
| ) | |
| if diff := cmp.Diff(expectedLockFileContent, string(buf)); diff != "" { | |
| t.Errorf("unexpected difference in lock file content: %s", diff) | |
| } | |
| }) | |
| t.Run("dev_override providers are still represented in the dependency lock file after init", func(t *testing.T) { | |
| t.Parallel() | |
| tf := e2e.NewBinary(t, terraformBin, fixturePath) | |
| // Lockfile contains both simple and simple6 providers already | |
| priorLockFile := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| } | |
| provider "registry.terraform.io/hashicorp/simple6" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, | |
| simple5v1_0_0Hash, | |
| simple6v1_0_0Hash, | |
| ) | |
| lockFile := filepath.Join(tf.WorkDir(), ".terraform.lock.hcl") | |
| if err := os.WriteFile(lockFile, []byte(priorLockFile), 0644); err != nil { | |
| t.Fatalf("error writing prior lock file: %s", err) | |
| } | |
| // The simple6 provider is a dev_override | |
| tf.AddEnv("TF_CLI_CONFIG_FILE=" + cliConfigFilePath) | |
| // The init process should succeed. | |
| stdout, stderr, err := tf.Run( | |
| "init", | |
| "-no-color", | |
| fmt.Sprintf("-plugin-dir=%s", absolutePathToCache), | |
| ) | |
| if err != nil { | |
| t.Fatalf("unexpected error: %s\nstdout: %s\nstderr: %s", err, stdout, stderr) | |
| } | |
| if got, want := stdout, "Warning: Provider development overrides are in effect"; !strings.Contains(got, want) { | |
| t.Fatalf("stdout doesn't include the warning about overridden providers\nwant: %s\n%s", want, got) | |
| } | |
| // Lockfile is unchanged despite use of a dev_override simple6 provider | |
| buf, err := os.ReadFile(lockFile) | |
| if err != nil { | |
| t.Fatalf("unexpected error accessing lock file: %s", err) | |
| } | |
| buf = bytes.TrimSpace(buf) | |
| if diff := cmp.Diff(priorLockFile, string(buf)); diff != "" { | |
| t.Fatalf("unexpected difference in lock file content: %s", diff) | |
| } | |
| }) | |
| t.Run("dev_override providers are unchanged in the dependency lock file during init -upgrade", func(t *testing.T) { | |
| t.Parallel() | |
| tf := e2e.NewBinary(t, terraformBin, fixturePath) | |
| // Lockfile contains both simple and simple6 providers already | |
| priorLockFile := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| } | |
| provider "registry.terraform.io/hashicorp/simple6" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, | |
| simple5v1_0_0Hash, | |
| simple6v1_0_0Hash, | |
| ) | |
| lockFile := filepath.Join(tf.WorkDir(), ".terraform.lock.hcl") | |
| if err := os.WriteFile(lockFile, []byte(priorLockFile), 0644); err != nil { | |
| t.Fatalf("error writing prior lock file: %s", err) | |
| } | |
| // The simple6 provider is a dev_override | |
| tf.AddEnv("TF_CLI_CONFIG_FILE=" + cliConfigFilePath) | |
| // The init -upgrade process should succeed. | |
| stdout, stderr, err := tf.Run( | |
| "init", | |
| "-no-color", | |
| "-upgrade", | |
| fmt.Sprintf("-plugin-dir=%s", absolutePathToCache), | |
| ) | |
| if err != nil { | |
| t.Fatalf("unexpected error: %s\nstdout: %s\nstderr: %s", err, stdout, stderr) | |
| } | |
| if got, want := stdout, "Warning: Provider development overrides are in effect"; !strings.Contains(got, want) { | |
| t.Fatalf("stdout doesn't include the warning about overridden providers\nwant: %s\n%s", want, got) | |
| } | |
| // Lockfile shows evidence of upgrade process | |
| // simple provider is upgraded to the newer 2.0.0 version, | |
| // but the dev_override simple6 provider is removed from the lockfile. | |
| buf, err := os.ReadFile(lockFile) | |
| if err != nil { | |
| t.Fatalf("unexpected error accessing lock file: %s", err) | |
| } | |
| buf = bytes.TrimSpace(buf) | |
| expectedLockFileContent := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "2.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| } | |
| provider "registry.terraform.io/hashicorp/simple6" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, | |
| simple5v2_0_0Hash, | |
| simple6v1_0_0Hash, // not upgraded to 2.0.0 | |
| ) | |
| if diff := cmp.Diff(expectedLockFileContent, string(buf)); diff != "" { | |
| t.Errorf("unexpected difference in lock file content: %s", diff) | |
| } | |
| }) | |
| } | |
| // TestProviderInstall_unmanaged verifies provider plugin installation behaviour | |
| // when an unmanaged provider is in use. | |
| func TestProviderInstall_unmanaged(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 := "testdata/provider-plugin" // Reused | |
| // In temp dir create a plugin cache to be used in the test cases. | |
| // The cache is supplied to commands using the -plugin-dir init flag. | |
| // There are 4 providers total: | |
| // - simple provider with versions 1.0.0 and 2.0.0 available | |
| // - simple6 provider with versions 1.0.0 and 2.0.0 available | |
| td := t.TempDir() | |
| providerVersionOld := "1.0.0" | |
| providerVersionNew := "2.0.0" | |
| platform := getproviders.CurrentPlatform.String() | |
| absolutePathToCache := filepath.Join(td, "cache") | |
| simple5Provider := filepath.Join(td, "terraform-provider-simple") | |
| simple5ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple/main", simple5Provider) | |
| for _, v := range []string{providerVersionOld, providerVersionNew} { | |
| dir := filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple", v, platform) | |
| if err := os.MkdirAll(dir, os.ModePerm); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Create an executable copy of the simple5ProviderExe file per version in the cache dir | |
| data, err := os.ReadFile(simple5ProviderExe) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.WriteFile(filepath.Join(dir, "terraform-provider-simple"), data, 0755); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| simple6Provider := filepath.Join(td, "terraform-provider-simple6") | |
| simple6ProviderExe := e2e.GoBuild("github.com/hashicorp/terraform/internal/provider-simple-v6/main", simple6Provider) | |
| for _, v := range []string{providerVersionOld, providerVersionNew} { | |
| dir := filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple6", v, platform) | |
| if err := os.MkdirAll(dir, os.ModePerm); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Create an executable copy of the simple6ProviderExe file per version in the cache dir | |
| data, err := os.ReadFile(simple6ProviderExe) | |
| if err != nil { | |
| t.Fatal(err) | |
| } | |
| if err := os.WriteFile(filepath.Join(dir, "terraform-provider-simple6"), data, 0755); err != nil { | |
| t.Fatal(err) | |
| } | |
| } | |
| // Get hashes of 3 of the 4 providers | |
| // These are used when creating or asserting against lock files. | |
| var simple5v1_0_0Hash providerreqs.Hash | |
| var simple6v1_0_0Hash providerreqs.Hash | |
| var simple5v2_0_0Hash providerreqs.Hash | |
| var err error | |
| loc := getproviders.PackageLocalDir(filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple", providerVersionOld, platform)) | |
| if simple5v1_0_0Hash, err = getproviders.PackageHash(loc); err != nil { | |
| t.Fatal(err) | |
| } | |
| loc = getproviders.PackageLocalDir(filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple", providerVersionNew, platform)) | |
| if simple5v2_0_0Hash, err = getproviders.PackageHash(loc); err != nil { | |
| t.Fatal(err) | |
| } | |
| loc = getproviders.PackageLocalDir(filepath.Join(absolutePathToCache, "registry.terraform.io/hashicorp", "simple6", providerVersionOld, platform)) | |
| if simple6v1_0_0Hash, err = getproviders.PackageHash(loc); err != nil { | |
| t.Fatal(err) | |
| } | |
| // Launch a separate simple6 provider process to be re-used as an unmanaged provider. | |
| // Tests will use this via the TF_REATTACH_PROVIDERS environment variable. | |
| reattachConfig, _ := reattachedProviderForTest(t, addrs.NewDefaultProvider("simple6"), 6) | |
| t.Run("unmanaged provider not installed when provider not present in dependency lock file", func(t *testing.T) { | |
| t.Parallel() | |
| tf := e2e.NewBinary(t, terraformBin, fixturePath) | |
| // There is no lock file present at start | |
| lockFile := filepath.Join(tf.WorkDir(), ".terraform.lock.hcl") | |
| _, err := os.Stat(lockFile) | |
| if err == nil { | |
| t.Fatal("expected error due to file not existing, got no error") | |
| } | |
| if !os.IsNotExist(err) { | |
| t.Fatalf("expected error due to file not existing, got different error: %s", err) | |
| } | |
| // The simple6 provider is unmanaged | |
| tf.AddEnv("TF_REATTACH_PROVIDERS=" + reattachConfig) | |
| // The init process should succeed. | |
| stdout, stderr, err := tf.Run( | |
| "init", | |
| "-no-color", | |
| fmt.Sprintf("-plugin-dir=%s", absolutePathToCache)) | |
| if err != nil { | |
| t.Fatalf("unexpected error: %s\nstdout: %s\nstderr: %s", err, stdout, stderr) | |
| } | |
| if got, want := stdout, "Warning: Unmanaged providers are in effect"; !strings.Contains(got, want) { | |
| t.Fatalf("stdout doesn't include the warning about unmanaged providers\nwant: %s\n%s", want, got) | |
| } | |
| // Lock file should have been created | |
| buf, err := os.ReadFile(lockFile) | |
| if err != nil { | |
| t.Fatalf("unexpected error accessing lock file: %s", err) | |
| } | |
| buf = bytes.TrimSpace(buf) | |
| // We expect the lock file to not contain the simple6 provider that's being unmanaged, | |
| // because that provider is skipped during the installation process. | |
| // The simple (v5) provider is installed as usual, pulling in the latest version. | |
| expectedLockFileContent := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "2.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, simple5v2_0_0Hash) | |
| if diff := cmp.Diff(expectedLockFileContent, string(buf)); diff != "" { | |
| t.Errorf("unexpected difference in lock file content: %s", diff) | |
| } | |
| }) | |
| t.Run("unmanaged providers are still represented in the dependency lock file after init", func(t *testing.T) { | |
| t.Parallel() | |
| tf := e2e.NewBinary(t, terraformBin, fixturePath) | |
| // Lockfile contains both simple and simple6 providers already | |
| priorLockFile := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| } | |
| provider "registry.terraform.io/hashicorp/simple6" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, | |
| simple5v1_0_0Hash, | |
| simple6v1_0_0Hash, | |
| ) | |
| lockFile := filepath.Join(tf.WorkDir(), ".terraform.lock.hcl") | |
| if err := os.WriteFile(lockFile, []byte(priorLockFile), 0644); err != nil { | |
| t.Fatalf("error writing prior lock file: %s", err) | |
| } | |
| // The simple6 provider is unmanaged | |
| tf.AddEnv("TF_REATTACH_PROVIDERS=" + reattachConfig) | |
| // The init process should succeed. | |
| stdout, stderr, err := tf.Run( | |
| "init", | |
| "-no-color", | |
| fmt.Sprintf("-plugin-dir=%s", absolutePathToCache)) | |
| if err != nil { | |
| t.Fatalf("unexpected error: %s\nstdout: %s\nstderr: %s", err, stdout, stderr) | |
| } | |
| if got, want := stdout, "Warning: Unmanaged providers are in effect"; !strings.Contains(got, want) { | |
| t.Fatalf("stdout doesn't include the warning about unmanaged providers\nwant: %s\n%s", want, got) | |
| } | |
| // Lockfile is unchanged despite use of an unmanaged simple6 provider | |
| buf, err := os.ReadFile(lockFile) | |
| if err != nil { | |
| t.Fatalf("unexpected error accessing lock file: %s", err) | |
| } | |
| buf = bytes.TrimSpace(buf) | |
| if diff := cmp.Diff(priorLockFile, string(buf)); diff != "" { | |
| t.Fatalf("unexpected difference in lock file content: %s", diff) | |
| } | |
| }) | |
| t.Run("unmanaged providers are unchanged in the dependency lock file during init -upgrade", func(t *testing.T) { | |
| t.Parallel() | |
| tf := e2e.NewBinary(t, terraformBin, fixturePath) | |
| // Lockfile contains both simple and simple6 providers already at older version 1.0.0 | |
| priorLockFile := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| } | |
| provider "registry.terraform.io/hashicorp/simple6" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, | |
| simple5v1_0_0Hash, | |
| simple6v1_0_0Hash, | |
| ) | |
| lockFile := filepath.Join(tf.WorkDir(), ".terraform.lock.hcl") | |
| if err := os.WriteFile(lockFile, []byte(priorLockFile), 0644); err != nil { | |
| t.Fatalf("error writing prior lock file: %s", err) | |
| } | |
| // The simple6 provider is unmanaged | |
| tf.AddEnv("TF_REATTACH_PROVIDERS=" + reattachConfig) | |
| // The init -upgrade process should succeed. | |
| stdout, stderr, err := tf.Run( | |
| "init", | |
| "-upgrade", | |
| "-no-color", | |
| fmt.Sprintf("-plugin-dir=%s", absolutePathToCache)) | |
| if err != nil { | |
| t.Fatalf("unexpected error: %s\nstdout: %s\nstderr: %s", err, stdout, stderr) | |
| } | |
| if got, want := stdout, "Warning: Unmanaged providers are in effect"; !strings.Contains(got, want) { | |
| t.Fatalf("stdout doesn't include the warning about unmanaged providers\nwant: %s\n%s", want, got) | |
| } | |
| // Lockfile shows evidence of upgrade process | |
| // simple provider is upgraded to the newer 2.0.0 version, | |
| // but the unmanaged simple6 provider is unchanged due to being unmanaged. | |
| buf, err := os.ReadFile(lockFile) | |
| if err != nil { | |
| t.Fatalf("unexpected error accessing lock file: %s", err) | |
| } | |
| buf = bytes.TrimSpace(buf) | |
| expectedLockFileContent := fmt.Sprintf(`# This file is maintained automatically by "terraform init". | |
| # Manual edits may be lost in future updates. | |
| provider "registry.terraform.io/hashicorp/simple" { | |
| version = "2.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| } | |
| provider "registry.terraform.io/hashicorp/simple6" { | |
| version = "1.0.0" | |
| hashes = [ | |
| "%s", | |
| ] | |
| }`, | |
| simple5v2_0_0Hash, | |
| simple6v1_0_0Hash, // not upgraded to 2.0.0 | |
| ) | |
| if diff := cmp.Diff(expectedLockFileContent, string(buf)); diff != "" { | |
| t.Errorf("unexpected difference in lock file content: %s", diff) | |
| } | |
| }) | |
| } | |
| // reattachConfigForTest launches a provider process and returns a reattach config string | |
| // that can be used as the value for the TF_REATTACH_PROVIDERS environment variable in tests. | |
| // Cleanup of the provider process is handled internally. | |
| func reattachedProviderForTest(t *testing.T, provider addrs.Provider, protocol int) (string, *providerServer) { | |
| t.Helper() | |
| if !slices.Contains([]int{5, 6}, protocol) { | |
| t.Fatalf("test setup tried to create a provider using protocol version %d, which is unsupported. Choose between 5 and 6.", protocol) | |
| } | |
| reattachCh := make(chan *plugin.ReattachConfig) | |
| closeCh := make(chan struct{}) | |
| server := &providerServer{ | |
| ProviderServer: grpcwrap.Provider6(simple.Provider()), | |
| } | |
| ctx, cancel := context.WithCancel(context.Background()) | |
| t.Cleanup(func() { | |
| cancel() | |
| <-closeCh | |
| }) | |
| 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 server | |
| }, | |
| }, | |
| }, | |
| }, | |
| }) | |
| config := <-reattachCh | |
| if config == nil { | |
| t.Fatalf("no reattach config received") | |
| } | |
| reattachStr, err := json.Marshal(map[string]reattachConfig{ | |
| provider.String(): { | |
| 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) | |
| } | |
| return string(reattachStr), server | |
| } |