448 lines
12 KiB
Go
448 lines
12 KiB
Go
package command
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"net/http/httptest"
|
|
"os"
|
|
"path"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/google/go-cmp/cmp"
|
|
svchost "github.com/hashicorp/terraform-svchost"
|
|
"github.com/hashicorp/terraform-svchost/disco"
|
|
"github.com/hashicorp/terraform/helper/copy"
|
|
"github.com/hashicorp/terraform/internal/getproviders"
|
|
"github.com/mitchellh/cli"
|
|
)
|
|
|
|
// This map from provider type name to namespace is used by the fake registry
|
|
// when called via LookupLegacyProvider. Providers not in this map will return
|
|
// a 404 Not Found error.
|
|
var legacyProviderNamespaces = map[string]string{
|
|
"foo": "hashicorp",
|
|
"bar": "hashicorp",
|
|
"baz": "terraform-providers",
|
|
}
|
|
|
|
func verifyExpectedFiles(t *testing.T, expectedPath string) {
|
|
// Compare output and expected file trees
|
|
var outputFiles, expectedFiles []string
|
|
|
|
// Gather list of output files in the current working directory
|
|
err := filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
|
if !info.IsDir() {
|
|
outputFiles = append(outputFiles, path)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal("error listing output files:", err)
|
|
}
|
|
|
|
// Gather list of expected files
|
|
revertChdir := testChdir(t, expectedPath)
|
|
err = filepath.Walk(".", func(path string, info os.FileInfo, err error) error {
|
|
if !info.IsDir() {
|
|
expectedFiles = append(expectedFiles, path)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Fatal("error listing expected files:", err)
|
|
}
|
|
revertChdir()
|
|
|
|
// If the file trees don't match, give up early
|
|
if diff := cmp.Diff(expectedFiles, outputFiles); diff != "" {
|
|
t.Fatalf("expected and output file trees do not match\n%s", diff)
|
|
}
|
|
|
|
// Check that the contents of each file is correct
|
|
for _, filePath := range outputFiles {
|
|
output, err := ioutil.ReadFile(path.Join(".", filePath))
|
|
if err != nil {
|
|
t.Fatalf("failed to read output %s: %s", filePath, err)
|
|
}
|
|
expected, err := ioutil.ReadFile(path.Join(expectedPath, filePath))
|
|
if err != nil {
|
|
t.Fatalf("failed to read expected %s: %s", filePath, err)
|
|
}
|
|
|
|
if diff := cmp.Diff(expected, output); diff != "" {
|
|
t.Fatalf("expected and output file for %s do not match\n%s", filePath, diff)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestZeroThirteenUpgrade_success(t *testing.T) {
|
|
registrySource, close := testRegistrySource(t)
|
|
defer close()
|
|
|
|
testCases := map[string]string{
|
|
"implicit": "013upgrade-implicit-providers",
|
|
"explicit": "013upgrade-explicit-providers",
|
|
"provider not found": "013upgrade-provider-not-found",
|
|
"implicit not found": "013upgrade-implicit-not-found",
|
|
"file exists": "013upgrade-file-exists",
|
|
"no providers": "013upgrade-no-providers",
|
|
"submodule": "013upgrade-submodule",
|
|
"providers with source": "013upgrade-providers-with-source",
|
|
"preserves comments": "013upgrade-preserves-comments",
|
|
"multiple blocks": "013upgrade-multiple-blocks",
|
|
"multiple files": "013upgrade-multiple-files",
|
|
"existing versions.tf": "013upgrade-existing-versions-tf",
|
|
"skipped files": "013upgrade-skipped-files",
|
|
}
|
|
for name, testPath := range testCases {
|
|
t.Run(name, func(t *testing.T) {
|
|
inputPath, err := filepath.Abs(testFixturePath(path.Join(testPath, "input")))
|
|
if err != nil {
|
|
t.Fatalf("failed to find input path %s: %s", testPath, err)
|
|
}
|
|
|
|
expectedPath, err := filepath.Abs(testFixturePath(path.Join(testPath, "expected")))
|
|
if err != nil {
|
|
t.Fatalf("failed to find expected path %s: %s", testPath, err)
|
|
}
|
|
|
|
td := tempDir(t)
|
|
copy.CopyDir(inputPath, td)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
ProviderSource: registrySource,
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run([]string{"-yes"}); code != 0 {
|
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
}
|
|
|
|
output := ui.OutputWriter.String()
|
|
if !strings.Contains(output, "Upgrade complete") {
|
|
t.Fatal("unexpected output:", output)
|
|
}
|
|
|
|
verifyExpectedFiles(t, expectedPath)
|
|
})
|
|
}
|
|
}
|
|
|
|
// Ensure that non-default upgrade paths are supported, and that the output is
|
|
// in the correct place. This test is very similar to the table tests above,
|
|
// but with a different expected output path, and with an argument passed to
|
|
// the Run call.
|
|
func TestZeroThirteenUpgrade_submodule(t *testing.T) {
|
|
registrySource, close := testRegistrySource(t)
|
|
defer close()
|
|
|
|
testPath := "013upgrade-submodule"
|
|
|
|
inputPath, err := filepath.Abs(testFixturePath(path.Join(testPath, "input")))
|
|
if err != nil {
|
|
t.Fatalf("failed to find input path %s: %s", testPath, err)
|
|
}
|
|
|
|
// The expected output for processing a submodule is different
|
|
expectedPath, err := filepath.Abs(testFixturePath(path.Join(testPath, "expected-module")))
|
|
if err != nil {
|
|
t.Fatalf("failed to find expected path %s: %s", testPath, err)
|
|
}
|
|
|
|
td := tempDir(t)
|
|
copy.CopyDir(inputPath, td)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
ProviderSource: registrySource,
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
// Here we pass a target module directory to process
|
|
if code := c.Run([]string{"-yes", "module"}); code != 0 {
|
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
}
|
|
|
|
output := ui.OutputWriter.String()
|
|
if !strings.Contains(output, "Upgrade complete") {
|
|
t.Fatal("unexpected output:", output)
|
|
}
|
|
|
|
verifyExpectedFiles(t, expectedPath)
|
|
}
|
|
|
|
// Verify that JSON and override files are skipped with a warning. Generated
|
|
// output for this config is verified in the table driven tests above.
|
|
func TestZeroThirteenUpgrade_skippedFiles(t *testing.T) {
|
|
inputPath := testFixturePath(path.Join("013upgrade-skipped-files", "input"))
|
|
|
|
td := tempDir(t)
|
|
copy.CopyDir(inputPath, td)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run([]string{"-yes"}); code != 0 {
|
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
}
|
|
|
|
output := ui.OutputWriter.String()
|
|
if !strings.Contains(output, "Upgrade complete") {
|
|
t.Fatal("unexpected output:", output)
|
|
}
|
|
|
|
errMsg := ui.ErrorWriter.String()
|
|
if !strings.Contains(errMsg, `The JSON configuration file "variables.tf.json" was skipped`) {
|
|
t.Fatal("missing JSON skipped file warning:", errMsg)
|
|
}
|
|
if !strings.Contains(errMsg, `The override configuration file "bar_override.tf" was skipped`) {
|
|
t.Fatal("missing override skipped file warning:", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestZeroThirteenUpgrade_confirm(t *testing.T) {
|
|
inputPath := testFixturePath(path.Join("013upgrade-explicit-providers", "input"))
|
|
|
|
td := tempDir(t)
|
|
copy.CopyDir(inputPath, td)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
inputBuf := &bytes.Buffer{}
|
|
ui.InputReader = inputBuf
|
|
inputBuf.WriteString("yes")
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run(nil); code != 0 {
|
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
}
|
|
|
|
output := ui.OutputWriter.String()
|
|
if !strings.Contains(output, "Upgrade complete") {
|
|
t.Fatal("unexpected output:", output)
|
|
}
|
|
}
|
|
|
|
func TestZeroThirteenUpgrade_cancel(t *testing.T) {
|
|
inputPath := testFixturePath(path.Join("013upgrade-explicit-providers", "input"))
|
|
|
|
td := tempDir(t)
|
|
copy.CopyDir(inputPath, td)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
inputBuf := &bytes.Buffer{}
|
|
ui.InputReader = inputBuf
|
|
inputBuf.WriteString("no")
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run(nil); code != 0 {
|
|
t.Fatalf("bad: \n%s", ui.ErrorWriter.String())
|
|
}
|
|
|
|
output := ui.OutputWriter.String()
|
|
if !strings.Contains(output, "Upgrade cancelled") {
|
|
t.Fatal("unexpected output:", output)
|
|
}
|
|
if strings.Contains(output, "Upgrade complete") {
|
|
t.Fatal("unexpected output:", output)
|
|
}
|
|
}
|
|
|
|
func TestZeroThirteenUpgrade_unsupportedVersion(t *testing.T) {
|
|
inputPath := testFixturePath("013upgrade-unsupported-version")
|
|
|
|
td := tempDir(t)
|
|
copy.CopyDir(inputPath, td)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run([]string{"-yes"}); code == 0 {
|
|
t.Fatal("expected error, got:", ui.OutputWriter)
|
|
}
|
|
|
|
errMsg := ui.ErrorWriter.String()
|
|
if !strings.Contains(errMsg, `Unsupported Terraform Core version`) {
|
|
t.Fatal("missing version constraint error:", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestZeroThirteenUpgrade_invalidFlags(t *testing.T) {
|
|
td := tempDir(t)
|
|
os.MkdirAll(td, 0755)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run([]string{"--whoops"}); code == 0 {
|
|
t.Fatal("expected error, got:", ui.OutputWriter)
|
|
}
|
|
|
|
errMsg := ui.ErrorWriter.String()
|
|
if !strings.Contains(errMsg, "Usage: terraform 0.13upgrade") {
|
|
t.Fatal("unexpected error:", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestZeroThirteenUpgrade_tooManyArguments(t *testing.T) {
|
|
td := tempDir(t)
|
|
os.MkdirAll(td, 0755)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run([]string{".", "./modules/test"}); code == 0 {
|
|
t.Fatal("expected error, got:", ui.OutputWriter)
|
|
}
|
|
|
|
errMsg := ui.ErrorWriter.String()
|
|
if !strings.Contains(errMsg, "Error: Too many arguments") {
|
|
t.Fatal("unexpected error:", errMsg)
|
|
}
|
|
}
|
|
|
|
func TestZeroThirteenUpgrade_empty(t *testing.T) {
|
|
td := tempDir(t)
|
|
os.MkdirAll(td, 0755)
|
|
defer os.RemoveAll(td)
|
|
defer testChdir(t, td)()
|
|
|
|
ui := new(cli.MockUi)
|
|
c := &ZeroThirteenUpgradeCommand{
|
|
Meta: Meta{
|
|
testingOverrides: metaOverridesForProvider(testProvider()),
|
|
Ui: ui,
|
|
},
|
|
}
|
|
|
|
if code := c.Run([]string{"-yes"}); code == 0 {
|
|
t.Fatal("expected error, got:", ui.OutputWriter)
|
|
}
|
|
|
|
errMsg := ui.ErrorWriter.String()
|
|
if !strings.Contains(errMsg, "Not a module directory") {
|
|
t.Fatal("unexpected error:", errMsg)
|
|
}
|
|
}
|
|
|
|
// testServices starts up a local HTTP server running a fake provider registry
|
|
// service which responds only to discovery requests and legacy provider lookup
|
|
// API calls.
|
|
//
|
|
// The final return value is a function to call at the end of a test function
|
|
// to shut down the test server. After you call that function, the discovery
|
|
// object becomes useless.
|
|
func testServices(t *testing.T) (services *disco.Disco, cleanup func()) {
|
|
server := httptest.NewServer(http.HandlerFunc(fakeRegistryHandler))
|
|
|
|
services = disco.New()
|
|
services.ForceHostServices(svchost.Hostname("registry.terraform.io"), map[string]interface{}{
|
|
"providers.v1": server.URL + "/providers/v1/",
|
|
})
|
|
|
|
return services, func() {
|
|
server.Close()
|
|
}
|
|
}
|
|
|
|
// testRegistrySource is a wrapper around testServices that uses the created
|
|
// discovery object to produce a Source instance that is ready to use with the
|
|
// fake registry services.
|
|
//
|
|
// As with testServices, the final return value is a function to call at the end
|
|
// of your test in order to shut down the test server.
|
|
func testRegistrySource(t *testing.T) (source *getproviders.RegistrySource, cleanup func()) {
|
|
services, close := testServices(t)
|
|
source = getproviders.NewRegistrySource(services)
|
|
return source, close
|
|
}
|
|
|
|
func fakeRegistryHandler(resp http.ResponseWriter, req *http.Request) {
|
|
path := req.URL.EscapedPath()
|
|
|
|
if !strings.HasPrefix(path, "/providers/v1/") {
|
|
resp.WriteHeader(404)
|
|
resp.Write([]byte(`not a provider registry endpoint`))
|
|
return
|
|
}
|
|
|
|
pathParts := strings.Split(path, "/")[3:]
|
|
|
|
if len(pathParts) != 3 {
|
|
resp.WriteHeader(404)
|
|
resp.Write([]byte(`unrecognized path scheme`))
|
|
return
|
|
}
|
|
|
|
if pathParts[0] != "-" || pathParts[2] != "versions" {
|
|
resp.WriteHeader(404)
|
|
resp.Write([]byte(`this registry only supports legacy namespace lookup requests`))
|
|
}
|
|
|
|
name := pathParts[1]
|
|
if namespace, ok := legacyProviderNamespaces[name]; ok {
|
|
resp.Header().Set("Content-Type", "application/json")
|
|
resp.WriteHeader(200)
|
|
resp.Write([]byte(fmt.Sprintf(`{"id":"%s/%s"}`, namespace, name)))
|
|
} else {
|
|
resp.WriteHeader(404)
|
|
resp.Write([]byte(`provider not found`))
|
|
}
|
|
}
|