github.com/opentofu/opentofu@v1.7.1/internal/command/e2etest/init_test.go (about)

     1  // Copyright (c) The OpenTofu Authors
     2  // SPDX-License-Identifier: MPL-2.0
     3  // Copyright (c) 2023 HashiCorp, Inc.
     4  // SPDX-License-Identifier: MPL-2.0
     5  
     6  package e2etest
     7  
     8  import (
     9  	"bytes"
    10  	"encoding/json"
    11  	"fmt"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"strings"
    16  	"testing"
    17  
    18  	"github.com/google/go-cmp/cmp"
    19  
    20  	"github.com/opentofu/opentofu/internal/e2e"
    21  )
    22  
    23  func TestInitProviders(t *testing.T) {
    24  	t.Parallel()
    25  
    26  	// This test reaches out to registry.opentofu.org to download the
    27  	// template provider, so it can only run if network access is allowed.
    28  	// We intentionally don't try to stub this here, because there's already
    29  	// a stubbed version of this in the "command" package and so the goal here
    30  	// is to test the interaction with the real repository.
    31  	skipIfCannotAccessNetwork(t)
    32  
    33  	fixturePath := filepath.Join("testdata", "template-provider")
    34  	tf := e2e.NewBinary(t, tofuBin, fixturePath)
    35  
    36  	stdout, stderr, err := tf.Run("init")
    37  	if err != nil {
    38  		t.Errorf("unexpected error: %s", err)
    39  	}
    40  
    41  	if stderr != "" {
    42  		t.Errorf("unexpected stderr output:\n%s", stderr)
    43  	}
    44  
    45  	if !strings.Contains(stdout, "OpenTofu has been successfully initialized!") {
    46  		t.Errorf("success message is missing from output:\n%s", stdout)
    47  	}
    48  
    49  	if !strings.Contains(stdout, "- Installing hashicorp/template v") {
    50  		t.Errorf("provider download message is missing from output:\n%s", stdout)
    51  		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
    52  	}
    53  
    54  	if !strings.Contains(stdout, "OpenTofu has created a lock file") {
    55  		t.Errorf("lock file notification is missing from output:\n%s", stdout)
    56  	}
    57  
    58  }
    59  
    60  func TestInitProvidersInternal(t *testing.T) {
    61  	t.Parallel()
    62  
    63  	// This test should _not_ reach out anywhere because the "terraform"
    64  	// provider is internal to the core tofu binary.
    65  
    66  	t.Run("output in human readable format", func(t *testing.T) {
    67  		fixturePath := filepath.Join("testdata", "tf-provider")
    68  		tf := e2e.NewBinary(t, tofuBin, fixturePath)
    69  
    70  		stdout, stderr, err := tf.Run("init")
    71  		if err != nil {
    72  			t.Errorf("unexpected error: %s", err)
    73  		}
    74  
    75  		if stderr != "" {
    76  			t.Errorf("unexpected stderr output:\n%s", stderr)
    77  		}
    78  
    79  		if !strings.Contains(stdout, "OpenTofu has been successfully initialized!") {
    80  			t.Errorf("success message is missing from output:\n%s", stdout)
    81  		}
    82  
    83  		if strings.Contains(stdout, "Installing hashicorp/terraform") {
    84  			// Shouldn't have downloaded anything with this config, because the
    85  			// provider is built in.
    86  			t.Errorf("provider download message appeared in output:\n%s", stdout)
    87  		}
    88  
    89  		if strings.Contains(stdout, "Installing terraform.io/builtin/terraform") {
    90  			// Shouldn't have downloaded anything with this config, because the
    91  			// provider is built in.
    92  			t.Errorf("provider download message appeared in output:\n%s", stdout)
    93  		}
    94  	})
    95  
    96  	t.Run("output in machine readable format", func(t *testing.T) {
    97  		fixturePath := filepath.Join("testdata", "tf-provider")
    98  		tf := e2e.NewBinary(t, tofuBin, fixturePath)
    99  
   100  		stdout, stderr, err := tf.Run("init", "-json")
   101  		if err != nil {
   102  			t.Errorf("unexpected error: %s", err)
   103  		}
   104  
   105  		if stderr != "" {
   106  			t.Errorf("unexpected stderr output:\n%s", stderr)
   107  		}
   108  
   109  		// we can not check timestamp, so the sub string is not a valid json object
   110  		if !strings.Contains(stdout, `{"@level":"info","@message":"OpenTofu has been successfully initialized!","@module":"tofu.ui"`) {
   111  			t.Errorf("success message is missing from output:\n%s", stdout)
   112  		}
   113  
   114  		if strings.Contains(stdout, "Installing hashicorp/terraform") {
   115  			// Shouldn't have downloaded anything with this config, because the
   116  			// provider is built in.
   117  			t.Errorf("provider download message appeared in output:\n%s", stdout)
   118  		}
   119  
   120  		if strings.Contains(stdout, "Installing terraform.io/builtin/terraform") {
   121  			// Shouldn't have downloaded anything with this config, because the
   122  			// provider is built in.
   123  			t.Errorf("provider download message appeared in output:\n%s", stdout)
   124  		}
   125  	})
   126  
   127  }
   128  
   129  func TestInitProvidersVendored(t *testing.T) {
   130  	t.Parallel()
   131  
   132  	// This test will try to reach out to registry.opentofu.org as one of the
   133  	// possible installation locations for
   134  	// hashicorp/null, where it will find that
   135  	// versions do exist but will ultimately select the version that is
   136  	// vendored due to the version constraint.
   137  	skipIfCannotAccessNetwork(t)
   138  
   139  	fixturePath := filepath.Join("testdata", "vendored-provider")
   140  	tf := e2e.NewBinary(t, tofuBin, fixturePath)
   141  
   142  	// Our fixture dir has a generic os_arch dir, which we need to customize
   143  	// to the actual OS/arch where this test is running in order to get the
   144  	// desired result.
   145  	fixtMachineDir := tf.Path("terraform.d/plugins/registry.opentofu.org/hashicorp/null/1.0.0+local/os_arch")
   146  	wantMachineDir := tf.Path("terraform.d/plugins/registry.opentofu.org/hashicorp/null/1.0.0+local/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
   147  	err := os.Rename(fixtMachineDir, wantMachineDir)
   148  	if err != nil {
   149  		t.Fatalf("unexpected error: %s", err)
   150  	}
   151  
   152  	stdout, stderr, err := tf.Run("init")
   153  	if err != nil {
   154  		t.Errorf("unexpected error: %s", err)
   155  	}
   156  
   157  	if stderr != "" {
   158  		t.Errorf("unexpected stderr output:\n%s", stderr)
   159  	}
   160  
   161  	if !strings.Contains(stdout, "OpenTofu has been successfully initialized!") {
   162  		t.Errorf("success message is missing from output:\n%s", stdout)
   163  	}
   164  
   165  	if !strings.Contains(stdout, "- Installing hashicorp/null v1.0.0+local") {
   166  		t.Errorf("provider download message is missing from output:\n%s", stdout)
   167  		t.Logf("(this can happen if you have a copy of the plugin in one of the global plugin search dirs)")
   168  	}
   169  
   170  }
   171  
   172  func TestInitProvidersLocalOnly(t *testing.T) {
   173  	t.Parallel()
   174  
   175  	// This test should not reach out to the network if it is behaving as
   176  	// intended. If it _does_ try to access an upstream registry and encounter
   177  	// an error doing so then that's a legitimate test failure that should be
   178  	// fixed. (If it incorrectly reaches out anywhere then it's likely to be
   179  	// to the host "example.com", which is the placeholder domain we use in
   180  	// the test fixture.)
   181  
   182  	t.Run("output in human readable format", func(t *testing.T) {
   183  		fixturePath := filepath.Join("testdata", "local-only-provider")
   184  		tf := e2e.NewBinary(t, tofuBin, fixturePath)
   185  		// If you run this test on a workstation with a plugin-cache directory
   186  		// configured, it will leave a bad directory behind and tofu init will
   187  		// not work until you remove it.
   188  		//
   189  		// To avoid this, we will  "zero out" any existing cli config file.
   190  		tf.AddEnv("TF_CLI_CONFIG_FILE=")
   191  
   192  		// Our fixture dir has a generic os_arch dir, which we need to customize
   193  		// to the actual OS/arch where this test is running in order to get the
   194  		// desired result.
   195  		fixtMachineDir := tf.Path("terraform.d/plugins/example.com/awesomecorp/happycloud/1.2.0/os_arch")
   196  		wantMachineDir := tf.Path("terraform.d/plugins/example.com/awesomecorp/happycloud/1.2.0/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
   197  		err := os.Rename(fixtMachineDir, wantMachineDir)
   198  		if err != nil {
   199  			t.Fatalf("unexpected error: %s", err)
   200  		}
   201  
   202  		stdout, stderr, err := tf.Run("init")
   203  		if err != nil {
   204  			t.Errorf("unexpected error: %s", err)
   205  		}
   206  
   207  		if stderr != "" {
   208  			t.Errorf("unexpected stderr output:\n%s", stderr)
   209  		}
   210  
   211  		if !strings.Contains(stdout, "OpenTofu has been successfully initialized!") {
   212  			t.Errorf("success message is missing from output:\n%s", stdout)
   213  		}
   214  
   215  		if !strings.Contains(stdout, "- Installing example.com/awesomecorp/happycloud v1.2.0") {
   216  			t.Errorf("provider download message is missing from output:\n%s", stdout)
   217  			t.Logf("(this can happen if you have a conflicting copy of the plugin in one of the global plugin search dirs)")
   218  		}
   219  	})
   220  
   221  	t.Run("output in machine readable format", func(t *testing.T) {
   222  		fixturePath := filepath.Join("testdata", "local-only-provider")
   223  		tf := e2e.NewBinary(t, tofuBin, fixturePath)
   224  		// If you run this test on a workstation with a plugin-cache directory
   225  		// configured, it will leave a bad directory behind and tofu init will
   226  		// not work until you remove it.
   227  		//
   228  		// To avoid this, we will  "zero out" any existing cli config file.
   229  		tf.AddEnv("TF_CLI_CONFIG_FILE=")
   230  
   231  		// Our fixture dir has a generic os_arch dir, which we need to customize
   232  		// to the actual OS/arch where this test is running in order to get the
   233  		// desired result.
   234  		fixtMachineDir := tf.Path("terraform.d/plugins/example.com/awesomecorp/happycloud/1.2.0/os_arch")
   235  		wantMachineDir := tf.Path("terraform.d/plugins/example.com/awesomecorp/happycloud/1.2.0/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
   236  		err := os.Rename(fixtMachineDir, wantMachineDir)
   237  		if err != nil {
   238  			t.Fatalf("unexpected error: %s", err)
   239  		}
   240  
   241  		stdout, stderr, err := tf.Run("init", "-json")
   242  		if err != nil {
   243  			t.Errorf("unexpected error: %s", err)
   244  		}
   245  
   246  		if stderr != "" {
   247  			t.Errorf("unexpected stderr output:\n%s", stderr)
   248  		}
   249  
   250  		// we can not check timestamp, so the sub string is not a valid json object
   251  		if !strings.Contains(stdout, `{"@level":"info","@message":"OpenTofu has been successfully initialized!","@module":"tofu.ui"`) {
   252  			t.Errorf("success message is missing from output:\n%s", stdout)
   253  		}
   254  
   255  		if !strings.Contains(stdout, `{"@level":"info","@message":"- Installing example.com/awesomecorp/happycloud v1.2.0...","@module":"tofu.ui"`) {
   256  			t.Errorf("provider download message is missing from output:\n%s", stdout)
   257  			t.Logf("(this can happen if you have a conflicting copy of the plugin in one of the global plugin search dirs)")
   258  		}
   259  	})
   260  
   261  }
   262  
   263  func TestInitProvidersCustomMethod(t *testing.T) {
   264  	t.Parallel()
   265  
   266  	// This test should not reach out to the network if it is behaving as
   267  	// intended. If it _does_ try to access an upstream registry and encounter
   268  	// an error doing so then that's a legitimate test failure that should be
   269  	// fixed. (If it incorrectly reaches out anywhere then it's likely to be
   270  	// to the host "example.com", which is the placeholder domain we use in
   271  	// the test fixture.)
   272  
   273  	for _, configFile := range []string{"cliconfig.tfrc", "cliconfig.tfrc.json"} {
   274  		t.Run(configFile, func(t *testing.T) {
   275  			fixturePath := filepath.Join("testdata", "custom-provider-install-method")
   276  			tf := e2e.NewBinary(t, tofuBin, fixturePath)
   277  
   278  			// Our fixture dir has a generic os_arch dir, which we need to customize
   279  			// to the actual OS/arch where this test is running in order to get the
   280  			// desired result.
   281  			fixtMachineDir := tf.Path("fs-mirror/example.com/awesomecorp/happycloud/1.2.0/os_arch")
   282  			wantMachineDir := tf.Path("fs-mirror/example.com/awesomecorp/happycloud/1.2.0/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
   283  			err := os.Rename(fixtMachineDir, wantMachineDir)
   284  			if err != nil {
   285  				t.Fatalf("unexpected error: %s", err)
   286  			}
   287  
   288  			// We'll use a local CLI configuration file taken from our fixture
   289  			// directory so we can force a custom installation method config.
   290  			tf.AddEnv("TF_CLI_CONFIG_FILE=" + tf.Path(configFile))
   291  
   292  			stdout, stderr, err := tf.Run("init")
   293  			if err != nil {
   294  				t.Errorf("unexpected error: %s", err)
   295  			}
   296  
   297  			if stderr != "" {
   298  				t.Errorf("unexpected stderr output:\n%s", stderr)
   299  			}
   300  
   301  			if !strings.Contains(stdout, "OpenTofu has been successfully initialized!") {
   302  				t.Errorf("success message is missing from output:\n%s", stdout)
   303  			}
   304  
   305  			if !strings.Contains(stdout, "- Installing example.com/awesomecorp/happycloud v1.2.0") {
   306  				t.Errorf("provider download message is missing from output:\n%s", stdout)
   307  			}
   308  		})
   309  	}
   310  }
   311  
   312  func TestInitProviders_pluginCache(t *testing.T) {
   313  	t.Parallel()
   314  
   315  	// This test reaches out to registry.opentofu.org to access plugin
   316  	// metadata, and download the null plugin, though the template plugin
   317  	// should come from local cache.
   318  	skipIfCannotAccessNetwork(t)
   319  
   320  	fixturePath := filepath.Join("testdata", "plugin-cache")
   321  	tf := e2e.NewBinary(t, tofuBin, fixturePath)
   322  
   323  	// Our fixture dir has a generic os_arch dir, which we need to customize
   324  	// to the actual OS/arch where this test is running in order to get the
   325  	// desired result.
   326  	fixtMachineDir := tf.Path("cache/registry.opentofu.org/hashicorp/template/2.1.0/os_arch")
   327  	wantMachineDir := tf.Path("cache/registry.opentofu.org/hashicorp/template/2.1.0/", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH))
   328  	err := os.Rename(fixtMachineDir, wantMachineDir)
   329  	if err != nil {
   330  		t.Fatalf("unexpected error: %s", err)
   331  	}
   332  
   333  	cmd := tf.Cmd("init")
   334  
   335  	// convert the slashes if building for windows.
   336  	p := filepath.FromSlash("./cache")
   337  	cmd.Env = append(cmd.Env, "TF_PLUGIN_CACHE_DIR="+p)
   338  	err = cmd.Run()
   339  	if err != nil {
   340  		t.Errorf("unexpected error: %s", err)
   341  	}
   342  
   343  	path := filepath.FromSlash(fmt.Sprintf(".terraform/providers/registry.opentofu.org/hashicorp/template/2.1.0/%s_%s/terraform-provider-template_v2.1.0_x4", runtime.GOOS, runtime.GOARCH))
   344  	content, err := tf.ReadFile(path)
   345  	if err != nil {
   346  		t.Fatalf("failed to read installed plugin from %s: %s", path, err)
   347  	}
   348  	if strings.TrimSpace(string(content)) != "this is not a real plugin" {
   349  		t.Errorf("template plugin was not installed from local cache")
   350  	}
   351  
   352  	nullLinkPath := filepath.FromSlash(fmt.Sprintf(".terraform/providers/registry.opentofu.org/hashicorp/null/2.1.0/%s_%s/terraform-provider-null", runtime.GOOS, runtime.GOARCH))
   353  	if runtime.GOOS == "windows" {
   354  		nullLinkPath = nullLinkPath + ".exe"
   355  	}
   356  	if !tf.FileExists(nullLinkPath) {
   357  		t.Errorf("null plugin was not installed into %s", nullLinkPath)
   358  	}
   359  
   360  	nullCachePath := filepath.FromSlash(fmt.Sprintf("cache/registry.opentofu.org/hashicorp/null/2.1.0/%s_%s/terraform-provider-null", runtime.GOOS, runtime.GOARCH))
   361  	if runtime.GOOS == "windows" {
   362  		nullCachePath = nullCachePath + ".exe"
   363  	}
   364  	if !tf.FileExists(nullCachePath) {
   365  		t.Errorf("null plugin is not in cache after install. expected in: %s", nullCachePath)
   366  	}
   367  }
   368  
   369  func TestInit_fromModule(t *testing.T) {
   370  	t.Parallel()
   371  
   372  	// This test reaches out to registry.opentofu.org and github.com to lookup
   373  	// and fetch a module.
   374  	skipIfCannotAccessNetwork(t)
   375  
   376  	fixturePath := filepath.Join("testdata", "empty")
   377  	tf := e2e.NewBinary(t, tofuBin, fixturePath)
   378  
   379  	cmd := tf.Cmd("init", "-from-module=hashicorp/vault/aws")
   380  	cmd.Stdin = nil
   381  	cmd.Stderr = &bytes.Buffer{}
   382  
   383  	err := cmd.Run()
   384  	if err != nil {
   385  		t.Errorf("unexpected error: %s", err)
   386  	}
   387  
   388  	stderr := cmd.Stderr.(*bytes.Buffer).String()
   389  	if stderr != "" {
   390  		t.Errorf("unexpected stderr output:\n%s", stderr)
   391  	}
   392  
   393  	content, err := tf.ReadFile("main.tf")
   394  	if err != nil {
   395  		t.Fatalf("failed to read main.tf: %s", err)
   396  	}
   397  	if !bytes.Contains(content, []byte("vault")) {
   398  		t.Fatalf("main.tf doesn't appear to be a vault configuration: \n%s", content)
   399  	}
   400  }
   401  
   402  func TestInitProviderNotFound(t *testing.T) {
   403  	t.Parallel()
   404  
   405  	// This test will reach out to registry.opentofu.org as one of the possible
   406  	// installation locations for hashicorp/nonexist, which should not exist.
   407  	skipIfCannotAccessNetwork(t)
   408  
   409  	fixturePath := filepath.Join("testdata", "provider-not-found")
   410  	tf := e2e.NewBinary(t, tofuBin, fixturePath)
   411  
   412  	t.Run("registry provider not found", func(t *testing.T) {
   413  		_, stderr, err := tf.Run("init", "-no-color")
   414  		if err == nil {
   415  			t.Fatal("expected error, got success")
   416  		}
   417  
   418  		oneLineStderr := strings.ReplaceAll(stderr, "\n", " ")
   419  		if !strings.Contains(oneLineStderr, "provider registry registry.opentofu.org does not have a provider named registry.opentofu.org/hashicorp/nonexist") {
   420  			t.Errorf("expected error message is missing from output:\n%s", stderr)
   421  		}
   422  
   423  		if !strings.Contains(oneLineStderr, "All modules should specify their required_providers") {
   424  			t.Errorf("expected error message is missing from output:\n%s", stderr)
   425  		}
   426  	})
   427  
   428  	t.Run("registry provider not found output in json format", func(t *testing.T) {
   429  		stdout, _, err := tf.Run("init", "-no-color", "-json")
   430  		if err == nil {
   431  			t.Fatal("expected error, got success")
   432  		}
   433  
   434  		oneLineStdout := strings.ReplaceAll(stdout, "\n", " ")
   435  		if !strings.Contains(oneLineStdout, `"diagnostic":{"severity":"error","summary":"Failed to query available provider packages","detail":"Could not retrieve the list of available versions for provider hashicorp/nonexist: provider registry registry.opentofu.org does not have a provider named registry.opentofu.org/hashicorp/nonexist\n\nAll modules should specify their required_providers so that external consumers will get the correct providers when using a module. To see which modules are currently depending on hashicorp/nonexist, run the following command:\n    tofu providers\n\nIf you believe this provider is missing from the registry, please submit a issue on the OpenTofu Registry https://github.com/opentofu/registry/issues/"},"type":"diagnostic"}`) {
   436  			t.Errorf("expected error message is missing from output:\n%s", stdout)
   437  		}
   438  	})
   439  
   440  	t.Run("local provider not found", func(t *testing.T) {
   441  		// The -plugin-dir directory must exist for the provider installer to search it.
   442  		pluginDir := tf.Path("empty-for-json")
   443  		if err := os.Mkdir(pluginDir, os.ModePerm); err != nil {
   444  			t.Fatal(err)
   445  		}
   446  
   447  		_, stderr, err := tf.Run("init", "-no-color", "-plugin-dir="+pluginDir)
   448  		if err == nil {
   449  			t.Fatal("expected error, got success")
   450  		}
   451  
   452  		if !strings.Contains(stderr, "provider registry.opentofu.org/hashicorp/nonexist was not\nfound in any of the search locations\n\n  - "+pluginDir) {
   453  			t.Errorf("expected error message is missing from output:\n%s", stderr)
   454  		}
   455  	})
   456  
   457  	t.Run("local provider not found output in json format", func(t *testing.T) {
   458  		// The -plugin-dir directory must exist for the provider installer to search it.
   459  		pluginDir := tf.Path("empty")
   460  		if err := os.Mkdir(pluginDir, os.ModePerm); err != nil {
   461  			t.Fatal(err)
   462  		}
   463  
   464  		stdout, _, err := tf.Run("init", "-no-color", "-plugin-dir="+pluginDir, "-json")
   465  		if err == nil {
   466  			t.Fatal("expected error, got success")
   467  		}
   468  
   469  		escapedPluginDir := escapeStringJSON(pluginDir)
   470  
   471  		if !strings.Contains(stdout, `"diagnostic":{"severity":"error","summary":"Failed to query available provider packages","detail":"Could not retrieve the list of available versions for provider hashicorp/nonexist: provider registry.opentofu.org/hashicorp/nonexist was not found in any of the search locations\n\n  - `+escapedPluginDir+`"},"type":"diagnostic"}`) {
   472  			t.Errorf("expected error message is missing from output (pluginDir = '%s'):\n%s", escapedPluginDir, stdout)
   473  		}
   474  	})
   475  
   476  	t.Run("special characters enabled", func(t *testing.T) {
   477  		_, stderr, err := tf.Run("init")
   478  		if err == nil {
   479  			t.Fatal("expected error, got success")
   480  		}
   481  
   482  		expectedErr := `╷
   483  │ Error: Failed to query available provider packages
   484  │` + ` ` + `
   485  │ Could not retrieve the list of available versions for provider
   486  │ hashicorp/nonexist: provider registry registry.opentofu.org does not have a
   487  │ provider named registry.opentofu.org/hashicorp/nonexist
   488  │ 
   489  │ All modules should specify their required_providers so that external
   490  │ consumers will get the correct providers when using a module. To see which
   491  │ modules are currently depending on hashicorp/nonexist, run the following
   492  │ command:
   493  │     tofu providers
   494  │ 
   495  │ If you believe this provider is missing from the registry, please submit a
   496  │ issue on the OpenTofu Registry https://github.com/opentofu/registry/issues/
   497  ╵
   498  
   499  `
   500  		if stripAnsi(stderr) != expectedErr {
   501  			t.Errorf("wrong output:\n%s", cmp.Diff(stripAnsi(stderr), expectedErr))
   502  		}
   503  	})
   504  }
   505  
   506  // The following test is temporarily removed until the OpenTofu registry returns a deprecation warning
   507  // https://github.com/opentofu/registry/issues/108
   508  //func TestInitProviderWarnings(t *testing.T) {
   509  //	t.Parallel()
   510  //
   511  //  // This test will reach out to registry.terraform.io as one of the possible
   512  //  // installation locations for hashicorp/terraform, which is an archived package that is no longer needed.
   513  //	skipIfCannotAccessNetwork(t)
   514  //
   515  //	fixturePath := filepath.Join("testdata", "provider-warnings")
   516  //	tf := e2e.NewBinary(t, tofuBin, fixturePath)
   517  //
   518  //	stdout, _, err := tf.Run("init")
   519  //	if err == nil {
   520  //		t.Fatal("expected error, got success")
   521  //	}
   522  //
   523  //	if !strings.Contains(stdout, "This provider is archived and no longer needed.") {
   524  //		t.Errorf("expected warning message is missing from output:\n%s", stdout)
   525  //	}
   526  //
   527  //}
   528  
   529  func escapeStringJSON(v string) string {
   530  	b := &strings.Builder{}
   531  
   532  	enc := json.NewEncoder(b)
   533  
   534  	enc.SetEscapeHTML(false)
   535  
   536  	if err := enc.Encode(v); err != nil {
   537  		panic("failed to escapeStringJSON: " + v)
   538  	}
   539  
   540  	marshaledV := b.String()
   541  
   542  	// shouldn't happen
   543  	if len(marshaledV) < 2 {
   544  		return string(marshaledV)
   545  	}
   546  
   547  	return string(marshaledV[1 : len(marshaledV)-2])
   548  }