From 39deb1ff6b0934008c7019437b5bb65d41e80d6d Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 11 Apr 2017 10:56:04 -0700 Subject: [PATCH 01/82] plugin/discovery: helpers for wrangling plugin versions With forthcoming support for versioned plugins we need to be able to answer questions like what versions of plugins are currently installed, what's the newest version of a given plugin available, etc. PluginMetaSet gives us a building block for this sort of plugin version wrangling. --- plugin/discovery/meta.go | 28 +++ plugin/discovery/meta_set.go | 151 +++++++++++++ plugin/discovery/meta_set_test.go | 345 ++++++++++++++++++++++++++++++ 3 files changed, 524 insertions(+) create mode 100644 plugin/discovery/meta.go create mode 100644 plugin/discovery/meta_set.go create mode 100644 plugin/discovery/meta_set_test.go diff --git a/plugin/discovery/meta.go b/plugin/discovery/meta.go new file mode 100644 index 0000000000..16c7e87124 --- /dev/null +++ b/plugin/discovery/meta.go @@ -0,0 +1,28 @@ +package discovery + +import ( + "github.com/blang/semver" +) + +// PluginMeta is metadata about a plugin, useful for launching the plugin +// and for understanding which plugins are available. +type PluginMeta struct { + // Name is the name of the plugin, e.g. as inferred from the plugin + // binary's filename, or by explicit configuration. + Name string + + // Version is the semver version of the plugin, expressed as a string + // that might not be semver-valid. (Call VersionObj to attempt to + // parse it and thus detect if it is invalid.) + Version string + + // Path is the absolute path of the executable that can be launched + // to provide the RPC server for this plugin. + Path string +} + +// VersionObj returns the semver version of the plugin as an object, or +// an error if the version string is not semver-syntax-compliant. +func (m PluginMeta) VersionObj() (semver.Version, error) { + return semver.Make(m.Version) +} diff --git a/plugin/discovery/meta_set.go b/plugin/discovery/meta_set.go new file mode 100644 index 0000000000..72c0019b3d --- /dev/null +++ b/plugin/discovery/meta_set.go @@ -0,0 +1,151 @@ +package discovery + +import ( + "github.com/blang/semver" +) + +// A PluginMetaSet is a set of PluginMeta objects meeting a certain criteria. +// +// Methods on this type allow filtering of the set to produce subsets that +// meet more restrictive criteria. +type PluginMetaSet map[PluginMeta]struct{} + +// Add inserts the given PluginMeta into the receiving set. This is a no-op +// if the given meta is already present. +func (s PluginMetaSet) Add(p PluginMeta) { + s[p] = struct{}{} +} + +// Remove removes the given PluginMeta from the receiving set. This is a no-op +// if the given meta is not already present. +func (s PluginMetaSet) Remove(p PluginMeta) { + delete(s, p) +} + +// Has returns true if the given meta is in the receiving set, or false +// otherwise. +func (s PluginMetaSet) Has(p PluginMeta) bool { + _, ok := s[p] + return ok +} + +// Count returns the number of metas in the set +func (s PluginMetaSet) Count() int { + return len(s) +} + +// ValidateVersions returns two new PluginMetaSets, separating those with +// versions that have syntax-valid semver versions from those that don't. +// +// Eliminating invalid versions from consideration (and possibly warning about +// them) is usually the first step of working with a meta set after discovery +// has completed. +func (s PluginMetaSet) ValidateVersions() (valid, invalid PluginMetaSet) { + valid = make(PluginMetaSet) + invalid = make(PluginMetaSet) + for p := range s { + if _, err := p.VersionObj(); err == nil { + valid.Add(p) + } else { + invalid.Add(p) + } + } + return +} + +// WithName returns the subset of metas that have the given name. +func (s PluginMetaSet) WithName(name string) PluginMetaSet { + ns := make(PluginMetaSet) + for p := range s { + if p.Name == name { + ns.Add(p) + } + } + return ns +} + +// ByName groups the metas in the set by their Names, returning a map. +func (s PluginMetaSet) ByName() map[string]PluginMetaSet { + ret := make(map[string]PluginMetaSet) + for p := range s { + if _, ok := ret[p.Name]; !ok { + ret[p.Name] = make(PluginMetaSet) + } + ret[p.Name].Add(p) + } + return ret +} + +// Newest returns the one item from the set that has the newest Version value. +// +// The result is meaningful only if the set is already filtered such that +// all of the metas have the same Name. +// +// If there isn't at least one meta in the set then this function will panic. +// Use Count() to ensure that there is at least one value before calling. +// +// If any of the metas have invalid version strings then this function will +// panic. Use ValidateVersions() first to filter out metas with invalid +// versions. +// +// If two metas have the same Version then one is arbitrarily chosen. This +// situation should be avoided by pre-filtering the set. +func (s PluginMetaSet) Newest() PluginMeta { + if len(s) == 0 { + panic("can't call NewestStable on empty PluginMetaSet") + } + + var first = true + var winner PluginMeta + var winnerVersion semver.Version + for p := range s { + version, err := p.VersionObj() + if err != nil { + panic(err) + } + + if first == true || version.GT(winnerVersion) { + winner = p + winnerVersion = version + first = false + } + } + + return winner +} + +// ConstrainVersions takes a map of version constraints by name and attempts to +// return a map from name to a set of metas that have the matching +// name and an appropriate version. +// +// If any of the given constraints match *no* plugins then its PluginMetaSet +// in the returned map will be nil. +// +// All viable metas are returned, so the caller can apply any desired filtering +// to reduce down to a single option. For example, calling Newest() to obtain +// the highest available version. +// +// If any of the metas in the set have invalid version strings then this +// function will panic. Use ValidateVersions() first to filter out metas with +// invalid versions. +func (s PluginMetaSet) ConstrainVersions(reqd map[string]semver.Range) map[string]PluginMetaSet { + ret := make(map[string]PluginMetaSet) + for p := range s { + name := p.Name + constraint, ok := reqd[name] + if !ok { + continue + } + if _, ok := ret[p.Name]; !ok { + ret[p.Name] = make(PluginMetaSet) + } + version, err := p.VersionObj() + if err != nil { + panic(err) + } + if constraint(version) { + ret[p.Name].Add(p) + } + } + return ret +} diff --git a/plugin/discovery/meta_set_test.go b/plugin/discovery/meta_set_test.go new file mode 100644 index 0000000000..15fc7bae5b --- /dev/null +++ b/plugin/discovery/meta_set_test.go @@ -0,0 +1,345 @@ +package discovery + +import ( + "fmt" + "strings" + "testing" + + "github.com/blang/semver" +) + +func TestPluginMetaSetManipulation(t *testing.T) { + metas := []PluginMeta{ + { + Name: "foo", + Version: "1.0.0", + Path: "test-foo", + }, + { + Name: "bar", + Version: "2.0.0", + Path: "test-bar", + }, + { + Name: "baz", + Version: "2.0.0", + Path: "test-bar", + }, + } + s := make(PluginMetaSet) + + if count := s.Count(); count != 0 { + t.Fatalf("set has Count %d before any items added", count) + } + + // Can we add metas? + for _, p := range metas { + s.Add(p) + if !s.Has(p) { + t.Fatalf("%q not in set after adding it", p.Name) + } + } + + if got, want := s.Count(), len(metas); got != want { + t.Fatalf("set has Count %d after all items added; want %d", got, want) + } + + // Can we still retrieve earlier ones after we added later ones? + for _, p := range metas { + if !s.Has(p) { + t.Fatalf("%q not in set after all adds", p.Name) + } + } + + // Can we remove metas? + for _, p := range metas { + s.Remove(p) + if s.Has(p) { + t.Fatalf("%q still in set after removing it", p.Name) + } + } + + if count := s.Count(); count != 0 { + t.Fatalf("set has Count %d after all items removed", count) + } +} + +func TestPluginMetaSetValidateVersions(t *testing.T) { + metas := []PluginMeta{ + { + Name: "foo", + Version: "1.0.0", + Path: "test-foo", + }, + { + Name: "bar", + Version: "0.0.1", + Path: "test-bar", + }, + { + Name: "baz", + Version: "bananas", + Path: "test-bar", + }, + } + s := make(PluginMetaSet) + + for _, p := range metas { + s.Add(p) + } + + valid, invalid := s.ValidateVersions() + if count := valid.Count(); count != 2 { + t.Errorf("valid set has %d metas; want 2", count) + } + if count := invalid.Count(); count != 1 { + t.Errorf("valid set has %d metas; want 1", count) + } + + if !valid.Has(metas[0]) { + t.Errorf("'foo' not in valid set") + } + if !valid.Has(metas[1]) { + t.Errorf("'bar' not in valid set") + } + if !invalid.Has(metas[2]) { + t.Errorf("'baz' not in invalid set") + } + + if invalid.Has(metas[0]) { + t.Errorf("'foo' in invalid set") + } + if invalid.Has(metas[1]) { + t.Errorf("'bar' in invalid set") + } + if valid.Has(metas[2]) { + t.Errorf("'baz' in valid set") + } + +} + +func TestPluginMetaSetWithName(t *testing.T) { + tests := []struct { + metas []PluginMeta + name string + wantCount int + }{ + { + []PluginMeta{}, + "foo", + 0, + }, + { + []PluginMeta{ + { + Name: "foo", + Version: "0.0.1", + Path: "foo", + }, + }, + "foo", + 1, + }, + { + []PluginMeta{ + { + Name: "foo", + Version: "0.0.1", + Path: "foo", + }, + }, + "bar", + 0, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("Test%02d", i), func(t *testing.T) { + s := make(PluginMetaSet) + for _, p := range test.metas { + s.Add(p) + } + filtered := s.WithName(test.name) + if gotCount := filtered.Count(); gotCount != test.wantCount { + t.Errorf("got count %d in %#v; want %d", gotCount, filtered, test.wantCount) + } + }) + } +} + +func TestPluginMetaSetByName(t *testing.T) { + metas := []PluginMeta{ + { + Name: "foo", + Version: "1.0.0", + Path: "test-foo", + }, + { + Name: "foo", + Version: "2.0.0", + Path: "test-foo-2", + }, + { + Name: "bar", + Version: "0.0.1", + Path: "test-bar", + }, + { + Name: "baz", + Version: "1.2.0", + Path: "test-bar", + }, + } + s := make(PluginMetaSet) + + for _, p := range metas { + s.Add(p) + } + + byName := s.ByName() + if got, want := len(byName), 3; got != want { + t.Errorf("%d keys in ByName map; want %d", got, want) + } + if got, want := len(byName["foo"]), 2; got != want { + t.Errorf("%d metas for 'foo'; want %d", got, want) + } + if got, want := len(byName["bar"]), 1; got != want { + t.Errorf("%d metas for 'bar'; want %d", got, want) + } + if got, want := len(byName["baz"]), 1; got != want { + t.Errorf("%d metas for 'baz'; want %d", got, want) + } + + if !byName["foo"].Has(metas[0]) { + t.Errorf("%#v missing from 'foo' set", metas[0]) + } + if !byName["foo"].Has(metas[1]) { + t.Errorf("%#v missing from 'foo' set", metas[1]) + } + if !byName["bar"].Has(metas[2]) { + t.Errorf("%#v missing from 'bar' set", metas[2]) + } + if !byName["baz"].Has(metas[3]) { + t.Errorf("%#v missing from 'baz' set", metas[3]) + } +} + +func TestPluginMetaSetNewest(t *testing.T) { + tests := []struct { + versions []string + want string + }{ + { + []string{ + "0.0.1", + }, + "0.0.1", + }, + { + []string{ + "0.0.1", + "0.0.2", + }, + "0.0.2", + }, + { + []string{ + "1.0.0", + "1.0.0-beta1", + }, + "1.0.0", + }, + { + []string{ + "0.0.1", + "1.0.0", + }, + "1.0.0", + }, + } + + for _, test := range tests { + t.Run(strings.Join(test.versions, "|"), func(t *testing.T) { + s := make(PluginMetaSet) + for _, version := range test.versions { + s.Add(PluginMeta{ + Name: "foo", + Version: version, + Path: "foo-V" + version, + }) + } + + newest := s.Newest() + if newest.Version != test.want { + t.Errorf("version is %q; want %q", newest.Version, test.want) + } + }) + } +} + +func TestPluginMetaSetConstrainVersions(t *testing.T) { + metas := []PluginMeta{ + { + Name: "foo", + Version: "1.0.0", + Path: "test-foo", + }, + { + Name: "foo", + Version: "2.0.0", + Path: "test-foo-2", + }, + { + Name: "foo", + Version: "3.0.0", + Path: "test-foo-2", + }, + { + Name: "bar", + Version: "0.0.5", + Path: "test-bar", + }, + { + Name: "baz", + Version: "0.0.1", + Path: "test-bar", + }, + } + s := make(PluginMetaSet) + + for _, p := range metas { + s.Add(p) + } + + byName := s.ConstrainVersions(map[string]semver.Range{ + "foo": semver.MustParseRange(">=2.0.0"), + "bar": semver.MustParseRange(">=0.0.0"), + "baz": semver.MustParseRange(">=1.0.0"), + "fun": semver.MustParseRange(">5.0.0"), + }) + if got, want := len(byName), 3; got != want { + t.Errorf("%d keys in map; want %d", got, want) + } + + if got, want := len(byName["foo"]), 2; got != want { + t.Errorf("%d metas for 'foo'; want %d", got, want) + } + if got, want := len(byName["bar"]), 1; got != want { + t.Errorf("%d metas for 'bar'; want %d", got, want) + } + if got, want := len(byName["baz"]), 0; got != want { + t.Errorf("%d metas for 'baz'; want %d", got, want) + } + // "fun" is not in the map at all, because we have no metas for that name + + if !byName["foo"].Has(metas[1]) { + t.Errorf("%#v missing from 'foo' set", metas[1]) + } + if !byName["foo"].Has(metas[2]) { + t.Errorf("%#v missing from 'foo' set", metas[2]) + } + if !byName["bar"].Has(metas[3]) { + t.Errorf("%#v missing from 'bar' set", metas[3]) + } + +} From a94e9a43620105c55acc162e05a7bad8f57bda91 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 12 Apr 2017 10:39:04 -0700 Subject: [PATCH 02/82] plugin/discovery: find plugins in a given set of directories For now this supports both our old and new directory layouts, so we can preserve compatibility with existing configurations until a future major release where support for the old paths will be removed. Currently this allows both styles in all given directories, which means we support more variants than we actually intend to but this is accepted to keep the interface simple such that we can easily remove the legacy exception later. The documentation will reflect only the subset of path layouts that we actually intend to support. --- plugin/discovery/find.go | 167 ++++++++++++++++++ plugin/discovery/find_test.go | 104 +++++++++++ .../mockos_mockarch/terraform-foo-bar-V0.0.1 | 0 .../mockos_mockarch/terraform-foo-bar-V1.0.0 | 0 .../terraform-foo-missing-version | 0 .../terraform-notfoo-bar-V0.0.1 | 0 .../legacy-style-plugins/terraform-foo-bar | 0 .../legacy-style-plugins/terraform-foo-baz | 0 .../legacy-style-plugins/terraform-notfoo-bar | 0 plugin/discovery/test-fixtures/not-a-dir | 0 10 files changed, 271 insertions(+) create mode 100644 plugin/discovery/find.go create mode 100644 plugin/discovery/find_test.go create mode 100644 plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-bar-V0.0.1 create mode 100644 plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-bar-V1.0.0 create mode 100644 plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-missing-version create mode 100644 plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-notfoo-bar-V0.0.1 create mode 100644 plugin/discovery/test-fixtures/legacy-style-plugins/terraform-foo-bar create mode 100644 plugin/discovery/test-fixtures/legacy-style-plugins/terraform-foo-baz create mode 100644 plugin/discovery/test-fixtures/legacy-style-plugins/terraform-notfoo-bar create mode 100644 plugin/discovery/test-fixtures/not-a-dir diff --git a/plugin/discovery/find.go b/plugin/discovery/find.go new file mode 100644 index 0000000000..603a491181 --- /dev/null +++ b/plugin/discovery/find.go @@ -0,0 +1,167 @@ +package discovery + +import ( + "io/ioutil" + "path/filepath" + "runtime" + "strings" +) + +const machineName = runtime.GOOS + "_" + runtime.GOARCH + +// FindPlugins looks in the given directories for files whose filenames +// suggest that they are plugins of the given kind (e.g. "provider") and +// returns a PluginMetaSet representing the discovered potential-plugins. +// +// Currently this supports two different naming schemes. The current +// standard naming scheme is a subdirectory called $GOOS-$GOARCH containing +// files named terraform-$KIND-$NAME-V$VERSION. The legacy naming scheme is +// files directly in the given directory whose names are like +// terraform-$KIND-$NAME. +// +// Only one plugin will be returned for each unique plugin (name, version) +// pair, with preference given to files found in earlier directories. +// +// This is a convenience wrapper around FindPluginPaths and ResolvePluginsPaths. +func FindPlugins(kind string, dirs []string) PluginMetaSet { + return ResolvePluginPaths(FindPluginPaths(kind, dirs)) +} + +// FindPluginPaths looks in the given directories for files whose filenames +// suggest that they are plugins of the given kind (e.g. "provider"). +// +// The return value is a list of absolute paths that appear to refer to +// plugins in the given directories, based only on what can be inferred +// from the naming scheme. The paths returned are ordered such that files +// in later dirs appear after files in earlier dirs in the given directory +// list. Within the same directory plugins are returned in a consistent but +// undefined order. +func FindPluginPaths(kind string, dirs []string) []string { + // This is just a thin wrapper around findPluginPaths so that we can + // use the latter in tests with a fake machineName so we can use our + // test fixtures. + return findPluginPaths(kind, machineName, dirs) +} + +func findPluginPaths(kind string, machineName string, dirs []string) []string { + prefix := "terraform-" + kind + "-" + + ret := make([]string, 0, len(dirs)) + + for _, baseDir := range dirs { + baseItems, err := ioutil.ReadDir(baseDir) + if err != nil { + // Ignore missing dirs, non-dirs, etc + continue + } + + for _, item := range baseItems { + fullName := item.Name() + + if fullName == machineName && item.Mode().IsDir() { + // Current-style $GOOS-$GOARCH directory prefix + machineDir := filepath.Join(baseDir, machineName) + machineItems, err := ioutil.ReadDir(machineDir) + if err != nil { + continue + } + + for _, item := range machineItems { + fullName := item.Name() + + if !strings.HasPrefix(fullName, prefix) { + continue + } + + // New-style paths must have a version segment in filename + if !strings.Contains(fullName, "-V") { + continue + } + + absPath, err := filepath.Abs(filepath.Join(machineDir, fullName)) + if err != nil { + continue + } + + ret = append(ret, filepath.Clean(absPath)) + } + + continue + } + + if strings.HasPrefix(fullName, prefix) { + // Legacy style with files directly in the base directory + absPath, err := filepath.Abs(filepath.Join(baseDir, fullName)) + if err != nil { + continue + } + + ret = append(ret, filepath.Clean(absPath)) + } + } + } + + return ret +} + +// ResolvePluginPaths takes a list of paths to plugin executables (as returned +// by e.g. FindPluginPaths) and produces a PluginMetaSet describing the +// referenced plugins. +// +// If the same combination of plugin name and version appears multiple times, +// the earlier reference will be preferred. Several different versions of +// the same plugin name may be returned, in which case the methods of +// PluginMetaSet can be used to filter down. +func ResolvePluginPaths(paths []string) PluginMetaSet { + s := make(PluginMetaSet) + + type nameVersion struct { + Name string + Version string + } + found := make(map[nameVersion]struct{}) + + for _, path := range paths { + baseName := filepath.Base(path) + if !strings.HasPrefix(baseName, "terraform-") { + // Should never happen with reasonable input + continue + } + baseName = baseName[10:] + firstDash := strings.Index(baseName, "-") + if firstDash == -1 { + // Should never happen with reasonable input + continue + } + + baseName = baseName[firstDash+1:] + if baseName == "" { + // Should never happen with reasonable input + continue + } + + parts := strings.SplitN(baseName, "-V", 2) + name := parts[0] + version := "0.0.0" + if len(parts) == 2 { + version = parts[1] + } + + if _, ok := found[nameVersion{name, version}]; ok { + // Skip duplicate versions of the same plugin + // (We do this during this step because after this we will be + // dealing with sets and thus lose our ordering with which to + // decide preference.) + continue + } + + s.Add(PluginMeta{ + Name: name, + Version: version, + Path: path, + }) + found[nameVersion{name, version}] = struct{}{} + } + + return s +} diff --git a/plugin/discovery/find_test.go b/plugin/discovery/find_test.go new file mode 100644 index 0000000000..97ae8ed2c6 --- /dev/null +++ b/plugin/discovery/find_test.go @@ -0,0 +1,104 @@ +package discovery + +import ( + "os" + "path/filepath" + "reflect" + "testing" +) + +func TestFindPluginPaths(t *testing.T) { + got := findPluginPaths( + "foo", + "mockos_mockarch", + []string{ + "test-fixtures/current-style-plugins", + "test-fixtures/legacy-style-plugins", + "test-fixtures/non-existent", + "test-fixtures/not-a-dir", + }, + ) + want := []string{ + filepath.Join("test-fixtures", "current-style-plugins", "mockos_mockarch", "terraform-foo-bar-V0.0.1"), + filepath.Join("test-fixtures", "current-style-plugins", "mockos_mockarch", "terraform-foo-bar-V1.0.0"), + filepath.Join("test-fixtures", "legacy-style-plugins", "terraform-foo-bar"), + filepath.Join("test-fixtures", "legacy-style-plugins", "terraform-foo-baz"), + } + + // Turn the paths back into relative paths, since we don't care exactly + // where this code is present on the system for the sake of this test. + baseDir, err := os.Getwd() + if err != nil { + // Should never happen + panic(err) + } + for i, absPath := range got { + if !filepath.IsAbs(absPath) { + t.Errorf("got non-absolute path %s", absPath) + } + + got[i], err = filepath.Rel(baseDir, absPath) + if err != nil { + t.Fatalf("Can't make %s relative to current directory %s", absPath, baseDir) + } + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %#v\nwant: %#v", got, want) + } +} + +func TestResolvePluginPaths(t *testing.T) { + got := ResolvePluginPaths([]string{ + "/example/mockos_mockarch/terraform-foo-bar-V0.0.1", + "/example/mockos_mockarch/terraform-foo-baz-V0.0.1", + "/example/mockos_mockarch/terraform-foo-baz-V1.0.0", + "/example/terraform-foo-bar", + "/example/mockos_mockarch/terraform-foo-bar-Vbananas", + "/example/mockos_mockarch/terraform-foo-bar-V", + "/example2/mockos_mockarch/terraform-foo-bar-V0.0.1", + }) + + want := []PluginMeta{ + { + Name: "bar", + Version: "0.0.1", + Path: "/example/mockos_mockarch/terraform-foo-bar-V0.0.1", + }, + { + Name: "baz", + Version: "0.0.1", + Path: "/example/mockos_mockarch/terraform-foo-baz-V0.0.1", + }, + { + Name: "baz", + Version: "1.0.0", + Path: "/example/mockos_mockarch/terraform-foo-baz-V1.0.0", + }, + { + Name: "bar", + Version: "0.0.0", + Path: "/example/terraform-foo-bar", + }, + { + Name: "bar", + Version: "bananas", + Path: "/example/mockos_mockarch/terraform-foo-bar-Vbananas", + }, + { + Name: "bar", + Version: "", + Path: "/example/mockos_mockarch/terraform-foo-bar-V", + }, + } + + if got, want := got.Count(), len(want); got != want { + t.Errorf("got %d items; want %d", got, want) + } + + for _, wantMeta := range want { + if !got.Has(wantMeta) { + t.Errorf("missing meta %#v", wantMeta) + } + } +} diff --git a/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-bar-V0.0.1 b/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-bar-V0.0.1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-bar-V1.0.0 b/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-bar-V1.0.0 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-missing-version b/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-missing-version new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-notfoo-bar-V0.0.1 b/plugin/discovery/test-fixtures/current-style-plugins/mockos_mockarch/terraform-notfoo-bar-V0.0.1 new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/discovery/test-fixtures/legacy-style-plugins/terraform-foo-bar b/plugin/discovery/test-fixtures/legacy-style-plugins/terraform-foo-bar new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/discovery/test-fixtures/legacy-style-plugins/terraform-foo-baz b/plugin/discovery/test-fixtures/legacy-style-plugins/terraform-foo-baz new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/discovery/test-fixtures/legacy-style-plugins/terraform-notfoo-bar b/plugin/discovery/test-fixtures/legacy-style-plugins/terraform-notfoo-bar new file mode 100644 index 0000000000..e69de29bb2 diff --git a/plugin/discovery/test-fixtures/not-a-dir b/plugin/discovery/test-fixtures/not-a-dir new file mode 100644 index 0000000000..e69de29bb2 From 0a08214a7472f983c1c9463aa3671d12dc76bc36 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 13 Apr 2017 13:08:21 -0700 Subject: [PATCH 03/82] plugin/discovery: SHA256() method to get the hash of each plugin This will be used later to verify that installed plugins match what's available on the releases server. --- plugin/discovery/meta.go | 22 ++++++++++++++++++++++ plugin/discovery/meta_test.go | 22 ++++++++++++++++++++++ 2 files changed, 44 insertions(+) create mode 100644 plugin/discovery/meta_test.go diff --git a/plugin/discovery/meta.go b/plugin/discovery/meta.go index 16c7e87124..d93296cfc6 100644 --- a/plugin/discovery/meta.go +++ b/plugin/discovery/meta.go @@ -1,6 +1,10 @@ package discovery import ( + "crypto/sha256" + "io" + "os" + "github.com/blang/semver" ) @@ -26,3 +30,21 @@ type PluginMeta struct { func (m PluginMeta) VersionObj() (semver.Version, error) { return semver.Make(m.Version) } + +// SHA256 returns a SHA256 hash of the content of the referenced executable +// file, or an error if the file's contents cannot be read. +func (m PluginMeta) SHA256() ([]byte, error) { + f, err := os.Open(m.Path) + if err != nil { + return nil, err + } + defer f.Close() + + h := sha256.New() + _, err = io.Copy(h, f) + if err != nil { + return nil, err + } + + return h.Sum(nil), nil +} diff --git a/plugin/discovery/meta_test.go b/plugin/discovery/meta_test.go new file mode 100644 index 0000000000..9249d6f442 --- /dev/null +++ b/plugin/discovery/meta_test.go @@ -0,0 +1,22 @@ +package discovery + +import ( + "fmt" + "testing" +) + +func TestMetaSHA256(t *testing.T) { + m := PluginMeta{ + Path: "test-fixtures/current-style-plugins/mockos_mockarch/terraform-foo-bar-V0.0.1", + } + hash, err := m.SHA256() + if err != nil { + t.Fatalf("failed: %s", err) + } + + got := fmt.Sprintf("%x", hash) + want := "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" // (hash of empty file) + if got != want { + t.Errorf("incorrect hash %s; want %s", got, want) + } +} From 551bc9c4a4fdc193f7d4a641d525d1fc6f7e1fb3 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 13 Apr 2017 14:06:49 -0700 Subject: [PATCH 04/82] plugin/discovery: build plugin clients These new methods ClientConfig and Client provide the bridge into the main plugin infrastructure by configuring and instantiating (respectively) a client object for the referenced plugin. This stops short of getting the proxy object from the client since that then requires referencing the interface for the plugin kind, which would then create a dependency on the main terraform package which we'd rather avoid here. It'll be the responsibility of the caller in the command package to do the final wiring to get a provider instance out of a provider plugin client. --- plugin/discovery/meta.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/plugin/discovery/meta.go b/plugin/discovery/meta.go index d93296cfc6..0c3a1ff4af 100644 --- a/plugin/discovery/meta.go +++ b/plugin/discovery/meta.go @@ -4,8 +4,11 @@ import ( "crypto/sha256" "io" "os" + "os/exec" "github.com/blang/semver" + plugin "github.com/hashicorp/go-plugin" + tfplugin "github.com/hashicorp/terraform/plugin" ) // PluginMeta is metadata about a plugin, useful for launching the plugin @@ -48,3 +51,19 @@ func (m PluginMeta) SHA256() ([]byte, error) { return h.Sum(nil), nil } + +// ClientConfig returns a configuration object that can be used to instantiate +// a client for the referenced plugin. +func (m PluginMeta) ClientConfig() *plugin.ClientConfig { + return &plugin.ClientConfig{ + Cmd: exec.Command(m.Path), + HandshakeConfig: tfplugin.Handshake, + Managed: true, + Plugins: tfplugin.PluginMap, + } +} + +// Client returns a plugin client for the referenced plugin. +func (m PluginMeta) Client() *plugin.Client { + return plugin.NewClient(m.ClientConfig()) +} From bcd395e6bdfd255183cacf9c7b2ebe439374f602 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 13 Apr 2017 14:13:02 -0700 Subject: [PATCH 05/82] plugin/discovery: Allow overriding the paths of certain plugin names The .terraformrc file allows the user to override the executable location for certain plugins. This mechanism allows us to retain that behavior for a deprecation period by treating such executables as an unversioned plugin for the given name and excluding all other candidates for that name, thus ensuring that the override will "win". Users must eventually transition away from using this mechanism and use vendor directories instead, because these unversioned overrides will never match for a provider referenced with non-zero version constraints. --- plugin/discovery/meta_set.go | 30 +++++++++++++ plugin/discovery/meta_set_test.go | 74 +++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+) diff --git a/plugin/discovery/meta_set.go b/plugin/discovery/meta_set.go index 72c0019b3d..37220f5157 100644 --- a/plugin/discovery/meta_set.go +++ b/plugin/discovery/meta_set.go @@ -149,3 +149,33 @@ func (s PluginMetaSet) ConstrainVersions(reqd map[string]semver.Range) map[strin } return ret } + +// OverridePaths returns a new set where any existing plugins with the given +// names are removed and replaced with the single path given in the map. +// +// This is here only to continue to support the legacy way of overriding +// plugin binaries in the .terraformrc file. It treats all given plugins +// as pre-versioning (version 0.0.0). This mechanism will eventually be +// phased out, with vendor directories being the intended replacement. +func (s PluginMetaSet) OverridePaths(paths map[string]string) PluginMetaSet { + ret := make(PluginMetaSet) + for p := range s { + if _, ok := paths[p.Name]; ok { + // Skip plugins that we're overridding + continue + } + + ret.Add(p) + } + + // Now add the metadata for overriding plugins + for name, path := range paths { + ret.Add(PluginMeta{ + Name: name, + Version: "0.0.0", + Path: path, + }) + } + + return ret +} diff --git a/plugin/discovery/meta_set_test.go b/plugin/discovery/meta_set_test.go index 15fc7bae5b..21aaed29de 100644 --- a/plugin/discovery/meta_set_test.go +++ b/plugin/discovery/meta_set_test.go @@ -343,3 +343,77 @@ func TestPluginMetaSetConstrainVersions(t *testing.T) { } } + +func TestPluginMetaSetOverridePaths(t *testing.T) { + + metas := []PluginMeta{ + { + Name: "foo", + Version: "1.0.0", + Path: "test-foo-1", + }, + { + Name: "foo", + Version: "2.0.0", + Path: "test-foo-2", + }, + { + Name: "foo", + Version: "3.0.0", + Path: "test-foo-3", + }, + { + Name: "bar", + Version: "0.0.5", + Path: "test-bar-5", + }, + { + Name: "bar", + Version: "0.0.6", + Path: "test-bar-6", + }, + { + Name: "baz", + Version: "0.0.1", + Path: "test-bar", + }, + } + s := make(PluginMetaSet) + + for _, p := range metas { + s.Add(p) + } + + ns := s.OverridePaths(map[string]string{ + "foo": "override-foo", + "fun": "override-fun", + }) + + if got, want := ns.Count(), 5; got != want { + t.Errorf("got %d metas; want %d", got, want) + } + + if !ns.Has(metas[3]) { + t.Errorf("new set is missing %#v", metas[3]) + } + if !ns.Has(metas[4]) { + t.Errorf("new set is missing %#v", metas[4]) + } + if !ns.Has(metas[5]) { + t.Errorf("new set is missing %#v", metas[5]) + } + if !ns.Has(PluginMeta{ + Name: "foo", + Version: "0.0.0", + Path: "override-foo", + }) { + t.Errorf("new set is missing 'foo' override") + } + if !ns.Has(PluginMeta{ + Name: "fun", + Version: "0.0.0", + Path: "override-fun", + }) { + t.Errorf("new set is missing 'fun' override") + } +} From 8364383c359a6b738a436d1b7745ccdce178df47 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 13 Apr 2017 18:05:58 -0700 Subject: [PATCH 06/82] Push plugin discovery down into command package Previously we did plugin discovery in the main package, but as we move towards versioned plugins we need more information available in order to resolve plugins, so we move this responsibility into the command package itself. For the moment this is just preserving the existing behavior as long as there are only internal and unversioned plugins present. This is the final state for provisioners in 0.10, since we don't want to support versioned provisioners yet. For providers this is just a checkpoint along the way, since further work is required to apply version constraints from configuration and support additional plugin search directories. The automatic plugin discovery behavior is not desirable for tests because we want to mock the plugins there, so we add a new backdoor for the tests to use to skip the plugin discovery and just provide their own mock implementations. Most of this diff is thus noisy rework of the tests to use this new mechanism. --- command/apply_destroy_test.go | 16 +- command/apply_test.go | 134 ++++++++--------- command/command_test.go | 8 +- command/console_test.go | 8 +- command/debug_json2dot_test.go | 4 +- command/fmt_test.go | 24 +-- command/get_test.go | 24 +-- command/graph_test.go | 16 +- command/import_test.go | 54 +++---- command/init_test.go | 76 +++++----- command/meta.go | 38 +++-- command/output_test.go | 52 +++---- command/plan_test.go | 84 +++++------ command/plugins.go | 163 ++++++++++++++++++++ command/push_test.go | 52 +++---- command/refresh_test.go | 60 ++++---- command/show_test.go | 24 +-- command/state_list_test.go | 12 +- command/state_mv_test.go | 32 ++-- command/state_pull_test.go | 8 +- command/state_push_test.go | 24 +-- command/state_rm_test.go | 12 +- command/state_show_test.go | 20 +-- command/unlock_test.go | 4 +- commands.go | 7 +- config.go | 265 +-------------------------------- main.go | 10 +- plugins.go | 37 +++++ 28 files changed, 611 insertions(+), 657 deletions(-) create mode 100644 command/plugins.go create mode 100644 plugins.go diff --git a/command/apply_destroy_test.go b/command/apply_destroy_test.go index 376c5e29e3..c2d399e723 100644 --- a/command/apply_destroy_test.go +++ b/command/apply_destroy_test.go @@ -33,8 +33,8 @@ func TestApply_destroy(t *testing.T) { c := &ApplyCommand{ Destroy: true, Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -123,8 +123,8 @@ func TestApply_destroyLockedState(t *testing.T) { c := &ApplyCommand{ Destroy: true, Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -155,8 +155,8 @@ func TestApply_destroyPlan(t *testing.T) { c := &ApplyCommand{ Destroy: true, Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -199,8 +199,8 @@ func TestApply_destroyTargeted(t *testing.T) { c := &ApplyCommand{ Destroy: true, Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/apply_test.go b/command/apply_test.go index 5700ade690..0937eee079 100644 --- a/command/apply_test.go +++ b/command/apply_test.go @@ -28,8 +28,8 @@ func TestApply(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -74,8 +74,8 @@ func TestApply_lockedState(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -112,8 +112,8 @@ func TestApply_lockedStateWait(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -188,8 +188,8 @@ func TestApply_parallelism(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(provider), - Ui: ui, + testingOverrides: metaOverridesForProvider(provider), + Ui: ui, }, } @@ -241,8 +241,8 @@ func TestApply_configInvalid(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -276,8 +276,8 @@ func TestApply_defaultState(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -314,8 +314,8 @@ func TestApply_error(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -403,8 +403,8 @@ func TestApply_init(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -460,8 +460,8 @@ func TestApply_input(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -495,8 +495,8 @@ func TestApply_inputPartial(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -535,8 +535,8 @@ func TestApply_noArgs(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -584,8 +584,8 @@ func TestApply_plan(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -630,8 +630,8 @@ func TestApply_plan_backup(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -672,8 +672,8 @@ func TestApply_plan_noBackup(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -729,8 +729,8 @@ func TestApply_plan_remoteState(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -782,8 +782,8 @@ func TestApply_planWithVarFile(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -824,8 +824,8 @@ func TestApply_planVars(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -855,12 +855,10 @@ func TestApply_planNoModuleFiles(t *testing.T) { Module: testModule(t, "apply-plan-no-module"), }) - contextOpts := testCtxConfig(p) - apply := &ApplyCommand{ Meta: Meta{ - ContextOpts: contextOpts, - Ui: new(cli.MockUi), + testingOverrides: metaOverridesForProvider(p), + Ui: new(cli.MockUi), }, } args := []string{ @@ -895,8 +893,8 @@ func TestApply_refresh(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -961,8 +959,8 @@ func TestApply_shutdown(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, ShutdownCh: shutdownCh, @@ -1072,8 +1070,8 @@ func TestApply_state(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1145,8 +1143,8 @@ func TestApply_stateNoExist(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1164,8 +1162,8 @@ func TestApply_sensitiveOutput(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1198,8 +1196,8 @@ func TestApply_stateFuture(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1239,8 +1237,8 @@ func TestApply_statePast(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1260,8 +1258,8 @@ func TestApply_vars(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1303,8 +1301,8 @@ func TestApply_varFile(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1356,8 +1354,8 @@ func TestApply_varFileDefault(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1408,8 +1406,8 @@ func TestApply_varFileDefaultJSON(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1471,8 +1469,8 @@ func TestApply_backup(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1540,8 +1538,8 @@ func TestApply_disableBackup(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1608,8 +1606,8 @@ func TestApply_terraformEnv(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -1663,8 +1661,8 @@ func TestApply_terraformEnvNonDefault(t *testing.T) { ui := new(cli.MockUi) c := &ApplyCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/command_test.go b/command/command_test.go index 21a5a8d414..77cb3e741d 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -69,8 +69,8 @@ func testFixturePath(name string) string { return filepath.Join(fixtureDir, name) } -func testCtxConfig(p terraform.ResourceProvider) *terraform.ContextOpts { - return &terraform.ContextOpts{ +func metaOverridesForProvider(p terraform.ResourceProvider) *testingOverrides { + return &testingOverrides{ Providers: map[string]terraform.ResourceProviderFactory{ "test": func() (terraform.ResourceProvider, error) { return p, nil @@ -79,8 +79,8 @@ func testCtxConfig(p terraform.ResourceProvider) *terraform.ContextOpts { } } -func testCtxConfigWithShell(p terraform.ResourceProvider, pr terraform.ResourceProvisioner) *terraform.ContextOpts { - return &terraform.ContextOpts{ +func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr terraform.ResourceProvisioner) *testingOverrides { + return &testingOverrides{ Providers: map[string]terraform.ResourceProviderFactory{ "test": func() (terraform.ResourceProvider, error) { return p, nil diff --git a/command/console_test.go b/command/console_test.go index 0c9b0f8d45..c30ade9e33 100644 --- a/command/console_test.go +++ b/command/console_test.go @@ -25,8 +25,8 @@ func TestConsole_basic(t *testing.T) { ui := new(cli.MockUi) c := &ConsoleCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -61,8 +61,8 @@ func TestConsole_tfvars(t *testing.T) { ui := new(cli.MockUi) c := &ConsoleCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/debug_json2dot_test.go b/command/debug_json2dot_test.go index 689897ea0d..75c1a60bd7 100644 --- a/command/debug_json2dot_test.go +++ b/command/debug_json2dot_test.go @@ -30,8 +30,8 @@ func TestDebugJSON2Dot(t *testing.T) { ui := new(cli.MockUi) c := &DebugJSON2DotCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } diff --git a/command/fmt_test.go b/command/fmt_test.go index 191cd47ee7..3fc34e196a 100644 --- a/command/fmt_test.go +++ b/command/fmt_test.go @@ -22,8 +22,8 @@ func TestFmt_errorReporting(t *testing.T) { ui := new(cli.MockUi) c := &FmtCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -43,8 +43,8 @@ func TestFmt_tooManyArgs(t *testing.T) { ui := new(cli.MockUi) c := &FmtCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -82,8 +82,8 @@ func TestFmt_workingDirectory(t *testing.T) { ui := new(cli.MockUi) c := &FmtCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -108,8 +108,8 @@ func TestFmt_directoryArg(t *testing.T) { ui := new(cli.MockUi) c := &FmtCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -131,8 +131,8 @@ func TestFmt_stdinArg(t *testing.T) { ui := new(cli.MockUi) c := &FmtCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, input: input, } @@ -158,8 +158,8 @@ func TestFmt_nonDefaultOptions(t *testing.T) { ui := new(cli.MockUi) c := &FmtCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } diff --git a/command/get_test.go b/command/get_test.go index b934151927..69980db059 100644 --- a/command/get_test.go +++ b/command/get_test.go @@ -15,9 +15,9 @@ func TestGet(t *testing.T) { ui := new(cli.MockUi) c := &GetCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, - dataDir: tempDir(t), + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + dataDir: tempDir(t), }, } @@ -41,9 +41,9 @@ func TestGet_multipleArgs(t *testing.T) { ui := new(cli.MockUi) c := &GetCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, - dataDir: tempDir(t), + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + dataDir: tempDir(t), }, } @@ -69,9 +69,9 @@ func TestGet_noArgs(t *testing.T) { ui := new(cli.MockUi) c := &GetCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, - dataDir: tempDir(t), + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + dataDir: tempDir(t), }, } @@ -96,9 +96,9 @@ func TestGet_update(t *testing.T) { ui := new(cli.MockUi) c := &GetCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, - dataDir: tempDir(t), + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + dataDir: tempDir(t), }, } diff --git a/command/graph_test.go b/command/graph_test.go index 6eabc29b38..cf6eaab2bc 100644 --- a/command/graph_test.go +++ b/command/graph_test.go @@ -16,8 +16,8 @@ func TestGraph(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -38,8 +38,8 @@ func TestGraph_multipleArgs(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -65,8 +65,8 @@ func TestGraph_noArgs(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -105,8 +105,8 @@ func TestGraph_plan(t *testing.T) { ui := new(cli.MockUi) c := &GraphCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } diff --git a/command/import_test.go b/command/import_test.go index 82d2d5be9e..5b8838ccb7 100644 --- a/command/import_test.go +++ b/command/import_test.go @@ -15,8 +15,8 @@ func TestImport(t *testing.T) { ui := new(cli.MockUi) c := &ImportCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -55,8 +55,8 @@ func TestImport_providerConfig(t *testing.T) { ui := new(cli.MockUi) c := &ImportCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -111,8 +111,8 @@ func TestImport_providerConfigDisable(t *testing.T) { ui := new(cli.MockUi) c := &ImportCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -168,8 +168,8 @@ func TestImport_providerConfigWithVar(t *testing.T) { ui := new(cli.MockUi) c := &ImportCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -225,8 +225,8 @@ func TestImport_providerConfigWithVarDefault(t *testing.T) { ui := new(cli.MockUi) c := &ImportCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -281,8 +281,8 @@ func TestImport_providerConfigWithVarFile(t *testing.T) { ui := new(cli.MockUi) c := &ImportCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -335,7 +335,7 @@ func TestRefresh_badState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -366,7 +366,7 @@ func TestRefresh_cwd(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -438,7 +438,7 @@ func TestRefresh_defaultState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -510,7 +510,7 @@ func TestRefresh_futureState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -553,7 +553,7 @@ func TestRefresh_pastState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -612,7 +612,7 @@ func TestRefresh_outPath(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -687,7 +687,7 @@ func TestRefresh_var(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -717,7 +717,7 @@ func TestRefresh_varFile(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -752,7 +752,7 @@ func TestRefresh_varFileDefault(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -802,7 +802,7 @@ func TestRefresh_varsUnset(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -842,7 +842,7 @@ func TestRefresh_backup(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -927,7 +927,7 @@ func TestRefresh_disableBackup(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -992,7 +992,7 @@ func TestRefresh_displaysOutputs(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), + testingOverrides: metaOverridesForProvider(p), Ui: ui, }, } @@ -1021,8 +1021,8 @@ func TestImport_customProvider(t *testing.T) { ui := new(cli.MockUi) c := &ImportCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/init_test.go b/command/init_test.go index eea5797c16..5cc5c008e8 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -17,8 +17,8 @@ func TestInit(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -54,8 +54,8 @@ func TestInit_cwd(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -81,8 +81,8 @@ func TestInit_empty(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -96,8 +96,8 @@ func TestInit_multipleArgs(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -134,8 +134,8 @@ func TestInit_dstInSrc(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -162,8 +162,8 @@ func TestInit_get(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -189,8 +189,8 @@ func TestInit_copyGet(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -222,8 +222,8 @@ func TestInit_backend(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -248,8 +248,8 @@ func TestInit_backendUnset(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -273,8 +273,8 @@ func TestInit_backendUnset(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -301,8 +301,8 @@ func TestInit_backendConfigFile(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -333,8 +333,8 @@ func TestInit_backendConfigFileChange(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -360,8 +360,8 @@ func TestInit_backendConfigKV(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -387,8 +387,8 @@ func TestInit_copyBackendDst(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -426,8 +426,8 @@ func TestInit_backendReinitWithExtra(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -474,8 +474,8 @@ func TestInit_backendReinitConfigToExtra(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -518,8 +518,8 @@ func TestInit_inputFalse(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -546,7 +546,7 @@ func TestInit_remoteState(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), + testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, }, } @@ -582,7 +582,7 @@ func TestInit_remoteStateSubdir(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), + testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, }, } @@ -626,7 +626,7 @@ func TestInit_remoteStateWithLocal(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), + testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, }, } @@ -664,7 +664,7 @@ func TestInit_remoteStateWithRemote(t *testing.T) { ui := new(cli.MockUi) c := &InitCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), + testingOverrides: metaOverridesForProvider(testProvider()), Ui: ui, }, } diff --git a/command/meta.go b/command/meta.go index 0b9375f720..40d13aa1e9 100644 --- a/command/meta.go +++ b/command/meta.go @@ -32,9 +32,10 @@ type Meta struct { // command with a Meta field. These are expected to be set externally // (not from within the command itself). - Color bool // True if output should be colored - ContextOpts *terraform.ContextOpts // Opts copied to initialize - Ui cli.Ui // Ui for output + Color bool // True if output should be colored + GlobalPluginDirs []string // Additional paths to search for plugins + PluginOverrides *PluginOverrides // legacy overrides from .terraformrc file + Ui cli.Ui // Ui for output // ExtraHooks are extra hooks to add to the context. ExtraHooks []terraform.Hook @@ -46,6 +47,9 @@ type Meta struct { // Modify the data directory location. Defaults to DefaultDataDir dataDir string + // Override certain behavior for tests within this package + testingOverrides *testingOverrides + //---------------------------------------------------------- // Private: do not set these //---------------------------------------------------------- @@ -109,6 +113,16 @@ type Meta struct { reconfigure bool } +type PluginOverrides struct { + Providers map[string]string + Provisioners map[string]string +} + +type testingOverrides struct { + Providers map[string]terraform.ResourceProviderFactory + Provisioners map[string]terraform.ResourceProvisionerFactory +} + // initStatePaths is used to initialize the default values for // statePath, stateOutPath, and backupPath func (m *Meta) initStatePaths() { @@ -199,14 +213,7 @@ func (m *Meta) StdinPiped() bool { // context with the settings from this Meta. func (m *Meta) contextOpts() *terraform.ContextOpts { var opts terraform.ContextOpts - if v := m.ContextOpts; v != nil { - opts = *v - } - opts.Hooks = []terraform.Hook{m.uiHook(), &terraform.DebugHook{}} - if m.ContextOpts != nil { - opts.Hooks = append(opts.Hooks, m.ContextOpts.Hooks...) - } opts.Hooks = append(opts.Hooks, m.ExtraHooks...) vs := make(map[string]interface{}) @@ -226,6 +233,17 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { opts.Parallelism = m.parallelism opts.Shadow = m.shadow + // If testingOverrides are set, we'll skip the plugin discovery process + // and just work with what we've been given, thus allowing the tests + // to provide mock providers and provisioners. + if m.testingOverrides != nil { + opts.Providers = m.testingOverrides.Providers + opts.Provisioners = m.testingOverrides.Provisioners + } else { + opts.Providers = m.providerFactories() + opts.Provisioners = m.provisionerFactories() + } + opts.Meta = &terraform.ContextMeta{ Env: m.Env(), } diff --git a/command/output_test.go b/command/output_test.go index 01f5c034e6..298cd84440 100644 --- a/command/output_test.go +++ b/command/output_test.go @@ -31,8 +31,8 @@ func TestOutput(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -79,8 +79,8 @@ func TestModuleOutput(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -129,8 +129,8 @@ func TestModuleOutputs(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -177,8 +177,8 @@ func TestOutput_nestedListAndMap(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -216,8 +216,8 @@ func TestOutput_json(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -256,8 +256,8 @@ func TestMissingModuleOutput(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -292,8 +292,8 @@ func TestOutput_badVar(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -330,8 +330,8 @@ func TestOutput_blank(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -355,8 +355,8 @@ func TestOutput_manyArgs(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -373,8 +373,8 @@ func TestOutput_noArgs(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -391,8 +391,8 @@ func TestOutput_noState(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -420,8 +420,8 @@ func TestOutput_noVars(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -480,8 +480,8 @@ func TestOutput_stateDefault(t *testing.T) { ui := new(cli.MockUi) c := &OutputCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } diff --git a/command/plan_test.go b/command/plan_test.go index 6fd7d20cf2..c0f8f98d15 100644 --- a/command/plan_test.go +++ b/command/plan_test.go @@ -28,8 +28,8 @@ func TestPlan(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -61,8 +61,8 @@ func TestPlan_lockedState(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -89,8 +89,8 @@ func TestPlan_plan(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -128,8 +128,8 @@ func TestPlan_destroy(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -165,8 +165,8 @@ func TestPlan_noState(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -205,8 +205,8 @@ func TestPlan_outPath(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -262,8 +262,8 @@ func TestPlan_outPathNoChange(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -318,8 +318,8 @@ func TestPlan_outBackend(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -382,8 +382,8 @@ func TestPlan_outBackendLegacy(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -412,8 +412,8 @@ func TestPlan_refresh(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -450,8 +450,8 @@ func TestPlan_state(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -506,8 +506,8 @@ func TestPlan_stateDefault(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -535,8 +535,8 @@ func TestPlan_stateFuture(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -576,8 +576,8 @@ func TestPlan_statePast(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -608,8 +608,8 @@ func TestPlan_validate(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -632,8 +632,8 @@ func TestPlan_vars(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -676,8 +676,8 @@ func TestPlan_varsUnset(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -702,8 +702,8 @@ func TestPlan_varFile(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -752,8 +752,8 @@ func TestPlan_varFileDefault(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -795,8 +795,8 @@ func TestPlan_detailedExitcode(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -820,8 +820,8 @@ func TestPlan_detailedExitcode_emptyDiff(t *testing.T) { ui := new(cli.MockUi) c := &PlanCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/plugins.go b/command/plugins.go new file mode 100644 index 0000000000..113378db00 --- /dev/null +++ b/command/plugins.go @@ -0,0 +1,163 @@ +package command + +import ( + "log" + "os/exec" + "strings" + + plugin "github.com/hashicorp/go-plugin" + tfplugin "github.com/hashicorp/terraform/plugin" + "github.com/hashicorp/terraform/plugin/discovery" + "github.com/hashicorp/terraform/terraform" +) + +func (m *Meta) providerFactories() map[string]terraform.ResourceProviderFactory { + var dirs []string + + // When searching the following directories, earlier entries get precedence + // if the same plugin version is found twice, but newer versions will + // always get preference below regardless of where they are coming from. + // TODO: Add auto-install dir, default vendor dir and optional override + // vendor dir(s). + dirs = append(dirs, ".") + dirs = append(dirs, m.GlobalPluginDirs...) + + plugins := discovery.FindPlugins("provider", dirs) + plugins, _ = plugins.ValidateVersions() + + // For now our goal is to just find the latest version of each plugin + // we have on the system, emulating our pre-versioning behavior. + // TODO: Reorganize how providers are handled so that we can use + // version constraints from configuration to select which plugins + // we will use when multiple are available. + + factories := make(map[string]terraform.ResourceProviderFactory) + + // Wire up the internal provisioners first. These might be overridden + // by discovered providers below. + for name := range InternalProviders { + client, err := internalPluginClient("provider", name) + if err != nil { + log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err) + continue + } + factories[name] = providerFactory(client) + } + + byName := plugins.ByName() + for name, metas := range byName { + // Since we validated versions above and we partitioned the sets + // by name, we're guaranteed that the metas in our set all have + // valid versions and that there's at least one meta. + newest := metas.Newest() + client := newest.Client() + factories[name] = providerFactory(client) + } + + return factories +} + +func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { + var dirs []string + + // When searching the following directories, earlier entries get precedence + // if the same plugin version is found twice, but newer versions will + // always get preference below regardless of where they are coming from. + // + // NOTE: Currently we don't use versioning for provisioners, so the + // version handling here is just the minimum required to be able to use + // the plugin discovery package. All provisioner plugins should always + // be versionless, which we treat as version 0.0.0 here. + dirs = append(dirs, ".") + dirs = append(dirs, m.GlobalPluginDirs...) + + plugins := discovery.FindPlugins("provisioner", dirs) + plugins, _ = plugins.ValidateVersions() + + // For now our goal is to just find the latest version of each plugin + // we have on the system. All provisioners should be at version 0.0.0 + // currently, so there should actually only be one instance of each plugin + // name here, even though the discovery interface forces us to pretend + // that might not be true. + + factories := make(map[string]terraform.ResourceProvisionerFactory) + + // Wire up the internal provisioners first. These might be overridden + // by discovered provisioners below. + for name := range InternalProvisioners { + client, err := internalPluginClient("provisioner", name) + if err != nil { + log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err) + continue + } + factories[name] = provisionerFactory(client) + } + + byName := plugins.ByName() + for name, metas := range byName { + // Since we validated versions above and we partitioned the sets + // by name, we're guaranteed that the metas in our set all have + // valid versions and that there's at least one meta. + newest := metas.Newest() + client := newest.Client() + factories[name] = provisionerFactory(client) + } + + return factories +} + +func internalPluginClient(kind, name string) (*plugin.Client, error) { + cmdLine, err := BuildPluginCommandString(kind, name) + if err != nil { + return nil, err + } + + // See the docstring for BuildPluginCommandString for why we need to do + // this split here. + cmdArgv := strings.Split(cmdLine, TFSPACE) + + cfg := &plugin.ClientConfig{ + Cmd: exec.Command(cmdArgv[0], cmdArgv[1:]...), + HandshakeConfig: tfplugin.Handshake, + Managed: true, + Plugins: tfplugin.PluginMap, + } + + return plugin.NewClient(cfg), nil +} + +func providerFactory(client *plugin.Client) terraform.ResourceProviderFactory { + return func() (terraform.ResourceProvider, error) { + // Request the RPC client so we can get the provider + // so we can build the actual RPC-implemented provider. + rpcClient, err := client.Client() + if err != nil { + return nil, err + } + + raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) + if err != nil { + return nil, err + } + + return raw.(terraform.ResourceProvider), nil + } +} + +func provisionerFactory(client *plugin.Client) terraform.ResourceProvisionerFactory { + return func() (terraform.ResourceProvisioner, error) { + // Request the RPC client so we can get the provisioner + // so we can build the actual RPC-implemented provisioner. + rpcClient, err := client.Client() + if err != nil { + return nil, err + } + + raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) + if err != nil { + return nil, err + } + + return raw.(terraform.ResourceProvisioner), nil + } +} diff --git a/command/push_test.go b/command/push_test.go index 4afeba3b36..473a31843d 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -40,8 +40,8 @@ func TestPush_good(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -101,8 +101,8 @@ func TestPush_goodBackendInit(t *testing.T) { ui = new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -148,8 +148,8 @@ func TestPush_noUploadModules(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -168,8 +168,8 @@ func TestPush_noUploadModules(t *testing.T) { ui := new(cli.MockUi) c := &GetCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -238,8 +238,8 @@ func TestPush_input(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -297,8 +297,8 @@ func TestPush_inputPartial(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -367,8 +367,8 @@ func TestPush_localOverride(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -446,8 +446,8 @@ func TestPush_remoteOverride(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -537,8 +537,8 @@ func TestPush_preferAtlas(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -613,8 +613,8 @@ func TestPush_tfvars(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -688,8 +688,8 @@ func TestPush_name(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, client: client, @@ -716,8 +716,8 @@ func TestPush_noState(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -801,8 +801,8 @@ func TestPush_plan(t *testing.T) { ui := new(cli.MockUi) c := &PushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } diff --git a/command/refresh_test.go b/command/refresh_test.go index 12241e8097..c53c84bd3c 100644 --- a/command/refresh_test.go +++ b/command/refresh_test.go @@ -22,8 +22,8 @@ func TestRefresh(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -71,8 +71,8 @@ func TestRefresh_empty(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -105,8 +105,8 @@ func TestRefresh_lockedState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -145,8 +145,8 @@ func TestRefresh_cwd(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -217,8 +217,8 @@ func TestRefresh_defaultState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -290,8 +290,8 @@ func TestRefresh_futureState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -333,8 +333,8 @@ func TestRefresh_pastState(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -392,8 +392,8 @@ func TestRefresh_outPath(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -467,8 +467,8 @@ func TestRefresh_var(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -497,8 +497,8 @@ func TestRefresh_varFile(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -532,8 +532,8 @@ func TestRefresh_varFileDefault(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -582,8 +582,8 @@ func TestRefresh_varsUnset(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -622,8 +622,8 @@ func TestRefresh_backup(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -707,8 +707,8 @@ func TestRefresh_disableBackup(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -776,8 +776,8 @@ func TestRefresh_displaysOutputs(t *testing.T) { ui := new(cli.MockUi) c := &RefreshCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/show_test.go b/command/show_test.go index ad5c52f880..dfcb4ac7ca 100644 --- a/command/show_test.go +++ b/command/show_test.go @@ -16,8 +16,8 @@ func TestShow(t *testing.T) { ui := new(cli.MockUi) c := &ShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -61,8 +61,8 @@ func TestShow_noArgs(t *testing.T) { ui := new(cli.MockUi) c := &ShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -93,8 +93,8 @@ func TestShow_noArgsNoState(t *testing.T) { ui := new(cli.MockUi) c := &ShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -112,8 +112,8 @@ func TestShow_plan(t *testing.T) { ui := new(cli.MockUi) c := &ShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -138,8 +138,8 @@ func TestShow_noArgsRemoteState(t *testing.T) { ui := new(cli.MockUi) c := &ShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } @@ -162,8 +162,8 @@ func TestShow_state(t *testing.T) { ui := new(cli.MockUi) c := &ShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(testProvider()), - Ui: ui, + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, }, } diff --git a/command/state_list_test.go b/command/state_list_test.go index 86c4f7194b..4c484f09be 100644 --- a/command/state_list_test.go +++ b/command/state_list_test.go @@ -17,8 +17,8 @@ func TestStateList(t *testing.T) { ui := new(cli.MockUi) c := &StateListCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -48,8 +48,8 @@ func TestStateList_backendState(t *testing.T) { ui := new(cli.MockUi) c := &StateListCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -74,8 +74,8 @@ func TestStateList_noState(t *testing.T) { ui := new(cli.MockUi) c := &StateListCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/state_mv_test.go b/command/state_mv_test.go index d479b4ccb9..983d67b8d8 100644 --- a/command/state_mv_test.go +++ b/command/state_mv_test.go @@ -47,8 +47,8 @@ func TestStateMv(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -114,8 +114,8 @@ func TestStateMv_backupExplicit(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -169,8 +169,8 @@ func TestStateMv_stateOutNew(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -241,8 +241,8 @@ func TestStateMv_stateOutExisting(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -282,8 +282,8 @@ func TestStateMv_noState(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -343,8 +343,8 @@ func TestStateMv_stateOutNew_count(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -521,8 +521,8 @@ func TestStateMv_stateOutNew_largeCount(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -602,8 +602,8 @@ func TestStateMv_stateOutNew_nestedModule(t *testing.T) { ui := new(cli.MockUi) c := &StateMvCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/state_pull_test.go b/command/state_pull_test.go index ac022d726b..c93f4e43c3 100644 --- a/command/state_pull_test.go +++ b/command/state_pull_test.go @@ -21,8 +21,8 @@ func TestStatePull(t *testing.T) { ui := new(cli.MockUi) c := &StatePullCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -46,8 +46,8 @@ func TestStatePull_noState(t *testing.T) { ui := new(cli.MockUi) c := &StatePullCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/state_push_test.go b/command/state_push_test.go index 10f1718a9b..2e2e8700f7 100644 --- a/command/state_push_test.go +++ b/command/state_push_test.go @@ -23,8 +23,8 @@ func TestStatePush_empty(t *testing.T) { ui := new(cli.MockUi) c := &StatePushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -52,8 +52,8 @@ func TestStatePush_replaceMatch(t *testing.T) { ui := new(cli.MockUi) c := &StatePushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -88,8 +88,8 @@ func TestStatePush_replaceMatchStdin(t *testing.T) { ui := new(cli.MockUi) c := &StatePushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -117,8 +117,8 @@ func TestStatePush_lineageMismatch(t *testing.T) { ui := new(cli.MockUi) c := &StatePushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -146,8 +146,8 @@ func TestStatePush_serialNewer(t *testing.T) { ui := new(cli.MockUi) c := &StatePushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -175,8 +175,8 @@ func TestStatePush_serialOlder(t *testing.T) { ui := new(cli.MockUi) c := &StatePushCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/state_rm_test.go b/command/state_rm_test.go index bb37345029..22256366c7 100644 --- a/command/state_rm_test.go +++ b/command/state_rm_test.go @@ -47,8 +47,8 @@ func TestStateRm(t *testing.T) { ui := new(cli.MockUi) c := &StateRmCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -113,8 +113,8 @@ func TestStateRm_backupExplicit(t *testing.T) { ui := new(cli.MockUi) c := &StateRmCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -147,8 +147,8 @@ func TestStateRm_noState(t *testing.T) { ui := new(cli.MockUi) c := &StateRmCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/state_show_test.go b/command/state_show_test.go index 9e0ede3966..e2cd96d382 100644 --- a/command/state_show_test.go +++ b/command/state_show_test.go @@ -35,8 +35,8 @@ func TestStateShow(t *testing.T) { ui := new(cli.MockUi) c := &StateShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -93,8 +93,8 @@ func TestStateShow_multi(t *testing.T) { ui := new(cli.MockUi) c := &StateShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -115,8 +115,8 @@ func TestStateShow_noState(t *testing.T) { ui := new(cli.MockUi) c := &StateShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -135,8 +135,8 @@ func TestStateShow_emptyState(t *testing.T) { ui := new(cli.MockUi) c := &StateShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } @@ -164,8 +164,8 @@ func TestStateShow_emptyStateWithModule(t *testing.T) { ui := new(cli.MockUi) c := &StateShowCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/command/unlock_test.go b/command/unlock_test.go index 892947afb6..342df3b671 100644 --- a/command/unlock_test.go +++ b/command/unlock_test.go @@ -35,8 +35,8 @@ func TestUnlock(t *testing.T) { ui := new(cli.MockUi) c := &UnlockCommand{ Meta: Meta{ - ContextOpts: testCtxConfig(p), - Ui: ui, + testingOverrides: metaOverridesForProvider(p), + Ui: ui, }, } diff --git a/commands.go b/commands.go index 409f4d85df..c4dca2d670 100644 --- a/commands.go +++ b/commands.go @@ -30,9 +30,10 @@ func init() { } meta := command.Meta{ - Color: true, - ContextOpts: &ContextOpts, - Ui: Ui, + Color: true, + GlobalPluginDirs: globalPluginDirs(), + PluginOverrides: &PluginOverrides, + Ui: Ui, } // The command list is included in the terraform -help diff --git a/config.go b/config.go index d0df909a9b..d9391c3cec 100644 --- a/config.go +++ b/config.go @@ -6,17 +6,9 @@ import ( "io/ioutil" "log" "os" - "os/exec" - "path/filepath" - "strings" - "github.com/hashicorp/go-plugin" "github.com/hashicorp/hcl" "github.com/hashicorp/terraform/command" - tfplugin "github.com/hashicorp/terraform/plugin" - "github.com/hashicorp/terraform/terraform" - "github.com/kardianos/osext" - "github.com/mitchellh/cli" ) // Config is the structure of the configuration for the Terraform CLI. @@ -35,8 +27,9 @@ type Config struct { // can be overridden by user configurations. var BuiltinConfig Config -// ContextOpts are the global ContextOpts we use to initialize the CLI. -var ContextOpts terraform.ContextOpts +// PluginOverrides are paths that override discovered plugins, set from +// the config file. +var PluginOverrides command.PluginOverrides // ConfigFile returns the default path to the configuration file. // @@ -85,88 +78,6 @@ func LoadConfig(path string) (*Config, error) { return &result, nil } -// Discover plugins located on disk, and fall back on plugins baked into the -// Terraform binary. -// -// We look in the following places for plugins: -// -// 1. Terraform configuration path -// 2. Path where Terraform is installed -// 3. Path where Terraform is invoked -// -// Whichever file is discoverd LAST wins. -// -// Finally, we look at the list of plugins compiled into Terraform. If any of -// them has not been found on disk we use the internal version. This allows -// users to add / replace plugins without recompiling the main binary. -func (c *Config) Discover(ui cli.Ui) error { - // Look in ~/.terraform.d/plugins/ - dir, err := ConfigDir() - if err != nil { - log.Printf("[ERR] Error loading config directory: %s", err) - } else { - if err := c.discover(filepath.Join(dir, "plugins")); err != nil { - return err - } - } - - // Next, look in the same directory as the Terraform executable, usually - // /usr/local/bin. If found, this replaces what we found in the config path. - exePath, err := osext.Executable() - if err != nil { - log.Printf("[ERR] Error loading exe directory: %s", err) - } else { - if err := c.discover(filepath.Dir(exePath)); err != nil { - return err - } - } - - // Finally look in the cwd (where we are invoke Terraform). If found, this - // replaces anything we found in the config / install paths. - if err := c.discover("."); err != nil { - return err - } - - // Finally, if we have a plugin compiled into Terraform and we didn't find - // a replacement on disk, we'll just use the internal version. Only do this - // from the main process, or the log output will break the plugin handshake. - if os.Getenv("TF_PLUGIN_MAGIC_COOKIE") == "" { - for name, _ := range command.InternalProviders { - if path, found := c.Providers[name]; found { - // Allow these warnings to be suppressed via TF_PLUGIN_DEV=1 or similar - if os.Getenv("TF_PLUGIN_DEV") == "" { - ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provider.\n"+ - " If you did not expect to see this message you will need to remove the old plugin.\n"+ - " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) - } - } else { - cmd, err := command.BuildPluginCommandString("provider", name) - if err != nil { - return err - } - c.Providers[name] = cmd - } - } - for name, _ := range command.InternalProvisioners { - if path, found := c.Provisioners[name]; found { - if os.Getenv("TF_PLUGIN_DEV") == "" { - ui.Warn(fmt.Sprintf("[WARN] %s overrides an internal plugin for %s-provisioner.\n"+ - " If you did not expect to see this message you will need to remove the old plugin.\n"+ - " See https://www.terraform.io/docs/internals/internal-plugins.html", path, name)) - } - } else { - cmd, err := command.BuildPluginCommandString("provisioner", name) - if err != nil { - return err - } - c.Provisioners[name] = cmd - } - } - } - - return nil -} - // Merge merges two configurations and returns a third entirely // new configuration with the two merged. func (c1 *Config) Merge(c2 *Config) *Config { @@ -196,173 +107,3 @@ func (c1 *Config) Merge(c2 *Config) *Config { return &result } - -func (c *Config) discover(path string) error { - var err error - - if !filepath.IsAbs(path) { - path, err = filepath.Abs(path) - if err != nil { - return err - } - } - - err = c.discoverSingle( - filepath.Join(path, "terraform-provider-*"), &c.Providers) - if err != nil { - return err - } - - err = c.discoverSingle( - filepath.Join(path, "terraform-provisioner-*"), &c.Provisioners) - if err != nil { - return err - } - - return nil -} - -func (c *Config) discoverSingle(glob string, m *map[string]string) error { - matches, err := filepath.Glob(glob) - if err != nil { - return err - } - - if *m == nil { - *m = make(map[string]string) - } - - for _, match := range matches { - file := filepath.Base(match) - - // If the filename has a ".", trim up to there - if idx := strings.Index(file, "."); idx >= 0 { - file = file[:idx] - } - - // Look for foo-bar-baz. The plugin name is "baz" - parts := strings.SplitN(file, "-", 3) - if len(parts) != 3 { - continue - } - - log.Printf("[DEBUG] Discovered plugin: %s = %s", parts[2], match) - (*m)[parts[2]] = match - } - - return nil -} - -// ProviderFactories returns the mapping of prefixes to -// ResourceProviderFactory that can be used to instantiate a -// binary-based plugin. -func (c *Config) ProviderFactories() map[string]terraform.ResourceProviderFactory { - result := make(map[string]terraform.ResourceProviderFactory) - for k, v := range c.Providers { - result[k] = c.providerFactory(v) - } - - return result -} - -func (c *Config) providerFactory(path string) terraform.ResourceProviderFactory { - // Build the plugin client configuration and init the plugin - var config plugin.ClientConfig - config.Cmd = pluginCmd(path) - config.HandshakeConfig = tfplugin.Handshake - config.Managed = true - config.Plugins = tfplugin.PluginMap - client := plugin.NewClient(&config) - - return func() (terraform.ResourceProvider, error) { - // Request the RPC client so we can get the provider - // so we can build the actual RPC-implemented provider. - rpcClient, err := client.Client() - if err != nil { - return nil, err - } - - raw, err := rpcClient.Dispense(tfplugin.ProviderPluginName) - if err != nil { - return nil, err - } - - return raw.(terraform.ResourceProvider), nil - } -} - -// ProvisionerFactories returns the mapping of prefixes to -// ResourceProvisionerFactory that can be used to instantiate a -// binary-based plugin. -func (c *Config) ProvisionerFactories() map[string]terraform.ResourceProvisionerFactory { - result := make(map[string]terraform.ResourceProvisionerFactory) - for k, v := range c.Provisioners { - result[k] = c.provisionerFactory(v) - } - - return result -} - -func (c *Config) provisionerFactory(path string) terraform.ResourceProvisionerFactory { - // Build the plugin client configuration and init the plugin - var config plugin.ClientConfig - config.HandshakeConfig = tfplugin.Handshake - config.Cmd = pluginCmd(path) - config.Managed = true - config.Plugins = tfplugin.PluginMap - client := plugin.NewClient(&config) - - return func() (terraform.ResourceProvisioner, error) { - rpcClient, err := client.Client() - if err != nil { - return nil, err - } - - raw, err := rpcClient.Dispense(tfplugin.ProvisionerPluginName) - if err != nil { - return nil, err - } - - return raw.(terraform.ResourceProvisioner), nil - } -} - -func pluginCmd(path string) *exec.Cmd { - cmdPath := "" - - // If the path doesn't contain a separator, look in the same - // directory as the Terraform executable first. - if !strings.ContainsRune(path, os.PathSeparator) { - exePath, err := osext.Executable() - if err == nil { - temp := filepath.Join( - filepath.Dir(exePath), - filepath.Base(path)) - - if _, err := os.Stat(temp); err == nil { - cmdPath = temp - } - } - - // If we still haven't found the executable, look for it - // in the PATH. - if v, err := exec.LookPath(path); err == nil { - cmdPath = v - } - } - - // No plugin binary found, so try to use an internal plugin. - if strings.Contains(path, command.TFSPACE) { - parts := strings.Split(path, command.TFSPACE) - return exec.Command(parts[0], parts[1:]...) - } - - // If we still don't have a path, then just set it to the original - // given path. - if cmdPath == "" { - cmdPath = path - } - - // Build the command to execute the plugin - return exec.Command(cmdPath) -} diff --git a/main.go b/main.go index 237581200e..ca4ec7c620 100644 --- a/main.go +++ b/main.go @@ -108,10 +108,6 @@ func wrappedMain() int { // Load the configuration config := BuiltinConfig - if err := config.Discover(Ui); err != nil { - Ui.Error(fmt.Sprintf("Error discovering plugins: %s", err)) - return 1 - } // Load the configuration file if we have one, that can be used to // define extra providers and provisioners. @@ -185,9 +181,9 @@ func wrappedMain() int { HelpWriter: os.Stdout, } - // Initialize the TFConfig settings for the commands... - ContextOpts.Providers = config.ProviderFactories() - ContextOpts.Provisioners = config.ProvisionerFactories() + // Pass in the overriding plugin paths from config + PluginOverrides.Providers = config.Providers + PluginOverrides.Provisioners = config.Provisioners exitCode, err := cliRunner.Run() if err != nil { diff --git a/plugins.go b/plugins.go new file mode 100644 index 0000000000..9717724a0a --- /dev/null +++ b/plugins.go @@ -0,0 +1,37 @@ +package main + +import ( + "log" + "path/filepath" + + "github.com/kardianos/osext" +) + +// globalPluginDirs returns directories that should be searched for +// globally-installed plugins (not specific to the current configuration). +// +// Earlier entries in this slice get priority over later when multiple copies +// of the same plugin version are found, but newer versions always override +// older versions where both satisfy the provider version constraints. +func globalPluginDirs() []string { + var ret []string + + // Look in the same directory as the Terraform executable. + // If found, this replaces what we found in the config path. + exePath, err := osext.Executable() + if err != nil { + log.Printf("[ERROR] Error discovering exe directory: %s", err) + } else { + ret = append(ret, filepath.Dir(exePath)) + } + + // Look in ~/.terraform.d/plugins/ , or its equivalent on non-UNIX + dir, err := ConfigDir() + if err != nil { + log.Printf("[ERROR] Error finding global config directory: %s", err) + } else { + ret = append(ret, filepath.Join(dir, "plugins")) + } + + return ret +} From 73fc9985b2a390012c9af06700f9c800bcec7a00 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 11 Apr 2017 15:38:44 -0700 Subject: [PATCH 07/82] config: add "version" argument to provider blocks, disabled In future we will support version constraints on providers, so we're reserving this attribute name that is currently not used by any builtin providers. For now using this will produce an error, since the rest of Terraform (outside of the config parser) doesn't currently have this notion and we don't want people to start trying to use it until its behavior is fully defined and implemented. It may be used by third-party providers, so this is a breaking change worth warning about in CHANGELOG but one whose impact should be small. Any third-party providers using this name should migrate to using a new attribute name instead moving forward. --- config/config.go | 11 +++++++- config/config_test.go | 27 +++++++++++++++++++ config/loader_hcl.go | 14 ++++++++++ config/test-fixtures/provider-version/main.tf | 6 +++++ 4 files changed, 57 insertions(+), 1 deletion(-) create mode 100644 config/test-fixtures/provider-version/main.tf diff --git a/config/config.go b/config/config.go index a157824290..83b4e197a5 100644 --- a/config/config.go +++ b/config/config.go @@ -64,6 +64,7 @@ type Module struct { type ProviderConfig struct { Name string Alias string + Version string RawConfig *RawConfig } @@ -349,7 +350,8 @@ func (c *Config) Validate() error { } } - // Check that providers aren't declared multiple times. + // Check that providers aren't declared multiple times, and that versions + // aren't used yet since they aren't properly supported. providerSet := make(map[string]struct{}) for _, p := range c.ProviderConfigs { name := p.FullName() @@ -360,6 +362,13 @@ func (c *Config) Validate() error { continue } + if p.Version != "" { + errs = append(errs, fmt.Errorf( + "provider.%s: version constraints are not yet supported; remove the 'version' argument from configuration", + name, + )) + } + providerSet[name] = struct{}{} } diff --git a/config/config_test.go b/config/config_test.go index d1ca8d70b4..a70b9831c0 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -207,6 +207,12 @@ func TestConfigValidate_table(t *testing.T) { false, "", }, + { + "provider with version constraint", + "provider-version", + true, + "version constraints are not yet supported", + }, } for i, tc := range cases { @@ -673,3 +679,24 @@ func TestConfigDataCount(t *testing.T) { t.Fatal("count key still exists in RawConfig") } } + +func TestConfigProviderVersion(t *testing.T) { + c := testConfig(t, "provider-version") + + if len(c.ProviderConfigs) != 1 { + t.Fatal("expected 1 provider") + } + + p := c.ProviderConfigs[0] + if p.Name != "aws" { + t.Fatalf("expected provider name 'aws', got %q", p.Name) + } + + if p.Version != "0.0.1" { + t.Fatalf("expected providers version '0.0.1', got %q", p.Version) + } + + if _, ok := p.RawConfig.Raw["version"]; ok { + t.Fatal("'version' should not exist in raw config") + } +} diff --git a/config/loader_hcl.go b/config/loader_hcl.go index 9abb1960f3..2c30d05673 100644 --- a/config/loader_hcl.go +++ b/config/loader_hcl.go @@ -562,6 +562,7 @@ func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { } delete(config, "alias") + delete(config, "version") rawConfig, err := NewRawConfig(config) if err != nil { @@ -583,9 +584,22 @@ func loadProvidersHcl(list *ast.ObjectList) ([]*ProviderConfig, error) { } } + // If we have a version field then extract it + var version string + if a := listVal.Filter("version"); len(a.Items) > 0 { + err := hcl.DecodeObject(&version, a.Items[0].Val) + if err != nil { + return nil, fmt.Errorf( + "Error reading version for provider[%s]: %s", + n, + err) + } + } + result = append(result, &ProviderConfig{ Name: n, Alias: alias, + Version: version, RawConfig: rawConfig, }) } diff --git a/config/test-fixtures/provider-version/main.tf b/config/test-fixtures/provider-version/main.tf new file mode 100644 index 0000000000..c7f296eaf5 --- /dev/null +++ b/config/test-fixtures/provider-version/main.tf @@ -0,0 +1,6 @@ +provider "aws" { + version = "0.0.1" + a = "a" + b = "b" +} + From 9b4f15c261100764503804504e20f60ab8af2582 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 14 Apr 2017 17:24:08 -0700 Subject: [PATCH 08/82] plugin: move Client function into plugin, from plugin/discovery Having this as a method of PluginMeta felt most natural, but unfortunately that means that discovery must depend on plugin and plugin in turn depends on core Terraform, thus making the discovery package hard to use without creating dependency cycles. To resolve this, we invert the dependency and make the plugin package be responsible for instantiating clients given a meta, using a top-level function. --- command/plugins.go | 4 ++-- plugin/client.go | 24 ++++++++++++++++++++++++ plugin/discovery/meta.go | 19 ------------------- 3 files changed, 26 insertions(+), 21 deletions(-) create mode 100644 plugin/client.go diff --git a/command/plugins.go b/command/plugins.go index 113378db00..fa4e48ab4a 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -50,7 +50,7 @@ func (m *Meta) providerFactories() map[string]terraform.ResourceProviderFactory // by name, we're guaranteed that the metas in our set all have // valid versions and that there's at least one meta. newest := metas.Newest() - client := newest.Client() + client := tfplugin.Client(newest) factories[name] = providerFactory(client) } @@ -99,7 +99,7 @@ func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFa // by name, we're guaranteed that the metas in our set all have // valid versions and that there's at least one meta. newest := metas.Newest() - client := newest.Client() + client := tfplugin.Client(newest) factories[name] = provisionerFactory(client) } diff --git a/plugin/client.go b/plugin/client.go new file mode 100644 index 0000000000..3a5cb7af05 --- /dev/null +++ b/plugin/client.go @@ -0,0 +1,24 @@ +package plugin + +import ( + "os/exec" + + plugin "github.com/hashicorp/go-plugin" + "github.com/hashicorp/terraform/plugin/discovery" +) + +// ClientConfig returns a configuration object that can be used to instantiate +// a client for the plugin described by the given metadata. +func ClientConfig(m discovery.PluginMeta) *plugin.ClientConfig { + return &plugin.ClientConfig{ + Cmd: exec.Command(m.Path), + HandshakeConfig: Handshake, + Managed: true, + Plugins: PluginMap, + } +} + +// Client returns a plugin client for the plugin described by the given metadata. +func Client(m discovery.PluginMeta) *plugin.Client { + return plugin.NewClient(ClientConfig(m)) +} diff --git a/plugin/discovery/meta.go b/plugin/discovery/meta.go index 0c3a1ff4af..d93296cfc6 100644 --- a/plugin/discovery/meta.go +++ b/plugin/discovery/meta.go @@ -4,11 +4,8 @@ import ( "crypto/sha256" "io" "os" - "os/exec" "github.com/blang/semver" - plugin "github.com/hashicorp/go-plugin" - tfplugin "github.com/hashicorp/terraform/plugin" ) // PluginMeta is metadata about a plugin, useful for launching the plugin @@ -51,19 +48,3 @@ func (m PluginMeta) SHA256() ([]byte, error) { return h.Sum(nil), nil } - -// ClientConfig returns a configuration object that can be used to instantiate -// a client for the referenced plugin. -func (m PluginMeta) ClientConfig() *plugin.ClientConfig { - return &plugin.ClientConfig{ - Cmd: exec.Command(m.Path), - HandshakeConfig: tfplugin.Handshake, - Managed: true, - Plugins: tfplugin.PluginMap, - } -} - -// Client returns a plugin client for the referenced plugin. -func (m PluginMeta) Client() *plugin.Client { - return plugin.NewClient(m.ClientConfig()) -} From 7e7d4c70df18d3400a6923130077e4867f5f2562 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 14 Apr 2017 17:33:18 -0700 Subject: [PATCH 09/82] config: allow version constraints on providers, but validate them We now accept syntactically-valid version constraints on provider blocks, though we still don't actually do anything with them. --- config/config.go | 16 ++++++++++------ config/config_test.go | 10 ++++++++-- .../provider-version-invalid/main.tf | 5 +++++ 3 files changed, 23 insertions(+), 8 deletions(-) create mode 100644 config/test-fixtures/provider-version-invalid/main.tf diff --git a/config/config.go b/config/config.go index 83b4e197a5..34ce8cf2d0 100644 --- a/config/config.go +++ b/config/config.go @@ -8,6 +8,7 @@ import ( "strconv" "strings" + "github.com/blang/semver" "github.com/hashicorp/go-multierror" "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" @@ -350,8 +351,8 @@ func (c *Config) Validate() error { } } - // Check that providers aren't declared multiple times, and that versions - // aren't used yet since they aren't properly supported. + // Check that providers aren't declared multiple times and that their + // version constraints, where present, are syntactically valid. providerSet := make(map[string]struct{}) for _, p := range c.ProviderConfigs { name := p.FullName() @@ -363,10 +364,13 @@ func (c *Config) Validate() error { } if p.Version != "" { - errs = append(errs, fmt.Errorf( - "provider.%s: version constraints are not yet supported; remove the 'version' argument from configuration", - name, - )) + _, err := semver.ParseRange(p.Version) + if err != nil { + errs = append(errs, fmt.Errorf( + "provider.%s: invalid version constraint %q: %s", + name, p.Version, err, + )) + } } providerSet[name] = struct{}{} diff --git a/config/config_test.go b/config/config_test.go index a70b9831c0..dea2ab87e1 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -208,10 +208,16 @@ func TestConfigValidate_table(t *testing.T) { "", }, { - "provider with version constraint", + "provider with valid version constraint", "provider-version", + false, + "", + }, + { + "provider with invalid version constraint", + "provider-version-invalid", true, - "version constraints are not yet supported", + "invalid version constraint", }, } diff --git a/config/test-fixtures/provider-version-invalid/main.tf b/config/test-fixtures/provider-version-invalid/main.tf new file mode 100644 index 0000000000..a3d4d9cabf --- /dev/null +++ b/config/test-fixtures/provider-version-invalid/main.tf @@ -0,0 +1,5 @@ +provider "aws" { + version = "bananas" + a = "a" + b = "b" +} From 0b14c2cdb3c9591ff35e9900b146b6972197ffdb Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 14 Apr 2017 19:07:33 -0700 Subject: [PATCH 10/82] Resolve resource provider types in config package Previously the logic for inferring a provider type from a resource name was buried a utility function in the 'terraform' package. Instead here we lift it up into the 'config' package where we can make broader use of it and where it's easier to discover. --- config/config.go | 27 +++++++++++++++++++++++ config/config_test.go | 50 ++++++++++++++++++++++++++++++++++++++++++ terraform/util.go | 20 ++++------------- terraform/util_test.go | 50 ------------------------------------------ 4 files changed, 81 insertions(+), 66 deletions(-) diff --git a/config/config.go b/config/config.go index 34ce8cf2d0..0648d6917a 100644 --- a/config/config.go +++ b/config/config.go @@ -240,6 +240,33 @@ func (r *Resource) Id() string { } } +// ProviderFullName returns the full name of the provider for this resource, +// which may either be specified explicitly using the "provider" meta-argument +// or implied by the prefix on the resource type name. +func (r *Resource) ProviderFullName() string { + return ResourceProviderFullName(r.Type, r.Provider) +} + +// ResourceProviderFullName returns the full (dependable) name of the +// provider for a hypothetical resource with the given resource type and +// explicit provider string. If the explicit provider string is empty then +// the provider name is inferred from the resource type name. +func ResourceProviderFullName(resourceType, explicitProvider string) string { + if explicitProvider != "" { + return explicitProvider + } + + idx := strings.IndexRune(resourceType, '_') + if idx == -1 { + // If no underscores, the resource name is assumed to be + // also the provider name, e.g. if the provider exposes + // only a single resource of each type. + return resourceType + } + + return resourceType[:idx] +} + // Validate does some basic semantic checking of the configuration. func (c *Config) Validate() error { if c == nil { diff --git a/config/config_test.go b/config/config_test.go index dea2ab87e1..db62e1c54a 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -706,3 +706,53 @@ func TestConfigProviderVersion(t *testing.T) { t.Fatal("'version' should not exist in raw config") } } + +func TestResourceProviderFullName(t *testing.T) { + type testCase struct { + ResourceName string + Alias string + Expected string + } + + tests := []testCase{ + { + // If no alias is provided, the first underscore-separated segment + // is assumed to be the provider name. + ResourceName: "aws_thing", + Alias: "", + Expected: "aws", + }, + { + // If we have more than one underscore then it's the first one that we'll use. + ResourceName: "aws_thingy_thing", + Alias: "", + Expected: "aws", + }, + { + // A provider can export a resource whose name is just the bare provider name, + // e.g. because the provider only has one resource and so any additional + // parts would be redundant. + ResourceName: "external", + Alias: "", + Expected: "external", + }, + { + // Alias always overrides the default extraction of the name + ResourceName: "aws_thing", + Alias: "tls.baz", + Expected: "tls.baz", + }, + } + + for _, test := range tests { + got := ResourceProviderFullName(test.ResourceName, test.Alias) + if got != test.Expected { + t.Errorf( + "(%q, %q) produced %q; want %q", + test.ResourceName, test.Alias, + got, + test.Expected, + ) + } + } +} diff --git a/terraform/util.go b/terraform/util.go index f41f0d7d63..752241af1e 100644 --- a/terraform/util.go +++ b/terraform/util.go @@ -2,7 +2,8 @@ package terraform import ( "sort" - "strings" + + "github.com/hashicorp/terraform/config" ) // Semaphore is a wrapper around a channel to provide @@ -47,21 +48,8 @@ func (s Semaphore) Release() { } } -// resourceProvider returns the provider name for the given type. -func resourceProvider(t, alias string) string { - if alias != "" { - return alias - } - - idx := strings.IndexRune(t, '_') - if idx == -1 { - // If no underscores, the resource name is assumed to be - // also the provider name, e.g. if the provider exposes - // only a single resource of each type. - return t - } - - return t[:idx] +func resourceProvider(resourceType, explicitProvider string) string { + return config.ResourceProviderFullName(resourceType, explicitProvider) } // strSliceContains checks if a given string is contained in a slice diff --git a/terraform/util_test.go b/terraform/util_test.go index 9c5712cfa1..8b3907e236 100644 --- a/terraform/util_test.go +++ b/terraform/util_test.go @@ -49,56 +49,6 @@ func TestStrSliceContains(t *testing.T) { } } -func TestUtilResourceProvider(t *testing.T) { - type testCase struct { - ResourceName string - Alias string - Expected string - } - - tests := []testCase{ - { - // If no alias is provided, the first underscore-separated segment - // is assumed to be the provider name. - ResourceName: "aws_thing", - Alias: "", - Expected: "aws", - }, - { - // If we have more than one underscore then it's the first one that we'll use. - ResourceName: "aws_thingy_thing", - Alias: "", - Expected: "aws", - }, - { - // A provider can export a resource whose name is just the bare provider name, - // e.g. because the provider only has one resource and so any additional - // parts would be redundant. - ResourceName: "external", - Alias: "", - Expected: "external", - }, - { - // Alias always overrides the default extraction of the name - ResourceName: "aws_thing", - Alias: "tls.baz", - Expected: "tls.baz", - }, - } - - for _, test := range tests { - got := resourceProvider(test.ResourceName, test.Alias) - if got != test.Expected { - t.Errorf( - "(%q, %q) produced %q; want %q", - test.ResourceName, test.Alias, - got, - test.Expected, - ) - } - } -} - func TestUniqueStrings(t *testing.T) { cases := []struct { Input []string From 32a5c626390d7bf04c2d7d44ef8d911ba360c763 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 14 Apr 2017 19:34:12 -0700 Subject: [PATCH 11/82] config: parse provider version constraints into a constraint map --- config/providers.go | 103 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 config/providers.go diff --git a/config/providers.go b/config/providers.go new file mode 100644 index 0000000000..7a50782f3a --- /dev/null +++ b/config/providers.go @@ -0,0 +1,103 @@ +package config + +import "github.com/blang/semver" + +// ProviderVersionConstraint presents a constraint for a particular +// provider, identified by its full name. +type ProviderVersionConstraint struct { + Constraint string + ProviderType string +} + +// ProviderVersionConstraints is a map from provider full name to its associated +// ProviderVersionConstraint, as produced by Config.RequiredProviders. +type ProviderVersionConstraints map[string]ProviderVersionConstraint + +// RequiredProviders returns the ProviderVersionConstraints for this +// module. +// +// This includes both providers that are explicitly requested by provider +// blocks and those that are used implicitly by instantiating one of their +// resource types. In the latter case, the returned semver Range will +// accept any version of the provider. +func (c *Config) RequiredProviders() ProviderVersionConstraints { + ret := make(ProviderVersionConstraints, len(c.ProviderConfigs)) + + configs := c.ProviderConfigsByFullName() + + // In order to find the *implied* dependencies (those without explicit + // "provider" blocks) we need to walk over all of the resources and + // cross-reference with the provider configs. + for _, rc := range c.Resources { + providerName := rc.ProviderFullName() + var providerType string + + // Default to (effectively) no constraint whatsoever, but we might + // override if there's an explicit constraint in config. + constraint := ">=0.0.0" + + config, ok := configs[providerName] + if ok { + if config.Version != "" { + constraint = config.Version + } + providerType = config.Name + } else { + providerType = providerName + } + + ret[providerName] = ProviderVersionConstraint{ + ProviderType: providerType, + Constraint: constraint, + } + } + + return ret +} + +// RequiredRanges returns a semver.Range for each distinct provider type in +// the constraint map. If the same provider type appears more than once +// (e.g. because aliases are in use) then their respective constraints are +// combined such that they must *all* apply. +// +// The result of this method can be passed to the +// PluginMetaSet.ConstrainVersions method within the plugin/discovery +// package in order to filter down the available plugins to those which +// satisfy the given constraints. +// +// This function will panic if any of the constraints within cannot be +// parsed as semver ranges. This is guaranteed to never happen for a +// constraint set that was built from a configuration that passed validation. +func (cons ProviderVersionConstraints) RequiredRanges() map[string]semver.Range { + ret := make(map[string]semver.Range, len(cons)) + + for _, con := range cons { + spec := semver.MustParseRange(con.Constraint) + if existing, exists := ret[con.ProviderType]; exists { + ret[con.ProviderType] = existing.AND(spec) + } else { + ret[con.ProviderType] = spec + } + } + + return ret +} + +// ProviderConfigsByFullName returns a map from provider full names (as +// returned by ProviderConfig.FullName()) to the corresponding provider +// configs. +// +// This function returns no new information than what's already in +// c.ProviderConfigs, but returns it in a more convenient shape. If there +// is more than one provider config with the same full name then the result +// is undefined, but that is guaranteed not to happen for any config that +// has passed validation. +func (c *Config) ProviderConfigsByFullName() map[string]*ProviderConfig { + ret := make(map[string]*ProviderConfig, len(c.ProviderConfigs)) + + for _, pc := range c.ProviderConfigs { + ret[pc.FullName()] = pc + } + + return ret +} From a8a64c66c0c7c74d08c033fa15650c509a775128 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 14 Apr 2017 19:39:35 -0700 Subject: [PATCH 12/82] config/module: helper to visit all modules in a tree --- config/module/tree.go | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/config/module/tree.go b/config/module/tree.go index b6f90fd930..4b0b153f72 100644 --- a/config/module/tree.go +++ b/config/module/tree.go @@ -92,6 +92,25 @@ func (t *Tree) Children() map[string]*Tree { return t.children } +// DeepEach calls the provided callback for the receiver and then all of +// its descendents in the tree, allowing an operation to be performed on +// all modules in the tree. +// +// Parents will be visited before their children but otherwise the order is +// not defined. +func (t *Tree) DeepEach(cb func(*Tree)) { + t.lock.RLock() + defer t.lock.RUnlock() + t.deepEach(cb) +} + +func (t *Tree) deepEach(cb func(*Tree)) { + cb(t) + for _, c := range t.children { + c.deepEach(cb) + } +} + // Loaded says whether or not this tree has been loaded or not yet. func (t *Tree) Loaded() bool { t.lock.RLock() From a1e29ae290870e05b5aa12b8731c4b89c839646c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 19 Apr 2017 17:04:09 -0700 Subject: [PATCH 13/82] plugin/discovery: use go-version instead of semver The semver library we were using doesn't have support for a "pessimistic constraint" where e.g. the user wants to accept only minor or patch version upgrades. This is important for providers since users will generally want to pin their dependencies to not inadvertantly accept breaking changes. So here we switch to hashicorp's home-grown go-version library, which has the ~> constraint operator for this sort of constraint. Given how much the old version object was already intruding into the interface and creating dependency noise in callers, this also now wraps the "raw" go-version objects in package-local structs, thus keeping the details encapsulated and allowing callers to deal just with this package's own types. --- plugin/discovery/find.go | 2 +- plugin/discovery/meta.go | 13 +----- plugin/discovery/meta_set.go | 24 +++++------ plugin/discovery/meta_set_test.go | 16 +++---- plugin/discovery/requirements.go | 26 +++++++++++ plugin/discovery/version.go | 37 ++++++++++++++++ plugin/discovery/version_set.go | 64 ++++++++++++++++++++++++++++ plugin/discovery/version_set_test.go | 64 ++++++++++++++++++++++++++++ 8 files changed, 211 insertions(+), 35 deletions(-) create mode 100644 plugin/discovery/requirements.go create mode 100644 plugin/discovery/version.go create mode 100644 plugin/discovery/version_set.go create mode 100644 plugin/discovery/version_set_test.go diff --git a/plugin/discovery/find.go b/plugin/discovery/find.go index 603a491181..923923dccc 100644 --- a/plugin/discovery/find.go +++ b/plugin/discovery/find.go @@ -157,7 +157,7 @@ func ResolvePluginPaths(paths []string) PluginMetaSet { s.Add(PluginMeta{ Name: name, - Version: version, + Version: VersionStr(version), Path: path, }) found[nameVersion{name, version}] = struct{}{} diff --git a/plugin/discovery/meta.go b/plugin/discovery/meta.go index d93296cfc6..bdcebcb9dc 100644 --- a/plugin/discovery/meta.go +++ b/plugin/discovery/meta.go @@ -4,8 +4,6 @@ import ( "crypto/sha256" "io" "os" - - "github.com/blang/semver" ) // PluginMeta is metadata about a plugin, useful for launching the plugin @@ -16,21 +14,14 @@ type PluginMeta struct { Name string // Version is the semver version of the plugin, expressed as a string - // that might not be semver-valid. (Call VersionObj to attempt to - // parse it and thus detect if it is invalid.) - Version string + // that might not be semver-valid. + Version VersionStr // Path is the absolute path of the executable that can be launched // to provide the RPC server for this plugin. Path string } -// VersionObj returns the semver version of the plugin as an object, or -// an error if the version string is not semver-syntax-compliant. -func (m PluginMeta) VersionObj() (semver.Version, error) { - return semver.Make(m.Version) -} - // SHA256 returns a SHA256 hash of the content of the referenced executable // file, or an error if the file's contents cannot be read. func (m PluginMeta) SHA256() ([]byte, error) { diff --git a/plugin/discovery/meta_set.go b/plugin/discovery/meta_set.go index 37220f5157..066870e6e8 100644 --- a/plugin/discovery/meta_set.go +++ b/plugin/discovery/meta_set.go @@ -1,9 +1,5 @@ package discovery -import ( - "github.com/blang/semver" -) - // A PluginMetaSet is a set of PluginMeta objects meeting a certain criteria. // // Methods on this type allow filtering of the set to produce subsets that @@ -44,7 +40,7 @@ func (s PluginMetaSet) ValidateVersions() (valid, invalid PluginMetaSet) { valid = make(PluginMetaSet) invalid = make(PluginMetaSet) for p := range s { - if _, err := p.VersionObj(); err == nil { + if _, err := p.Version.Parse(); err == nil { valid.Add(p) } else { invalid.Add(p) @@ -97,14 +93,14 @@ func (s PluginMetaSet) Newest() PluginMeta { var first = true var winner PluginMeta - var winnerVersion semver.Version + var winnerVersion Version for p := range s { - version, err := p.VersionObj() + version, err := p.Version.Parse() if err != nil { panic(err) } - if first == true || version.GT(winnerVersion) { + if first == true || version.newerThan(winnerVersion) { winner = p winnerVersion = version first = false @@ -114,11 +110,11 @@ func (s PluginMetaSet) Newest() PluginMeta { return winner } -// ConstrainVersions takes a map of version constraints by name and attempts to +// ConstrainVersions takes a set of requirements and attempts to // return a map from name to a set of metas that have the matching // name and an appropriate version. // -// If any of the given constraints match *no* plugins then its PluginMetaSet +// If any of the given requirements match *no* plugins then its PluginMetaSet // in the returned map will be nil. // // All viable metas are returned, so the caller can apply any desired filtering @@ -128,22 +124,22 @@ func (s PluginMetaSet) Newest() PluginMeta { // If any of the metas in the set have invalid version strings then this // function will panic. Use ValidateVersions() first to filter out metas with // invalid versions. -func (s PluginMetaSet) ConstrainVersions(reqd map[string]semver.Range) map[string]PluginMetaSet { +func (s PluginMetaSet) ConstrainVersions(reqd PluginRequirements) map[string]PluginMetaSet { ret := make(map[string]PluginMetaSet) for p := range s { name := p.Name - constraint, ok := reqd[name] + allowedVersions, ok := reqd[name] if !ok { continue } if _, ok := ret[p.Name]; !ok { ret[p.Name] = make(PluginMetaSet) } - version, err := p.VersionObj() + version, err := p.Version.Parse() if err != nil { panic(err) } - if constraint(version) { + if allowedVersions.Has(version) { ret[p.Name].Add(p) } } diff --git a/plugin/discovery/meta_set_test.go b/plugin/discovery/meta_set_test.go index 21aaed29de..18485f58d0 100644 --- a/plugin/discovery/meta_set_test.go +++ b/plugin/discovery/meta_set_test.go @@ -4,8 +4,6 @@ import ( "fmt" "strings" "testing" - - "github.com/blang/semver" ) func TestPluginMetaSetManipulation(t *testing.T) { @@ -264,13 +262,13 @@ func TestPluginMetaSetNewest(t *testing.T) { for _, version := range test.versions { s.Add(PluginMeta{ Name: "foo", - Version: version, + Version: VersionStr(version), Path: "foo-V" + version, }) } newest := s.Newest() - if newest.Version != test.want { + if newest.Version != VersionStr(test.want) { t.Errorf("version is %q; want %q", newest.Version, test.want) } }) @@ -311,11 +309,11 @@ func TestPluginMetaSetConstrainVersions(t *testing.T) { s.Add(p) } - byName := s.ConstrainVersions(map[string]semver.Range{ - "foo": semver.MustParseRange(">=2.0.0"), - "bar": semver.MustParseRange(">=0.0.0"), - "baz": semver.MustParseRange(">=1.0.0"), - "fun": semver.MustParseRange(">5.0.0"), + byName := s.ConstrainVersions(PluginRequirements{ + "foo": ConstraintStr(">=2.0.0").MustParse(), + "bar": ConstraintStr(">=0.0.0").MustParse(), + "baz": ConstraintStr(">=1.0.0").MustParse(), + "fun": ConstraintStr(">5.0.0").MustParse(), }) if got, want := len(byName), 3; got != want { t.Errorf("%d keys in map; want %d", got, want) diff --git a/plugin/discovery/requirements.go b/plugin/discovery/requirements.go new file mode 100644 index 0000000000..e82909540d --- /dev/null +++ b/plugin/discovery/requirements.go @@ -0,0 +1,26 @@ +package discovery + +// PluginRequirements describes a set of plugins (assumed to be of a consistent +// kind) that are required to exist and have versions within the given +// corresponding sets. +// +// PluginRequirements is a map from plugin name to VersionSet. +type PluginRequirements map[string]VersionSet + +// Merge takes the contents of the receiver and the other given requirements +// object and merges them together into a single requirements structure +// that satisfies both sets of requirements. +func (r PluginRequirements) Merge(other PluginRequirements) PluginRequirements { + ret := make(PluginRequirements) + for n, vs := range r { + ret[n] = vs + } + for n, vs := range other { + if existing, exists := ret[n]; exists { + ret[n] = existing.Intersection(vs) + } else { + ret[n] = vs + } + } + return ret +} diff --git a/plugin/discovery/version.go b/plugin/discovery/version.go new file mode 100644 index 0000000000..160a969b0c --- /dev/null +++ b/plugin/discovery/version.go @@ -0,0 +1,37 @@ +package discovery + +import ( + version "github.com/hashicorp/go-version" +) + +// A VersionStr is a string containing a possibly-invalid representation +// of a semver version number. Call Parse on it to obtain a real Version +// object, or discover that it is invalid. +type VersionStr string + +// Parse transforms a VersionStr into a Version if it is +// syntactically valid. If it isn't then an error is returned instead. +func (s VersionStr) Parse() (Version, error) { + raw, err := version.NewVersion(string(s)) + if err != nil { + return Version{}, err + } + return Version{raw}, nil +} + +// Version represents a version number that has been parsed from +// a semver string and known to be valid. +type Version struct { + // We wrap this here just because it avoids a proliferation of + // direct go-version imports all over the place, and keeps the + // version-processing details within this package. + raw *version.Version +} + +func (v Version) String() string { + return v.raw.String() +} + +func (v Version) newerThan(other Version) bool { + return v.raw.GreaterThan(other.raw) +} diff --git a/plugin/discovery/version_set.go b/plugin/discovery/version_set.go new file mode 100644 index 0000000000..951000b206 --- /dev/null +++ b/plugin/discovery/version_set.go @@ -0,0 +1,64 @@ +package discovery + +import ( + version "github.com/hashicorp/go-version" +) + +// A ConstraintStr is a string containing a possibly-invalid representation +// of a version constraint provided in configuration. Call Parse on it to +// obtain a real Constraint object, or discover that it is invalid. +type ConstraintStr string + +// Parse transforms a ConstraintStr into a VersionSet if it is +// syntactically valid. If it isn't then an error is returned instead. +func (s ConstraintStr) Parse() (VersionSet, error) { + raw, err := version.NewConstraint(string(s)) + if err != nil { + return VersionSet{}, err + } + return VersionSet{raw}, nil +} + +// MustParse is like Parse but it panics if the constraint string is invalid. +func (s ConstraintStr) MustParse() VersionSet { + ret, err := s.Parse() + if err != nil { + panic(err) + } + return ret +} + +// VersionSet represents a set of versions which any given Version is either +// a member of or not. +type VersionSet struct { + // Internally a version set is actually a list of constraints that + // *remove* versions from the set. Thus a VersionSet with an empty + // Constraints list would be one that contains *all* versions. + raw version.Constraints +} + +// Has returns true if the given version is in the receiving set. +func (s VersionSet) Has(v Version) bool { + return s.raw.Check(v.raw) +} + +// Intersection combines the receving set with the given other set to produce a +// set that is the intersection of both sets, which is to say that it contains +// only the versions that are members of both sets. +func (s VersionSet) Intersection(other VersionSet) VersionSet { + raw := make(version.Constraints, 0, len(s.raw)+len(other.raw)) + + // Since "raw" is a list of constraints that remove versions from the set, + // "Intersection" is implemented by concatenating together those lists, + // thus leaving behind only the versions not removed by either list. + raw = append(raw, s.raw...) + raw = append(raw, other.raw...) + + return VersionSet{raw} +} + +// String returns a string representation of the set members as a set +// of range constraints. +func (s VersionSet) String() string { + return s.raw.String() +} diff --git a/plugin/discovery/version_set_test.go b/plugin/discovery/version_set_test.go new file mode 100644 index 0000000000..ecd3b12d0a --- /dev/null +++ b/plugin/discovery/version_set_test.go @@ -0,0 +1,64 @@ +package discovery + +import ( + "fmt" + "testing" +) + +func TestVersionSet(t *testing.T) { + tests := []struct { + ConstraintStr string + VersionStr string + ShouldHave bool + }{ + // These test cases are not exhaustive since the underlying go-version + // library is well-tested. This is mainly here just to exercise our + // wrapper code, but also used as an opportunity to cover some basic + // but important cases such as the ~> constraint so that we'll be more + // likely to catch any accidental breaking behavior changes in the + // underlying library. + { + ">=1.0.0", + "1.0.0", + true, + }, + { + ">=1.0.0", + "0.0.0", + false, + }, + { + ">=1.0.0", + "1.1.0-beta1", + true, + }, + { + "~>1.1.0", + "1.1.2-beta1", + true, + }, + { + "~>1.1.0", + "1.2.0", + false, + }, + } + + for _, test := range tests { + t.Run(fmt.Sprintf("%s has %s", test.ConstraintStr, test.VersionStr), func(t *testing.T) { + accepted, err := ConstraintStr(test.ConstraintStr).Parse() + if err != nil { + t.Fatalf("unwanted error parsing constraints string %q: %s", test.ConstraintStr, err) + } + + version, err := VersionStr(test.VersionStr).Parse() + if err != nil { + t.Fatalf("unwanted error parsing version string %q: %s", test.VersionStr, err) + } + + if got, want := accepted.Has(version), test.ShouldHave; got != want { + t.Errorf("Has returned %#v; want %#v", got, want) + } + }) + } +} From e89b5390cac18623d2048dd77c5bb65355fcef83 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 19 Apr 2017 17:53:11 -0700 Subject: [PATCH 14/82] moduledeps: new package for representing module dependencies As we add support for versioned providers, it's getting more complex to track the dependencies of each module and of the configuration as a whole, so this new package is intended to give us some room to model that nicely as a building block for the various aspects of dependency management. This package is not responsible for *building* the dependency data structure, since that requires knowledge of core Terraform and that would create cyclic package dependencies. A later change will add some logic in Terraform to create a Module tree based on the combination of a given configuration and state, returning an instance of this package's Module type. The Module.PluginRequirements method flattens the provider-oriented requirements into a set of plugin-oriented requirements (flattening any provider aliases) giving us what we need to work with the plugin/discovery package to find matching installed plugins. Other later uses of this package will include selecting plugin archives to auto-install from releases.hashicorp.com as part of "terraform init", where the module-oriented level of abstraction here should be useful for giving users good, specific feedback when constraints cannot be met. A "reason" is tracked for each provider dependency with the intent that this would later drive a UI for users to see and understand why a given dependency is present, to aid in debugging sticky issues with dependency resolution. --- moduledeps/dependencies.go | 43 +++++++ moduledeps/doc.go | 7 ++ moduledeps/module.go | 135 ++++++++++++++++++++++ moduledeps/module_test.go | 216 ++++++++++++++++++++++++++++++++++++ moduledeps/provider.go | 30 +++++ moduledeps/provider_test.go | 36 ++++++ 6 files changed, 467 insertions(+) create mode 100644 moduledeps/dependencies.go create mode 100644 moduledeps/doc.go create mode 100644 moduledeps/module.go create mode 100644 moduledeps/module_test.go create mode 100644 moduledeps/provider.go create mode 100644 moduledeps/provider_test.go diff --git a/moduledeps/dependencies.go b/moduledeps/dependencies.go new file mode 100644 index 0000000000..fa999ab2ff --- /dev/null +++ b/moduledeps/dependencies.go @@ -0,0 +1,43 @@ +package moduledeps + +import ( + "github.com/hashicorp/terraform/plugin/discovery" +) + +// Providers describes a set of provider dependencies for a given module. +// +// Each named provider instance can have one version constraint. +type Providers map[ProviderInstance]ProviderDependency + +// ProviderDependency describes the dependency for a particular provider +// instance, including both the set of allowed versions and the reason for +// the dependency. +type ProviderDependency struct { + Versions discovery.VersionSet + Reason ProviderDependencyReason +} + +// ProviderDependencyReason is an enumeration of reasons why a dependency might be +// present. +type ProviderDependencyReason int + +const ( + // ProviderDependencyExplicit means that there is an explicit "provider" + // block in the configuration for this module. + ProviderDependencyExplicit ProviderDependencyReason = iota + + // ProviderDependencyImplicit means that there is no explicit "provider" + // block but there is at least one resource that uses this provider. + ProviderDependencyImplicit + + // ProviderDependencyInherited is a special case of + // ProviderDependencyImplicit where a parent module has defined a + // configuration for the provider that has been inherited by at least one + // resource in this module. + ProviderDependencyInherited + + // ProviderDependencyFromState means that this provider is not currently + // referenced by configuration at all, but some existing instances in + // the state still depend on it. + ProviderDependencyFromState +) diff --git a/moduledeps/doc.go b/moduledeps/doc.go new file mode 100644 index 0000000000..7eff083157 --- /dev/null +++ b/moduledeps/doc.go @@ -0,0 +1,7 @@ +// Package moduledeps contains types that can be used to describe the +// providers required for all of the modules in a module tree. +// +// It does not itself contain the functionality for populating such +// data structures; that's in Terraform core, since this package intentionally +// does not depend on terraform core to avoid package dependency cycles. +package moduledeps diff --git a/moduledeps/module.go b/moduledeps/module.go new file mode 100644 index 0000000000..5c11f35f59 --- /dev/null +++ b/moduledeps/module.go @@ -0,0 +1,135 @@ +package moduledeps + +import ( + "sort" + "strings" + + "github.com/hashicorp/terraform/plugin/discovery" +) + +// Module represents the dependencies of a single module, as well being +// a node in a tree of such structures representing the dependencies of +// an entire configuration. +type Module struct { + Name string + Providers Providers + Children []*Module +} + +// WalkFunc is a callback type for use with Module.WalkTree +type WalkFunc func(path []string, parent *Module, current *Module) error + +// WalkTree calls the given callback once for the receiver and then +// once for each descendent, in an order such that parents are called +// before their children and siblings are called in the order they +// appear in the Children slice. +// +// When calling the callback, parent will be nil for the first call +// for the receiving module, and then set to the direct parent of +// each module for the subsequent calls. +// +// The path given to the callback is valid only until the callback +// returns, after which it will be mutated and reused. Callbacks must +// therefore copy the path slice if they wish to retain it. +// +// If the given callback returns an error, the walk will be aborted at +// that point and that error returned to the caller. +// +// This function is not thread-safe for concurrent modifications of the +// data structure, so it's the caller's responsibility to arrange for that +// should it be needed. +// +// It is safe for a callback to modify the descendents of the "current" +// module, including the ordering of the Children slice itself, but the +// callback MUST NOT modify the parent module. +func (m *Module) WalkTree(cb WalkFunc) error { + return walkModuleTree(make([]string, 0, 1), nil, m, cb) +} + +func walkModuleTree(path []string, parent *Module, current *Module, cb WalkFunc) error { + path = append(path, current.Name) + err := cb(path, parent, current) + if err != nil { + return err + } + + for _, child := range current.Children { + err := walkModuleTree(path, current, child, cb) + if err != nil { + return err + } + } + return nil +} + +// SortChildren sorts the Children slice into lexicographic order by +// name, in-place. +// +// This is primarily useful prior to calling WalkTree so that the walk +// will proceed in a consistent order. +func (m *Module) SortChildren() { + sort.Sort(sortModules{m.Children}) +} + +// SortDescendents is a convenience wrapper for calling SortChildren on +// the receiver and all of its descendent modules. +func (m *Module) SortDescendents() { + m.WalkTree(func(path []string, parent *Module, current *Module) error { + current.SortChildren() + return nil + }) +} + +type sortModules struct { + modules []*Module +} + +func (s sortModules) Len() int { + return len(s.modules) +} + +func (s sortModules) Less(i, j int) bool { + cmp := strings.Compare(s.modules[i].Name, s.modules[j].Name) + return cmp < 0 +} + +func (s sortModules) Swap(i, j int) { + s.modules[i], s.modules[j] = s.modules[j], s.modules[i] +} + +// PluginRequirements produces a PluginRequirements structure that can +// be used with discovery.PluginMetaSet.ConstrainVersions to identify +// suitable plugins to satisfy the module's provider dependencies. +// +// This method only considers the direct requirements of the receiver. +// Use AllPluginRequirements to flatten the dependencies for the +// entire tree of modules. +func (m *Module) PluginRequirements() discovery.PluginRequirements { + ret := make(discovery.PluginRequirements) + for inst, dep := range m.Providers { + // m.Providers is keyed on provider names, such as "aws.foo". + // a PluginRequirements wants keys to be provider *types*, such + // as "aws". If there are multiple aliases for the same + // provider then we will flatten them into a single requirement + // by using Intersection to merge the version sets. + pty := inst.Type() + if existing, exists := ret[pty]; exists { + ret[pty] = existing.Intersection(dep.Versions) + } else { + ret[pty] = dep.Versions + } + } + return ret +} + +// AllPluginRequirements calls PluginRequirements for the receiver and all +// of its descendents, and merges the result into a single PluginRequirements +// structure that would satisfy all of the modules together. +func (m *Module) AllPluginRequirements() discovery.PluginRequirements { + var ret discovery.PluginRequirements + m.WalkTree(func(path []string, parent *Module, current *Module) error { + ret = ret.Merge(current.PluginRequirements()) + return nil + }) + return ret +} diff --git a/moduledeps/module_test.go b/moduledeps/module_test.go new file mode 100644 index 0000000000..63d907d38c --- /dev/null +++ b/moduledeps/module_test.go @@ -0,0 +1,216 @@ +package moduledeps + +import ( + "fmt" + "reflect" + "testing" + + "github.com/hashicorp/terraform/plugin/discovery" +) + +func TestModuleWalkTree(t *testing.T) { + type walkStep struct { + Path []string + ParentName string + } + + tests := []struct { + Root *Module + WalkOrder []walkStep + }{ + { + &Module{ + Name: "root", + Children: nil, + }, + []walkStep{ + { + Path: []string{"root"}, + ParentName: "", + }, + }, + }, + { + &Module{ + Name: "root", + Children: []*Module{ + { + Name: "child", + }, + }, + }, + []walkStep{ + { + Path: []string{"root"}, + ParentName: "", + }, + { + Path: []string{"root", "child"}, + ParentName: "root", + }, + }, + }, + { + &Module{ + Name: "root", + Children: []*Module{ + { + Name: "child", + Children: []*Module{ + { + Name: "grandchild", + }, + }, + }, + }, + }, + []walkStep{ + { + Path: []string{"root"}, + ParentName: "", + }, + { + Path: []string{"root", "child"}, + ParentName: "root", + }, + { + Path: []string{"root", "child", "grandchild"}, + ParentName: "child", + }, + }, + }, + { + &Module{ + Name: "root", + Children: []*Module{ + { + Name: "child1", + Children: []*Module{ + { + Name: "grandchild1", + }, + }, + }, + { + Name: "child2", + Children: []*Module{ + { + Name: "grandchild2", + }, + }, + }, + }, + }, + []walkStep{ + { + Path: []string{"root"}, + ParentName: "", + }, + { + Path: []string{"root", "child1"}, + ParentName: "root", + }, + { + Path: []string{"root", "child1", "grandchild1"}, + ParentName: "child1", + }, + { + Path: []string{"root", "child2"}, + ParentName: "root", + }, + { + Path: []string{"root", "child2", "grandchild2"}, + ParentName: "child2", + }, + }, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + wo := test.WalkOrder + test.Root.WalkTree(func(path []string, parent *Module, current *Module) error { + if len(wo) == 0 { + t.Fatalf("ran out of walk steps while expecting one for %#v", path) + } + step := wo[0] + wo = wo[1:] + if got, want := path, step.Path; !reflect.DeepEqual(got, want) { + t.Errorf("wrong path %#v; want %#v", got, want) + } + parentName := "" + if parent != nil { + parentName = parent.Name + } + if got, want := parentName, step.ParentName; got != want { + t.Errorf("wrong parent name %q; want %q", got, want) + } + + if got, want := current.Name, path[len(path)-1]; got != want { + t.Errorf("mismatching current.Name %q and final path element %q", got, want) + } + return nil + }) + }) + } +} + +func TestModuleSortChildren(t *testing.T) { + m := &Module{ + Name: "root", + Children: []*Module{ + { + Name: "apple", + }, + { + Name: "zebra", + }, + { + Name: "xylophone", + }, + { + Name: "pig", + }, + }, + } + + m.SortChildren() + + want := []string{"apple", "pig", "xylophone", "zebra"} + var got []string + for _, c := range m.Children { + got = append(got, c.Name) + } + + if !reflect.DeepEqual(want, got) { + t.Errorf("wrong order %#v; want %#v", want, got) + } +} + +func TestModulePluginRequirements(t *testing.T) { + m := &Module{ + Name: "root", + Providers: Providers{ + "foo": ProviderDependency{ + Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), + }, + "foo.bar": ProviderDependency{ + Versions: discovery.ConstraintStr(">=2.0.0").MustParse(), + }, + "baz": ProviderDependency{ + Versions: discovery.ConstraintStr(">=3.0.0").MustParse(), + }, + }, + } + + reqd := m.PluginRequirements() + if len(reqd) != 2 { + t.Errorf("wrong number of elements in %#v; want 2", reqd) + } + if got, want := reqd["foo"].String(), ">=1.0.0,>=2.0.0"; got != want { + t.Errorf("wrong combination of versions for 'foo' %q; want %q", got, want) + } + if got, want := reqd["baz"].String(), ">=3.0.0"; got != want { + t.Errorf("wrong combination of versions for 'baz' %q; want %q", got, want) + } +} diff --git a/moduledeps/provider.go b/moduledeps/provider.go new file mode 100644 index 0000000000..89ceefb2cf --- /dev/null +++ b/moduledeps/provider.go @@ -0,0 +1,30 @@ +package moduledeps + +import ( + "strings" +) + +// ProviderInstance describes a particular provider instance by its full name, +// like "null" or "aws.foo". +type ProviderInstance string + +// Type returns the provider type of this instance. For example, for an instance +// named "aws.foo" the type is "aws". +func (p ProviderInstance) Type() string { + t := string(p) + if dotPos := strings.Index(t, "."); dotPos != -1 { + t = t[:dotPos] + } + return t +} + +// Alias returns the alias of this provider, if any. An instance named "aws.foo" +// has the alias "foo", while an instance named just "docker" has no alias, +// so the empty string would be returned. +func (p ProviderInstance) Alias() string { + t := string(p) + if dotPos := strings.Index(t, "."); dotPos != -1 { + return t[dotPos+1:] + } + return "" +} diff --git a/moduledeps/provider_test.go b/moduledeps/provider_test.go new file mode 100644 index 0000000000..c18a0d0a9d --- /dev/null +++ b/moduledeps/provider_test.go @@ -0,0 +1,36 @@ +package moduledeps + +import ( + "testing" +) + +func TestProviderInstance(t *testing.T) { + tests := []struct { + Name string + WantType string + WantAlias string + }{ + { + Name: "aws", + WantType: "aws", + WantAlias: "", + }, + { + Name: "aws.foo", + WantType: "aws", + WantAlias: "foo", + }, + } + + for _, test := range tests { + t.Run(test.Name, func(t *testing.T) { + inst := ProviderInstance(test.Name) + if got, want := inst.Type(), test.WantType; got != want { + t.Errorf("got type %q; want %q", got, want) + } + if got, want := inst.Alias(), test.WantAlias; got != want { + t.Errorf("got alias %q; want %q", got, want) + } + }) + } +} From cfc08ae7e3fde48a51764f5293cefbc193a53860 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 20 Apr 2017 18:13:54 -0700 Subject: [PATCH 15/82] plugin/discovery: provide an AllVersions set Various implied dependencies will accept all versions, so this is convenient for populating dependency data structures for such implied dependencies. --- plugin/discovery/version_set.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/plugin/discovery/version_set.go b/plugin/discovery/version_set.go index 951000b206..3dce045b44 100644 --- a/plugin/discovery/version_set.go +++ b/plugin/discovery/version_set.go @@ -37,6 +37,15 @@ type VersionSet struct { raw version.Constraints } +// AllVersions is a VersionSet containing all versions +var AllVersions VersionSet + +func init() { + AllVersions = VersionSet{ + raw: make(version.Constraints, 0), + } +} + // Has returns true if the given version is in the receiving set. func (s VersionSet) Has(v Version) bool { return s.raw.Check(v.raw) From c20f25a10efacc816e620ad8c5039ebc76462a42 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 20 Apr 2017 18:15:04 -0700 Subject: [PATCH 16/82] moduledeps: Module.Equals Compares two modules for deep equality. This is primarily provided to enable easy testing of code that constructs module tree structures. --- moduledeps/module.go | 61 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/moduledeps/module.go b/moduledeps/module.go index 5c11f35f59..f54f06bfc0 100644 --- a/moduledeps/module.go +++ b/moduledeps/module.go @@ -133,3 +133,64 @@ func (m *Module) AllPluginRequirements() discovery.PluginRequirements { }) return ret } + +// Equal returns true if the receiver is the root of an identical tree +// to the other given Module. This is a deep comparison that considers +// the equality of all downstream modules too. +// +// The children are considered to be ordered, so callers may wish to use +// SortDescendents first to normalize the order of the slices of child nodes. +// +// The implementation of this function is not optimized since it is provided +// primarily for use in tests. +func (m *Module) Equal(other *Module) bool { + // take care of nils first + if m == nil && other == nil { + return true + } else if (m == nil && other != nil) || (m != nil && other == nil) { + return false + } + + if m.Name != other.Name { + return false + } + + if len(m.Providers) != len(other.Providers) { + return false + } + if len(m.Children) != len(other.Children) { + return false + } + + // Can't use reflect.DeepEqual on this provider structure because + // the nested VersionSet objects contain function pointers that + // never compare as equal. So we'll need to walk it the long way. + for inst, dep := range m.Providers { + if _, exists := other.Providers[inst]; !exists { + return false + } + + if dep.Reason != other.Providers[inst].Reason { + return false + } + + // VersionSets are not too easy to compare robustly, so + // we'll just use their string representations as a proxy + // for now. + if dep.Versions.String() != other.Providers[inst].Versions.String() { + return false + } + } + + // Above we already checked that we have the same number of children + // in each module, so now we just need to check that they are + // recursively equal. + for i := range m.Children { + if !m.Children[i].Equal(other.Children[i]) { + return false + } + } + + // If we fall out here then they are equal + return true +} From 25a6d8f4713eb4816cb6e9762d22b4422ce44c60 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 20 Apr 2017 18:22:32 -0700 Subject: [PATCH 17/82] core: build a module dependency tree from config+state This new private function takes a configuration tree and a state structure and finds all of the explicit and implied provider dependencies represented, returning them as a moduledeps.Module tree structure. It annotates each dependency with a "reason", which is intended to be useful to a user trying to figure out where a particular dependency is coming from, though we don't yet have any UI to view this. Nothing calls this yet, but a subsequent commit will use the result of this to produce a constraint-conforming map of provider factories during context initialization. --- terraform/module_dependencies.go | 156 +++++++++++ terraform/module_dependencies_test.go | 261 ++++++++++++++++++ .../main.tf | 7 + .../main.tf | 2 + .../module-deps-explicit-provider/main.tf | 8 + .../module-deps-implicit-provider/main.tf | 8 + .../child/child.tf | 17 ++ .../grandchild/grandchild.tf | 11 + .../module-deps-inherit-provider/main.tf | 11 + 9 files changed, 481 insertions(+) create mode 100644 terraform/module_dependencies.go create mode 100644 terraform/module_dependencies_test.go create mode 100644 terraform/test-fixtures/module-deps-explicit-provider-resource/main.tf create mode 100644 terraform/test-fixtures/module-deps-explicit-provider-unconstrained/main.tf create mode 100644 terraform/test-fixtures/module-deps-explicit-provider/main.tf create mode 100644 terraform/test-fixtures/module-deps-implicit-provider/main.tf create mode 100644 terraform/test-fixtures/module-deps-inherit-provider/child/child.tf create mode 100644 terraform/test-fixtures/module-deps-inherit-provider/grandchild/grandchild.tf create mode 100644 terraform/test-fixtures/module-deps-inherit-provider/main.tf diff --git a/terraform/module_dependencies.go b/terraform/module_dependencies.go new file mode 100644 index 0000000000..79cfcb822a --- /dev/null +++ b/terraform/module_dependencies.go @@ -0,0 +1,156 @@ +package terraform + +import ( + "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/moduledeps" + "github.com/hashicorp/terraform/plugin/discovery" +) + +// moduleTreeDependencies returns the dependencies of the tree of modules +// described by the given configuration tree and state. +// +// Both configuration and state are required because there can be resources +// implied by instances in the state that no longer exist in config. +// +// This function will panic if any invalid version constraint strings are +// present in the configuration. This is guaranteed not to happen for any +// configuration that has passed a call to Config.Validate(). +func moduleTreeDependencies(root *module.Tree, state *State) *moduledeps.Module { + + // First we walk the configuration tree to build the overall structure + // and capture the explicit/implicit/inherited provider dependencies. + deps := moduleTreeConfigDependencies(root, nil) + + // Next we walk over the resources in the state to catch any additional + // dependencies created by existing resources that are no longer in config. + // Most things we find in state will already be present in 'deps', but + // we're interested in the rare thing that isn't. + moduleTreeMergeStateDependencies(deps, state) + + return deps +} + +func moduleTreeConfigDependencies(root *module.Tree, inheritProviders map[string]*config.ProviderConfig) *moduledeps.Module { + if root == nil { + // If no config is provided, we'll make a synthetic root. + // This isn't necessarily correct if we're called with a nil that + // *isn't* at the root, but in practice that can never happen. + return &moduledeps.Module{ + Name: "root", + } + } + + ret := &moduledeps.Module{ + Name: root.Name(), + } + + cfg := root.Config() + providerConfigs := cfg.ProviderConfigsByFullName() + + // Provider dependencies + { + providers := make(moduledeps.Providers, len(providerConfigs)) + + // Any providerConfigs elements are *explicit* provider dependencies, + // which is the only situation where the user might provide an actual + // version constraint. We'll take care of these first. + for fullName, pCfg := range providerConfigs { + inst := moduledeps.ProviderInstance(fullName) + versionSet := discovery.AllVersions + if pCfg.Version != "" { + versionSet = discovery.ConstraintStr(pCfg.Version).MustParse() + } + providers[inst] = moduledeps.ProviderDependency{ + Versions: versionSet, + Reason: moduledeps.ProviderDependencyExplicit, + } + } + + // Each resource in the configuration creates an *implicit* provider + // dependency, though we'll only record it if there isn't already + // an explicit dependency on the same provider. + for _, rc := range cfg.Resources { + fullName := rc.ProviderFullName() + inst := moduledeps.ProviderInstance(fullName) + if _, exists := providers[inst]; exists { + // Explicit dependency already present + continue + } + + reason := moduledeps.ProviderDependencyImplicit + if _, inherited := inheritProviders[fullName]; inherited { + reason = moduledeps.ProviderDependencyInherited + } + + providers[inst] = moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: reason, + } + } + + ret.Providers = providers + } + + childInherit := make(map[string]*config.ProviderConfig) + for k, v := range inheritProviders { + childInherit[k] = v + } + for k, v := range providerConfigs { + childInherit[k] = v + } + for _, c := range root.Children() { + ret.Children = append(ret.Children, moduleTreeConfigDependencies(c, childInherit)) + } + + return ret +} + +func moduleTreeMergeStateDependencies(root *moduledeps.Module, state *State) { + if state == nil { + return + } + + findModule := func(path []string) *moduledeps.Module { + module := root + for _, name := range path[1:] { // skip initial "root" + var next *moduledeps.Module + for _, cm := range module.Children { + if cm.Name == name { + next = cm + break + } + } + + if next == nil { + // If we didn't find a next node, we'll need to make one + next = &moduledeps.Module{ + Name: name, + } + module.Children = append(module.Children, next) + } + + module = next + } + return module + } + + for _, ms := range state.Modules { + module := findModule(ms.Path) + + for _, is := range ms.Resources { + fullName := config.ResourceProviderFullName(is.Type, is.Provider) + inst := moduledeps.ProviderInstance(fullName) + if _, exists := module.Providers[inst]; !exists { + if module.Providers == nil { + module.Providers = make(moduledeps.Providers) + } + module.Providers[inst] = moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, + } + } + } + } + +} diff --git a/terraform/module_dependencies_test.go b/terraform/module_dependencies_test.go new file mode 100644 index 0000000000..b17c42daa2 --- /dev/null +++ b/terraform/module_dependencies_test.go @@ -0,0 +1,261 @@ +package terraform + +import ( + "testing" + + "github.com/davecgh/go-spew/spew" + "github.com/hashicorp/terraform/config/module" + "github.com/hashicorp/terraform/moduledeps" + "github.com/hashicorp/terraform/plugin/discovery" +) + +func TestModuleTreeDependencies(t *testing.T) { + tests := map[string]struct { + ConfigDir string // directory name from test-fixtures dir + State *State + Want *moduledeps.Module + }{ + "no config or state": { + "", + nil, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{}, + Children: nil, + }, + }, + "empty config no state": { + "empty", + nil, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{}, + Children: nil, + }, + }, + "explicit provider": { + "module-deps-explicit-provider", + nil, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, + }, + "foo.bar": moduledeps.ProviderDependency{ + Versions: discovery.ConstraintStr(">=2.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, + }, + }, + Children: nil, + }, + }, + "explicit provider unconstrained": { + "module-deps-explicit-provider-unconstrained", + nil, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, + }, + }, + Children: nil, + }, + }, + "implicit provider": { + "module-deps-implicit-provider", + nil, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyImplicit, + }, + "foo.baz": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyImplicit, + }, + }, + Children: nil, + }, + }, + "explicit provider with resource": { + "module-deps-explicit-provider-resource", + nil, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, + }, + }, + Children: nil, + }, + }, + "inheritance of providers": { + "module-deps-inherit-provider", + nil, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, + }, + "bar": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, + }, + }, + Children: []*moduledeps.Module{ + { + Name: "child", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyInherited, + }, + "baz": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyImplicit, + }, + }, + Children: []*moduledeps.Module{ + { + Name: "grandchild", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, + }, + "bar": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyInherited, + }, + }, + }, + }, + }, + }, + }, + }, + "provider from state": { + "empty", + &State{ + Modules: []*ModuleState{ + { + Path: []string{"root"}, + Resources: map[string]*ResourceState{ + "foo_bar.baz": { + Type: "foo_bar", + Provider: "", + }, + }, + }, + }, + }, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, + }, + }, + Children: nil, + }, + }, + "providers in both config and state": { + "module-deps-explicit-provider", + &State{ + Modules: []*ModuleState{ + { + Path: []string{"root"}, + Resources: map[string]*ResourceState{ + "foo_bar.test1": { + Type: "foo_bar", + Provider: "", + }, + "foo_bar.test2": { + Type: "foo_bar", + Provider: "foo.bar", + }, + "baz_bar.test": { + Type: "baz_bar", + Provider: "", + }, + }, + }, + // note that we've skipped root.child intentionally here, + // to verify that we'll infer it based on the following + // module rather than crashing. + { + Path: []string{"root", "child", "grandchild"}, + Resources: map[string]*ResourceState{ + "banana_skin.test": { + Type: "banana_skin", + Provider: "", + }, + }, + }, + }, + }, + &moduledeps.Module{ + Name: "root", + Providers: moduledeps.Providers{ + "foo": moduledeps.ProviderDependency{ + Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, + }, + "foo.bar": moduledeps.ProviderDependency{ + Versions: discovery.ConstraintStr(">=2.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, + }, + "baz": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, + }, + }, + Children: []*moduledeps.Module{ + { + Name: "child", + Children: []*moduledeps.Module{ + { + Name: "grandchild", + Providers: moduledeps.Providers{ + "banana": moduledeps.ProviderDependency{ + Versions: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, + }, + }, + }, + }, + }, + }, + }, + }, + } + + for name, test := range tests { + t.Run(name, func(t *testing.T) { + var root *module.Tree + if test.ConfigDir != "" { + root = testModule(t, test.ConfigDir) + } + + got := moduleTreeDependencies(root, test.State) + if !got.Equal(test.Want) { + t.Errorf( + "wrong dependency tree\ngot: %s\nwant: %s", + spew.Sdump(got), + spew.Sdump(test.Want), + ) + } + }) + } +} diff --git a/terraform/test-fixtures/module-deps-explicit-provider-resource/main.tf b/terraform/test-fixtures/module-deps-explicit-provider-resource/main.tf new file mode 100644 index 0000000000..d5990b09c9 --- /dev/null +++ b/terraform/test-fixtures/module-deps-explicit-provider-resource/main.tf @@ -0,0 +1,7 @@ +provider "foo" { + version = ">=1.0.0" +} + +resource "foo_bar" "test1" { + +} diff --git a/terraform/test-fixtures/module-deps-explicit-provider-unconstrained/main.tf b/terraform/test-fixtures/module-deps-explicit-provider-unconstrained/main.tf new file mode 100644 index 0000000000..6144ff5395 --- /dev/null +++ b/terraform/test-fixtures/module-deps-explicit-provider-unconstrained/main.tf @@ -0,0 +1,2 @@ +provider "foo" { +} diff --git a/terraform/test-fixtures/module-deps-explicit-provider/main.tf b/terraform/test-fixtures/module-deps-explicit-provider/main.tf new file mode 100644 index 0000000000..27d423759d --- /dev/null +++ b/terraform/test-fixtures/module-deps-explicit-provider/main.tf @@ -0,0 +1,8 @@ +provider "foo" { + version = ">=1.0.0" +} + +provider "foo" { + version = ">=2.0.0" + alias = "bar" +} diff --git a/terraform/test-fixtures/module-deps-implicit-provider/main.tf b/terraform/test-fixtures/module-deps-implicit-provider/main.tf new file mode 100644 index 0000000000..15aa2cb727 --- /dev/null +++ b/terraform/test-fixtures/module-deps-implicit-provider/main.tf @@ -0,0 +1,8 @@ + +resource "foo_bar" "test1" { + +} + +resource "foo_bar" "test2" { + provider = "foo.baz" +} diff --git a/terraform/test-fixtures/module-deps-inherit-provider/child/child.tf b/terraform/test-fixtures/module-deps-inherit-provider/child/child.tf new file mode 100644 index 0000000000..51e0950a04 --- /dev/null +++ b/terraform/test-fixtures/module-deps-inherit-provider/child/child.tf @@ -0,0 +1,17 @@ + +# "foo" is inherited from the parent module +resource "foo_bar" "test" { + +} + +# but we don't use the "bar" provider inherited from the parent + +# "baz" is introduced here for the first time, so it's an implicit +# dependency +resource "baz_bar" "test" { + +} + +module "grandchild" { + source = "../grandchild" +} diff --git a/terraform/test-fixtures/module-deps-inherit-provider/grandchild/grandchild.tf b/terraform/test-fixtures/module-deps-inherit-provider/grandchild/grandchild.tf new file mode 100644 index 0000000000..c5a07249f2 --- /dev/null +++ b/terraform/test-fixtures/module-deps-inherit-provider/grandchild/grandchild.tf @@ -0,0 +1,11 @@ + +# Here we *override* the foo from the parent +provider "foo" { + +} + +# We also use the "bar" provider defined at the root, which was +# completely ignored by the child module in between. +resource "bar_thing" "test" { + +} diff --git a/terraform/test-fixtures/module-deps-inherit-provider/main.tf b/terraform/test-fixtures/module-deps-inherit-provider/main.tf new file mode 100644 index 0000000000..9842855b96 --- /dev/null +++ b/terraform/test-fixtures/module-deps-inherit-provider/main.tf @@ -0,0 +1,11 @@ + +provider "foo" { +} + +provider "bar" { + +} + +module "child" { + source = "./child" +} From 8bfc6e7b1cce8ddbbca60df467e590d9bd6e71e0 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 21 Apr 2017 15:42:23 -0700 Subject: [PATCH 18/82] core: add missing ResourceState types in context tests Previously the Type of a ResourceState was generally ignored, but we're now starting to use it to figure out which providers are needed to support the resources in state so our tests need to set it accurately in order to get the expected result. --- terraform/context_apply_test.go | 5 +++++ terraform/context_plan_test.go | 1 + terraform/context_refresh_test.go | 5 ++++- 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index abe0e524a0..0a59ab5f41 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -7919,6 +7919,7 @@ func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.ifailedprovisioners": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "ifailedprovisioners", Tainted: true, @@ -8046,6 +8047,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo.0": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", Attributes: map[string]string{ @@ -8055,6 +8057,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { }, }, "aws_instance.foo.1": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "i-bcd234", Attributes: map[string]string{ @@ -8064,6 +8067,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { }, }, "aws_eip.foo.0": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "eip-abc123", Attributes: map[string]string{ @@ -8073,6 +8077,7 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { }, }, "aws_eip.foo.1": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "eip-bcd234", Attributes: map[string]string{ diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index aeabcb0217..69ab385165 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -2950,6 +2950,7 @@ func TestContext2Plan_ignoreChanges(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Attributes: map[string]string{"ami": "ami-abcd1234"}, diff --git a/terraform/context_refresh_test.go b/terraform/context_refresh_test.go index b29e63679d..0276b05d5b 100644 --- a/terraform/context_refresh_test.go +++ b/terraform/context_refresh_test.go @@ -605,7 +605,7 @@ func TestContext2Refresh_dataOrphan(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "data.null_data_source.bar": &ResourceState{ - Type: "foo", + Type: "null_data_source", Primary: &InstanceState{ ID: "foo", }, @@ -894,6 +894,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "i-abc123", Attributes: map[string]string{ @@ -912,6 +913,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) { Path: append(rootModulePath, "child"), Resources: map[string]*ResourceState{ "aws_instance.bar": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "i-bcd234", Attributes: map[string]string{ @@ -938,6 +940,7 @@ func TestContext2Refresh_orphanModule(t *testing.T) { Path: append(rootModulePath, "child", "grandchild"), Resources: map[string]*ResourceState{ "aws_instance.baz": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "i-cde345", }, From 1c0b715999bb5a848be1c1bebccd513c7896db8c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 21 Apr 2017 15:43:15 -0700 Subject: [PATCH 19/82] core: return explicit caption if tests fail to construct context The previous error was very generic, making it hard to quickly tell from the test output that the error was during context initialization. --- terraform/context_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/terraform/context_test.go b/terraform/context_test.go index 3534e9aa36..3b96d2a63c 100644 --- a/terraform/context_test.go +++ b/terraform/context_test.go @@ -151,7 +151,7 @@ func testContext2(t *testing.T, opts *ContextOpts) *Context { ctx, err := NewContext(opts) if err != nil { - t.Fatalf("err: %s", err) + t.Fatalf("failed to create test context\n\n%s\n", err) } return ctx From 0573ff679355aefdc0088085c6ecaba9dc988a55 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 21 Apr 2017 16:43:14 -0700 Subject: [PATCH 20/82] helper/resource: pass config when testing import Previously having a config was mutually exclusive with running an import, but we need to provide a config so that the provider is declared, or else we can't actually complete the import in the future world where providers are installed dynamically based on their declarations. --- helper/resource/testing.go | 15 +++++++++------ helper/resource/testing_import_state_test.go | 6 ++++++ helper/resource/testing_test.go | 12 ++++++++---- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/helper/resource/testing.go b/helper/resource/testing.go index ebdbde2b5d..a700a8c798 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -400,15 +400,18 @@ func Test(t TestT, c TestCase) { var err error log.Printf("[WARN] Test: Executing step %d", i) - // Determine the test mode to execute - if step.Config != "" { - state, err = testStepConfig(opts, state, step) - } else if step.ImportState { - state, err = testStepImportState(opts, state, step) - } else { + if step.Config == "" && !step.ImportState { err = fmt.Errorf( "unknown test mode for step. Please see TestStep docs\n\n%#v", step) + } else { + if step.ImportState { + // Can optionally set step.Config in addition to + // step.ImportState, to provide config for the import. + state, err = testStepImportState(opts, state, step) + } else { + state, err = testStepConfig(opts, state, step) + } } // If there was an error, exit diff --git a/helper/resource/testing_import_state_test.go b/helper/resource/testing_import_state_test.go index 96b1edc3d1..8ef8323bac 100644 --- a/helper/resource/testing_import_state_test.go +++ b/helper/resource/testing_import_state_test.go @@ -40,6 +40,7 @@ func TestTest_importState(t *testing.T) { Steps: []TestStep{ TestStep{ + Config: testConfigStrProvider, ResourceName: "test_instance.foo", ImportState: true, ImportStateId: "foo", @@ -89,6 +90,7 @@ func TestTest_importStateFail(t *testing.T) { Steps: []TestStep{ TestStep{ + Config: testConfigStrProvider, ResourceName: "test_instance.foo", ImportState: true, ImportStateId: "foo", @@ -163,6 +165,7 @@ func TestTest_importStateDetectId(t *testing.T) { Config: testConfigStr, }, TestStep{ + Config: testConfigStr, ResourceName: "test_instance.foo", ImportState: true, ImportStateCheck: checkFn, @@ -236,6 +239,7 @@ func TestTest_importStateIdPrefix(t *testing.T) { Config: testConfigStr, }, { + Config: testConfigStr, ResourceName: "test_instance.foo", ImportState: true, ImportStateCheck: checkFn, @@ -309,6 +313,7 @@ func TestTest_importStateVerify(t *testing.T) { Config: testConfigStr, }, TestStep{ + Config: testConfigStr, ResourceName: "test_instance.foo", ImportState: true, ImportStateVerify: true, @@ -371,6 +376,7 @@ func TestTest_importStateVerifyFail(t *testing.T) { Config: testConfigStr, }, TestStep{ + Config: testConfigStr, ResourceName: "test_instance.foo", ImportState: true, ImportStateVerify: true, diff --git a/helper/resource/testing_test.go b/helper/resource/testing_test.go index cfadc25095..d1c967bbd6 100644 --- a/helper/resource/testing_test.go +++ b/helper/resource/testing_test.go @@ -619,10 +619,6 @@ func testProvider() *terraform.MockResourceProvider { return mp } -const testConfigStr = ` -resource "test_instance" "foo" {} -` - func TestTest_Main(t *testing.T) { flag.Parse() if *flagSweep == "" { @@ -777,3 +773,11 @@ func TestTest_Main(t *testing.T) { func mockSweeperFunc(s string) error { return nil } + +const testConfigStr = ` +resource "test_instance" "foo" {} +` + +const testConfigStrProvider = ` +provider "test" {} +` From d6b6dbb5c6253c3f5f31b6b3794e578471a20220 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 21 Apr 2017 17:22:19 -0700 Subject: [PATCH 21/82] command: correct provider name in the test fixture for push Currently this doesn't matter much, but we're about to start checking the availability of providers early on and so we need to use the correct name for the mock set of providers we use in command tests, which includes only a provider named "test". Without this change, the "push" tests will begin failing once we start verifying this, since there's no "aws" provider available in the test context. --- command/test-fixtures/push/main.tf | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/test-fixtures/push/main.tf b/command/test-fixtures/push/main.tf index 2651626363..28f267cd26 100644 --- a/command/test-fixtures/push/main.tf +++ b/command/test-fixtures/push/main.tf @@ -1,4 +1,4 @@ -resource "aws_instance" "foo" {} +resource "test_instance" "foo" {} atlas { name = "foo" From ba3ee00837d941c8a2046d52337f59f6cd0920af Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 18 Apr 2017 16:54:11 -0700 Subject: [PATCH 22/82] core: ResourceProviderResolver interface ResourceProviderResolver is an extra level of indirection before we get to a map[string]ResourceProviderFactory, which accepts a map of version constraints and uses it to choose from potentially-many available versions of each provider to produce a single ResourceProviderFactory for each one requested. As of this commit the ResourceProviderResolver interface is not used. In a future commit the ContextOpts.Providers map will be replaced with a resolver instance, with the creation of the factory delayed until the version constraints have been resolved. --- terraform/resource_provider.go | 50 ++++++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/terraform/resource_provider.go b/terraform/resource_provider.go index 1a68c8699c..949d426e76 100644 --- a/terraform/resource_provider.go +++ b/terraform/resource_provider.go @@ -1,5 +1,11 @@ package terraform +import ( + "fmt" + + "github.com/hashicorp/terraform/plugin/discovery" +) + // ResourceProvider is an interface that must be implemented by any // resource provider: the thing that creates and manages the resources in // a Terraform configuration. @@ -171,6 +177,50 @@ type DataSource struct { Name string } +// ResourceProviderResolver is an interface implemented by objects that are +// able to resolve a given set of resource provider version constraints +// into ResourceProviderFactory callbacks. +type ResourceProviderResolver interface { + // Given a constraint map, return a ResourceProviderFactory for each + // requested provider. If some or all of the constraints cannot be + // satisfied, return a non-nil slice of errors describing the problems. + ResolveProviders(reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, []error) +} + +// ResourceProviderResolverFunc wraps a callback function and turns it into +// a ResourceProviderResolver implementation, for convenience in situations +// where a function and its associated closure are sufficient as a resolver +// implementation. +type ResourceProviderResolverFunc func(reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, []error) + +// ResolveProviders implements ResourceProviderResolver by calling the +// wrapped function. +func (f ResourceProviderResolverFunc) ResolveProviders(reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, []error) { + return f(reqd) +} + +// ResourceProviderResolverFixed returns a ResourceProviderResolver that +// has a fixed set of provider factories provided by the caller. The returned +// resolver ignores version constraints entirely and just returns the given +// factory for each requested provider name. +// +// This function is primarily used in tests, to provide mock providers or +// in-process providers under test. +func ResourceProviderResolverFixed(factories map[string]ResourceProviderFactory) ResourceProviderResolver { + return ResourceProviderResolverFunc(func(reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, []error) { + ret := make(map[string]ResourceProviderFactory, len(reqd)) + var errs []error + for name := range reqd { + if factory, exists := factories[name]; exists { + ret[name] = factory + } else { + errs = append(errs, fmt.Errorf("provider %q is not available", name)) + } + } + return ret, errs + }) +} + // ResourceProviderFactory is a function type that creates a new instance // of a resource provider. type ResourceProviderFactory func() (ResourceProvider, error) From 7ca592ac066d48573cf4b8d68a57bbce5c66a21b Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 21 Apr 2017 18:06:30 -0700 Subject: [PATCH 23/82] core: use ResourceProviderResolver to resolve providers Previously the set of providers was fixed early on in the command package processing. In order to be version-aware we need to defer this work until later, so this interface exists so we can hold on to the possibly-many versions of plugins we have available and then later, once we've finished determining the provider dependencies, select the appropriate version of each provider to produce the final set of providers to use. This commit establishes the use of this new mechanism, and thus populates the provider factory map with only the providers that result from the dependency resolution process. This disables support for internal provider plugins, though the mechanisms for building and launching these are still here vestigially, to be cleaned up in a subsequent commit. This also adds a new awkward quirk to the "terraform import" workflow where one can't import a resource from a provider that isn't already mentioned (implicitly or explicitly) in config. We will do some UX work in subsequent commits to make this behavior better. This breaks many tests due to the change in interface, but to keep this particular diff reasonably easy to read the test fixes are split into a separate commit. --- command/meta.go | 8 ++--- command/plugins.go | 62 ++++++++++++++++++---------------- terraform/context.go | 19 +++++++++-- terraform/resource_provider.go | 24 +++++++++++++ 4 files changed, 76 insertions(+), 37 deletions(-) diff --git a/command/meta.go b/command/meta.go index 40d13aa1e9..4e3233362b 100644 --- a/command/meta.go +++ b/command/meta.go @@ -119,8 +119,8 @@ type PluginOverrides struct { } type testingOverrides struct { - Providers map[string]terraform.ResourceProviderFactory - Provisioners map[string]terraform.ResourceProvisionerFactory + ProviderResolver terraform.ResourceProviderResolver + Provisioners map[string]terraform.ResourceProvisionerFactory } // initStatePaths is used to initialize the default values for @@ -237,10 +237,10 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { // and just work with what we've been given, thus allowing the tests // to provide mock providers and provisioners. if m.testingOverrides != nil { - opts.Providers = m.testingOverrides.Providers + opts.ProviderResolver = m.testingOverrides.ProviderResolver opts.Provisioners = m.testingOverrides.Provisioners } else { - opts.Providers = m.providerFactories() + opts.ProviderResolver = m.providerResolver() opts.Provisioners = m.provisionerFactories() } diff --git a/command/plugins.go b/command/plugins.go index fa4e48ab4a..a0f94bc1fb 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -1,6 +1,7 @@ package command import ( + "fmt" "log" "os/exec" "strings" @@ -11,7 +12,35 @@ import ( "github.com/hashicorp/terraform/terraform" ) -func (m *Meta) providerFactories() map[string]terraform.ResourceProviderFactory { +// multiVersionProviderResolver is an implementation of +// terraform.ResourceProviderResolver that matches the given version constraints +// against a set of versioned provider plugins to find the newest version of +// each that satisfies the given constraints. +type multiVersionProviderResolver struct { + Available discovery.PluginMetaSet +} + +func (r *multiVersionProviderResolver) ResolveProviders( + reqd discovery.PluginRequirements, +) (map[string]terraform.ResourceProviderFactory, []error) { + factories := make(map[string]terraform.ResourceProviderFactory, len(reqd)) + var errs []error + + candidates := r.Available.ConstrainVersions(reqd) + for name := range reqd { + if metas := candidates[name]; metas != nil { + newest := metas.Newest() + client := tfplugin.Client(newest) + factories[name] = providerFactory(client) + } else { + errs = append(errs, fmt.Errorf("provider.%s: no suitable version installed", name)) + } + } + + return factories, errs +} + +func (m *Meta) providerResolver() terraform.ResourceProviderResolver { var dirs []string // When searching the following directories, earlier entries get precedence @@ -25,36 +54,9 @@ func (m *Meta) providerFactories() map[string]terraform.ResourceProviderFactory plugins := discovery.FindPlugins("provider", dirs) plugins, _ = plugins.ValidateVersions() - // For now our goal is to just find the latest version of each plugin - // we have on the system, emulating our pre-versioning behavior. - // TODO: Reorganize how providers are handled so that we can use - // version constraints from configuration to select which plugins - // we will use when multiple are available. - - factories := make(map[string]terraform.ResourceProviderFactory) - - // Wire up the internal provisioners first. These might be overridden - // by discovered providers below. - for name := range InternalProviders { - client, err := internalPluginClient("provider", name) - if err != nil { - log.Printf("[WARN] failed to build command line for internal plugin %q: %s", name, err) - continue - } - factories[name] = providerFactory(client) + return &multiVersionProviderResolver{ + Available: plugins, } - - byName := plugins.ByName() - for name, metas := range byName { - // Since we validated versions above and we partitioned the sets - // by name, we're guaranteed that the metas in our set all have - // valid versions and that there's at least one meta. - newest := metas.Newest() - client := tfplugin.Client(newest) - factories[name] = providerFactory(client) - } - - return factories } func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { diff --git a/terraform/context.go b/terraform/context.go index 306128edfb..e344b306d6 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -57,7 +57,7 @@ type ContextOpts struct { Parallelism int State *State StateFutureAllowed bool - Providers map[string]ResourceProviderFactory + ProviderResolver ResourceProviderResolver Provisioners map[string]ResourceProvisionerFactory Shadow bool Targets []string @@ -166,7 +166,6 @@ func NewContext(opts *ContextOpts) (*Context, error) { // set by environment variables if necessary. This includes // values taken from -var-file in addition. variables := make(map[string]interface{}) - if opts.Module != nil { var err error variables, err = Variables(opts.Module, opts.Variables) @@ -175,6 +174,20 @@ func NewContext(opts *ContextOpts) (*Context, error) { } } + // Bind available provider plugins to the constraints in config + var providers map[string]ResourceProviderFactory + if opts.ProviderResolver != nil { + var err error + deps := moduleTreeDependencies(opts.Module, state) + reqd := deps.AllPluginRequirements() + providers, err = resourceProviderFactories(opts.ProviderResolver, reqd) + if err != nil { + return nil, err + } + } else { + providers = make(map[string]ResourceProviderFactory) + } + diff := opts.Diff if diff == nil { diff = &Diff{} @@ -182,7 +195,7 @@ func NewContext(opts *ContextOpts) (*Context, error) { return &Context{ components: &basicComponentFactory{ - providers: opts.Providers, + providers: providers, provisioners: opts.Provisioners, }, destroy: opts.Destroy, diff --git a/terraform/resource_provider.go b/terraform/resource_provider.go index 949d426e76..37d59e4806 100644 --- a/terraform/resource_provider.go +++ b/terraform/resource_provider.go @@ -1,6 +1,8 @@ package terraform import ( + "bytes" + "errors" "fmt" "github.com/hashicorp/terraform/plugin/discovery" @@ -252,3 +254,25 @@ func ProviderHasDataSource(p ResourceProvider, n string) bool { return false } + +// resourceProviderFactories matches available plugins to the given version +// requirements to produce a map of compatible provider plugins if possible, +// or an error if the currently-available plugins are insufficient. +// +// This should be called only with configurations that have passed calls +// to config.Validate(), which ensures that all of the given version +// constraints are valid. It will panic if any invalid constraints are present. +func resourceProviderFactories(resolver ResourceProviderResolver, reqd discovery.PluginRequirements) (map[string]ResourceProviderFactory, error) { + ret, errs := resolver.ResolveProviders(reqd) + if errs != nil { + errBuf := &bytes.Buffer{} + errBuf.WriteString("Can't satisfy provider requirements with currently-installed plugins:\n\n") + for _, err := range errs { + fmt.Fprintf(errBuf, "* %s\n", err) + } + errBuf.WriteString("\nRun 'terraform init' to install the necessary provider plugins.\n") + return nil, errors.New(errBuf.String()) + } + + return ret, nil +} From c835ef8ff3efa73685f1190e276b123db1da8efd Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 21 Apr 2017 17:40:46 -0700 Subject: [PATCH 24/82] Update tests for the new ProviderResolver interface Rather than providing an already-resolved map of plugins to core, we now provide a "provider resolver" which knows how to resolve a set of provider dependencies, to be determined later, and produce that map. This requires the context to be instantiated in a different way, so this very noisy diff is a mostly-mechanical update of all of the existing places where contexts get created for testing, using some adapted versions of the pre-existing utilities for passing in mock providers. --- backend/local/testing.go | 11 +- command/command_test.go | 20 +- helper/resource/testing.go | 13 +- terraform/context_apply_test.go | 1476 +++++++++++++++++----------- terraform/context_import_test.go | 136 ++- terraform/context_input_test.go | 136 ++- terraform/context_plan_test.go | 861 +++++++++------- terraform/context_refresh_test.go | 202 ++-- terraform/context_validate_test.go | 298 +++--- terraform/debug_test.go | 8 +- 10 files changed, 1949 insertions(+), 1212 deletions(-) diff --git a/backend/local/testing.go b/backend/local/testing.go index 91ba0f9004..1d3c582cc3 100644 --- a/backend/local/testing.go +++ b/backend/local/testing.go @@ -47,14 +47,13 @@ func TestLocalProvider(t *testing.T, b *Local, name string) *terraform.MockResou if b.ContextOpts == nil { b.ContextOpts = &terraform.ContextOpts{} } - if b.ContextOpts.Providers == nil { - b.ContextOpts.Providers = make(map[string]terraform.ResourceProviderFactory) - } // Setup our provider - b.ContextOpts.Providers[name] = func() (terraform.ResourceProvider, error) { - return p, nil - } + b.ContextOpts.ProviderResolver = terraform.ResourceProviderResolverFixed( + map[string]terraform.ResourceProviderFactory{ + name: terraform.ResourceProviderFactoryFixed(p), + }, + ) return p } diff --git a/command/command_test.go b/command/command_test.go index 77cb3e741d..a416256f86 100644 --- a/command/command_test.go +++ b/command/command_test.go @@ -71,21 +71,25 @@ func testFixturePath(name string) string { func metaOverridesForProvider(p terraform.ResourceProvider) *testingOverrides { return &testingOverrides{ - Providers: map[string]terraform.ResourceProviderFactory{ - "test": func() (terraform.ResourceProvider, error) { - return p, nil + ProviderResolver: terraform.ResourceProviderResolverFixed( + map[string]terraform.ResourceProviderFactory{ + "test": func() (terraform.ResourceProvider, error) { + return p, nil + }, }, - }, + ), } } func metaOverridesForProviderAndProvisioner(p terraform.ResourceProvider, pr terraform.ResourceProvisioner) *testingOverrides { return &testingOverrides{ - Providers: map[string]terraform.ResourceProviderFactory{ - "test": func() (terraform.ResourceProvider, error) { - return p, nil + ProviderResolver: terraform.ResourceProviderResolverFixed( + map[string]terraform.ResourceProviderFactory{ + "test": func() (terraform.ResourceProvider, error) { + return p, nil + }, }, - }, + ), Provisioners: map[string]terraform.ResourceProvisionerFactory{ "shell": func() (terraform.ResourceProvisioner, error) { return pr, nil diff --git a/helper/resource/testing.go b/helper/resource/testing.go index a700a8c798..11b1a5cd6a 100644 --- a/helper/resource/testing.go +++ b/helper/resource/testing.go @@ -383,11 +383,11 @@ func Test(t TestT, c TestCase) { c.PreCheck() } - ctxProviders, err := testProviderFactories(c) + providerResolver, err := testProviderResolver(c) if err != nil { t.Fatal(err) } - opts := terraform.ContextOpts{Providers: ctxProviders} + opts := terraform.ContextOpts{ProviderResolver: providerResolver} // A single state variable to track the lifecycle, starting with no state var state *terraform.State @@ -499,16 +499,17 @@ func Test(t TestT, c TestCase) { } } -// testProviderFactories is a helper to build the ResourceProviderFactory map +// testProviderResolver is a helper to build a ResourceProviderResolver // with pre instantiated ResourceProviders, so that we can reset them for the // test, while only calling the factory function once. // Any errors are stored so that they can be returned by the factory in // terraform to match non-test behavior. -func testProviderFactories(c TestCase) (map[string]terraform.ResourceProviderFactory, error) { - ctxProviders := c.ProviderFactories // make(map[string]terraform.ResourceProviderFactory) +func testProviderResolver(c TestCase) (terraform.ResourceProviderResolver, error) { + ctxProviders := c.ProviderFactories if ctxProviders == nil { ctxProviders = make(map[string]terraform.ResourceProviderFactory) } + // add any fixed providers for k, p := range c.Providers { ctxProviders[k] = terraform.ResourceProviderFactoryFixed(p) @@ -530,7 +531,7 @@ func testProviderFactories(c TestCase) (map[string]terraform.ResourceProviderFac } } - return ctxProviders, nil + return terraform.ResourceProviderResolverFixed(ctxProviders), nil } // UnitTest is a helper to force the acceptance testing harness to run in the diff --git a/terraform/context_apply_test.go b/terraform/context_apply_test.go index 0a59ab5f41..044e6666d9 100644 --- a/terraform/context_apply_test.go +++ b/terraform/context_apply_test.go @@ -23,9 +23,11 @@ func TestContext2Apply_basic(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -56,9 +58,11 @@ func TestContext2Apply_escape(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -85,9 +89,11 @@ func TestContext2Apply_resourceCountOneList(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -117,9 +123,11 @@ func TestContext2Apply_resourceCountZeroList(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -173,9 +181,11 @@ func TestContext2Apply_resourceDependsOnModule(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -257,9 +267,11 @@ func TestContext2Apply_resourceDependsOnModuleStateOnly(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -294,9 +306,11 @@ func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) { p.ApplyFn = testApplyFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -338,9 +352,11 @@ func TestContext2Apply_resourceDependsOnModuleDestroy(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: globalState, Destroy: true, }) @@ -398,9 +414,11 @@ func TestContext2Apply_resourceDependsOnModuleGrandchild(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -452,9 +470,11 @@ func TestContext2Apply_resourceDependsOnModuleInModule(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -481,9 +501,11 @@ func TestContext2Apply_mapVarBetweenModules(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -520,9 +542,11 @@ func TestContext2Apply_refCount(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -553,9 +577,11 @@ func TestContext2Apply_providerAlias(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -589,9 +615,11 @@ func TestContext2Apply_providerAliasConfigure(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "another": testProviderFuncFixed(p2), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "another": testProviderFuncFixed(p2), + }, + ), }) if p, err := ctx.Plan(); err != nil { @@ -645,9 +673,11 @@ func TestContext2Apply_providerWarning(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -726,9 +756,11 @@ func TestContext2Apply_computedAttrRefTypeMismatch(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -753,9 +785,11 @@ func TestContext2Apply_emptyModule(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -800,9 +834,11 @@ func TestContext2Apply_createBeforeDestroy(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -854,9 +890,11 @@ func TestContext2Apply_createBeforeDestroyUpdate(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -920,9 +958,11 @@ func TestContext2Apply_createBeforeDestroy_dependsNonCBD(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -987,9 +1027,11 @@ func TestContext2Apply_createBeforeDestroy_hook(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -1054,9 +1096,11 @@ func TestContext2Apply_createBeforeDestroy_deposedCount(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -1115,9 +1159,11 @@ func TestContext2Apply_createBeforeDestroy_deposedOnly(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -1163,9 +1209,11 @@ func TestContext2Apply_destroyComputed(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, }) @@ -1233,9 +1281,11 @@ func testContext2Apply_destroyDependsOn(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, Parallelism: 1, // To check ordering @@ -1309,9 +1359,11 @@ func testContext2Apply_destroyDependsOnStateOnly(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, Parallelism: 1, // To check ordering @@ -1385,9 +1437,11 @@ func testContext2Apply_destroyDependsOnStateOnlyModule(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, Parallelism: 1, // To check ordering @@ -1416,9 +1470,11 @@ func TestContext2Apply_dataBasic(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) if p, err := ctx.Plan(); err != nil { @@ -1450,7 +1506,7 @@ func TestContext2Apply_destroyData(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "data.null_data_source.testing": &ResourceState{ - Type: "aws_instance", + Type: "null_data_source", Primary: &InstanceState{ ID: "-", Attributes: map[string]string{ @@ -1465,9 +1521,11 @@ func TestContext2Apply_destroyData(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, }) @@ -1524,9 +1582,11 @@ func TestContext2Apply_destroySkipsCBD(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, }) @@ -1564,9 +1624,11 @@ func TestContext2Apply_destroyModuleVarProviderConfig(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, }) @@ -1654,10 +1716,10 @@ func getContextForApply_destroyCrossProviders( }, } ctx := testContext2(t, &ContextOpts{ - Module: m, - Providers: providers, - State: state, - Destroy: true, + Module: m, + ProviderResolver: ResourceProviderResolverFixed(providers), + State: state, + Destroy: true, }) return ctx @@ -1670,9 +1732,11 @@ func TestContext2Apply_minimal(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -1698,9 +1762,11 @@ func TestContext2Apply_badDiff(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -1727,9 +1793,11 @@ func TestContext2Apply_cancel(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ApplyFn = func(*InstanceInfo, *InstanceState, *InstanceDiff) (*InstanceState, error) { @@ -1804,9 +1872,11 @@ func TestContext2Apply_cancelBlock(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) applyCh := make(chan struct{}) @@ -1885,9 +1955,11 @@ func TestContext2Apply_cancelProvisioner(t *testing.T) { pr := testProvisioner() ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -1947,9 +2019,11 @@ func TestContext2Apply_compute(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2015,9 +2089,11 @@ func TestContext2Apply_countDecrease(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2075,9 +2151,11 @@ func TestContext2Apply_countDecreaseToOneX(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2137,9 +2215,11 @@ func TestContext2Apply_countDecreaseToOneCorrupted(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2187,9 +2267,11 @@ func TestContext2Apply_countTainted(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2216,9 +2298,11 @@ func TestContext2Apply_countVariable(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2244,9 +2328,11 @@ func TestContext2Apply_countVariableRef(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2272,9 +2358,11 @@ func TestContext2Apply_mapVariableOverride(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "images": []map[string]interface{}{ map[string]interface{}{ @@ -2316,9 +2404,11 @@ func TestContext2Apply_moduleBasic(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2393,9 +2483,11 @@ func TestContext2Apply_moduleDestroyOrder(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, Destroy: true, }) @@ -2443,9 +2535,11 @@ func TestContext2Apply_moduleInheritAlias(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2508,9 +2602,11 @@ func TestContext2Apply_moduleOrphanInheritAlias(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, State: state, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2566,9 +2662,11 @@ func TestContext2Apply_moduleOrphanProvider(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, State: state, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2614,9 +2712,11 @@ func TestContext2Apply_moduleOrphanGrandchildProvider(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, State: state, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2649,9 +2749,11 @@ func TestContext2Apply_moduleGrandchildProvider(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2684,10 +2786,12 @@ func TestContext2Apply_moduleOnlyProvider(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "test": testProviderFuncFixed(pTest), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "test": testProviderFuncFixed(pTest), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2713,9 +2817,11 @@ func TestContext2Apply_moduleProviderAlias(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2741,9 +2847,11 @@ func TestContext2Apply_moduleProviderAliasTargets(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"no.thing"}, }) @@ -2772,9 +2880,11 @@ func TestContext2Apply_moduleProviderCloseNested(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -2834,9 +2944,11 @@ func TestContext2Apply_moduleVarRefExisting(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -2863,9 +2975,11 @@ func TestContext2Apply_moduleVarResourceCount(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "2", }, @@ -2882,9 +2996,11 @@ func TestContext2Apply_moduleVarResourceCount(t *testing.T) { ctx = testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "5", }, @@ -2907,9 +3023,11 @@ func TestContext2Apply_moduleBool(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -2937,9 +3055,11 @@ func TestContext2Apply_moduleTarget(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"module.B"}, }) @@ -2983,10 +3103,12 @@ func TestContext2Apply_multiProvider(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "do": testProviderFuncFixed(pDO), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "do": testProviderFuncFixed(pDO), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3026,10 +3148,12 @@ func TestContext2Apply_multiProviderDestroy(t *testing.T) { { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "vault": testProviderFuncFixed(p2), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "vault": testProviderFuncFixed(p2), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3081,10 +3205,12 @@ func TestContext2Apply_multiProviderDestroy(t *testing.T) { Destroy: true, State: state, Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "vault": testProviderFuncFixed(p2), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "vault": testProviderFuncFixed(p2), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3125,10 +3251,12 @@ func TestContext2Apply_multiProviderDestroyChild(t *testing.T) { { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "vault": testProviderFuncFixed(p2), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "vault": testProviderFuncFixed(p2), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3180,10 +3308,12 @@ func TestContext2Apply_multiProviderDestroyChild(t *testing.T) { Destroy: true, State: state, Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "vault": testProviderFuncFixed(p2), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "vault": testProviderFuncFixed(p2), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3218,9 +3348,11 @@ func TestContext2Apply_multiVar(t *testing.T) { // First, apply with a count of 3 ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "3", }, @@ -3248,9 +3380,11 @@ func TestContext2Apply_multiVar(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, State: state, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "1", }, @@ -3313,9 +3447,11 @@ func TestContext2Apply_multiVarComprehensive(t *testing.T) { // First, apply with a count of 3 ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "test": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "test": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "3", }, @@ -3438,9 +3574,11 @@ func TestContext2Apply_multiVarOrder(t *testing.T) { // First, apply with a count of 3 ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3472,9 +3610,11 @@ func TestContext2Apply_multiVarOrderInterp(t *testing.T) { // First, apply with a count of 3 ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3508,9 +3648,11 @@ func TestContext2Apply_multiVarCountDec(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "2", }, @@ -3569,9 +3711,11 @@ func TestContext2Apply_multiVarCountDec(t *testing.T) { ctx := testContext2(t, &ContextOpts{ State: s, Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "1", }, @@ -3609,9 +3753,11 @@ func TestContext2Apply_multiVarMissingState(t *testing.T) { // First, apply with a count of 3 ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "test": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "test": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3633,9 +3779,11 @@ func TestContext2Apply_nilDiff(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3674,9 +3822,11 @@ func TestContext2Apply_outputDependsOn(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3697,9 +3847,11 @@ func TestContext2Apply_outputDependsOn(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3750,9 +3902,11 @@ func TestContext2Apply_outputOrphan(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -3798,9 +3952,11 @@ func TestContext2Apply_outputOrphanModule(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -3832,10 +3988,12 @@ func TestContext2Apply_providerComputedVar(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "test": testProviderFuncFixed(pTest), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "test": testProviderFuncFixed(pTest), + }, + ), }) p.ConfigureFn = func(c *ResourceConfig) error { @@ -3882,9 +4040,11 @@ func TestContext2Apply_providerConfigureDisabled(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3908,9 +4068,11 @@ func TestContext2Apply_provisionerModule(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -3953,9 +4115,11 @@ func TestContext2Apply_Provisioner_compute(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4001,9 +4165,11 @@ func TestContext2Apply_provisionerCreateFail(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4040,9 +4206,11 @@ func TestContext2Apply_provisionerCreateFailNoId(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4077,9 +4245,11 @@ func TestContext2Apply_provisionerFail(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4134,9 +4304,11 @@ func TestContext2Apply_provisionerFail_createBeforeDestroy(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4182,9 +4354,11 @@ func TestContext2Apply_error_createBeforeDestroy(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) { @@ -4231,9 +4405,11 @@ func TestContext2Apply_errorDestroy_createBeforeDestroy(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) p.ApplyFn = func(info *InstanceInfo, is *InstanceState, id *InstanceDiff) (*InstanceState, error) { @@ -4286,9 +4462,9 @@ func TestContext2Apply_multiDepose_createBeforeDestroy(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ - Module: m, - Providers: ps, - State: state, + Module: m, + ProviderResolver: ResourceProviderResolverFixed(ps), + State: state, }) createdInstanceId := "bar" // Create works @@ -4326,9 +4502,9 @@ aws_instance.web: (1 deposed) createdInstanceId = "baz" ctx = testContext2(t, &ContextOpts{ - Module: m, - Providers: ps, - State: state, + Module: m, + ProviderResolver: ResourceProviderResolverFixed(ps), + State: state, }) if _, err := ctx.Plan(); err != nil { @@ -4411,9 +4587,11 @@ func TestContext2Apply_provisionerFailContinue(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4457,9 +4635,11 @@ func TestContext2Apply_provisionerFailContinueHook(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4516,9 +4696,11 @@ func TestContext2Apply_provisionerDestroy(t *testing.T) { Module: m, State: state, Destroy: true, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4572,9 +4754,11 @@ func TestContext2Apply_provisionerDestroyFail(t *testing.T) { Module: m, State: state, Destroy: true, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4643,9 +4827,11 @@ func TestContext2Apply_provisionerDestroyFailContinue(t *testing.T) { Module: m, State: state, Destroy: true, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4717,9 +4903,11 @@ func TestContext2Apply_provisionerDestroyFailContinueFail(t *testing.T) { Module: m, State: state, Destroy: true, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4794,9 +4982,11 @@ func TestContext2Apply_provisionerDestroyTainted(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, State: state, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4863,9 +5053,11 @@ func TestContext2Apply_provisionerDestroyModule(t *testing.T) { Module: m, State: state, Destroy: true, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4935,9 +5127,11 @@ func TestContext2Apply_provisionerDestroyRef(t *testing.T) { Module: m, State: state, Destroy: true, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -4998,9 +5192,11 @@ func TestContext2Apply_provisionerDestroyRefInvalid(t *testing.T) { Module: m, State: state, Destroy: true, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5032,9 +5228,11 @@ func TestContext2Apply_provisionerResourceRef(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5078,9 +5276,11 @@ func TestContext2Apply_provisionerSelfRef(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5131,9 +5331,11 @@ func TestContext2Apply_provisionerMultiSelfRef(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5191,9 +5393,11 @@ func TestContext2Apply_provisionerMultiSelfRefSingle(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5251,9 +5455,11 @@ func TestContext2Apply_provisionerMultiSelfRefCount(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5299,9 +5505,11 @@ func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) { { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5328,9 +5536,11 @@ func TestContext2Apply_provisionerExplicitSelfRef(t *testing.T) { Module: m, Destroy: true, State: state, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5362,9 +5572,11 @@ func TestContext2Apply_Provisioner_Diff(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5398,9 +5610,11 @@ func TestContext2Apply_Provisioner_Diff(t *testing.T) { // Re-create context with state ctx = testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5447,9 +5661,11 @@ func TestContext2Apply_outputDiffVars(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -5529,9 +5745,11 @@ func TestContext2Apply_Provisioner_ConnInfo(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -5571,9 +5789,11 @@ func TestContext2Apply_destroyX(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -5593,9 +5813,11 @@ func TestContext2Apply_destroyX(t *testing.T) { State: state, Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -5631,9 +5853,11 @@ func TestContext2Apply_destroyOrder(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -5654,9 +5878,11 @@ func TestContext2Apply_destroyOrder(t *testing.T) { State: state, Module: module.NewEmptyTree(), Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -5693,9 +5919,11 @@ func TestContext2Apply_destroyModulePrefix(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -5720,9 +5948,11 @@ func TestContext2Apply_destroyModulePrefix(t *testing.T) { State: state, Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -5764,9 +5994,11 @@ func TestContext2Apply_destroyNestedModule(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -5812,9 +6044,11 @@ func TestContext2Apply_destroyDeeplyNestedModule(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -5851,9 +6085,11 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) { { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -5880,9 +6116,11 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) { Module: m, State: state, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "key_name": "foobarkey", }, @@ -5907,9 +6145,11 @@ func TestContext2Apply_destroyModuleWithAttrsReferencingResource(t *testing.T) { } ctx, err = planFromFile.Context(&ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if err != nil { t.Fatalf("err: %s", err) @@ -5946,9 +6186,11 @@ func TestContext2Apply_destroyWithModuleVariableAndCount(t *testing.T) { { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -5971,9 +6213,11 @@ func TestContext2Apply_destroyWithModuleVariableAndCount(t *testing.T) { Module: m, State: state, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -5993,9 +6237,11 @@ func TestContext2Apply_destroyWithModuleVariableAndCount(t *testing.T) { } ctx, err = planFromFile.Context(&ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if err != nil { t.Fatalf("err: %s", err) @@ -6030,9 +6276,11 @@ func TestContext2Apply_destroyTargetWithModuleVariableAndCount(t *testing.T) { { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -6051,9 +6299,11 @@ func TestContext2Apply_destroyTargetWithModuleVariableAndCount(t *testing.T) { Destroy: true, Module: m, State: state, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"module.child"}, }) @@ -6092,9 +6342,11 @@ func TestContext2Apply_destroyWithModuleVariableAndCountNested(t *testing.T) { { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -6117,9 +6369,11 @@ func TestContext2Apply_destroyWithModuleVariableAndCountNested(t *testing.T) { Module: m, State: state, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -6139,9 +6393,11 @@ func TestContext2Apply_destroyWithModuleVariableAndCountNested(t *testing.T) { } ctx, err = planFromFile.Context(&ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if err != nil { t.Fatalf("err: %s", err) @@ -6176,9 +6432,11 @@ func TestContext2Apply_destroyOutputs(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -6199,9 +6457,11 @@ func TestContext2Apply_destroyOutputs(t *testing.T) { State: state, Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -6239,9 +6499,11 @@ func TestContext2Apply_destroyOrphan(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -6314,9 +6576,11 @@ func TestContext2Apply_destroyTaintedProvisioner(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -6351,9 +6615,11 @@ func TestContext2Apply_error(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ApplyFn = func(*InstanceInfo, *InstanceState, *InstanceDiff) (*InstanceState, error) { @@ -6420,9 +6686,11 @@ func TestContext2Apply_errorPartial(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -6479,9 +6747,11 @@ func TestContext2Apply_hook(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -6530,9 +6800,11 @@ func TestContext2Apply_hookOrphan(t *testing.T) { Module: m, State: state, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -6559,9 +6831,11 @@ func TestContext2Apply_idAttr(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ApplyFn = func(info *InstanceInfo, s *InstanceState, d *InstanceDiff) (*InstanceState, error) { @@ -6612,9 +6886,11 @@ func TestContext2Apply_outputBasic(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -6640,9 +6916,11 @@ func TestContext2Apply_outputInvalid(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -6661,9 +6939,11 @@ func TestContext2Apply_outputAdd(t *testing.T) { p1.DiffFn = testDiffFn ctx1 := testContext2(t, &ContextOpts{ Module: m1, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p1), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p1), + }, + ), }) if _, err := ctx1.Plan(); err != nil { @@ -6681,9 +6961,11 @@ func TestContext2Apply_outputAdd(t *testing.T) { p2.DiffFn = testDiffFn ctx2 := testContext2(t, &ContextOpts{ Module: m2, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p2), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p2), + }, + ), State: state1, }) @@ -6710,9 +6992,11 @@ func TestContext2Apply_outputList(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -6738,9 +7022,11 @@ func TestContext2Apply_outputMulti(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -6766,9 +7052,11 @@ func TestContext2Apply_outputMultiIndex(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -6829,9 +7117,11 @@ func TestContext2Apply_taintX(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -6895,9 +7185,11 @@ func TestContext2Apply_taintDep(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -6957,9 +7249,11 @@ func TestContext2Apply_taintDepRequiresNew(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -6988,9 +7282,11 @@ func TestContext2Apply_targeted(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"aws_instance.foo"}, }) @@ -7023,9 +7319,11 @@ func TestContext2Apply_targetedCount(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"aws_instance.foo"}, }) @@ -7055,9 +7353,11 @@ func TestContext2Apply_targetedCountIndex(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"aws_instance.foo[1]"}, }) @@ -7083,9 +7383,11 @@ func TestContext2Apply_targetedDestroy(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -7128,9 +7430,11 @@ func TestContext2Apply_targetedDestroyCountDeps(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -7166,9 +7470,11 @@ func TestContext2Apply_targetedDestroyModule(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -7219,9 +7525,11 @@ func TestContext2Apply_targetedDestroyCountIndex(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -7272,9 +7580,11 @@ func TestContext2Apply_targetedModule(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"module.child"}, }) @@ -7317,9 +7627,11 @@ func TestContext2Apply_targetedModuleDep(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"aws_instance.foo"}, }) @@ -7362,9 +7674,11 @@ func TestContext2Apply_targetedModuleUnrelatedOutputs(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"module.child2"}, State: &State{ Modules: []*ModuleState{ @@ -7432,9 +7746,11 @@ func TestContext2Apply_targetedModuleResource(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"module.child.aws_instance.foo"}, }) @@ -7469,9 +7785,11 @@ func TestContext2Apply_unknownAttribute(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -7497,9 +7815,11 @@ func TestContext2Apply_unknownAttributeInterpolate(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err == nil { @@ -7514,9 +7834,11 @@ func TestContext2Apply_vars(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "us-west-2", "test_list": []interface{}{"Hello", "World"}, @@ -7569,9 +7891,11 @@ func TestContext2Apply_varsEnv(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := ctx.Validate() @@ -7634,9 +7958,11 @@ func TestContext2Apply_createBefore_depends(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -7745,9 +8071,11 @@ func TestContext2Apply_singleDestroy(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -7779,9 +8107,11 @@ func TestContext2Apply_issue7824(t *testing.T) { // Apply cleanly step 0 ctx := testContext2(t, &ContextOpts{ Module: testModule(t, "issue-7824"), - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -7801,9 +8131,11 @@ func TestContext2Apply_issue7824(t *testing.T) { } ctx, err = planFromFile.Context(&ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), }) if err != nil { t.Fatalf("err: %s", err) @@ -7830,9 +8162,11 @@ func TestContext2Apply_issue5254(t *testing.T) { // Apply cleanly step 0 ctx := testContext2(t, &ContextOpts{ Module: testModule(t, "issue-5254/step-0"), - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -7849,9 +8183,11 @@ func TestContext2Apply_issue5254(t *testing.T) { ctx = testContext2(t, &ContextOpts{ Module: testModule(t, "issue-5254/step-1"), State: state, - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), }) plan, err = ctx.Plan() @@ -7871,9 +8207,11 @@ func TestContext2Apply_issue5254(t *testing.T) { } ctx, err = planFromFile.Context(&ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), }) if err != nil { t.Fatalf("err: %s", err) @@ -7909,9 +8247,11 @@ func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { p.ApplyFn = testApplyFn ctx := testContext2(t, &ContextOpts{ Module: testModule(t, "apply-tainted-targets"), - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"aws_instance.iambeingadded"}, State: &State{ Modules: []*ModuleState{ @@ -7949,9 +8289,11 @@ func TestContext2Apply_targetedWithTaintedInState(t *testing.T) { ctx, err = planFromFile.Context(&ContextOpts{ Module: testModule(t, "apply-tainted-targets"), - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if err != nil { t.Fatalf("err: %s", err) @@ -7983,9 +8325,11 @@ func TestContext2Apply_ignoreChangesCreate(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if p, err := ctx.Plan(); err != nil { @@ -8092,9 +8436,11 @@ func TestContext2Apply_ignoreChangesWithDep(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -8123,9 +8469,11 @@ func TestContext2Apply_ignoreChangesWildcard(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if p, err := ctx.Plan(); err != nil { @@ -8169,9 +8517,11 @@ func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testin { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) // First plan and apply a create operation @@ -8190,9 +8540,11 @@ func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testin Destroy: true, Module: m, State: state, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -8211,9 +8563,11 @@ func TestContext2Apply_destroyNestedModuleWithAttrsReferencingResource(t *testin } ctx, err = planFromFile.Context(&ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) if err != nil { t.Fatalf("err: %s", err) @@ -8247,9 +8601,11 @@ func TestContext2Apply_dataDependsOn(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) // the "provisioner" here writes to this variable, because the intent is to @@ -8310,9 +8666,11 @@ func TestContext2Apply_terraformEnv(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Meta: &ContextMeta{Env: "foo"}, Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -8340,9 +8698,11 @@ func TestContext2Apply_multiRef(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { diff --git a/terraform/context_import_test.go b/terraform/context_import_test.go index 3d87622add..ed05b947b0 100644 --- a/terraform/context_import_test.go +++ b/terraform/context_import_test.go @@ -9,9 +9,11 @@ import ( func TestContextImport_basic(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -42,9 +44,11 @@ func TestContextImport_basic(t *testing.T) { func TestContextImport_countIndex(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -76,9 +80,11 @@ func TestContextImport_countIndex(t *testing.T) { func TestContextImport_collision(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ @@ -126,9 +132,11 @@ func TestContextImport_collision(t *testing.T) { func TestContextImport_missingType(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -159,9 +167,11 @@ func TestContextImport_missingType(t *testing.T) { func TestContextImport_moduleProvider(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -212,9 +222,11 @@ func TestContextImport_moduleProvider(t *testing.T) { func TestContextImport_providerInherit(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -261,9 +273,11 @@ func TestContextImport_providerVarConfig(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: testModule(t, "import-provider-vars"), - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "bar", }, @@ -315,9 +329,11 @@ func TestContextImport_providerNonVarConfig(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: testModule(t, "import-provider-non-vars"), - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -343,9 +359,11 @@ func TestContextImport_providerNonVarConfig(t *testing.T) { func TestContextImport_refresh(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -384,9 +402,11 @@ func TestContextImport_refresh(t *testing.T) { func TestContextImport_refreshNil(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -422,9 +442,11 @@ func TestContextImport_refreshNil(t *testing.T) { func TestContextImport_module(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -456,9 +478,11 @@ func TestContextImport_module(t *testing.T) { func TestContextImport_moduleDepth2(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -490,9 +514,11 @@ func TestContextImport_moduleDepth2(t *testing.T) { func TestContextImport_moduleDiff(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ @@ -540,9 +566,11 @@ func TestContextImport_moduleDiff(t *testing.T) { func TestContextImport_moduleExisting(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ @@ -590,9 +618,11 @@ func TestContextImport_moduleExisting(t *testing.T) { func TestContextImport_multiState(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -628,9 +658,11 @@ func TestContextImport_multiState(t *testing.T) { func TestContextImport_multiStateSame(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ @@ -670,9 +702,11 @@ func TestContextImport_multiStateSame(t *testing.T) { func TestContextImport_customProvider(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ImportStateReturn = []*InstanceState{ diff --git a/terraform/context_input_test.go b/terraform/context_input_test.go index 5e3434bd80..928c114772 100644 --- a/terraform/context_input_test.go +++ b/terraform/context_input_test.go @@ -15,9 +15,11 @@ func TestContext2Input(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "us-west-2", "amis": []map[string]interface{}{ @@ -60,9 +62,11 @@ func TestContext2Input_moduleComputedOutputElement(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { @@ -81,9 +85,11 @@ func TestContext2Input_badVarDefault(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.InputFn = func(i UIInput, c *ResourceConfig) (*ResourceConfig, error) { @@ -103,9 +109,11 @@ func TestContext2Input_provider(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) var actual interface{} @@ -145,9 +153,11 @@ func TestContext2Input_providerMulti(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) var actual []interface{} @@ -191,9 +201,11 @@ func TestContext2Input_providerOnce(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) count := 0 @@ -219,9 +231,11 @@ func TestContext2Input_providerId(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), UIInput: input, }) @@ -269,9 +283,11 @@ func TestContext2Input_providerOnly(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "us-west-2", }, @@ -324,9 +340,11 @@ func TestContext2Input_providerVars(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "bar", }, @@ -372,9 +390,11 @@ func TestContext2Input_providerVarsModuleInherit(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), UIInput: input, }) @@ -401,9 +421,11 @@ func TestContext2Input_varOnly(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "us-west-2", }, @@ -456,9 +478,11 @@ func TestContext2Input_varOnlyUnset(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "foovalue", }, @@ -498,9 +522,11 @@ func TestContext2Input_varWithDefault(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{}, UIInput: input, }) @@ -544,9 +570,11 @@ func TestContext2Input_varPartiallyComputed(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "foovalue", }, @@ -607,9 +635,11 @@ func TestContext2Input_interpolateVar(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), UIInput: input, }) @@ -626,9 +656,11 @@ func TestContext2Input_hcl(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "hcl": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "hcl": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{}, UIInput: input, }) @@ -668,9 +700,11 @@ func TestContext2Input_submoduleTriggersInvalidCount(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), UIInput: input, }) diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 69ab385165..7c231063f3 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -17,9 +17,11 @@ func TestContext2Plan_basic(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -64,9 +66,11 @@ func TestContext2Plan_createBefore_deposed(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -98,9 +102,11 @@ func TestContext2Plan_createBefore_maintainRoot(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "in": "a,b,c", }, @@ -141,9 +147,11 @@ func TestContext2Plan_emptyDiff(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -164,9 +172,11 @@ func TestContext2Plan_escapedVar(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -187,9 +197,11 @@ func TestContext2Plan_minimal(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -210,9 +222,11 @@ func TestContext2Plan_modules(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -234,9 +248,11 @@ func TestContext2Plan_moduleCycle(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -259,9 +275,11 @@ func TestContext2Plan_moduleDeadlock(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -294,9 +312,11 @@ func TestContext2Plan_moduleInput(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -317,9 +337,11 @@ func TestContext2Plan_moduleInputComputed(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -340,9 +362,11 @@ func TestContext2Plan_moduleInputFromVar(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "52", }, @@ -366,9 +390,11 @@ func TestContext2Plan_moduleMultiVar(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -404,9 +430,11 @@ func TestContext2Plan_moduleOrphans(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -467,9 +495,11 @@ func TestContext2Plan_moduleOrphansWithProvisioner(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -514,33 +544,35 @@ func TestContext2Plan_moduleProviderInherit(t *testing.T) { m := testModule(t, "plan-module-provider-inherit") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": func() (ResourceProvider, error) { - l.Lock() - defer l.Unlock() - - p := testProvider("aws") - p.ConfigureFn = func(c *ResourceConfig) error { - if v, ok := c.Get("from"); !ok || v.(string) != "root" { - return fmt.Errorf("bad") - } - - return nil - } - p.DiffFn = func( - info *InstanceInfo, - state *InstanceState, - c *ResourceConfig) (*InstanceDiff, error) { - v, _ := c.Get("from") - + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { l.Lock() defer l.Unlock() - calls = append(calls, v.(string)) - return testDiffFn(info, state, c) - } - return p, nil + + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + if v, ok := c.Get("from"); !ok || v.(string) != "root" { + return fmt.Errorf("bad") + } + + return nil + } + p.DiffFn = func( + info *InstanceInfo, + state *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + v, _ := c.Get("from") + + l.Lock() + defer l.Unlock() + calls = append(calls, v.(string)) + return testDiffFn(info, state, c) + } + return p, nil + }, }, - }, + ), }) _, err := ctx.Plan() @@ -564,36 +596,38 @@ func TestContext2Plan_moduleProviderInheritDeep(t *testing.T) { m := testModule(t, "plan-module-provider-inherit-deep") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": func() (ResourceProvider, error) { - l.Lock() - defer l.Unlock() + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { + l.Lock() + defer l.Unlock() - var from string - p := testProvider("aws") - p.ConfigureFn = func(c *ResourceConfig) error { - v, ok := c.Get("from") - if !ok || v.(string) != "root" { - return fmt.Errorf("bad") + var from string + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + v, ok := c.Get("from") + if !ok || v.(string) != "root" { + return fmt.Errorf("bad") + } + + from = v.(string) + return nil } - from = v.(string) - return nil - } + p.DiffFn = func( + info *InstanceInfo, + state *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + if from != "root" { + return nil, fmt.Errorf("bad resource") + } - p.DiffFn = func( - info *InstanceInfo, - state *InstanceState, - c *ResourceConfig) (*InstanceDiff, error) { - if from != "root" { - return nil, fmt.Errorf("bad resource") + return testDiffFn(info, state, c) } - - return testDiffFn(info, state, c) - } - return p, nil + return p, nil + }, }, - }, + ), }) _, err := ctx.Plan() @@ -610,36 +644,38 @@ func TestContext2Plan_moduleProviderDefaults(t *testing.T) { m := testModule(t, "plan-module-provider-defaults") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": func() (ResourceProvider, error) { - l.Lock() - defer l.Unlock() - - p := testProvider("aws") - p.ConfigureFn = func(c *ResourceConfig) error { - if v, ok := c.Get("from"); !ok || v.(string) != "root" { - return fmt.Errorf("bad") - } - if v, ok := c.Get("to"); ok && v.(string) == "child" { - toCount++ - } - - return nil - } - p.DiffFn = func( - info *InstanceInfo, - state *InstanceState, - c *ResourceConfig) (*InstanceDiff, error) { - v, _ := c.Get("from") - + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { l.Lock() defer l.Unlock() - calls = append(calls, v.(string)) - return testDiffFn(info, state, c) - } - return p, nil + + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + if v, ok := c.Get("from"); !ok || v.(string) != "root" { + return fmt.Errorf("bad") + } + if v, ok := c.Get("to"); ok && v.(string) == "child" { + toCount++ + } + + return nil + } + p.DiffFn = func( + info *InstanceInfo, + state *InstanceState, + c *ResourceConfig) (*InstanceDiff, error) { + v, _ := c.Get("from") + + l.Lock() + defer l.Unlock() + calls = append(calls, v.(string)) + return testDiffFn(info, state, c) + } + return p, nil + }, }, - }, + ), }) _, err := ctx.Plan() @@ -668,30 +704,32 @@ func TestContext2Plan_moduleProviderDefaultsVar(t *testing.T) { m := testModule(t, "plan-module-provider-defaults-var") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": func() (ResourceProvider, error) { - l.Lock() - defer l.Unlock() - - p := testProvider("aws") - p.ConfigureFn = func(c *ResourceConfig) error { - var buf bytes.Buffer - if v, ok := c.Get("from"); ok { - buf.WriteString(v.(string) + "\n") - } - if v, ok := c.Get("to"); ok { - buf.WriteString(v.(string) + "\n") - } - + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": func() (ResourceProvider, error) { l.Lock() defer l.Unlock() - calls = append(calls, buf.String()) - return nil - } - p.DiffFn = testDiffFn - return p, nil + + p := testProvider("aws") + p.ConfigureFn = func(c *ResourceConfig) error { + var buf bytes.Buffer + if v, ok := c.Get("from"); ok { + buf.WriteString(v.(string) + "\n") + } + if v, ok := c.Get("to"); ok { + buf.WriteString(v.(string) + "\n") + } + + l.Lock() + defer l.Unlock() + calls = append(calls, buf.String()) + return nil + } + p.DiffFn = testDiffFn + return p, nil + }, }, - }, + ), Variables: map[string]interface{}{ "foo": "root", }, @@ -717,9 +755,11 @@ func TestContext2Plan_moduleProviderVar(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -740,9 +780,11 @@ func TestContext2Plan_moduleVar(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -763,9 +805,11 @@ func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -776,13 +820,15 @@ func TestContext2Plan_moduleVarWrongTypeBasic(t *testing.T) { func TestContext2Plan_moduleVarWrongTypeNested(t *testing.T) { m := testModule(t, "plan-module-wrong-var-type-nested") - p := testProvider("aws") + p := testProvider("null") p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -797,9 +843,11 @@ func TestContext2Plan_moduleVarWithDefaultValue(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -814,9 +862,11 @@ func TestContext2Plan_moduleVarComputed(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -837,9 +887,11 @@ func TestContext2Plan_nil(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -872,9 +924,11 @@ func TestContext2Plan_preventDestroy_bad(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -907,9 +961,11 @@ func TestContext2Plan_preventDestroy_good(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -942,9 +998,11 @@ func TestContext2Plan_preventDestroy_countBad(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -983,9 +1041,11 @@ func TestContext2Plan_preventDestroy_countGood(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -1024,9 +1084,11 @@ func TestContext2Plan_preventDestroy_countGoodNoChange(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -1063,9 +1125,11 @@ func TestContext2Plan_preventDestroy_destroyPlan(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -1100,9 +1164,11 @@ func TestContext2Plan_provisionerCycle(t *testing.T) { pr := testProvisioner() ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "local-exec": testProvisionerFuncFixed(pr), }, @@ -1120,9 +1186,11 @@ func TestContext2Plan_computed(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1143,9 +1211,11 @@ func TestContext2Plan_computedDataResource(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1190,9 +1260,11 @@ func TestContext2Plan_computedDataCountResource(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1266,9 +1338,11 @@ func TestContext2Plan_dataSourceTypeMismatch(t *testing.T) { }, }, }, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -1314,9 +1388,11 @@ func TestContext2Plan_dataResourceBecomesComputed(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -1379,9 +1455,11 @@ func TestContext2Plan_computedList(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1404,9 +1482,11 @@ func TestContext2Plan_computedMultiIndex(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1427,9 +1507,11 @@ func TestContext2Plan_count(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1454,9 +1536,11 @@ func TestContext2Plan_countComputed(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -1471,9 +1555,11 @@ func TestContext2Plan_countComputedModule(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -1491,9 +1577,11 @@ func TestContext2Plan_countModuleStatic(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1525,9 +1613,11 @@ func TestContext2Plan_countModuleStaticGrandchild(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1559,9 +1649,11 @@ func TestContext2Plan_countIndex(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1582,9 +1674,11 @@ func TestContext2Plan_countIndexZero(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1605,9 +1699,11 @@ func TestContext2Plan_countVar(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "count": "3", }, @@ -1631,9 +1727,11 @@ func TestContext2Plan_countZero(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1655,9 +1753,11 @@ func TestContext2Plan_countOneIndex(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -1709,9 +1809,11 @@ func TestContext2Plan_countDecreaseToOne(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -1752,9 +1854,11 @@ func TestContext2Plan_countIncreaseFromNotSet(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -1795,9 +1899,11 @@ func TestContext2Plan_countIncreaseFromOne(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -1853,9 +1959,11 @@ func TestContext2Plan_countIncreaseFromOneCorrupted(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -1930,9 +2038,11 @@ func TestContext2Plan_countIncreaseWithSplatReference(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -1999,9 +2109,11 @@ func TestContext2Plan_destroy(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, Destroy: true, }) @@ -2054,9 +2166,11 @@ func TestContext2Plan_moduleDestroy(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, Destroy: true, }) @@ -2106,9 +2220,11 @@ func TestContext2Plan_moduleDestroyCycle(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, Destroy: true, }) @@ -2156,9 +2272,11 @@ func TestContext2Plan_moduleDestroyMultivar(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, Destroy: true, }) @@ -2186,9 +2304,11 @@ func TestContext2Plan_pathVar(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -2221,6 +2341,7 @@ func TestContext2Plan_diffVar(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Attributes: map[string]string{ @@ -2234,9 +2355,11 @@ func TestContext2Plan_diffVar(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2278,9 +2401,11 @@ func TestContext2Plan_hook(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -2317,9 +2442,11 @@ func TestContext2Plan_orphan(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2343,9 +2470,11 @@ func TestContext2Plan_shadowUuid(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -2364,6 +2493,7 @@ func TestContext2Plan_state(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "bar", }, @@ -2374,9 +2504,11 @@ func TestContext2Plan_state(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2425,9 +2557,11 @@ func TestContext2Plan_taint(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2470,9 +2604,11 @@ func TestContext2Apply_taintIgnoreChanges(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2519,9 +2655,11 @@ func TestContext2Plan_taintDestroyInterpolatedCountRace(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -2559,9 +2697,11 @@ func TestContext2Plan_targeted(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"aws_instance.foo"}, }) @@ -2595,9 +2735,11 @@ func TestContext2Plan_targetedCrossModule(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"module.B"}, }) @@ -2634,9 +2776,11 @@ func TestContext2Plan_targetedModuleWithProvider(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), Targets: []string{"module.child2"}, }) @@ -2667,9 +2811,11 @@ func TestContext2Plan_targetedOrphan(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -2724,9 +2870,11 @@ func TestContext2Plan_targetedModuleOrphan(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -2782,9 +2930,11 @@ func TestContext2Plan_targetedModuleUntargetedVariable(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Targets: []string{"aws_instance.blue", "module.blue_mod"}, }) @@ -2833,9 +2983,11 @@ func TestContext2Plan_targetedOverTen(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -2906,9 +3058,11 @@ func TestContext2Plan_provider(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "bar", }, @@ -2928,9 +3082,11 @@ func TestContext2Plan_varListErr(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err := ctx.Plan() @@ -2962,9 +3118,11 @@ func TestContext2Plan_ignoreChanges(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "ami-1234abcd", }, @@ -2997,6 +3155,7 @@ func TestContext2Plan_ignoreChangesWildcard(t *testing.T) { Path: rootModulePath, Resources: map[string]*ResourceState{ "aws_instance.foo": &ResourceState{ + Type: "aws_instance", Primary: &InstanceState{ ID: "bar", Attributes: map[string]string{ @@ -3011,9 +3170,11 @@ func TestContext2Plan_ignoreChangesWildcard(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "ami-1234abcd", "bar": "t2.small", @@ -3066,9 +3227,11 @@ func TestContext2Plan_moduleMapLiteral(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3095,9 +3258,11 @@ func TestContext2Plan_computedValueInMap(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3122,9 +3287,11 @@ func TestContext2Plan_moduleVariableFromSplat(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Plan(); err != nil { @@ -3149,9 +3316,11 @@ func TestContext2Plan_createBeforeDestroy_depends_datasource(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -3187,9 +3356,11 @@ func TestContext2Plan_listOrder(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) plan, err := ctx.Plan() @@ -3239,9 +3410,11 @@ func TestContext2Plan_ignoreChangesWithFlatmaps(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) @@ -3338,9 +3511,11 @@ func TestContext2Plan_resourceNestedCount(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: s, }) diff --git a/terraform/context_refresh_test.go b/terraform/context_refresh_test.go index 0276b05d5b..0a977b4cca 100644 --- a/terraform/context_refresh_test.go +++ b/terraform/context_refresh_test.go @@ -13,9 +13,11 @@ func TestContext2Refresh(t *testing.T) { m := testModule(t, "refresh-basic") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -65,9 +67,11 @@ func TestContext2Refresh_dataComputedModuleVar(t *testing.T) { m := testModule(t, "refresh-data-module-var") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.RefreshFn = nil @@ -91,9 +95,11 @@ func TestContext2Refresh_targeted(t *testing.T) { m := testModule(t, "refresh-targeted") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -132,9 +138,11 @@ func TestContext2Refresh_targetedCount(t *testing.T) { m := testModule(t, "refresh-targeted-count") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -183,9 +191,11 @@ func TestContext2Refresh_targetedCountIndex(t *testing.T) { m := testModule(t, "refresh-targeted-count") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -226,9 +236,11 @@ func TestContext2Refresh_moduleComputedVar(t *testing.T) { m := testModule(t, "refresh-module-computed-var") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) // This was failing (see GH-2188) at some point, so this test just @@ -243,9 +255,11 @@ func TestContext2Refresh_delete(t *testing.T) { m := testModule(t, "refresh-basic") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -282,9 +296,11 @@ func TestContext2Refresh_ignoreUncreated(t *testing.T) { m := testModule(t, "refresh-basic") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: nil, }) @@ -309,9 +325,11 @@ func TestContext2Refresh_hook(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, Hooks: []Hook{h}, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -373,9 +391,11 @@ func TestContext2Refresh_modules(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -406,9 +426,11 @@ func TestContext2Refresh_moduleInputComputedOutput(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Refresh(); err != nil { @@ -422,9 +444,11 @@ func TestContext2Refresh_moduleVarModule(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) if _, err := ctx.Refresh(); err != nil { @@ -438,9 +462,11 @@ func TestContext2Refresh_noState(t *testing.T) { m := testModule(t, "refresh-no-state") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.RefreshFn = nil @@ -458,9 +484,11 @@ func TestContext2Refresh_output(t *testing.T) { m := testModule(t, "refresh-output") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -510,9 +538,11 @@ func TestContext2Refresh_outputPartial(t *testing.T) { m := testModule(t, "refresh-output-partial") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -566,9 +596,11 @@ func TestContext2Refresh_stateBasic(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -615,9 +647,11 @@ func TestContext2Refresh_dataOrphan(t *testing.T) { }, } ctx := testContext2(t, &ContextOpts{ - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -645,9 +679,11 @@ func TestContext2Refresh_dataState(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -715,9 +751,11 @@ func TestContext2Refresh_dataStateRefData(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "null": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "null": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -757,9 +795,11 @@ func TestContext2Refresh_tainted(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -793,9 +833,13 @@ func TestContext2Refresh_unknownProvider(t *testing.T) { p := testProvider("aws") p.ApplyFn = testApplyFn p.DiffFn = testDiffFn - ctx := testContext2(t, &ContextOpts{ - Module: m, - Providers: map[string]ResourceProviderFactory{}, + + _, err := NewContext(&ContextOpts{ + Module: m, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{}, + ), + Shadow: true, State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -813,8 +857,12 @@ func TestContext2Refresh_unknownProvider(t *testing.T) { }, }) - if _, err := ctx.Refresh(); err == nil { - t.Fatal("should error") + if err == nil { + t.Fatal("successfully created context; want error") + } + + if !strings.Contains(err.Error(), "Can't satisfy provider requirements") { + t.Fatalf("wrong error: %s", err) } } @@ -823,9 +871,11 @@ func TestContext2Refresh_vars(t *testing.T) { m := testModule(t, "refresh-vars") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ @@ -957,9 +1007,11 @@ func TestContext2Refresh_orphanModule(t *testing.T) { } ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -982,9 +1034,11 @@ func TestContext2Validate(t *testing.T) { m := testModule(t, "validate-good") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() diff --git a/terraform/context_validate_test.go b/terraform/context_validate_test.go index e2442e8234..4e9ab910d2 100644 --- a/terraform/context_validate_test.go +++ b/terraform/context_validate_test.go @@ -11,9 +11,11 @@ func TestContext2Validate_badCount(t *testing.T) { m := testModule(t, "validate-bad-count") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -30,9 +32,11 @@ func TestContext2Validate_badVar(t *testing.T) { m := testModule(t, "validate-bad-var") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -49,9 +53,11 @@ func TestContext2Validate_varMapOverrideOld(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo.foo": "bar", }, @@ -86,10 +92,12 @@ func TestContext2Validate_computedVar(t *testing.T) { m := testModule(t, "validate-computed-var") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - "test": testProviderFuncFixed(testProvider("test")), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + "test": testProviderFuncFixed(testProvider("test")), + }, + ), }) p.ValidateFn = func(c *ResourceConfig) ([]string, []error) { @@ -123,9 +131,11 @@ func TestContext2Validate_countComputed(t *testing.T) { m := testModule(t, "validate-count-computed") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -142,9 +152,11 @@ func TestContext2Validate_countNegative(t *testing.T) { m := testModule(t, "validate-count-negative") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -161,9 +173,11 @@ func TestContext2Validate_countVariable(t *testing.T) { m := testModule(t, "apply-count-variable") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -180,9 +194,11 @@ func TestContext2Validate_countVariableNoDefault(t *testing.T) { m := testModule(t, "validate-count-variable") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -201,9 +217,11 @@ func TestContext2Validate_cycle(t *testing.T) { m := testModule(t, "validate-cycle") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -221,9 +239,11 @@ func TestContext2Validate_moduleBadOutput(t *testing.T) { m := testModule(t, "validate-bad-module-output") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -240,9 +260,11 @@ func TestContext2Validate_moduleGood(t *testing.T) { m := testModule(t, "validate-good-module") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -259,9 +281,11 @@ func TestContext2Validate_moduleBadResource(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")} @@ -280,9 +304,11 @@ func TestContext2Validate_moduleDepsShouldNotCycle(t *testing.T) { p := testProvider("aws") ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := ctx.Validate() @@ -300,9 +326,11 @@ func TestContext2Validate_moduleProviderInherit(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ValidateFn = func(c *ResourceConfig) ([]string, []error) { @@ -323,9 +351,11 @@ func TestContext2Validate_moduleProviderInheritOrphan(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: &State{ Modules: []*ModuleState{ &ModuleState{ @@ -369,9 +399,11 @@ func TestContext2Validate_moduleProviderVar(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "provider_var": "bar", }, @@ -395,9 +427,11 @@ func TestContext2Validate_moduleProviderInheritUnused(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ValidateFn = func(c *ResourceConfig) ([]string, []error) { @@ -433,9 +467,11 @@ func TestContext2Validate_orphans(t *testing.T) { } c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -458,9 +494,11 @@ func TestContext2Validate_providerConfig_bad(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ValidateReturnErrors = []error{fmt.Errorf("bad")} @@ -482,9 +520,11 @@ func TestContext2Validate_providerConfig_badEmpty(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ValidateReturnErrors = []error{fmt.Errorf("bad")} @@ -503,9 +543,11 @@ func TestContext2Validate_providerConfig_good(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -523,9 +565,11 @@ func TestContext2Validate_provisionerConfig_bad(t *testing.T) { pr := testProvisioner() c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -554,9 +598,11 @@ func TestContext2Validate_provisionerConfig_good(t *testing.T) { } c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -576,9 +622,11 @@ func TestContext2Validate_requiredVar(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -595,9 +643,11 @@ func TestContext2Validate_resourceConfig_bad(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) p.ValidateResourceReturnErrors = []error{fmt.Errorf("bad")} @@ -616,9 +666,11 @@ func TestContext2Validate_resourceConfig_good(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -635,9 +687,11 @@ func TestContext2Validate_resourceNameSymbol(t *testing.T) { m := testModule(t, "validate-resource-name-symbol") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -654,9 +708,11 @@ func TestContext2Validate_selfRef(t *testing.T) { m := testModule(t, "validate-self-ref") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -673,9 +729,11 @@ func TestContext2Validate_selfRefMulti(t *testing.T) { m := testModule(t, "validate-self-ref-multi") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -692,9 +750,11 @@ func TestContext2Validate_selfRefMultiAll(t *testing.T) { m := testModule(t, "validate-self-ref-multi-all") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) w, e := c.Validate() @@ -727,9 +787,11 @@ func TestContext2Validate_tainted(t *testing.T) { } c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), State: state, }) @@ -755,9 +817,11 @@ func TestContext2Validate_targetedDestroy(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Provisioners: map[string]ResourceProvisionerFactory{ "shell": testProvisionerFuncFixed(pr), }, @@ -794,9 +858,11 @@ func TestContext2Validate_varRefFilled(t *testing.T) { p := testProvider("aws") c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "bar", }, @@ -826,9 +892,11 @@ func TestContext2Validate_interpolateVar(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), UIInput: input, }) @@ -853,9 +921,11 @@ func TestContext2Validate_interpolateComputedModuleVarDef(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), UIInput: input, }) @@ -879,9 +949,11 @@ func TestContext2Validate_interpolateMap(t *testing.T) { ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "template": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "template": testProviderFuncFixed(p), + }, + ), UIInput: input, }) @@ -902,9 +974,11 @@ func TestContext2Validate_PlanGraphBuilder(t *testing.T) { p.DiffFn = testDiffFn c := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), Variables: map[string]interface{}{ "foo": "us-west-2", "test_list": []interface{}{"Hello", "World"}, diff --git a/terraform/debug_test.go b/terraform/debug_test.go index b80111fc71..6e75116cd2 100644 --- a/terraform/debug_test.go +++ b/terraform/debug_test.go @@ -98,9 +98,11 @@ func TestDebug_plan(t *testing.T) { p.DiffFn = testDiffFn ctx := testContext2(t, &ContextOpts{ Module: m, - Providers: map[string]ResourceProviderFactory{ - "aws": testProviderFuncFixed(p), - }, + ProviderResolver: ResourceProviderResolverFixed( + map[string]ResourceProviderFactory{ + "aws": testProviderFuncFixed(p), + }, + ), }) _, err = ctx.Plan() From 4ab8973520465c2bbc7f7b7d6232d48dadcce5f4 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 21 Apr 2017 17:19:37 -0700 Subject: [PATCH 25/82] core: provide config to all import context tests We're going to use config to determine provider dependencies, so we need to always provide a config when instantiating a context or we'll end up loading no providers at all. We previously had a test for running "terraform import -config=''" to disable the config entirely, but this test is now removed because it makes no sense. The actual functionality its testing still remains for now, but it will be removed in a subsequent commit when we start requiring that a resource to be imported must already exist in configuration. --- command/import_test.go | 61 ++----------------- .../import-provider-aliased/main.tf | 5 ++ .../import-provider-implicit/main.tf | 4 ++ terraform/context_import_test.go | 40 ++++++++++-- 4 files changed, 47 insertions(+), 63 deletions(-) create mode 100644 command/test-fixtures/import-provider-aliased/main.tf create mode 100644 command/test-fixtures/import-provider-implicit/main.tf diff --git a/command/import_test.go b/command/import_test.go index 5b8838ccb7..1be2563bd9 100644 --- a/command/import_test.go +++ b/command/import_test.go @@ -9,6 +9,8 @@ import ( ) func TestImport(t *testing.T) { + defer testChdir(t, testFixturePath("import-provider-implicit"))() + statePath := testTempFile(t) p := testProvider() @@ -102,63 +104,6 @@ func TestImport_providerConfig(t *testing.T) { testStateOutput(t, statePath, testImportStr) } -func TestImport_providerConfigDisable(t *testing.T) { - defer testChdir(t, testFixturePath("import-provider"))() - - statePath := testTempFile(t) - - p := testProvider() - ui := new(cli.MockUi) - c := &ImportCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - p.ImportStateFn = nil - p.ImportStateReturn = []*terraform.InstanceState{ - &terraform.InstanceState{ - ID: "yay", - Ephemeral: terraform.EphemeralState{ - Type: "test_instance", - }, - }, - } - - configured := false - p.ConfigureFn = func(c *terraform.ResourceConfig) error { - configured = true - - if v, ok := c.Get("foo"); ok { - return fmt.Errorf("bad value: %#v", v) - } - - return nil - } - - args := []string{ - "-state", statePath, - "-config", "", - "test_instance.foo", - "bar", - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - // Verify that we were called - if !configured { - t.Fatal("Configure should be called") - } - - if !p.ImportStateCalled { - t.Fatal("ImportState should be called") - } - - testStateOutput(t, statePath, testImportStr) -} - func TestImport_providerConfigWithVar(t *testing.T) { defer testChdir(t, testFixturePath("import-provider-var"))() @@ -1015,6 +960,8 @@ func TestRefresh_displaysOutputs(t *testing.T) { */ func TestImport_customProvider(t *testing.T) { + defer testChdir(t, testFixturePath("import-provider-aliased"))() + statePath := testTempFile(t) p := testProvider() diff --git a/command/test-fixtures/import-provider-aliased/main.tf b/command/test-fixtures/import-provider-aliased/main.tf new file mode 100644 index 0000000000..92f563ae8f --- /dev/null +++ b/command/test-fixtures/import-provider-aliased/main.tf @@ -0,0 +1,5 @@ +provider "test" { + foo = "bar" + + alias = "alias" +} diff --git a/command/test-fixtures/import-provider-implicit/main.tf b/command/test-fixtures/import-provider-implicit/main.tf new file mode 100644 index 0000000000..02ffc5bc77 --- /dev/null +++ b/command/test-fixtures/import-provider-implicit/main.tf @@ -0,0 +1,4 @@ +# Declaring this resource implies that we depend on the +# "test" provider, making it available for import. +resource "test_instance" "foo" { +} diff --git a/terraform/context_import_test.go b/terraform/context_import_test.go index ed05b947b0..1316b5c0b5 100644 --- a/terraform/context_import_test.go +++ b/terraform/context_import_test.go @@ -8,7 +8,9 @@ import ( func TestContextImport_basic(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -43,7 +45,9 @@ func TestContextImport_basic(t *testing.T) { func TestContextImport_countIndex(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -79,7 +83,9 @@ func TestContextImport_countIndex(t *testing.T) { func TestContextImport_collision(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -131,7 +137,9 @@ func TestContextImport_collision(t *testing.T) { func TestContextImport_missingType(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -166,7 +174,9 @@ func TestContextImport_missingType(t *testing.T) { func TestContextImport_moduleProvider(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -192,8 +202,6 @@ func TestContextImport_moduleProvider(t *testing.T) { return nil } - m := testModule(t, "import-provider") - state, err := ctx.Import(&ImportOpts{ Module: m, Targets: []*ImportTarget{ @@ -221,7 +229,9 @@ func TestContextImport_moduleProvider(t *testing.T) { // Test that import sets up the graph properly for provider inheritance func TestContextImport_providerInherit(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider-inherit") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -247,8 +257,6 @@ func TestContextImport_providerInherit(t *testing.T) { return nil } - m := testModule(t, "import-provider-inherit") - _, err := ctx.Import(&ImportOpts{ Module: m, Targets: []*ImportTarget{ @@ -271,8 +279,9 @@ func TestContextImport_providerInherit(t *testing.T) { // that configuration for import. func TestContextImport_providerVarConfig(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider-vars") ctx := testContext2(t, &ContextOpts{ - Module: testModule(t, "import-provider-vars"), + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -327,8 +336,9 @@ func TestContextImport_providerVarConfig(t *testing.T) { // Test that provider configs can't reference resources. func TestContextImport_providerNonVarConfig(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider-non-vars") ctx := testContext2(t, &ContextOpts{ - Module: testModule(t, "import-provider-non-vars"), + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -358,7 +368,9 @@ func TestContextImport_providerNonVarConfig(t *testing.T) { func TestContextImport_refresh(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -401,7 +413,9 @@ func TestContextImport_refresh(t *testing.T) { func TestContextImport_refreshNil(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -441,7 +455,9 @@ func TestContextImport_refreshNil(t *testing.T) { func TestContextImport_module(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -477,7 +493,9 @@ func TestContextImport_module(t *testing.T) { func TestContextImport_moduleDepth2(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -513,7 +531,9 @@ func TestContextImport_moduleDepth2(t *testing.T) { func TestContextImport_moduleDiff(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -565,7 +585,9 @@ func TestContextImport_moduleDiff(t *testing.T) { func TestContextImport_moduleExisting(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -617,7 +639,9 @@ func TestContextImport_moduleExisting(t *testing.T) { func TestContextImport_multiState(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -657,7 +681,9 @@ func TestContextImport_multiState(t *testing.T) { func TestContextImport_multiStateSame(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), @@ -701,7 +727,9 @@ func TestContextImport_multiStateSame(t *testing.T) { func TestContextImport_customProvider(t *testing.T) { p := testProvider("aws") + m := testModule(t, "import-provider") ctx := testContext2(t, &ContextOpts{ + Module: m, ProviderResolver: ResourceProviderResolverFixed( map[string]ResourceProviderFactory{ "aws": testProviderFuncFixed(p), From ccb3a7c5847a2bfbf8505431baa2d732867873ef Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 25 Apr 2017 16:49:49 -0700 Subject: [PATCH 26/82] core: expose terraform.ModuleTreeDependencies as a public function This is a generally-useful utility for computing dependency trees, so no reason to restrict it to just the terraform package. --- terraform/context.go | 2 +- terraform/module_dependencies.go | 4 ++-- terraform/module_dependencies_test.go | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index e344b306d6..e5d4cc1a6f 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -178,7 +178,7 @@ func NewContext(opts *ContextOpts) (*Context, error) { var providers map[string]ResourceProviderFactory if opts.ProviderResolver != nil { var err error - deps := moduleTreeDependencies(opts.Module, state) + deps := ModuleTreeDependencies(opts.Module, state) reqd := deps.AllPluginRequirements() providers, err = resourceProviderFactories(opts.ProviderResolver, reqd) if err != nil { diff --git a/terraform/module_dependencies.go b/terraform/module_dependencies.go index 79cfcb822a..ffc6920c86 100644 --- a/terraform/module_dependencies.go +++ b/terraform/module_dependencies.go @@ -7,7 +7,7 @@ import ( "github.com/hashicorp/terraform/plugin/discovery" ) -// moduleTreeDependencies returns the dependencies of the tree of modules +// ModuleTreeDependencies returns the dependencies of the tree of modules // described by the given configuration tree and state. // // Both configuration and state are required because there can be resources @@ -16,7 +16,7 @@ import ( // This function will panic if any invalid version constraint strings are // present in the configuration. This is guaranteed not to happen for any // configuration that has passed a call to Config.Validate(). -func moduleTreeDependencies(root *module.Tree, state *State) *moduledeps.Module { +func ModuleTreeDependencies(root *module.Tree, state *State) *moduledeps.Module { // First we walk the configuration tree to build the overall structure // and capture the explicit/implicit/inherited provider dependencies. diff --git a/terraform/module_dependencies_test.go b/terraform/module_dependencies_test.go index b17c42daa2..6fced8a80d 100644 --- a/terraform/module_dependencies_test.go +++ b/terraform/module_dependencies_test.go @@ -248,7 +248,7 @@ func TestModuleTreeDependencies(t *testing.T) { root = testModule(t, test.ConfigDir) } - got := moduleTreeDependencies(root, test.State) + got := ModuleTreeDependencies(root, test.State) if !got.Equal(test.Want) { t.Errorf( "wrong dependency tree\ngot: %s\nwant: %s", From 8b037432e70d94ebbb3e152d4be75e3c951f013f Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 25 Apr 2017 18:05:53 -0700 Subject: [PATCH 27/82] vendoring of treeprint library --- vendor/github.com/xlab/treeprint/LICENSE | 20 ++ vendor/github.com/xlab/treeprint/README.md | 126 +++++++ vendor/github.com/xlab/treeprint/helpers.go | 47 +++ vendor/github.com/xlab/treeprint/struct.go | 340 ++++++++++++++++++ vendor/github.com/xlab/treeprint/treeprint.go | 184 ++++++++++ vendor/vendor.json | 6 + 6 files changed, 723 insertions(+) create mode 100644 vendor/github.com/xlab/treeprint/LICENSE create mode 100644 vendor/github.com/xlab/treeprint/README.md create mode 100644 vendor/github.com/xlab/treeprint/helpers.go create mode 100644 vendor/github.com/xlab/treeprint/struct.go create mode 100644 vendor/github.com/xlab/treeprint/treeprint.go diff --git a/vendor/github.com/xlab/treeprint/LICENSE b/vendor/github.com/xlab/treeprint/LICENSE new file mode 100644 index 0000000000..5ab533ad2f --- /dev/null +++ b/vendor/github.com/xlab/treeprint/LICENSE @@ -0,0 +1,20 @@ +The MIT License (MIT) +Copyright © 2016 Maxim Kupriianov + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the “Software”), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/vendor/github.com/xlab/treeprint/README.md b/vendor/github.com/xlab/treeprint/README.md new file mode 100644 index 0000000000..8d67775b84 --- /dev/null +++ b/vendor/github.com/xlab/treeprint/README.md @@ -0,0 +1,126 @@ +treeprint [![GoDoc](https://godoc.org/github.com/xlab/treeprint?status.svg)](https://godoc.org/github.com/xlab/treeprint) ![test coverage](https://img.shields.io/badge/coverage-68.6%25-green.svg) +========= + +Package `treeprint` provides a simple ASCII tree composing tool. + +SYSTEME FIGURE + +If you are familiar with the [tree](http://mama.indstate.edu/users/ice/tree/) utility that is a recursive directory listing command that produces a depth indented listing of files, then you have the idea of what it would look like. + +On my system the command yields the following + +``` + $ tree +. +├── LICENSE +├── README.md +├── treeprint.go +└── treeprint_test.go + +0 directories, 4 files +``` + +and I'd like to have the same format for my Go data structures when I print them. + +## Installation + +``` +$ go get github.com/xlab/treeprint +``` + +## Concept of work + +The general idea is that you initialise a new tree with `treeprint.New()` and then add nodes and +branches into it. Use `AddNode()` when you want add a node on the same level as the target or +use `AddBranch()` when you want to go a level deeper. So `tree.AddBranch().AddNode().AddNode()` would +create a new level with two distinct nodes on it. So `tree.AddNode().AddNode()` is a flat thing and +`tree.AddBranch().AddBranch().AddBranch()` is a high thing. Use `String()` or `Bytes()` on a branch +to render a subtree, or use it on the root to print the whole tree. + +## Use cases + +When you want to render a complex data structure: + +```go +func main() { + tree := treeprint.New() + + // create a new branch in the root + one := tree.AddBranch("one") + + // add some nodes + one.AddNode("subnode1").AddNode("subnode2") + + // create a new sub-branch + one.AddBranch("two"). + AddNode("subnode1").AddNode("subnode2"). // add some nodes + AddBranch("three"). // add a new sub-branch + AddNode("subnode1").AddNode("subnode2") // add some nodes too + + // add one more node that should surround the inner branch + one.AddNode("subnode3") + + // add a new node to the root + tree.AddNode("outernode") + + fmt.Println(tree.String()) +} +``` + +Will give you: + +``` +. +├── one +│   ├── subnode1 +│   ├── subnode2 +│   ├── two +│   │   ├── subnode1 +│   │   ├── subnode2 +│   │   └── three +│   │   ├── subnode1 +│   │   └── subnode2 +│   └── subnode3 +└── outernode +``` + +Another case, when you have to make a tree where any leaf may have some meta-data (as `tree` is capable of it): + +```go +func main { + tree := treeprint.New() + + tree.AddNode("Dockerfile") + tree.AddNode("Makefile") + tree.AddNode("aws.sh") + tree.AddMetaBranch(" 204", "bin"). + AddNode("dbmaker").AddNode("someserver").AddNode("testtool") + tree.AddMetaBranch(" 374", "deploy"). + AddNode("Makefile").AddNode("bootstrap.sh") + tree.AddMetaNode("122K", "testtool.a") + + fmt.Println(tree.String()) +} +``` + +Output: + +``` +. +├── Dockerfile +├── Makefile +├── aws.sh +├── [ 204] bin +│   ├── dbmaker +│   ├── someserver +│   └── testtool +├── [ 374] deploy +│   ├── Makefile +│   └── bootstrap.sh +└── [122K] testtool.a +``` + +Yay! So it works. + +## License +MIT diff --git a/vendor/github.com/xlab/treeprint/helpers.go b/vendor/github.com/xlab/treeprint/helpers.go new file mode 100644 index 0000000000..a091a5a0f0 --- /dev/null +++ b/vendor/github.com/xlab/treeprint/helpers.go @@ -0,0 +1,47 @@ +package treeprint + +import ( + "reflect" + "strings" +) + +func isEmpty(v *reflect.Value) bool { + switch v.Kind() { + case reflect.Array, reflect.Map, reflect.Slice, reflect.String: + return v.Len() == 0 + case reflect.Bool: + return !v.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return v.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return v.Uint() == 0 + case reflect.Float32, reflect.Float64: + return v.Float() == 0 + case reflect.Interface, reflect.Ptr: + return v.IsNil() + } + return false +} + +func tagSpec(tag string) (name string, omit bool) { + parts := strings.Split(tag, ",") + if len(parts) < 2 { + return tag, false + } + if parts[1] == "omitempty" { + return parts[0], true + } + return parts[0], false +} + +func filterTags(tag reflect.StructTag) string { + tags := strings.Split(string(tag), " ") + filtered := make([]string, 0, len(tags)) + for i := range tags { + if strings.HasPrefix(tags[i], "tree:") { + continue + } + filtered = append(filtered, tags[i]) + } + return strings.Join(filtered, " ") +} diff --git a/vendor/github.com/xlab/treeprint/struct.go b/vendor/github.com/xlab/treeprint/struct.go new file mode 100644 index 0000000000..b6277a95c0 --- /dev/null +++ b/vendor/github.com/xlab/treeprint/struct.go @@ -0,0 +1,340 @@ +package treeprint + +import ( + "fmt" + "reflect" + "strings" +) + +type StructTreeOption int + +const ( + StructNameTree StructTreeOption = iota + StructValueTree + StructTagTree + StructTypeTree + StructTypeSizeTree +) + +func FromStruct(v interface{}, opt ...StructTreeOption) (Tree, error) { + var treeOpt StructTreeOption + if len(opt) > 0 { + treeOpt = opt[0] + } + switch treeOpt { + case StructNameTree: + tree := New() + err := nameTree(tree, v) + return tree, err + case StructValueTree: + tree := New() + err := valueTree(tree, v) + return tree, err + case StructTagTree: + tree := New() + err := tagTree(tree, v) + return tree, err + case StructTypeTree: + tree := New() + err := typeTree(tree, v) + return tree, err + case StructTypeSizeTree: + tree := New() + err := typeSizeTree(tree, v) + return tree, err + default: + err := fmt.Errorf("treeprint: invalid StructTreeOption %v", treeOpt) + return nil, err + } +} + +type FmtFunc func(name string, v interface{}) (string, bool) + +func FromStructWithMeta(v interface{}, fmtFunc FmtFunc) (Tree, error) { + if fmtFunc == nil { + tree := New() + err := nameTree(tree, v) + return tree, err + } + tree := New() + err := metaTree(tree, v, fmtFunc) + return tree, err +} + +func Repr(v interface{}) string { + tree := New() + vType := reflect.TypeOf(v) + vValue := reflect.ValueOf(v) + _, val, isStruct := getValue(vType, &vValue) + if !isStruct { + return fmt.Sprintf("%+v", val.Interface()) + } + err := valueTree(tree, val.Interface()) + if err != nil { + return err.Error() + } + return tree.String() +} + +func nameTree(tree Tree, v interface{}) error { + typ, val, err := checkType(v) + if err != nil { + return err + } + fields := typ.NumField() + for i := 0; i < fields; i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + name, skip, omit := getMeta(field.Name, field.Tag) + if skip || omit && isEmpty(&fieldValue) { + continue + } + typ, val, isStruct := getValue(field.Type, &fieldValue) + if !val.CanSet() { + continue + } + if !isStruct { + tree.AddNode(name) + continue + } else if subNum := typ.NumField(); subNum == 0 { + tree.AddNode(name) + continue + } + branch := tree.AddBranch(name) + if err := nameTree(branch, val.Interface()); err != nil { + err := fmt.Errorf("%v on struct branch %s", name) + return err + } + } + return nil +} + +func getMeta(fieldName string, tag reflect.StructTag) (name string, skip, omit bool) { + if tagStr := tag.Get("tree"); len(tagStr) > 0 { + name, omit = tagSpec(tagStr) + } + if name == "-" { + return fieldName, true, omit + } + if len(name) == 0 { + name = fieldName + } else if trimmed := strings.TrimSpace(name); len(trimmed) == 0 { + name = fieldName + } + return +} + +func valueTree(tree Tree, v interface{}) error { + typ, val, err := checkType(v) + if err != nil { + return err + } + fields := typ.NumField() + for i := 0; i < fields; i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + name, skip, omit := getMeta(field.Name, field.Tag) + if skip || omit && isEmpty(&fieldValue) { + continue + } + typ, val, isStruct := getValue(field.Type, &fieldValue) + if !val.CanSet() { + continue + } + if !isStruct { + tree.AddMetaNode(val.Interface(), name) + continue + } else if subNum := typ.NumField(); subNum == 0 { + tree.AddMetaNode(val.Interface(), name) + continue + } + branch := tree.AddBranch(name) + if err := valueTree(branch, val.Interface()); err != nil { + err := fmt.Errorf("%v on struct branch %s", name) + return err + } + } + return nil +} + +func tagTree(tree Tree, v interface{}) error { + typ, val, err := checkType(v) + if err != nil { + return err + } + fields := typ.NumField() + for i := 0; i < fields; i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + name, skip, omit := getMeta(field.Name, field.Tag) + if skip || omit && isEmpty(&fieldValue) { + continue + } + filteredTag := filterTags(field.Tag) + typ, val, isStruct := getValue(field.Type, &fieldValue) + if !val.CanSet() { + continue + } + if !isStruct { + tree.AddMetaNode(filteredTag, name) + continue + } else if subNum := typ.NumField(); subNum == 0 { + tree.AddMetaNode(filteredTag, name) + continue + } + branch := tree.AddMetaBranch(filteredTag, name) + if err := tagTree(branch, val.Interface()); err != nil { + err := fmt.Errorf("%v on struct branch %s", name) + return err + } + } + return nil +} + +func typeTree(tree Tree, v interface{}) error { + typ, val, err := checkType(v) + if err != nil { + return err + } + fields := typ.NumField() + for i := 0; i < fields; i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + name, skip, omit := getMeta(field.Name, field.Tag) + if skip || omit && isEmpty(&fieldValue) { + continue + } + typ, val, isStruct := getValue(field.Type, &fieldValue) + if !val.CanSet() { + continue + } + typename := fmt.Sprintf("%T", val.Interface()) + if !isStruct { + tree.AddMetaNode(typename, name) + continue + } else if subNum := typ.NumField(); subNum == 0 { + tree.AddMetaNode(typename, name) + continue + } + branch := tree.AddMetaBranch(typename, name) + if err := typeTree(branch, val.Interface()); err != nil { + err := fmt.Errorf("%v on struct branch %s", name) + return err + } + } + return nil +} + +func typeSizeTree(tree Tree, v interface{}) error { + typ, val, err := checkType(v) + if err != nil { + return err + } + fields := typ.NumField() + for i := 0; i < fields; i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + name, skip, omit := getMeta(field.Name, field.Tag) + if skip || omit && isEmpty(&fieldValue) { + continue + } + typ, val, isStruct := getValue(field.Type, &fieldValue) + if !val.CanSet() { + continue + } + typesize := typ.Size() + if !isStruct { + tree.AddMetaNode(typesize, name) + continue + } else if subNum := typ.NumField(); subNum == 0 { + tree.AddMetaNode(typesize, name) + continue + } + branch := tree.AddMetaBranch(typesize, name) + if err := typeSizeTree(branch, val.Interface()); err != nil { + err := fmt.Errorf("%v on struct branch %s", name) + return err + } + } + return nil +} + +func metaTree(tree Tree, v interface{}, fmtFunc FmtFunc) error { + typ, val, err := checkType(v) + if err != nil { + return err + } + fields := typ.NumField() + for i := 0; i < fields; i++ { + field := typ.Field(i) + fieldValue := val.Field(i) + name, skip, omit := getMeta(field.Name, field.Tag) + if skip || omit && isEmpty(&fieldValue) { + continue + } + typ, val, isStruct := getValue(field.Type, &fieldValue) + if !val.CanSet() { + continue + } + formatted, show := fmtFunc(name, val.Interface()) + if !isStruct { + if show { + tree.AddMetaNode(formatted, name) + continue + } + tree.AddNode(name) + continue + } else if subNum := typ.NumField(); subNum == 0 { + if show { + tree.AddMetaNode(formatted, name) + continue + } + tree.AddNode(name) + continue + } + var branch Tree + if show { + branch = tree.AddMetaBranch(formatted, name) + } else { + branch = tree.AddBranch(name) + } + if err := metaTree(branch, val.Interface(), fmtFunc); err != nil { + err := fmt.Errorf("%v on struct branch %s", name) + return err + } + } + return nil +} + +func getValue(typ reflect.Type, val *reflect.Value) (reflect.Type, *reflect.Value, bool) { + switch typ.Kind() { + case reflect.Ptr: + typ = typ.Elem() + if typ.Kind() == reflect.Struct { + elem := val.Elem() + return typ, &elem, true + } + case reflect.Struct: + return typ, val, true + } + return typ, val, false +} + +func checkType(v interface{}) (reflect.Type, *reflect.Value, error) { + typ := reflect.TypeOf(v) + val := reflect.ValueOf(v) + switch typ.Kind() { + case reflect.Ptr: + typ = typ.Elem() + if typ.Kind() != reflect.Struct { + err := fmt.Errorf("treeprint: %T is not a struct we could work with", v) + return nil, nil, err + } + val = val.Elem() + case reflect.Struct: + default: + err := fmt.Errorf("treeprint: %T is not a struct we could work with", v) + return nil, nil, err + } + return typ, &val, nil +} diff --git a/vendor/github.com/xlab/treeprint/treeprint.go b/vendor/github.com/xlab/treeprint/treeprint.go new file mode 100644 index 0000000000..7392cda168 --- /dev/null +++ b/vendor/github.com/xlab/treeprint/treeprint.go @@ -0,0 +1,184 @@ +// Package treeprint provides a simple ASCII tree composing tool. +package treeprint + +import ( + "bytes" + "fmt" + "io" + "reflect" +) + +type Value interface{} +type MetaValue interface{} + +// Tree represents a tree structure with leaf-nodes and branch-nodes. +type Tree interface { + // AddNode adds a new node to a branch. + AddNode(v Value) Tree + // AddMetaNode adds a new node with meta value provided to a branch. + AddMetaNode(meta MetaValue, v Value) Tree + // AddBranch adds a new branch node (a level deeper). + AddBranch(v Value) Tree + // AddMetaBranch adds a new branch node (a level deeper) with meta value provided. + AddMetaBranch(meta MetaValue, v Value) Tree + // Branch converts a leaf-node to a branch-node, + // applying this on a branch-node does no effect. + Branch() Tree + // FindByMeta finds a node whose meta value matches the provided one by reflect.DeepEqual, + // returns nil if not found. + FindByMeta(meta MetaValue) Tree + // FindByValue finds a node whose value matches the provided one by reflect.DeepEqual, + // returns nil if not found. + FindByValue(value Value) Tree + // String renders the tree or subtree as a string. + String() string + // Bytes renders the tree or subtree as byteslice. + Bytes() []byte +} + +type node struct { + Root *node + Meta MetaValue + Value Value + Nodes []*node +} + +func (n *node) AddNode(v Value) Tree { + n.Nodes = append(n.Nodes, &node{ + Root: n, + Value: v, + }) + if n.Root != nil { + return n.Root + } + return n +} + +func (n *node) AddMetaNode(meta MetaValue, v Value) Tree { + n.Nodes = append(n.Nodes, &node{ + Root: n, + Meta: meta, + Value: v, + }) + if n.Root != nil { + return n.Root + } + return n +} + +func (n *node) AddBranch(v Value) Tree { + branch := &node{ + Value: v, + } + n.Nodes = append(n.Nodes, branch) + return branch +} + +func (n *node) AddMetaBranch(meta MetaValue, v Value) Tree { + branch := &node{ + Meta: meta, + Value: v, + } + n.Nodes = append(n.Nodes, branch) + return branch +} + +func (n *node) Branch() Tree { + n.Root = nil + return n +} + +func (n *node) FindByMeta(meta MetaValue) Tree { + for _, node := range n.Nodes { + if reflect.DeepEqual(node.Meta, meta) { + return node + } + if v := node.FindByMeta(meta); v != nil { + return v + } + } + return nil +} + +func (n *node) FindByValue(value Value) Tree { + for _, node := range n.Nodes { + if reflect.DeepEqual(node.Value, value) { + return node + } + if v := node.FindByMeta(value); v != nil { + return v + } + } + return nil +} + +func (n *node) Bytes() []byte { + buf := new(bytes.Buffer) + level := 0 + levelEnded := make(map[int]bool) + if n.Root == nil { + buf.WriteString(string(EdgeTypeStart)) + buf.WriteByte('\n') + } else { + edge := EdgeTypeMid + if len(n.Nodes) == 0 { + edge = EdgeTypeEnd + levelEnded[level] = true + } + printValues(buf, 0, levelEnded, edge, n.Meta, n.Value) + } + if len(n.Nodes) > 0 { + printNodes(buf, level, levelEnded, n.Nodes) + } + return buf.Bytes() +} + +func (n *node) String() string { + return string(n.Bytes()) +} + +func printNodes(wr io.Writer, + level int, levelEnded map[int]bool, nodes []*node) { + + for i, node := range nodes { + edge := EdgeTypeMid + if i == len(nodes)-1 { + levelEnded[level] = true + edge = EdgeTypeEnd + } + printValues(wr, level, levelEnded, edge, node.Meta, node.Value) + if len(node.Nodes) > 0 { + printNodes(wr, level+1, levelEnded, node.Nodes) + } + } +} + +func printValues(wr io.Writer, + level int, levelEnded map[int]bool, edge EdgeType, meta MetaValue, val Value) { + + for i := 0; i < level; i++ { + if levelEnded[i] { + fmt.Fprint(wr, " ") + continue + } + fmt.Fprintf(wr, "%s   ", EdgeTypeLink) + } + if meta != nil { + fmt.Fprintf(wr, "%s [%v] %v\n", edge, meta, val) + return + } + fmt.Fprintf(wr, "%s %v\n", edge, val) +} + +type EdgeType string + +const ( + EdgeTypeStart EdgeType = "." + EdgeTypeLink EdgeType = "│" + EdgeTypeMid EdgeType = "├──" + EdgeTypeEnd EdgeType = "└──" +) + +func New() Tree { + return &node{} +} diff --git a/vendor/vendor.json b/vendor/vendor.json index ff93a45c70..7e06c4efdc 100644 --- a/vendor/vendor.json +++ b/vendor/vendor.json @@ -3210,6 +3210,12 @@ "revision": "ff0417f4272e480246b4507459b3f6ae721a87ac", "revisionTime": "2017-02-25T17:21:24Z" }, + { + "checksumSHA1": "xtw+Llokq30p1Gn+Q8JBZ7NtE+I=", + "path": "github.com/xlab/treeprint", + "revision": "1d6e342255576c977e946a2384fc487a22d3fceb", + "revisionTime": "2016-10-29T10:40:18Z" + }, { "checksumSHA1": "eXEiPlpDRaamJQ4vPX/9t333kQc=", "comment": "v1.5.4-13-g75ce5fb", From 3af0ecdf0131225ffccd20d2f2ea7632cf1e4b7c Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 25 Apr 2017 18:10:10 -0700 Subject: [PATCH 28/82] command: "terraform providers" command This new command prints out the tree of modules annotated with their associated required providers. The purpose of this command is to help users answer questions such as "why is this provider required?", "why is Terraform using an older version of this provider?", and "what combination of modules is creating an impossible provider version situation?" For configurations using many modules this sort of question is likely to come up a lot once we support versioned providers. As a bonus use-case, this command also shows explicitly when a provider configuration is being inherited from a parent module, to help users to understand where the configuration is coming from for each module when some child modules provide their own provider configurations. --- command/providers.go | 125 ++++++++++++++++++++++++ command/providers_test.go | 43 ++++++++ command/test-fixtures/providers/main.tf | 11 +++ commands.go | 6 ++ 4 files changed, 185 insertions(+) create mode 100644 command/providers.go create mode 100644 command/providers_test.go create mode 100644 command/test-fixtures/providers/main.tf diff --git a/command/providers.go b/command/providers.go new file mode 100644 index 0000000000..23221603f1 --- /dev/null +++ b/command/providers.go @@ -0,0 +1,125 @@ +package command + +import ( + "fmt" + "sort" + + "github.com/hashicorp/terraform/moduledeps" + "github.com/hashicorp/terraform/terraform" + "github.com/xlab/treeprint" +) + +// ProvidersCommand is a Command implementation that prints out information +// about the providers used in the current configuration/state. +type ProvidersCommand struct { + Meta +} + +func (c *ProvidersCommand) Help() string { + return providersCommandHelp +} + +func (c *ProvidersCommand) Synopsis() string { + return "Prints a tree of the providers used in the configuration" +} + +func (c *ProvidersCommand) Run(args []string) int { + + cmdFlags := c.Meta.flagSet("providers") + cmdFlags.Usage = func() { c.Ui.Error(c.Help()) } + if err := cmdFlags.Parse(args); err != nil { + return 1 + } + + configPath, err := ModulePath(cmdFlags.Args()) + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + + // Load the config + root, err := c.Module(configPath) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) + return 1 + } + + // Validate the config (to ensure the version constraints are valid) + err = root.Validate() + if err != nil { + c.Ui.Error(err.Error()) + return 1 + } + + // Load the backend + b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) + return 1 + } + + // Get the state + env := c.Env() + state, err := b.State(env) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) + return 1 + } + if err := state.RefreshState(); err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load state: %s", err)) + return 1 + } + + s := state.State() + + depTree := terraform.ModuleTreeDependencies(root, s) + depTree.SortDescendents() + + printRoot := treeprint.New() + providersCommandPopulateTreeNode(printRoot, depTree) + + c.Ui.Output(printRoot.String()) + + return 0 +} + +func providersCommandPopulateTreeNode(node treeprint.Tree, deps *moduledeps.Module) { + names := make([]string, 0, len(deps.Providers)) + for name := range deps.Providers { + names = append(names, string(name)) + } + sort.Strings(names) + + for _, name := range names { + dep := deps.Providers[moduledeps.ProviderInstance(name)] + versionsStr := dep.Versions.String() + if versionsStr != "" { + versionsStr = " " + versionsStr + } + var reasonStr string + switch dep.Reason { + case moduledeps.ProviderDependencyInherited: + reasonStr = " (inherited)" + case moduledeps.ProviderDependencyFromState: + reasonStr = " (from state)" + } + node.AddNode(fmt.Sprintf("provider.%s%s%s", name, versionsStr, reasonStr)) + } + + for _, child := range deps.Children { + childNode := node.AddBranch(fmt.Sprintf("module.%s", child.Name)) + providersCommandPopulateTreeNode(childNode, child) + } +} + +const providersCommandHelp = ` +Usage: terraform providers [dir] + + Prints out a tree of modules in the referenced configuration annotated with + their provider requirements. + + This provides an overview of all of the provider requirements across all + referenced modules, as an aid to understanding why particular provider + plugins are needed and why particular versions are selected. + +` diff --git a/command/providers_test.go b/command/providers_test.go new file mode 100644 index 0000000000..eb6c0dfc25 --- /dev/null +++ b/command/providers_test.go @@ -0,0 +1,43 @@ +package command + +import ( + "os" + "strings" + "testing" + + "github.com/mitchellh/cli" +) + +func TestProviders(t *testing.T) { + cwd, err := os.Getwd() + if err != nil { + t.Fatalf("err: %s", err) + } + if err := os.Chdir(testFixturePath("providers")); err != nil { + t.Fatalf("err: %s", err) + } + defer os.Chdir(cwd) + + ui := new(cli.MockUi) + c := &ProvidersCommand{ + Meta: Meta{ + Ui: ui, + }, + } + + args := []string{} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) + } + + output := ui.OutputWriter.String() + if !strings.Contains(output, "provider.foo") { + t.Errorf("output missing provider.foo\n\n%s", output) + } + if !strings.Contains(output, "provider.bar") { + t.Errorf("output missing provider.bar\n\n%s", output) + } + if !strings.Contains(output, "provider.baz") { + t.Errorf("output missing provider.baz\n\n%s", output) + } +} diff --git a/command/test-fixtures/providers/main.tf b/command/test-fixtures/providers/main.tf new file mode 100644 index 0000000000..d22a2b5097 --- /dev/null +++ b/command/test-fixtures/providers/main.tf @@ -0,0 +1,11 @@ +provider "foo" { + +} + +resource "bar_instance" "test" { + +} + +provider "baz" { + version = "1.2.0" +} diff --git a/commands.go b/commands.go index c4dca2d670..780b4652e0 100644 --- a/commands.go +++ b/commands.go @@ -149,6 +149,12 @@ func init() { }, nil }, + "providers": func() (cli.Command, error) { + return &command.ProvidersCommand{ + Meta: meta, + }, nil + }, + "push": func() (cli.Command, error) { return &command.PushCommand{ Meta: meta, From d7d8ea95436925686387be0b7318ac39dc87f0ad Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 28 Apr 2017 15:32:23 -0700 Subject: [PATCH 29/82] website: Initial docs for the new "providers" subcommand This will be fleshed out later as part of more holistic documentation for the new provider plugin separation, but this is some minimal documentation for just this subcommand. --- .../source/docs/commands/index.html.markdown | 1 + .../docs/commands/providers.html.markdown | 36 +++++++++++++++++++ website/source/layouts/docs.erb | 4 +++ 3 files changed, 41 insertions(+) create mode 100644 website/source/docs/commands/providers.html.markdown diff --git a/website/source/docs/commands/index.html.markdown b/website/source/docs/commands/index.html.markdown index faff71bff5..d48fcb1e6a 100644 --- a/website/source/docs/commands/index.html.markdown +++ b/website/source/docs/commands/index.html.markdown @@ -41,6 +41,7 @@ Common commands: init Initialize a new or existing Terraform configuration output Read an output from a state file plan Generate and show an execution plan + providers Prints a tree of the providers used in the configuration push Upload this Terraform module to Terraform Enterprise to run refresh Update local state file against real resources show Inspect Terraform state or plan diff --git a/website/source/docs/commands/providers.html.markdown b/website/source/docs/commands/providers.html.markdown new file mode 100644 index 0000000000..edf71721d0 --- /dev/null +++ b/website/source/docs/commands/providers.html.markdown @@ -0,0 +1,36 @@ +--- +layout: "docs" +page_title: "Command: providers" +sidebar_current: "docs-commands-providers" +description: |- + The "providers" sub-command prints information about the providers used + in the current configuration. +--- + +# Command: show + +The `terraform providers` command prints information about the providers +used in the current configuration. + +Provider dependencies are created in several different ways: + +* Explicit use of a `provider` block in configuration, optionally including + a version constraint. + +* Use of any resource belonging to a particular provider in a `resource` or + `data` block in configuration. + +* Existance of any resource instance belonging to a particular provider in + the current _state_. For example, if a particular resource is removed + from configuration, it continues to create a dependency on its provider + until its instances have been destroyed. + +This command gives an overview of all of the current dependencies, as an aid +to understanding why a particular provider is needed. + +## Usage + +Usage: `terraform show [config-path]` + +Pass an explicit configuration path to override the default of using the +current working directory. diff --git a/website/source/layouts/docs.erb b/website/source/layouts/docs.erb index c06eef29a4..9da9816f95 100644 --- a/website/source/layouts/docs.erb +++ b/website/source/layouts/docs.erb @@ -109,6 +109,10 @@ plan + > + providers + + > push From 718ede0636de5d17e384360ae9dbfc6b6581b2f8 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Mon, 1 May 2017 17:47:53 -0400 Subject: [PATCH 30/82] have Meta.Backend use a Config rather than loading Instead of providing the a path in BackendOpts, provide a loaded *config.Config instead. This reduces the number of places where configuration is loaded. --- command/apply.go | 9 ++++-- command/console.go | 11 ++++++- command/env_delete.go | 11 ++++++- command/env_list.go | 11 ++++++- command/env_new.go | 10 +++++- command/env_select.go | 11 ++++++- command/graph.go | 10 ++++-- command/import.go | 10 +++++- command/init.go | 8 ++--- command/meta_backend.go | 67 +++++++++-------------------------------- command/meta_new.go | 62 ++++++++++++++++++++++++++++++++++++++ command/plan.go | 9 ++++-- command/providers.go | 4 ++- command/push.go | 8 ++++- command/refresh.go | 10 +++++- command/unlock.go | 8 ++++- plugin/getter/get.go | 0 17 files changed, 184 insertions(+), 75 deletions(-) create mode 100644 plugin/getter/get.go diff --git a/command/apply.go b/command/apply.go index 1e49739a08..9c8c4904a2 100644 --- a/command/apply.go +++ b/command/apply.go @@ -132,10 +132,15 @@ func (c *ApplyCommand) Run(args []string) int { } */ + var conf *config.Config + if mod != nil { + conf = mod.Config() + } + // Load the backend b, err := c.Backend(&BackendOpts{ - ConfigPath: configPath, - Plan: plan, + Config: conf, + Plan: plan, }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) diff --git a/command/console.go b/command/console.go index 4a6420b77c..cd73ffe495 100644 --- a/command/console.go +++ b/command/console.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/helper/wrappedstreams" "github.com/hashicorp/terraform/repl" @@ -43,8 +44,16 @@ func (c *ConsoleCommand) Run(args []string) int { return 1 } + var conf *config.Config + if mod != nil { + conf = mod.Config() + } + // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: conf, + }) + if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/env_delete.go b/command/env_delete.go index a0fb01fead..3975f91570 100644 --- a/command/env_delete.go +++ b/command/env_delete.go @@ -43,8 +43,17 @@ func (c *EnvDeleteCommand) Run(args []string) int { return 1 } + cfg, err := c.Config(configPath) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) + return 1 + } + // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: cfg, + }) + if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/env_list.go b/command/env_list.go index 12d768e802..56d448d291 100644 --- a/command/env_list.go +++ b/command/env_list.go @@ -26,8 +26,17 @@ func (c *EnvListCommand) Run(args []string) int { return 1 } + cfg, err := c.Config(configPath) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) + return 1 + } + // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: cfg, + }) + if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/env_new.go b/command/env_new.go index 04c52a6d24..1b2c13a677 100644 --- a/command/env_new.go +++ b/command/env_new.go @@ -46,8 +46,16 @@ func (c *EnvNewCommand) Run(args []string) int { return 1 } + conf, err := c.Config(configPath) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) + } + // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: conf, + }) + if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/env_select.go b/command/env_select.go index e7bc8743e4..d65660fb87 100644 --- a/command/env_select.go +++ b/command/env_select.go @@ -31,8 +31,17 @@ func (c *EnvSelectCommand) Run(args []string) int { return 1 } + conf, err := c.Config(configPath) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) + return 1 + } + // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: conf, + }) + if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/graph.go b/command/graph.go index 45299787af..befe5b3296 100644 --- a/command/graph.go +++ b/command/graph.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/dag" "github.com/hashicorp/terraform/terraform" @@ -62,10 +63,15 @@ func (c *GraphCommand) Run(args []string) int { } } + var conf *config.Config + if mod != nil { + conf = mod.Config() + } + // Load the backend b, err := c.Backend(&BackendOpts{ - ConfigPath: configPath, - Plan: plan, + Config: conf, + Plan: plan, }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) diff --git a/command/import.go b/command/import.go index 1636ab9d98..a2f4d6c62a 100644 --- a/command/import.go +++ b/command/import.go @@ -7,6 +7,7 @@ import ( "strings" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/terraform" ) @@ -60,8 +61,15 @@ func (c *ImportCommand) Run(args []string) int { } } + var conf *config.Config + if mod != nil { + conf = mod.Config() + } + // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: conf, + }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/init.go b/command/init.go index ff0aadc9d8..3023d9f14f 100644 --- a/command/init.go +++ b/command/init.go @@ -106,11 +106,7 @@ func (c *InitCommand) Run(args []string) int { // If we're performing a get or loading the backend, then we perform // some extra tasks. if flagGet || flagBackend { - // Load the configuration in this directory so that we can know - // if we have anything to get or any backend to configure. We do - // this to improve the UX. Practically, we could call the functions - // below without checking this to the same effect. - conf, err := config.LoadDir(path) + conf, err := c.Config(path) if err != nil { c.Ui.Error(fmt.Sprintf( "Error loading configuration: %s", err)) @@ -145,7 +141,7 @@ func (c *InitCommand) Run(args []string) int { } opts := &BackendOpts{ - ConfigPath: path, + Config: conf, ConfigExtra: flagConfigExtra, Init: true, } diff --git a/command/meta_backend.go b/command/meta_backend.go index b57999cec8..112d8ccc1e 100644 --- a/command/meta_backend.go +++ b/command/meta_backend.go @@ -9,11 +9,9 @@ import ( "fmt" "io/ioutil" "log" - "os" "path/filepath" "strings" - "github.com/hashicorp/errwrap" "github.com/hashicorp/go-multierror" "github.com/hashicorp/hcl" "github.com/hashicorp/terraform/backend" @@ -29,9 +27,9 @@ import ( // BackendOpts are the options used to initialize a backend.Backend. type BackendOpts struct { - // ConfigPath is a path to a file or directory containing the backend - // configuration (declaration). - ConfigPath string + // Module is the root module from which we will extract the terraform and + // backend configuration. + Config *config.Config // ConfigFile is a path to a file that contains configuration that // is merged directly into the backend configuration when loaded @@ -178,71 +176,34 @@ func (m *Meta) Operation() *backend.Operation { // backendConfig returns the local configuration for the backend func (m *Meta) backendConfig(opts *BackendOpts) (*config.Backend, error) { - // If no explicit path was given then it is okay for there to be - // no backend configuration found. - emptyOk := opts.ConfigPath == "" - - // Determine the path to the configuration. - path := opts.ConfigPath - - // If we had no path set, it is an error. We can't initialize unset - if path == "" { - path = "." - } - - // Expand the path - if !filepath.IsAbs(path) { - var err error - path, err = filepath.Abs(path) + if opts.Config == nil { + // check if the config was missing, or just not required + conf, err := m.Config(".") if err != nil { - return nil, fmt.Errorf( - "Error expanding path to backend config %q: %s", path, err) + return nil, err } - } - log.Printf("[DEBUG] command: loading backend config file: %s", path) - - // We first need to determine if we're loading a file or a directory. - fi, err := os.Stat(path) - if err != nil { - if os.IsNotExist(err) && emptyOk { - log.Printf( - "[INFO] command: backend config not found, returning nil: %s", - path) + if conf == nil { + log.Println("[INFO] command: no config, returning nil") return nil, nil } - return nil, err + log.Println("[WARNING] BackendOpts.Config not set, but config found") + opts.Config = conf } - var f func(string) (*config.Config, error) = config.LoadFile - if fi.IsDir() { - f = config.LoadDir - } - - // Load the configuration - c, err := f(path) - if err != nil { - // Check for the error where we have no config files and return nil - // as the configuration type. - if errwrap.ContainsType(err, new(config.ErrNoConfigsFound)) { - log.Printf( - "[INFO] command: backend config not found, returning nil: %s", - path) - return nil, nil - } - - return nil, err - } + c := opts.Config // If there is no Terraform configuration block, no backend config if c.Terraform == nil { + log.Println("[INFO] command: empty terraform config, returning nil") return nil, nil } // Get the configuration for the backend itself. backend := c.Terraform.Backend if backend == nil { + log.Println("[INFO] command: empty backend config, returning nil") return nil, nil } diff --git a/command/meta_new.go b/command/meta_new.go index 3138cd2dfd..a1d376b34d 100644 --- a/command/meta_new.go +++ b/command/meta_new.go @@ -2,7 +2,9 @@ package command import ( "fmt" + "log" "os" + "path/filepath" "strconv" "github.com/hashicorp/errwrap" @@ -51,6 +53,66 @@ func (m *Meta) Module(path string) (*module.Tree, error) { return mod, nil } +// Config loads the root config for the path specified. Path may be a directory +// or file. The absence of configuration is not an error and returns a nil Config. +func (m *Meta) Config(path string) (*config.Config, error) { + // If no explicit path was given then it is okay for there to be + // no backend configuration found. + emptyOk := path == "" + + // If we had no path set, it is an error. We can't initialize unset + if path == "" { + path = "." + } + + // Expand the path + if !filepath.IsAbs(path) { + var err error + path, err = filepath.Abs(path) + if err != nil { + return nil, fmt.Errorf( + "Error expanding path to backend config %q: %s", path, err) + } + } + + log.Printf("[DEBUG] command: loading backend config file: %s", path) + + // We first need to determine if we're loading a file or a directory. + fi, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) && emptyOk { + log.Printf( + "[INFO] command: backend config not found, returning nil: %s", + path) + return nil, nil + } + + return nil, err + } + + var f func(string) (*config.Config, error) = config.LoadFile + if fi.IsDir() { + f = config.LoadDir + } + + // Load the configuration + c, err := f(path) + if err != nil { + // Check for the error where we have no config files and return nil + // as the configuration type. + if errwrap.ContainsType(err, new(config.ErrNoConfigsFound)) { + log.Printf( + "[INFO] command: backend config not found, returning nil: %s", + path) + return nil, nil + } + + return nil, err + } + + return c, nil +} + // Plan returns the plan for the given path. // // This only has an effect if the path itself looks like a plan. diff --git a/command/plan.go b/command/plan.go index 0b66fdffd6..a38a228728 100644 --- a/command/plan.go +++ b/command/plan.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" ) @@ -68,10 +69,14 @@ func (c *PlanCommand) Run(args []string) int { } } + var conf *config.Config + if mod != nil { + conf = mod.Config() + } // Load the backend b, err := c.Backend(&BackendOpts{ - ConfigPath: configPath, - Plan: plan, + Config: conf, + Plan: plan, }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) diff --git a/command/providers.go b/command/providers.go index 23221603f1..03bf6feae1 100644 --- a/command/providers.go +++ b/command/providers.go @@ -52,7 +52,9 @@ func (c *ProvidersCommand) Run(args []string) int { } // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: root.Config(), + }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/push.go b/command/push.go index 3a4c2060e3..564889210f 100644 --- a/command/push.go +++ b/command/push.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/atlas-go/archive" "github.com/hashicorp/atlas-go/v1" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/terraform" ) @@ -98,9 +99,14 @@ func (c *PushCommand) Run(args []string) int { return 1 } + var conf *config.Config + if mod != nil { + conf = mod.Config() + } + // Load the backend b, err := c.Backend(&BackendOpts{ - ConfigPath: configPath, + Config: conf, }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) diff --git a/command/refresh.go b/command/refresh.go index 3f1b8bf286..72623ed45e 100644 --- a/command/refresh.go +++ b/command/refresh.go @@ -6,6 +6,7 @@ import ( "strings" "github.com/hashicorp/terraform/backend" + "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/terraform" ) @@ -43,8 +44,15 @@ func (c *RefreshCommand) Run(args []string) int { return 1 } + var conf *config.Config + if mod != nil { + conf = mod.Config() + } + // Load the backend - b, err := c.Backend(&BackendOpts{ConfigPath: configPath}) + b, err := c.Backend(&BackendOpts{ + Config: conf, + }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) return 1 diff --git a/command/unlock.go b/command/unlock.go index 666e4f3467..acaf3307f6 100644 --- a/command/unlock.go +++ b/command/unlock.go @@ -43,9 +43,15 @@ func (c *UnlockCommand) Run(args []string) int { return 1 } + conf, err := c.Config(configPath) + if err != nil { + c.Ui.Error(fmt.Sprintf("Failed to load root config module: %s", err)) + return 1 + } + // Load the backend b, err := c.Backend(&BackendOpts{ - ConfigPath: configPath, + Config: conf, }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) diff --git a/plugin/getter/get.go b/plugin/getter/get.go new file mode 100644 index 0000000000..e69de29bb2 From 7d2d951f27b2c9e9525f522959cd4648e6057e7b Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 3 May 2017 17:31:46 -0400 Subject: [PATCH 31/82] Rename VersionSet to Constraints VersionSet is a wrapper around version.Contraints, so rename it it as such. --- command/providers.go | 2 +- moduledeps/dependencies.go | 4 +- moduledeps/module.go | 10 ++-- moduledeps/module_test.go | 6 +-- plugin/discovery/requirements.go | 4 +- plugin/discovery/version_set.go | 37 +++++++-------- terraform/module_dependencies.go | 12 ++--- terraform/module_dependencies_test.go | 68 +++++++++++++-------------- terraform/test_failure | 9 ++++ 9 files changed, 79 insertions(+), 73 deletions(-) create mode 100644 terraform/test_failure diff --git a/command/providers.go b/command/providers.go index 03bf6feae1..6928faaf6b 100644 --- a/command/providers.go +++ b/command/providers.go @@ -94,7 +94,7 @@ func providersCommandPopulateTreeNode(node treeprint.Tree, deps *moduledeps.Modu for _, name := range names { dep := deps.Providers[moduledeps.ProviderInstance(name)] - versionsStr := dep.Versions.String() + versionsStr := dep.Constraints.String() if versionsStr != "" { versionsStr = " " + versionsStr } diff --git a/moduledeps/dependencies.go b/moduledeps/dependencies.go index fa999ab2ff..87c8431eab 100644 --- a/moduledeps/dependencies.go +++ b/moduledeps/dependencies.go @@ -13,8 +13,8 @@ type Providers map[ProviderInstance]ProviderDependency // instance, including both the set of allowed versions and the reason for // the dependency. type ProviderDependency struct { - Versions discovery.VersionSet - Reason ProviderDependencyReason + Constraints discovery.Constraints + Reason ProviderDependencyReason } // ProviderDependencyReason is an enumeration of reasons why a dependency might be diff --git a/moduledeps/module.go b/moduledeps/module.go index f54f06bfc0..d09c19738e 100644 --- a/moduledeps/module.go +++ b/moduledeps/module.go @@ -114,9 +114,9 @@ func (m *Module) PluginRequirements() discovery.PluginRequirements { // by using Intersection to merge the version sets. pty := inst.Type() if existing, exists := ret[pty]; exists { - ret[pty] = existing.Intersection(dep.Versions) + ret[pty] = existing.Intersection(dep.Constraints) } else { - ret[pty] = dep.Versions + ret[pty] = dep.Constraints } } return ret @@ -163,7 +163,7 @@ func (m *Module) Equal(other *Module) bool { } // Can't use reflect.DeepEqual on this provider structure because - // the nested VersionSet objects contain function pointers that + // the nested Constraints objects contain function pointers that // never compare as equal. So we'll need to walk it the long way. for inst, dep := range m.Providers { if _, exists := other.Providers[inst]; !exists { @@ -174,10 +174,10 @@ func (m *Module) Equal(other *Module) bool { return false } - // VersionSets are not too easy to compare robustly, so + // Constraints are not too easy to compare robustly, so // we'll just use their string representations as a proxy // for now. - if dep.Versions.String() != other.Providers[inst].Versions.String() { + if dep.Constraints.String() != other.Providers[inst].Constraints.String() { return false } } diff --git a/moduledeps/module_test.go b/moduledeps/module_test.go index 63d907d38c..630f3cd8b7 100644 --- a/moduledeps/module_test.go +++ b/moduledeps/module_test.go @@ -192,13 +192,13 @@ func TestModulePluginRequirements(t *testing.T) { Name: "root", Providers: Providers{ "foo": ProviderDependency{ - Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), + Constraints: discovery.ConstraintStr(">=1.0.0").MustParse(), }, "foo.bar": ProviderDependency{ - Versions: discovery.ConstraintStr(">=2.0.0").MustParse(), + Constraints: discovery.ConstraintStr(">=2.0.0").MustParse(), }, "baz": ProviderDependency{ - Versions: discovery.ConstraintStr(">=3.0.0").MustParse(), + Constraints: discovery.ConstraintStr(">=3.0.0").MustParse(), }, }, } diff --git a/plugin/discovery/requirements.go b/plugin/discovery/requirements.go index e82909540d..4f0857aa69 100644 --- a/plugin/discovery/requirements.go +++ b/plugin/discovery/requirements.go @@ -4,8 +4,8 @@ package discovery // kind) that are required to exist and have versions within the given // corresponding sets. // -// PluginRequirements is a map from plugin name to VersionSet. -type PluginRequirements map[string]VersionSet +// PluginRequirements is a map from plugin name to Constraints. +type PluginRequirements map[string]Constraints // Merge takes the contents of the receiver and the other given requirements // object and merges them together into a single requirements structure diff --git a/plugin/discovery/version_set.go b/plugin/discovery/version_set.go index 3dce045b44..73eae75914 100644 --- a/plugin/discovery/version_set.go +++ b/plugin/discovery/version_set.go @@ -9,18 +9,18 @@ import ( // obtain a real Constraint object, or discover that it is invalid. type ConstraintStr string -// Parse transforms a ConstraintStr into a VersionSet if it is +// Parse transforms a ConstraintStr into a Constraints if it is // syntactically valid. If it isn't then an error is returned instead. -func (s ConstraintStr) Parse() (VersionSet, error) { +func (s ConstraintStr) Parse() (Constraints, error) { raw, err := version.NewConstraint(string(s)) if err != nil { - return VersionSet{}, err + return Constraints{}, err } - return VersionSet{raw}, nil + return Constraints{raw}, nil } // MustParse is like Parse but it panics if the constraint string is invalid. -func (s ConstraintStr) MustParse() VersionSet { +func (s ConstraintStr) MustParse() Constraints { ret, err := s.Parse() if err != nil { panic(err) @@ -28,33 +28,30 @@ func (s ConstraintStr) MustParse() VersionSet { return ret } -// VersionSet represents a set of versions which any given Version is either +// Constraints represents a set of versions which any given Version is either // a member of or not. -type VersionSet struct { - // Internally a version set is actually a list of constraints that - // *remove* versions from the set. Thus a VersionSet with an empty - // Constraints list would be one that contains *all* versions. +type Constraints struct { raw version.Constraints } -// AllVersions is a VersionSet containing all versions -var AllVersions VersionSet +// AllVersions is a Constraints containing all versions +var AllVersions Constraints func init() { - AllVersions = VersionSet{ + AllVersions = Constraints{ raw: make(version.Constraints, 0), } } // Has returns true if the given version is in the receiving set. -func (s VersionSet) Has(v Version) bool { +func (s Constraints) Has(v Version) bool { return s.raw.Check(v.raw) } -// Intersection combines the receving set with the given other set to produce a -// set that is the intersection of both sets, which is to say that it contains -// only the versions that are members of both sets. -func (s VersionSet) Intersection(other VersionSet) VersionSet { +// Intersection combines the receiving set with the given other set to produce +// a set that is the intersection of both sets, which is to say that resulting +// constraints contain only the versions that are members of both. +func (s Constraints) Intersection(other Constraints) Constraints { raw := make(version.Constraints, 0, len(s.raw)+len(other.raw)) // Since "raw" is a list of constraints that remove versions from the set, @@ -63,11 +60,11 @@ func (s VersionSet) Intersection(other VersionSet) VersionSet { raw = append(raw, s.raw...) raw = append(raw, other.raw...) - return VersionSet{raw} + return Constraints{raw} } // String returns a string representation of the set members as a set // of range constraints. -func (s VersionSet) String() string { +func (s Constraints) String() string { return s.raw.String() } diff --git a/terraform/module_dependencies.go b/terraform/module_dependencies.go index ffc6920c86..b9f44a0e86 100644 --- a/terraform/module_dependencies.go +++ b/terraform/module_dependencies.go @@ -62,8 +62,8 @@ func moduleTreeConfigDependencies(root *module.Tree, inheritProviders map[string versionSet = discovery.ConstraintStr(pCfg.Version).MustParse() } providers[inst] = moduledeps.ProviderDependency{ - Versions: versionSet, - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: versionSet, + Reason: moduledeps.ProviderDependencyExplicit, } } @@ -84,8 +84,8 @@ func moduleTreeConfigDependencies(root *module.Tree, inheritProviders map[string } providers[inst] = moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: reason, + Constraints: discovery.AllVersions, + Reason: reason, } } @@ -146,8 +146,8 @@ func moduleTreeMergeStateDependencies(root *moduledeps.Module, state *State) { module.Providers = make(moduledeps.Providers) } module.Providers[inst] = moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyFromState, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, } } } diff --git a/terraform/module_dependencies_test.go b/terraform/module_dependencies_test.go index 6fced8a80d..e63739f817 100644 --- a/terraform/module_dependencies_test.go +++ b/terraform/module_dependencies_test.go @@ -40,12 +40,12 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "root", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.ConstraintStr(">=1.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, }, "foo.bar": moduledeps.ProviderDependency{ - Versions: discovery.ConstraintStr(">=2.0.0").MustParse(), - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.ConstraintStr(">=2.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, }, }, Children: nil, @@ -58,8 +58,8 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "root", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, }, }, Children: nil, @@ -72,12 +72,12 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "root", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyImplicit, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyImplicit, }, "foo.baz": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyImplicit, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyImplicit, }, }, Children: nil, @@ -90,8 +90,8 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "root", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.ConstraintStr(">=1.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, }, }, Children: nil, @@ -104,12 +104,12 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "root", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, }, "bar": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, }, }, Children: []*moduledeps.Module{ @@ -117,12 +117,12 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "child", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyInherited, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyInherited, }, "baz": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyImplicit, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyImplicit, }, }, Children: []*moduledeps.Module{ @@ -130,12 +130,12 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "grandchild", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyExplicit, }, "bar": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyInherited, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyInherited, }, }, }, @@ -163,8 +163,8 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "root", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyFromState, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, }, }, Children: nil, @@ -209,16 +209,16 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "root", Providers: moduledeps.Providers{ "foo": moduledeps.ProviderDependency{ - Versions: discovery.ConstraintStr(">=1.0.0").MustParse(), - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.ConstraintStr(">=1.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, }, "foo.bar": moduledeps.ProviderDependency{ - Versions: discovery.ConstraintStr(">=2.0.0").MustParse(), - Reason: moduledeps.ProviderDependencyExplicit, + Constraints: discovery.ConstraintStr(">=2.0.0").MustParse(), + Reason: moduledeps.ProviderDependencyExplicit, }, "baz": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyFromState, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, }, }, Children: []*moduledeps.Module{ @@ -229,8 +229,8 @@ func TestModuleTreeDependencies(t *testing.T) { Name: "grandchild", Providers: moduledeps.Providers{ "banana": moduledeps.ProviderDependency{ - Versions: discovery.AllVersions, - Reason: moduledeps.ProviderDependencyFromState, + Constraints: discovery.AllVersions, + Reason: moduledeps.ProviderDependencyFromState, }, }, }, diff --git a/terraform/test_failure b/terraform/test_failure new file mode 100644 index 0000000000..5d3ad1ac4e --- /dev/null +++ b/terraform/test_failure @@ -0,0 +1,9 @@ +--- FAIL: TestContext2Plan_moduleProviderInherit (0.01s) + context_plan_test.go:552: bad: []string{"child"} +map[string]dag.Vertex{} +"module.middle.null" +map[string]dag.Vertex{} +"module.middle.module.inner.null" +map[string]dag.Vertex{} +"aws" +FAIL From fa49c69793b65747526f05d84e07de9cd9b5ffba Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 3 May 2017 17:54:47 -0400 Subject: [PATCH 32/82] expose Version.NewerThan NewerThan is useful outside of the package --- plugin/discovery/meta_set.go | 2 +- plugin/discovery/version.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugin/discovery/meta_set.go b/plugin/discovery/meta_set.go index 066870e6e8..9fc0abb094 100644 --- a/plugin/discovery/meta_set.go +++ b/plugin/discovery/meta_set.go @@ -100,7 +100,7 @@ func (s PluginMetaSet) Newest() PluginMeta { panic(err) } - if first == true || version.newerThan(winnerVersion) { + if first == true || version.NewerThan(winnerVersion) { winner = p winnerVersion = version first = false diff --git a/plugin/discovery/version.go b/plugin/discovery/version.go index 160a969b0c..f2706bd530 100644 --- a/plugin/discovery/version.go +++ b/plugin/discovery/version.go @@ -32,6 +32,6 @@ func (v Version) String() string { return v.raw.String() } -func (v Version) newerThan(other Version) bool { +func (v Version) NewerThan(other Version) bool { return v.raw.GreaterThan(other.raw) } From 2749946f5cd7766770706dea5e9dda66b86e3cf9 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 3 May 2017 11:02:47 -0400 Subject: [PATCH 33/82] basic plugin getter Add discovery.GetProviders to fetch plugins from the relases site. This is an early version, with no tests, that only (probably) fetches plugins from the default location. The URLs are still subject to change, and since there are no plugin releases, it doesn't work at all yet. --- command/init.go | 68 ++++++++++++++-- command/plugins.go | 25 +++++- plugin/discovery/get.go | 170 ++++++++++++++++++++++++++++++++++++++++ plugin/getter/get.go | 0 4 files changed, 256 insertions(+), 7 deletions(-) create mode 100644 plugin/discovery/get.go delete mode 100644 plugin/getter/get.go diff --git a/command/init.go b/command/init.go index 3023d9f14f..7008bb6e0c 100644 --- a/command/init.go +++ b/command/init.go @@ -6,10 +6,13 @@ import ( "path/filepath" "strings" - "github.com/hashicorp/go-getter" + getter "github.com/hashicorp/go-getter" + "github.com/hashicorp/terraform/backend" "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/helper/variables" + "github.com/hashicorp/terraform/plugin/discovery" + "github.com/hashicorp/terraform/terraform" ) // InitCommand is a Command implementation that takes a Terraform @@ -19,7 +22,7 @@ type InitCommand struct { } func (c *InitCommand) Run(args []string) int { - var flagBackend, flagGet bool + var flagBackend, flagGet, flagGetPlugins bool var flagConfigExtra map[string]interface{} args = c.Meta.process(args, false) @@ -27,6 +30,7 @@ func (c *InitCommand) Run(args []string) int { cmdFlags.BoolVar(&flagBackend, "backend", true, "") cmdFlags.Var((*variables.FlagAny)(&flagConfigExtra), "backend-config", "") cmdFlags.BoolVar(&flagGet, "get", true, "") + cmdFlags.BoolVar(&flagGetPlugins, "get-plugins", true, "") cmdFlags.BoolVar(&c.forceInitCopy, "force-copy", false, "suppress prompts about copying state data") cmdFlags.BoolVar(&c.Meta.stateLock, "lock", true, "lock state") cmdFlags.DurationVar(&c.Meta.stateLockTimeout, "lock-timeout", 0, "lock timeout") @@ -103,6 +107,8 @@ func (c *InitCommand) Run(args []string) int { return 0 } + var back backend.Backend + // If we're performing a get or loading the backend, then we perform // some extra tasks. if flagGet || flagBackend { @@ -125,10 +131,12 @@ func (c *InitCommand) Run(args []string) int { "Error downloading modules: %s", err)) return 1 } + } - // If we're requesting backend configuration and configure it - if flagBackend { + // If we're requesting backend configuration or looking for required + // plugins, load the backend + if flagBackend || flagGetPlugins { header = true // Only output that we're initializing a backend if we have @@ -145,13 +153,36 @@ func (c *InitCommand) Run(args []string) int { ConfigExtra: flagConfigExtra, Init: true, } - if _, err := c.Backend(opts); err != nil { + if back, err = c.Backend(opts); err != nil { c.Ui.Error(err.Error()) return 1 } } } + // Now that we have loaded all modules, check the module tree for missing providers + if flagGetPlugins { + sMgr, err := back.State(c.Env()) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error loading state: %s", err)) + return 1 + } + + if err := sMgr.RefreshState(); err != nil { + c.Ui.Error(fmt.Sprintf( + "Error refreshing state: %s", err)) + return 1 + } + + err = c.getProviders(path, sMgr.State()) + if err != nil { + c.Ui.Error(fmt.Sprintf( + "Error getting plugins: %s", err)) + return 1 + } + } + // If we outputted information, then we need to output a newline // so that our success message is nicely spaced out from prior text. if header { @@ -163,6 +194,31 @@ func (c *InitCommand) Run(args []string) int { return 0 } +// load the complete module tree, and fetch any missing providers +func (c *InitCommand) getProviders(path string, state *terraform.State) error { + mod, err := c.Module(path) + if err != nil { + return err + } + + if err := mod.Validate(); err != nil { + return err + } + + requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() + missing := c.missingProviders(requirements) + + dst := c.pluginDir() + for provider, reqd := range missing { + err := discovery.GetProvider(dst, provider, reqd) + // TODO: return all errors + if err != nil { + return err + } + } + return nil +} + func (c *InitCommand) copySource(dst, src, pwd string) error { // Verify the directory is empty if empty, err := config.IsEmptyDir(dst); err != nil { @@ -226,6 +282,8 @@ Options: -get=true Download any modules for this configuration. + -get-plugins=true Download any missing plugins for this configuration. + -input=true Ask for input if necessary. If false, will error if input was required. diff --git a/command/plugins.go b/command/plugins.go index a0f94bc1fb..b36f529290 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -40,7 +40,9 @@ func (r *multiVersionProviderResolver) ResolveProviders( return factories, errs } -func (m *Meta) providerResolver() terraform.ResourceProviderResolver { +// providerPluginSet returns the set of valid providers that were discovered in +// the defined search paths. +func (m *Meta) providerPluginSet() discovery.PluginMetaSet { var dirs []string // When searching the following directories, earlier entries get precedence @@ -54,11 +56,30 @@ func (m *Meta) providerResolver() terraform.ResourceProviderResolver { plugins := discovery.FindPlugins("provider", dirs) plugins, _ = plugins.ValidateVersions() + return plugins +} + +func (m *Meta) providerResolver() terraform.ResourceProviderResolver { return &multiVersionProviderResolver{ - Available: plugins, + Available: m.providerPluginSet(), } } +// filter the requirements returning only the providers that we can't resolve +func (m *Meta) missingProviders(reqd discovery.PluginRequirements) discovery.PluginRequirements { + missing := make(discovery.PluginRequirements) + + candidates := m.providerPluginSet().ConstrainVersions(reqd) + + for name, versionSet := range reqd { + if metas := candidates[name]; metas == nil { + missing[name] = versionSet + } + } + + return missing +} + func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { var dirs []string diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go new file mode 100644 index 0000000000..779a02e389 --- /dev/null +++ b/plugin/discovery/get.go @@ -0,0 +1,170 @@ +package discovery + +import ( + "errors" + "fmt" + "io/ioutil" + "log" + "net/http" + "runtime" + "strings" + + "golang.org/x/net/html" + + getter "github.com/hashicorp/go-getter" +) + +const releasesURL = "https://releases.hashicorp.com/" + +// pluginURL generates URLs to lookup the versions of a plugin, or the file path. +// +// The URL for releases follows the pattern: +// https://releases.hashicorp.com/terraform-providers/terraform-provider-name/ + +// terraform-provider-name_/terraform-provider-name___. +// +// The name prefix common to all plugins of this type. +// This is either `terraform-provider` or `terraform-provisioner`. +type pluginBaseName string + +// base returns the top level directory for all plugins of this type +func (p pluginBaseName) base() string { + // the top level directory is the plural form of the plugin type + return releasesURL + string(p) + "s" +} + +// versions returns the url to the directory to list available versions for this plugin +func (p pluginBaseName) versions(name string) string { + return fmt.Sprintf("%s/%s-%s", p.base(), p, name) +} + +// file returns the full path to a plugin based on the plugin name, +// version, GOOS and GOARCH. +func (p pluginBaseName) file(name, version string) string { + releasesDir := fmt.Sprintf("%s-%s_%s/", p, name, version) + fileName := fmt.Sprintf("%s-%s_%s_%s_%s.zip", p, name, version, runtime.GOOS, runtime.GOARCH) + return fmt.Sprintf("%s/%s/%s", p.versions(name), releasesDir, fileName) +} + +var providersURL = pluginBaseName("terraform-provider") +var provisionersURL = pluginBaseName("terraform-provisioners") + +// GetProvider fetches a provider plugin based on the version constraints, and +// copies it to the dst directory. +// +// TODO: verify checksum and signature +func GetProvider(dst, provider string, req Constraints) error { + versions, err := listProviderVersions(provider) + // TODO: return multiple errors + if err != nil { + return err + } + + version := newestVersion(versions, req) + if version == nil { + return fmt.Errorf("no version of %q available that fulfills constraints %s", provider, req) + } + + url := providersURL.file(provider, version.String()) + + log.Printf("[DEBUG] getting provider %q version %q at %s", provider, version, url) + return getter.Get(dst, url) +} + +// take the list of available versions for a plugin, and the required +// Constraints, and return the latest available version that satisfies the +// constraints. +func newestVersion(available []*Version, required Constraints) *Version { + var latest *Version + for _, v := range available { + if required.Has(*v) { + if latest == nil { + latest = v + continue + } + + if v.NewerThan(*latest) { + latest = v + } + } + } + + return latest +} + +// list the version available for the named plugin +func listProviderVersions(name string) ([]*Version, error) { + versions, err := listPluginVersions(providersURL.versions(name)) + if err != nil { + return nil, fmt.Errorf("failed to fetch versions for provider %q: %s", name, err) + } + return versions, nil +} + +func listProvisionerVersions(name string) ([]*Version, error) { + versions, err := listPluginVersions(provisionersURL.versions(name)) + if err != nil { + return nil, fmt.Errorf("failed to fetch versions for provisioner %q: %s", name, err) + } + + return versions, nil +} + +// return a list of the plugin versions at the given URL +// TODO: This doesn't yet take into account plugin protocol version. +// That may have to be checked via an http header via a separate request +// to each plugin file. +func listPluginVersions(url string) ([]*Version, error) { + resp, err := http.Get(url) + if err != nil { + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + body, _ := ioutil.ReadAll(resp.Body) + log.Printf("[ERROR] failed to fetch plugin versions from %s\n%s\n%s", url, resp.Status, body) + return nil, errors.New(resp.Status) + } + + body, err := html.Parse(resp.Body) + if err != nil { + log.Fatal(err) + } + + names := []string{} + + // all we need to do is list links on the directory listing page that look like plugins + var f func(*html.Node) + f = func(n *html.Node) { + if n.Type == html.ElementNode && n.Data == "a" { + c := n.FirstChild + if c != nil && c.Type == html.TextNode && strings.HasPrefix(c.Data, "terraform-") { + names = append(names, c.Data) + fmt.Println(c.Data) + return + } + } + for c := n.FirstChild; c != nil; c = c.NextSibling { + f(c) + } + } + f(body) + + var versions []*Version + + for _, name := range names { + parts := strings.SplitN(name, "_", 2) + if len(parts) == 2 && parts[1] != "" { + v, err := VersionStr(parts[1]).Parse() + if err != nil { + // filter invalid versions scraped from the page + log.Printf("[WARN] invalid version found for %q: %s", name, err) + continue + } + + versions = append(versions, &v) + } + } + + return versions, nil +} diff --git a/plugin/getter/get.go b/plugin/getter/get.go deleted file mode 100644 index e69de29bb2..0000000000 From 66ebff90cdfaa6938f26f908c7ebad8d547fea17 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 3 May 2017 22:24:51 -0400 Subject: [PATCH 34/82] move some more plugin search path logic to command Make less to change when we remove the old search path --- command/plugins.go | 54 +++++++++++++++++++++++++++++----------------- plugins.go | 12 ----------- 2 files changed, 34 insertions(+), 32 deletions(-) diff --git a/command/plugins.go b/command/plugins.go index b36f529290..dfd168dbde 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -4,12 +4,15 @@ import ( "fmt" "log" "os/exec" + "path/filepath" + "runtime" "strings" plugin "github.com/hashicorp/go-plugin" tfplugin "github.com/hashicorp/terraform/plugin" "github.com/hashicorp/terraform/plugin/discovery" "github.com/hashicorp/terraform/terraform" + "github.com/kardianos/osext" ) // multiVersionProviderResolver is an implementation of @@ -40,20 +43,43 @@ func (r *multiVersionProviderResolver) ResolveProviders( return factories, errs } -// providerPluginSet returns the set of valid providers that were discovered in -// the defined search paths. -func (m *Meta) providerPluginSet() discovery.PluginMetaSet { - var dirs []string +// the default location for automatically installed plugins +func (m *Meta) pluginDir() string { + return filepath.Join(m.DataDir(), "plugins", fmt.Sprintf("%s_%s", runtime.GOOS, runtime.GOARCH)) +} + +// pluginDirs return a list of directories to search for plugins. +// +// Earlier entries in this slice get priority over later when multiple copies +// of the same plugin version are found, but newer versions always override +// older versions where both satisfy the provider version constraints. +func (m *Meta) pluginDirs() []string { // When searching the following directories, earlier entries get precedence // if the same plugin version is found twice, but newer versions will // always get preference below regardless of where they are coming from. // TODO: Add auto-install dir, default vendor dir and optional override // vendor dir(s). - dirs = append(dirs, ".") - dirs = append(dirs, m.GlobalPluginDirs...) + dirs := []string{"."} - plugins := discovery.FindPlugins("provider", dirs) + // Look in the same directory as the Terraform executable. + // If found, this replaces what we found in the config path. + exePath, err := osext.Executable() + if err != nil { + log.Printf("[ERROR] Error discovering exe directory: %s", err) + } else { + dirs = append(dirs, filepath.Dir(exePath)) + } + + dirs = append(dirs, m.pluginDir()) + dirs = append(dirs, m.GlobalPluginDirs...) + return dirs +} + +// providerPluginSet returns the set of valid providers that were discovered in +// the defined search paths. +func (m *Meta) providerPluginSet() discovery.PluginMetaSet { + plugins := discovery.FindPlugins("provider", m.pluginDirs()) plugins, _ = plugins.ValidateVersions() return plugins @@ -81,19 +107,7 @@ func (m *Meta) missingProviders(reqd discovery.PluginRequirements) discovery.Plu } func (m *Meta) provisionerFactories() map[string]terraform.ResourceProvisionerFactory { - var dirs []string - - // When searching the following directories, earlier entries get precedence - // if the same plugin version is found twice, but newer versions will - // always get preference below regardless of where they are coming from. - // - // NOTE: Currently we don't use versioning for provisioners, so the - // version handling here is just the minimum required to be able to use - // the plugin discovery package. All provisioner plugins should always - // be versionless, which we treat as version 0.0.0 here. - dirs = append(dirs, ".") - dirs = append(dirs, m.GlobalPluginDirs...) - + dirs := m.pluginDirs() plugins := discovery.FindPlugins("provisioner", dirs) plugins, _ = plugins.ValidateVersions() diff --git a/plugins.go b/plugins.go index 9717724a0a..bf23978062 100644 --- a/plugins.go +++ b/plugins.go @@ -3,8 +3,6 @@ package main import ( "log" "path/filepath" - - "github.com/kardianos/osext" ) // globalPluginDirs returns directories that should be searched for @@ -15,16 +13,6 @@ import ( // older versions where both satisfy the provider version constraints. func globalPluginDirs() []string { var ret []string - - // Look in the same directory as the Terraform executable. - // If found, this replaces what we found in the config path. - exePath, err := osext.Executable() - if err != nil { - log.Printf("[ERROR] Error discovering exe directory: %s", err) - } else { - ret = append(ret, filepath.Dir(exePath)) - } - // Look in ~/.terraform.d/plugins/ , or its equivalent on non-UNIX dir, err := ConfigDir() if err != nil { From 4c32cd432a3af47e53b2bbdb76e7dd0fce4ae471 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 4 May 2017 13:01:05 -0400 Subject: [PATCH 35/82] make getProvider pluggable --- command/init.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/command/init.go b/command/init.go index 7008bb6e0c..b3d816c31d 100644 --- a/command/init.go +++ b/command/init.go @@ -19,6 +19,12 @@ import ( // module and clones it to the working directory. type InitCommand struct { Meta + + // getProvider fetches providers that aren't found locally, and unpacks + // them into the dst directory. + // This uses discovery.GetProvider by default, but it provided here as a + // way to mock fetching providers for tests. + getProvider func(dst, provider string, req discovery.Constraints) error } func (c *InitCommand) Run(args []string) int { @@ -41,6 +47,11 @@ func (c *InitCommand) Run(args []string) int { return 1 } + // set getProvider if we don't have a test version already + if c.getProvider == nil { + c.getProvider = discovery.GetProvider + } + // Validate the arg count args = cmdFlags.Args() if len(args) > 2 { @@ -210,7 +221,7 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { dst := c.pluginDir() for provider, reqd := range missing { - err := discovery.GetProvider(dst, provider, reqd) + err := c.getProvider(dst, provider, reqd) // TODO: return all errors if err != nil { return err From 2994c6ac5d7a71a5aa38a92a8f3a4a4ed6585b0e Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 4 May 2017 14:03:57 -0400 Subject: [PATCH 36/82] add init getPlugin test add a mock plugin getter, and test that we can fetch requested version of the plugins. --- command/init_test.go | 84 +++++++++++++++++++ command/plugins_test.go | 56 +++++++++++++ .../test-fixtures/init-get-providers/main.tf | 11 +++ 3 files changed, 151 insertions(+) create mode 100644 command/plugins_test.go create mode 100644 command/test-fixtures/init-get-providers/main.tf diff --git a/command/init_test.go b/command/init_test.go index 5cc5c008e8..a95a5155b3 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -534,6 +534,90 @@ func TestInit_inputFalse(t *testing.T) { } } +func TestInit_getProvider(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-get-providers"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + getter := &mockGetProvider{ + Providers: map[string][]string{ + // looking for an exact version + "exact": []string{"1.2.3"}, + // config requires >= 2.3.3 + "greater_than": []string{"2.3.4", "2.3.3", "2.3.0"}, + // config specifies + "between": []string{"3.4.5", "2.3.4", "1.2.3"}, + }, + } + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + getProvider: getter.GetProvider, + } + + args := []string{} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + // check that we got the providers for our config + exactPath := filepath.Join(c.pluginDir(), getter.FileName("exact", "1.2.3")) + if _, err := os.Stat(exactPath); os.IsNotExist(err) { + t.Fatal("provider 'exact' not downloaded") + } + greaterThanPath := filepath.Join(c.pluginDir(), getter.FileName("greater_than", "2.3.4")) + if _, err := os.Stat(greaterThanPath); os.IsNotExist(err) { + t.Fatal("provider 'greater_than' not downloaded") + } + betweenPath := filepath.Join(c.pluginDir(), getter.FileName("between", "2.3.4")) + if _, err := os.Stat(betweenPath); os.IsNotExist(err) { + t.Fatal("provider 'between' not downloaded") + } +} + +func TestInit_getProviderMissing(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-get-providers"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + getter := &mockGetProvider{ + Providers: map[string][]string{ + // looking for exact version 1.2.3 + "exact": []string{"1.2.4"}, + // config requires >= 2.3.3 + "greater_than": []string{"2.3.4", "2.3.3", "2.3.0"}, + // config specifies + "between": []string{"3.4.5", "2.3.4", "1.2.3"}, + }, + } + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + getProvider: getter.GetProvider, + } + + args := []string{} + if code := c.Run(args); code == 0 { + t.Fatalf("expceted error, got output: \n%s", ui.OutputWriter.String()) + } + + if !strings.Contains(ui.ErrorWriter.String(), "no suitable version for provider") { + t.Fatalf("unexpected error output: %s", ui.ErrorWriter) + } +} + /* func TestInit_remoteState(t *testing.T) { tmp, cwd := testCwd(t) diff --git a/command/plugins_test.go b/command/plugins_test.go new file mode 100644 index 0000000000..6c065b450b --- /dev/null +++ b/command/plugins_test.go @@ -0,0 +1,56 @@ +package command + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/hashicorp/terraform/plugin/discovery" +) + +// mockGetProvider providers a GetProvider method for testing automatic +// provider downloads +type mockGetProvider struct { + // A map of provider names to available versions. + // The tests expect the versions to be in order from newest to oldest. + Providers map[string][]string +} + +func (m mockGetProvider) FileName(provider, version string) string { + return fmt.Sprintf("terraform-provider-%s-V%s-X4", provider, version) +} + +// GetProvider will check the Providers map to see if it can find a suitable +// version, and put an empty file in the dst directory. +func (m mockGetProvider) GetProvider(dst, provider string, req discovery.Constraints) error { + versions := m.Providers[provider] + if len(versions) == 0 { + return fmt.Errorf("provider %q not found", provider) + } + + err := os.MkdirAll(dst, 0755) + if err != nil { + return fmt.Errorf("error creating plugins directory: %s", err) + } + + for _, v := range versions { + version, err := discovery.VersionStr(v).Parse() + if err != nil { + panic(err) + } + + if req.Has(version) { + // provider filename + name := m.FileName(provider, v) + path := filepath.Join(dst, name) + f, err := os.Create(path) + if err != nil { + return fmt.Errorf("error fetching provider: %s", err) + } + f.Close() + return nil + } + } + + return fmt.Errorf("no suitable version for provider %q found with constraints %s", provider, req) +} diff --git a/command/test-fixtures/init-get-providers/main.tf b/command/test-fixtures/init-get-providers/main.tf new file mode 100644 index 0000000000..fdf27f3292 --- /dev/null +++ b/command/test-fixtures/init-get-providers/main.tf @@ -0,0 +1,11 @@ +provider "exact" { + version = "1.2.3" +} + +provider "greater_than" { + version = ">= 2.3.3" +} + +provider "between" { + version = "> 1.0.0 , < 3.0.0" +} From a547e7c2f0d15b7014f492bc50137d45953254c0 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 4 May 2017 14:32:18 -0400 Subject: [PATCH 37/82] add releases version listing test --- plugin/discovery/get.go | 51 +++++++++++++++++------------- plugin/discovery/get_test.go | 61 ++++++++++++++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 21 deletions(-) create mode 100644 plugin/discovery/get_test.go diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index 779a02e389..e9bc927a80 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -21,32 +21,42 @@ const releasesURL = "https://releases.hashicorp.com/" // The URL for releases follows the pattern: // https://releases.hashicorp.com/terraform-providers/terraform-provider-name/ + // terraform-provider-name_/terraform-provider-name___. -// -// The name prefix common to all plugins of this type. -// This is either `terraform-provider` or `terraform-provisioner`. -type pluginBaseName string +type pluginURL struct { + // the base url to search for releases + releases string + // The name prefix common to all plugins of this type. + // This is either `terraform-provider` or `terraform-provisioner`. + baseName string +} -// base returns the top level directory for all plugins of this type -func (p pluginBaseName) base() string { +// releasesURL returns the top level directory for all plugins of this type +func (p pluginURL) releasesURL() string { // the top level directory is the plural form of the plugin type - return releasesURL + string(p) + "s" + return p.releases + p.baseName + "s" } -// versions returns the url to the directory to list available versions for this plugin -func (p pluginBaseName) versions(name string) string { - return fmt.Sprintf("%s/%s-%s", p.base(), p, name) +// versionsURL returns the url to the directory to list available versionsURL for this plugin +func (p pluginURL) versionsURL(name string) string { + return fmt.Sprintf("%s/%s-%s", p.releasesURL(), p.baseName, name) } -// file returns the full path to a plugin based on the plugin name, +// fileURL returns the full path to a plugin based on the plugin name, // version, GOOS and GOARCH. -func (p pluginBaseName) file(name, version string) string { - releasesDir := fmt.Sprintf("%s-%s_%s/", p, name, version) - fileName := fmt.Sprintf("%s-%s_%s_%s_%s.zip", p, name, version, runtime.GOOS, runtime.GOARCH) - return fmt.Sprintf("%s/%s/%s", p.versions(name), releasesDir, fileName) +func (p pluginURL) fileURL(name, version string) string { + releasesDir := fmt.Sprintf("%s-%s_%s/", p.baseName, name, version) + fileName := fmt.Sprintf("%s-%s_%s_%s_%s.zip", p.baseName, name, version, runtime.GOOS, runtime.GOARCH) + return fmt.Sprintf("%s/%s/%s", p.versionsURL(name), releasesDir, fileName) } -var providersURL = pluginBaseName("terraform-provider") -var provisionersURL = pluginBaseName("terraform-provisioners") +var providersURL = pluginURL{ + releases: releasesURL, + baseName: "terraform-provider", +} + +var provisionersURL = pluginURL{ + releases: releasesURL, + baseName: "terraform-provisioners", +} // GetProvider fetches a provider plugin based on the version constraints, and // copies it to the dst directory. @@ -64,7 +74,7 @@ func GetProvider(dst, provider string, req Constraints) error { return fmt.Errorf("no version of %q available that fulfills constraints %s", provider, req) } - url := providersURL.file(provider, version.String()) + url := providersURL.fileURL(provider, version.String()) log.Printf("[DEBUG] getting provider %q version %q at %s", provider, version, url) return getter.Get(dst, url) @@ -93,7 +103,7 @@ func newestVersion(available []*Version, required Constraints) *Version { // list the version available for the named plugin func listProviderVersions(name string) ([]*Version, error) { - versions, err := listPluginVersions(providersURL.versions(name)) + versions, err := listPluginVersions(providersURL.versionsURL(name)) if err != nil { return nil, fmt.Errorf("failed to fetch versions for provider %q: %s", name, err) } @@ -101,7 +111,7 @@ func listProviderVersions(name string) ([]*Version, error) { } func listProvisionerVersions(name string) ([]*Version, error) { - versions, err := listPluginVersions(provisionersURL.versions(name)) + versions, err := listPluginVersions(provisionersURL.versionsURL(name)) if err != nil { return nil, fmt.Errorf("failed to fetch versions for provisioner %q: %s", name, err) } @@ -140,7 +150,6 @@ func listPluginVersions(url string) ([]*Version, error) { c := n.FirstChild if c != nil && c.Type == html.TextNode && strings.HasPrefix(c.Data, "terraform-") { names = append(names, c.Data) - fmt.Println(c.Data) return } } diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go new file mode 100644 index 0000000000..21fd931d35 --- /dev/null +++ b/plugin/discovery/get_test.go @@ -0,0 +1,61 @@ +package discovery + +import ( + "net/http" + "net/http/httptest" + "testing" +) + +func TestVersionListing(t *testing.T) { + handler := http.NewServeMux() + handler.HandleFunc("/terraform-providers/terraform-provider-test/", func(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(versionList)) + }) + + server := httptest.NewServer(handler) + defer server.Close() + + providersURL.releases = server.URL + "/" + + versions, err := listProviderVersions("test") + if err != nil { + t.Fatal(err) + } + + expectedSet := map[string]bool{ + "1.2.4": true, + "1.2.3": true, + "1.2.1": true, + } + + for _, v := range versions { + if !expectedSet[v.String()] { + t.Fatalf("didn't get version %s in listing", v) + } + delete(expectedSet, v.String()) + } +} + +const versionList = ` + + + +
+ Proudly fronted by Fastly +
+ + +` From 46190590cbfe4e7ede36e1434b3741bcad990f56 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 4 May 2017 14:37:16 -0400 Subject: [PATCH 38/82] change []*Version to []Version Versions are used as values, so don't keep them as pointers here --- plugin/discovery/get.go | 34 +++++++++++++++++++++------------- 1 file changed, 21 insertions(+), 13 deletions(-) diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index e9bc927a80..c53dd0ff59 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -69,8 +69,8 @@ func GetProvider(dst, provider string, req Constraints) error { return err } - version := newestVersion(versions, req) - if version == nil { + version, err := newestVersion(versions, req) + if err != nil { return fmt.Errorf("no version of %q available that fulfills constraints %s", provider, req) } @@ -80,29 +80,37 @@ func GetProvider(dst, provider string, req Constraints) error { return getter.Get(dst, url) } +var errVersionNotFound = errors.New("version not found") + // take the list of available versions for a plugin, and the required // Constraints, and return the latest available version that satisfies the // constraints. -func newestVersion(available []*Version, required Constraints) *Version { - var latest *Version +func newestVersion(available []Version, required Constraints) (Version, error) { + var latest Version + found := false + for _, v := range available { - if required.Has(*v) { - if latest == nil { + if required.Has(v) { + if !found { latest = v + found = true continue } - if v.NewerThan(*latest) { + if v.NewerThan(latest) { latest = v } } } - return latest + if !found { + return latest, errVersionNotFound + } + return latest, nil } // list the version available for the named plugin -func listProviderVersions(name string) ([]*Version, error) { +func listProviderVersions(name string) ([]Version, error) { versions, err := listPluginVersions(providersURL.versionsURL(name)) if err != nil { return nil, fmt.Errorf("failed to fetch versions for provider %q: %s", name, err) @@ -110,7 +118,7 @@ func listProviderVersions(name string) ([]*Version, error) { return versions, nil } -func listProvisionerVersions(name string) ([]*Version, error) { +func listProvisionerVersions(name string) ([]Version, error) { versions, err := listPluginVersions(provisionersURL.versionsURL(name)) if err != nil { return nil, fmt.Errorf("failed to fetch versions for provisioner %q: %s", name, err) @@ -123,7 +131,7 @@ func listProvisionerVersions(name string) ([]*Version, error) { // TODO: This doesn't yet take into account plugin protocol version. // That may have to be checked via an http header via a separate request // to each plugin file. -func listPluginVersions(url string) ([]*Version, error) { +func listPluginVersions(url string) ([]Version, error) { resp, err := http.Get(url) if err != nil { return nil, err @@ -159,7 +167,7 @@ func listPluginVersions(url string) ([]*Version, error) { } f(body) - var versions []*Version + var versions []Version for _, name := range names { parts := strings.SplitN(name, "_", 2) @@ -171,7 +179,7 @@ func listPluginVersions(url string) ([]*Version, error) { continue } - versions = append(versions, &v) + versions = append(versions, v) } } From 211f5b5d6e3d32ec876de4dfe14c83b2bcada7f1 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 4 May 2017 14:46:20 -0400 Subject: [PATCH 39/82] add test for newestVersion --- plugin/discovery/get_test.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go index 21fd931d35..23ba4af6a5 100644 --- a/plugin/discovery/get_test.go +++ b/plugin/discovery/get_test.go @@ -36,6 +36,41 @@ func TestVersionListing(t *testing.T) { } } +func TestNewestVersion(t *testing.T) { + var available []Version + for _, v := range []string{"1.2.3", "1.2.1", "1.2.4"} { + version, err := VersionStr(v).Parse() + if err != nil { + t.Fatal(err) + } + available = append(available, version) + } + + reqd, err := ConstraintStr(">1.2.1").Parse() + if err != nil { + t.Fatal(err) + } + + found, err := newestVersion(available, reqd) + if err != nil { + t.Fatal(err) + } + + if found.String() != "1.2.4" { + t.Fatalf("expected newest version 1.2.4, got: %s", found) + } + + reqd, err = ConstraintStr("> 1.2.4").Parse() + if err != nil { + t.Fatal(err) + } + + found, err = newestVersion(available, reqd) + if err == nil { + t.Fatalf("expceted error, got version %s", found) + } +} + const versionList = ` From 044ad5ef595932949dcfa1cf9f330949f258dd50 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 4 May 2017 18:53:02 -0400 Subject: [PATCH 40/82] rename some Constraints methods per code review --- command/plugins_test.go | 2 +- moduledeps/module.go | 2 +- plugin/discovery/get.go | 2 +- plugin/discovery/meta_set.go | 2 +- plugin/discovery/requirements.go | 2 +- plugin/discovery/version_set.go | 8 ++++---- plugin/discovery/version_set_test.go | 2 +- 7 files changed, 10 insertions(+), 10 deletions(-) diff --git a/command/plugins_test.go b/command/plugins_test.go index 6c065b450b..74544a3b16 100644 --- a/command/plugins_test.go +++ b/command/plugins_test.go @@ -39,7 +39,7 @@ func (m mockGetProvider) GetProvider(dst, provider string, req discovery.Constra panic(err) } - if req.Has(version) { + if req.Allows(version) { // provider filename name := m.FileName(provider, v) path := filepath.Join(dst, name) diff --git a/moduledeps/module.go b/moduledeps/module.go index d09c19738e..0e446108fe 100644 --- a/moduledeps/module.go +++ b/moduledeps/module.go @@ -114,7 +114,7 @@ func (m *Module) PluginRequirements() discovery.PluginRequirements { // by using Intersection to merge the version sets. pty := inst.Type() if existing, exists := ret[pty]; exists { - ret[pty] = existing.Intersection(dep.Constraints) + ret[pty] = existing.Append(dep.Constraints) } else { ret[pty] = dep.Constraints } diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index c53dd0ff59..bb8afae278 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -90,7 +90,7 @@ func newestVersion(available []Version, required Constraints) (Version, error) { found := false for _, v := range available { - if required.Has(v) { + if required.Allows(v) { if !found { latest = v found = true diff --git a/plugin/discovery/meta_set.go b/plugin/discovery/meta_set.go index 9fc0abb094..c5102d98f9 100644 --- a/plugin/discovery/meta_set.go +++ b/plugin/discovery/meta_set.go @@ -139,7 +139,7 @@ func (s PluginMetaSet) ConstrainVersions(reqd PluginRequirements) map[string]Plu if err != nil { panic(err) } - if allowedVersions.Has(version) { + if allowedVersions.Allows(version) { ret[p.Name].Add(p) } } diff --git a/plugin/discovery/requirements.go b/plugin/discovery/requirements.go index 4f0857aa69..1f58821e45 100644 --- a/plugin/discovery/requirements.go +++ b/plugin/discovery/requirements.go @@ -17,7 +17,7 @@ func (r PluginRequirements) Merge(other PluginRequirements) PluginRequirements { } for n, vs := range other { if existing, exists := ret[n]; exists { - ret[n] = existing.Intersection(vs) + ret[n] = existing.Append(vs) } else { ret[n] = vs } diff --git a/plugin/discovery/version_set.go b/plugin/discovery/version_set.go index 73eae75914..e1b0b1e2c1 100644 --- a/plugin/discovery/version_set.go +++ b/plugin/discovery/version_set.go @@ -43,15 +43,15 @@ func init() { } } -// Has returns true if the given version is in the receiving set. -func (s Constraints) Has(v Version) bool { +// Allows returns true if the given version is in the receiving set. +func (s Constraints) Allows(v Version) bool { return s.raw.Check(v.raw) } -// Intersection combines the receiving set with the given other set to produce +// Append combines the receiving set with the given other set to produce // a set that is the intersection of both sets, which is to say that resulting // constraints contain only the versions that are members of both. -func (s Constraints) Intersection(other Constraints) Constraints { +func (s Constraints) Append(other Constraints) Constraints { raw := make(version.Constraints, 0, len(s.raw)+len(other.raw)) // Since "raw" is a list of constraints that remove versions from the set, diff --git a/plugin/discovery/version_set_test.go b/plugin/discovery/version_set_test.go index ecd3b12d0a..ed2c4f1b76 100644 --- a/plugin/discovery/version_set_test.go +++ b/plugin/discovery/version_set_test.go @@ -56,7 +56,7 @@ func TestVersionSet(t *testing.T) { t.Fatalf("unwanted error parsing version string %q: %s", test.VersionStr, err) } - if got, want := accepted.Has(version), test.ShouldHave; got != want { + if got, want := accepted.Allows(version), test.ShouldHave; got != want { t.Errorf("Has returned %#v; want %#v", got, want) } }) From 05a5eb0047160b417f6b30e27a9ac5017c64e46f Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:01:52 -0700 Subject: [PATCH 41/82] core: ResourceAddress.HasResourceSpec method The resource address documentation defines a resource address as being in two parts: the module path and the resource spec. The resource spec can be omitted, which represents addressing _all_ resources in a module. In some cases (such as import) it doesn't make sense to address an entire module, so this helper makes it easy for validation code to check for this to reject insufficiently-specific resource addresses. --- terraform/resource_address.go | 9 +++++ terraform/resource_address_test.go | 57 ++++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) diff --git a/terraform/resource_address.go b/terraform/resource_address.go index a8a0c95530..a096c27061 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -89,6 +89,15 @@ func (r *ResourceAddress) String() string { return strings.Join(result, ".") } +// HasResourceSpec returns true if the address has a resource spec, as +// defined in the documentation: +// https://www.terraform.io/docs/internals/resource-addressing.html +// In particular, this returns false if the address contains only +// a module path, thus addressing the entire module. +func (r *ResourceAddress) HasResourceSpec() bool { + return r.Type != "" && r.Name != "" +} + // stateId returns the ID that this resource should be entered with // in the state. This is also used for diffs. In the future, we'd like to // move away from this string field so I don't export this. diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index 2219bc8663..3282b5289c 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -628,3 +628,60 @@ func TestResourceAddressStateId(t *testing.T) { }) } } + +func TestResourceAddressHasResourceSpec(t *testing.T) { + cases := []struct { + Input string + Want bool + }{ + { + "module.foo", + false, + }, + { + "module.foo.module.bar", + false, + }, + { + "null_resource.baz", + true, + }, + { + "null_resource.baz[0]", + true, + }, + { + "data.null_data_source.baz", + true, + }, + { + "data.null_data_source.baz[0]", + true, + }, + { + "module.foo.null_resource.baz", + true, + }, + { + "module.foo.data.null_data_source.baz", + true, + }, + { + "module.foo.module.bar.null_resource.baz", + true, + }, + } + + for _, test := range cases { + t.Run(test.Input, func(t *testing.T) { + addr, err := ParseResourceAddress(test.Input) + if err != nil { + t.Fatalf("error parsing address: %s", err) + } + got := addr.HasResourceSpec() + if got != test.Want { + t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) + } + }) + } +} From edf3cd7159ef4762a010dca1a50f929abb086458 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:03:54 -0700 Subject: [PATCH 42/82] core: ResourceAddress.WholeModuleAddress method This allows growing the scope of a resource address to include all of the resources in the same module as the targeted resource. This is useful to give context in error messages. --- terraform/resource_address.go | 10 ++++++ terraform/resource_address_test.go | 58 ++++++++++++++++++++++++++++++ 2 files changed, 68 insertions(+) diff --git a/terraform/resource_address.go b/terraform/resource_address.go index a096c27061..06fbced8d4 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -98,6 +98,16 @@ func (r *ResourceAddress) HasResourceSpec() bool { return r.Type != "" && r.Name != "" } +// WholeModuleAddress returns the resource address that refers to all +// resources in the same module as the receiver address. +func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress { + return &ResourceAddress{ + Path: r.Path, + Index: -1, + InstanceTypeSet: false, + } +} + // stateId returns the ID that this resource should be entered with // in the state. This is also used for diffs. In the future, we'd like to // move away from this string field so I don't export this. diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index 3282b5289c..bcf871c5e8 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -685,3 +685,61 @@ func TestResourceAddressHasResourceSpec(t *testing.T) { }) } } + +func TestResourceAddressWholeModuleAddress(t *testing.T) { + cases := []struct { + Input string + Want string + }{ + { + "module.foo", + "module.foo", + }, + { + "module.foo.module.bar", + "module.foo.module.bar", + }, + { + "null_resource.baz", + "", + }, + { + "null_resource.baz[0]", + "", + }, + { + "data.null_data_source.baz", + "", + }, + { + "data.null_data_source.baz[0]", + "", + }, + { + "module.foo.null_resource.baz", + "module.foo", + }, + { + "module.foo.data.null_data_source.baz", + "module.foo", + }, + { + "module.foo.module.bar.null_resource.baz", + "module.foo.module.bar", + }, + } + + for _, test := range cases { + t.Run(test.Input, func(t *testing.T) { + addr, err := ParseResourceAddress(test.Input) + if err != nil { + t.Fatalf("error parsing address: %s", err) + } + gotAddr := addr.WholeModuleAddress() + got := gotAddr.String() + if got != test.Want { + t.Fatalf("%q: wrong result %#v; want %#v", test.Input, got, test.Want) + } + }) + } +} From f695e8b330f8d88b291477df916ad5e8c11c00a1 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:05:01 -0700 Subject: [PATCH 43/82] provider/test: allow test_resource to be imported When working on the core import code, it's useful to have a zero-cost local resource to work with for quick iteration. --- builtin/providers/test/resource.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/builtin/providers/test/resource.go b/builtin/providers/test/resource.go index 64571009b4..c0e742acd4 100644 --- a/builtin/providers/test/resource.go +++ b/builtin/providers/test/resource.go @@ -12,6 +12,11 @@ func testResource() *schema.Resource { Read: testResourceRead, Update: testResourceUpdate, Delete: testResourceDelete, + + Importer: &schema.ResourceImporter{ + State: schema.ImportStatePassthrough, + }, + Schema: map[string]*schema.Schema{ "required": { Type: schema.TypeString, From b82ef2e30e022fd8d1ccd5eba44cf7dfdff5d168 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:06:33 -0700 Subject: [PATCH 44/82] core: ResourceAddress.MatchesConfig method This is a useful building block for filtering configuration based on a resource address. It is similar in principle to state filtering, but for specific resource configuration blocks. --- terraform/resource_address.go | 27 +++ terraform/resource_address_test.go | 178 ++++++++++++++++++ .../empty-with-child-module/child/child.tf | 3 + .../grandchild/grandchild.tf | 1 + .../empty-with-child-module/root.tf | 3 + 5 files changed, 212 insertions(+) create mode 100644 terraform/test-fixtures/empty-with-child-module/child/child.tf create mode 100644 terraform/test-fixtures/empty-with-child-module/grandchild/grandchild.tf create mode 100644 terraform/test-fixtures/empty-with-child-module/root.tf diff --git a/terraform/resource_address.go b/terraform/resource_address.go index 06fbced8d4..ca3f61b381 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -8,6 +8,7 @@ import ( "strings" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" ) // ResourceAddress is a way of identifying an individual resource (or, @@ -108,6 +109,32 @@ func (r *ResourceAddress) WholeModuleAddress() *ResourceAddress { } } +// MatchesConfig returns true if the receiver matches the given +// configuration resource within the given configuration module. +// +// Since resource configuration blocks represent all of the instances of +// a multi-instance resource, the index of the address (if any) is not +// considered. +func (r *ResourceAddress) MatchesConfig(mod *module.Tree, rc *config.Resource) bool { + if r.HasResourceSpec() { + if r.Mode != rc.Mode || r.Type != rc.Type || r.Name != rc.Name { + return false + } + } + + addrPath := r.Path + cfgPath := mod.Path() + + // normalize + if len(addrPath) == 0 { + addrPath = nil + } + if len(cfgPath) == 0 { + cfgPath = nil + } + return reflect.DeepEqual(addrPath, cfgPath) +} + // stateId returns the ID that this resource should be entered with // in the state. This is also used for diffs. In the future, we'd like to // move away from this string field so I don't export this. diff --git a/terraform/resource_address_test.go b/terraform/resource_address_test.go index bcf871c5e8..4cc2c013b5 100644 --- a/terraform/resource_address_test.go +++ b/terraform/resource_address_test.go @@ -1,10 +1,12 @@ package terraform import ( + "fmt" "reflect" "testing" "github.com/hashicorp/terraform/config" + "github.com/hashicorp/terraform/config/module" ) func TestParseResourceAddressInternal(t *testing.T) { @@ -743,3 +745,179 @@ func TestResourceAddressWholeModuleAddress(t *testing.T) { }) } } + +func TestResourceAddressMatchesConfig(t *testing.T) { + root := testModule(t, "empty-with-child-module") + child := root.Child([]string{"child"}) + grandchild := root.Child([]string{"child", "grandchild"}) + + tests := []struct { + Addr *ResourceAddress + Module *module.Tree + Resource *config.Resource + Want bool + }{ + { + &ResourceAddress{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + root, + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child"}, + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + child, + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child", "grandchild"}, + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + grandchild, + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child"}, + Index: -1, + }, + child, + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Path: []string{"child", "grandchild"}, + Index: -1, + }, + grandchild, + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + true, + }, + { + &ResourceAddress{ + Mode: config.DataResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + module.NewEmptyTree(), + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + false, + }, + { + &ResourceAddress{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + module.NewEmptyTree(), + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "pizza", + }, + false, + }, + { + &ResourceAddress{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + module.NewEmptyTree(), + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "aws_instance", + Name: "baz", + }, + false, + }, + { + &ResourceAddress{ + Path: []string{"child", "grandchild"}, + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + child, + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + false, + }, + { + &ResourceAddress{ + Path: []string{"child"}, + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + Index: -1, + }, + grandchild, + &config.Resource{ + Mode: config.ManagedResourceMode, + Type: "null_resource", + Name: "baz", + }, + false, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%02d-%s", i, test.Addr), func(t *testing.T) { + got := test.Addr.MatchesConfig(test.Module, test.Resource) + if got != test.Want { + t.Errorf( + "wrong result\naddr: %s\nmod: %#v\nrsrc: %#v\ngot: %#v\nwant: %#v", + test.Addr, test.Module.Path(), test.Resource, got, test.Want, + ) + } + }) + } +} diff --git a/terraform/test-fixtures/empty-with-child-module/child/child.tf b/terraform/test-fixtures/empty-with-child-module/child/child.tf new file mode 100644 index 0000000000..05e29577e0 --- /dev/null +++ b/terraform/test-fixtures/empty-with-child-module/child/child.tf @@ -0,0 +1,3 @@ +module "grandchild" { + source = "../grandchild" +} diff --git a/terraform/test-fixtures/empty-with-child-module/grandchild/grandchild.tf b/terraform/test-fixtures/empty-with-child-module/grandchild/grandchild.tf new file mode 100644 index 0000000000..4b41c9fcf9 --- /dev/null +++ b/terraform/test-fixtures/empty-with-child-module/grandchild/grandchild.tf @@ -0,0 +1 @@ +# Nothing here! diff --git a/terraform/test-fixtures/empty-with-child-module/root.tf b/terraform/test-fixtures/empty-with-child-module/root.tf new file mode 100644 index 0000000000..1f95749fa7 --- /dev/null +++ b/terraform/test-fixtures/empty-with-child-module/root.tf @@ -0,0 +1,3 @@ +module "child" { + source = "./child" +} From 190626e2a8c3138c5b46f2871ca6903c0c220dc7 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:08:15 -0700 Subject: [PATCH 45/82] core: improve consistency of ParseResourceAddress errors Previously one of the errors had a built-in context message and the other did not, making it hard for callers to present a user-friendly message in both cases. Now we generate an error message of the same form in both cases, with one case providing additional information. Ideally the main case would be able to give more specific guidance too, but that's hard to achieve with the current regexp-based parsing implementation. --- terraform/resource_address.go | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/terraform/resource_address.go b/terraform/resource_address.go index ca3f61b381..f34a24c5a5 100644 --- a/terraform/resource_address.go +++ b/terraform/resource_address.go @@ -231,7 +231,10 @@ func ParseResourceAddress(s string) (*ResourceAddress, error) { // not allowed to say "data." without a type following if mode == config.DataResourceMode && matches["type"] == "" { - return nil, fmt.Errorf("must target specific data instance") + return nil, fmt.Errorf( + "invalid resource address %q: must target specific data instance", + s, + ) } return &ResourceAddress{ @@ -335,7 +338,7 @@ func tokenizeResourceAddress(s string) (map[string]string, error) { groupNames := re.SubexpNames() rawMatches := re.FindAllStringSubmatch(s, -1) if len(rawMatches) != 1 { - return nil, fmt.Errorf("Problem parsing address: %q", s) + return nil, fmt.Errorf("invalid resource address %q", s) } matches := make(map[string]string) From f6305fcc27ca2d41e6a036561d6007374f7876f3 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:16:06 -0700 Subject: [PATCH 46/82] command: remove commented-out, misplaced tests For some reason there was a block of commented-out tests for the refresh command in the test file for the import command. Here we remove them to reduce the noise in this file. --- command/import_test.go | 685 ----------------------------------------- 1 file changed, 685 deletions(-) diff --git a/command/import_test.go b/command/import_test.go index 1be2563bd9..08dc97c0b6 100644 --- a/command/import_test.go +++ b/command/import_test.go @@ -274,691 +274,6 @@ func TestImport_providerConfigWithVarFile(t *testing.T) { testStateOutput(t, statePath, testImportStr) } -/* -func TestRefresh_badState(t *testing.T) { - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - args := []string{ - "-state", "i-should-not-exist-ever", - testFixturePath("refresh"), - } - if code := c.Run(args); code != 1 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } -} - -func TestRefresh_cwd(t *testing.T) { - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - if err := os.Chdir(testFixturePath("refresh")); err != nil { - t.Fatalf("err: %s", err) - } - defer os.Chdir(cwd) - - state := testState() - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - p.RefreshFn = nil - p.RefreshReturn = &terraform.InstanceState{ID: "yes"} - - args := []string{ - "-state", statePath, - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - if !p.RefreshCalled { - t.Fatal("refresh should be called") - } - - f, err := os.Open(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(newState.String()) - expected := strings.TrimSpace(testRefreshCwdStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestRefresh_defaultState(t *testing.T) { - originalState := testState() - - // Write the state file in a temporary directory with the - // default filename. - td, err := ioutil.TempDir("", "tf") - if err != nil { - t.Fatalf("err: %s", err) - } - statePath := filepath.Join(td, DefaultStateFilename) - - f, err := os.Create(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - err = terraform.WriteState(originalState, f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - // Change to that directory - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - if err := os.Chdir(filepath.Dir(statePath)); err != nil { - t.Fatalf("err: %s", err) - } - defer os.Chdir(cwd) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - p.RefreshFn = nil - p.RefreshReturn = &terraform.InstanceState{ID: "yes"} - - args := []string{ - testFixturePath("refresh"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - if !p.RefreshCalled { - t.Fatal("refresh should be called") - } - - f, err = os.Open(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := newState.RootModule().Resources["test_instance.foo"].Primary - expected := p.RefreshReturn - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } - - f, err = os.Open(statePath + DefaultBackupExtension) - if err != nil { - t.Fatalf("err: %s", err) - } - - backupState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual = backupState.RootModule().Resources["test_instance.foo"].Primary - expected = originalState.RootModule().Resources["test_instance.foo"].Primary - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } -} - -func TestRefresh_futureState(t *testing.T) { - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - if err := os.Chdir(testFixturePath("refresh")); err != nil { - t.Fatalf("err: %s", err) - } - defer os.Chdir(cwd) - - state := testState() - state.TFVersion = "99.99.99" - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - args := []string{ - "-state", statePath, - } - if code := c.Run(args); code == 0 { - t.Fatal("should fail") - } - - if p.RefreshCalled { - t.Fatal("refresh should not be called") - } - - f, err := os.Open(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(newState.String()) - expected := strings.TrimSpace(state.String()) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } -} - -func TestRefresh_pastState(t *testing.T) { - state := testState() - state.TFVersion = "0.1.0" - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - p.RefreshFn = nil - p.RefreshReturn = &terraform.InstanceState{ID: "yes"} - - args := []string{ - "-state", statePath, - testFixturePath("refresh"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - if !p.RefreshCalled { - t.Fatal("refresh should be called") - } - - f, err := os.Open(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := strings.TrimSpace(newState.String()) - expected := strings.TrimSpace(testRefreshStr) - if actual != expected { - t.Fatalf("bad:\n\n%s", actual) - } - - if newState.TFVersion != terraform.Version { - t.Fatalf("bad:\n\n%s", newState.TFVersion) - } -} - -func TestRefresh_outPath(t *testing.T) { - state := testState() - statePath := testStateFile(t, state) - - // Output path - outf, err := ioutil.TempFile("", "tf") - if err != nil { - t.Fatalf("err: %s", err) - } - outPath := outf.Name() - outf.Close() - os.Remove(outPath) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - p.RefreshFn = nil - p.RefreshReturn = &terraform.InstanceState{ID: "yes"} - - args := []string{ - "-state", statePath, - "-state-out", outPath, - testFixturePath("refresh"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - f, err := os.Open(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(newState, state) { - t.Fatalf("bad: %#v", newState) - } - - f, err = os.Open(outPath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err = terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := newState.RootModule().Resources["test_instance.foo"].Primary - expected := p.RefreshReturn - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } - - f, err = os.Open(outPath + DefaultBackupExtension) - if err != nil { - t.Fatalf("err: %s", err) - } - - backupState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actualStr := strings.TrimSpace(backupState.String()) - expectedStr := strings.TrimSpace(state.String()) - if actualStr != expectedStr { - t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) - } -} - -func TestRefresh_var(t *testing.T) { - state := testState() - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - args := []string{ - "-var", "foo=bar", - "-state", statePath, - testFixturePath("refresh-var"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - if !p.ConfigureCalled { - t.Fatal("configure should be called") - } - if p.ConfigureConfig.Config["value"].(string) != "bar" { - t.Fatalf("bad: %#v", p.ConfigureConfig.Config) - } -} - -func TestRefresh_varFile(t *testing.T) { - state := testState() - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - varFilePath := testTempFile(t) - if err := ioutil.WriteFile(varFilePath, []byte(refreshVarFile), 0644); err != nil { - t.Fatalf("err: %s", err) - } - - args := []string{ - "-var-file", varFilePath, - "-state", statePath, - testFixturePath("refresh-var"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - if !p.ConfigureCalled { - t.Fatal("configure should be called") - } - if p.ConfigureConfig.Config["value"].(string) != "bar" { - t.Fatalf("bad: %#v", p.ConfigureConfig.Config) - } -} - -func TestRefresh_varFileDefault(t *testing.T) { - state := testState() - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - varFileDir := testTempDir(t) - varFilePath := filepath.Join(varFileDir, "terraform.tfvars") - if err := ioutil.WriteFile(varFilePath, []byte(refreshVarFile), 0644); err != nil { - t.Fatalf("err: %s", err) - } - - cwd, err := os.Getwd() - if err != nil { - t.Fatalf("err: %s", err) - } - if err := os.Chdir(varFileDir); err != nil { - t.Fatalf("err: %s", err) - } - defer os.Chdir(cwd) - - args := []string{ - "-state", statePath, - testFixturePath("refresh-var"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - if !p.ConfigureCalled { - t.Fatal("configure should be called") - } - if p.ConfigureConfig.Config["value"].(string) != "bar" { - t.Fatalf("bad: %#v", p.ConfigureConfig.Config) - } -} - -func TestRefresh_varsUnset(t *testing.T) { - // Disable test mode so input would be asked - test = false - defer func() { test = true }() - - defaultInputReader = bytes.NewBufferString("bar\n") - - state := testState() - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - args := []string{ - "-state", statePath, - testFixturePath("refresh-unset-var"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } -} - -func TestRefresh_backup(t *testing.T) { - state := testState() - statePath := testStateFile(t, state) - - // Output path - outf, err := ioutil.TempFile("", "tf") - if err != nil { - t.Fatalf("err: %s", err) - } - outPath := outf.Name() - outf.Close() - os.Remove(outPath) - - // Backup path - backupf, err := ioutil.TempFile("", "tf") - if err != nil { - t.Fatalf("err: %s", err) - } - backupPath := backupf.Name() - backupf.Close() - os.Remove(backupPath) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - p.RefreshFn = nil - p.RefreshReturn = &terraform.InstanceState{ID: "yes"} - - args := []string{ - "-state", statePath, - "-state-out", outPath, - "-backup", backupPath, - testFixturePath("refresh"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - f, err := os.Open(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(newState, state) { - t.Fatalf("bad: %#v", newState) - } - - f, err = os.Open(outPath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err = terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := newState.RootModule().Resources["test_instance.foo"].Primary - expected := p.RefreshReturn - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } - - f, err = os.Open(backupPath) - if err != nil { - t.Fatalf("err: %s", err) - } - - backupState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actualStr := strings.TrimSpace(backupState.String()) - expectedStr := strings.TrimSpace(state.String()) - if actualStr != expectedStr { - t.Fatalf("bad:\n\n%s\n\n%s", actualStr, expectedStr) - } -} - -func TestRefresh_disableBackup(t *testing.T) { - state := testState() - statePath := testStateFile(t, state) - - // Output path - outf, err := ioutil.TempFile("", "tf") - if err != nil { - t.Fatalf("err: %s", err) - } - outPath := outf.Name() - outf.Close() - os.Remove(outPath) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - p.RefreshFn = nil - p.RefreshReturn = &terraform.InstanceState{ID: "yes"} - - args := []string{ - "-state", statePath, - "-state-out", outPath, - "-backup", "-", - testFixturePath("refresh"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - f, err := os.Open(statePath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err := terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - if !reflect.DeepEqual(newState, state) { - t.Fatalf("bad: %#v", newState) - } - - f, err = os.Open(outPath) - if err != nil { - t.Fatalf("err: %s", err) - } - - newState, err = terraform.ReadState(f) - f.Close() - if err != nil { - t.Fatalf("err: %s", err) - } - - actual := newState.RootModule().Resources["test_instance.foo"].Primary - expected := p.RefreshReturn - if !reflect.DeepEqual(actual, expected) { - t.Fatalf("bad: %#v", actual) - } - - // Ensure there is no backup - _, err = os.Stat(outPath + DefaultBackupExtension) - if err == nil || !os.IsNotExist(err) { - t.Fatalf("backup should not exist") - } -} - -func TestRefresh_displaysOutputs(t *testing.T) { - state := testState() - statePath := testStateFile(t, state) - - p := testProvider() - ui := new(cli.MockUi) - c := &RefreshCommand{ - Meta: Meta{ - testingOverrides: metaOverridesForProvider(p), - Ui: ui, - }, - } - - args := []string{ - "-state", statePath, - testFixturePath("refresh-output"), - } - if code := c.Run(args); code != 0 { - t.Fatalf("bad: %d\n\n%s", code, ui.ErrorWriter.String()) - } - - // Test that outputs were displayed - outputValue := "foo.example.com" - actual := ui.OutputWriter.String() - if !strings.Contains(actual, outputValue) { - t.Fatalf("Expected:\n%s\n\nTo include: %q", actual, outputValue) - } -} -*/ - func TestImport_customProvider(t *testing.T) { defer testChdir(t, testFixturePath("import-provider-aliased"))() From 7d8719150cc89653eb6b342e6117ef132a1fee50 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:20:08 -0700 Subject: [PATCH 47/82] command: validate import resource address early Previously we deferred validation of the resource address on the import command until we were in the core guts, which caused the error responses to be rather unhelpful. By validating these things early we can give better feedback to the user. --- command/import.go | 34 +++++++ command/import_test.go | 91 +++++++++++++++++++ .../import-missing-resource-config/main.tf | 5 + 3 files changed, 130 insertions(+) create mode 100644 command/test-fixtures/import-missing-resource-config/main.tf diff --git a/command/import.go b/command/import.go index a2f4d6c62a..3767b1124b 100644 --- a/command/import.go +++ b/command/import.go @@ -50,6 +50,23 @@ func (c *ImportCommand) Run(args []string) int { return 1 } + // Validate the provided resource address for syntax + addr, err := terraform.ParseResourceAddress(args[0]) + if err != nil { + c.Ui.Error(fmt.Sprintf(importCommandInvalidAddressFmt, err)) + return 1 + } + if !addr.HasResourceSpec() { + // module.foo target isn't allowed for import + c.Ui.Error(importCommandMissingResourceSpecMsg) + return 1 + } + if addr.Mode != config.ManagedResourceMode { + // can't import to a data resource address + c.Ui.Error(importCommandResourceModeMsg) + return 1 + } + // Load the module var mod *module.Tree if configPath != "" { @@ -204,3 +221,20 @@ Options: func (c *ImportCommand) Synopsis() string { return "Import existing infrastructure into Terraform" } + +const importCommandInvalidAddressFmt = `Error: %s + +For information on valid syntax, see: +https://www.terraform.io/docs/internals/resource-addressing.html +` + +const importCommandMissingResourceSpecMsg = `Error: resource address must include a full resource spec + +For information on valid syntax, see: +https://www.terraform.io/docs/internals/resource-addressing.html +` + +const importCommandResourceModeMsg = `Error: resource address must refer to a managed resource. + +Data resources cannot be imported. +` diff --git a/command/import_test.go b/command/import_test.go index 08dc97c0b6..324d78616d 100644 --- a/command/import_test.go +++ b/command/import_test.go @@ -2,6 +2,7 @@ package command import ( "fmt" + "strings" "testing" "github.com/hashicorp/terraform/terraform" @@ -315,6 +316,96 @@ func TestImport_customProvider(t *testing.T) { testStateOutput(t, statePath, testImportCustomProviderStr) } +func TestImport_dataResource(t *testing.T) { + defer testChdir(t, testFixturePath("import-missing-resource-config"))() + + statePath := testTempFile(t) + + p := testProvider() + ui := new(cli.MockUi) + c := &ImportCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "data.test_data_source.foo", + "bar", + } + code := c.Run(args) + if code != 1 { + t.Fatalf("import succeeded; expected failure") + } + + msg := ui.ErrorWriter.String() + if want := `resource address must refer to a managed resource`; !strings.Contains(msg, want) { + t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) + } +} + +func TestImport_invalidResourceAddr(t *testing.T) { + defer testChdir(t, testFixturePath("import-missing-resource-config"))() + + statePath := testTempFile(t) + + p := testProvider() + ui := new(cli.MockUi) + c := &ImportCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "bananas", + "bar", + } + code := c.Run(args) + if code != 1 { + t.Fatalf("import succeeded; expected failure") + } + + msg := ui.ErrorWriter.String() + if want := `invalid resource address "bananas"`; !strings.Contains(msg, want) { + t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) + } +} + +func TestImport_targetIsModule(t *testing.T) { + defer testChdir(t, testFixturePath("import-missing-resource-config"))() + + statePath := testTempFile(t) + + p := testProvider() + ui := new(cli.MockUi) + c := &ImportCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "module.foo", + "bar", + } + code := c.Run(args) + if code != 1 { + t.Fatalf("import succeeded; expected failure") + } + + msg := ui.ErrorWriter.String() + if want := `resource address must include a full resource spec`; !strings.Contains(msg, want) { + t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) + } +} + const testImportStr = ` test_instance.foo: ID = yay diff --git a/command/test-fixtures/import-missing-resource-config/main.tf b/command/test-fixtures/import-missing-resource-config/main.tf new file mode 100644 index 0000000000..d644bad319 --- /dev/null +++ b/command/test-fixtures/import-missing-resource-config/main.tf @@ -0,0 +1,5 @@ +provider "test" { + +} + +# No resource block present, so import fails From 9a398a779383795f8bb79548aea98efa9e5e4df9 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Tue, 16 May 2017 18:26:20 -0700 Subject: [PATCH 48/82] command: require resource to be in config before import Previously we encouraged users to import a resource and _then_ write the configuration block for it. This ordering creates lots of risk, since for various reasons users can end up subsequently running Terraform without any configuration in place, which then causes Terraform to want to destroy the resource that was imported. Now we invert this and require a minimal configuration block be written first. This helps ensure that the user ends up with a correlated resource config and state, protecting against any inconsistency caused by typos. This addresses #11835. --- command/import.go | 76 ++++++++++++++++--- command/import_test.go | 60 +++++++++++++++ .../import-provider-aliased/main.tf | 3 + .../import-provider-var-default/main.tf | 3 + .../import-provider-var-file/main.tf | 3 + .../test-fixtures/import-provider-var/main.tf | 3 + command/test-fixtures/import-provider/main.tf | 3 + website/source/docs/import/index.html.md | 10 +-- website/source/docs/import/usage.html.md | 42 ++++++---- 9 files changed, 173 insertions(+), 30 deletions(-) diff --git a/command/import.go b/command/import.go index 3767b1124b..1f0e18602b 100644 --- a/command/import.go +++ b/command/import.go @@ -78,14 +78,43 @@ func (c *ImportCommand) Run(args []string) int { } } - var conf *config.Config - if mod != nil { - conf = mod.Config() + // Verify that the given address points to something that exists in config. + // This is to reduce the risk that a typo in the resource address will + // import something that Terraform will want to immediately destroy on + // the next plan, and generally acts as a reassurance of user intent. + targetMod := mod.Child(addr.Path) + if targetMod == nil { + modulePath := addr.WholeModuleAddress().String() + if modulePath == "" { + c.Ui.Error(importCommandMissingConfigMsg) + } else { + c.Ui.Error(fmt.Sprintf(importCommandMissingModuleFmt, modulePath)) + } + return 1 + } + rcs := targetMod.Config().Resources + var rc *config.Resource + for _, thisRc := range rcs { + if addr.MatchesConfig(targetMod, thisRc) { + rc = thisRc + break + } + } + if rc == nil { + modulePath := addr.WholeModuleAddress().String() + if modulePath == "" { + modulePath = "the root module" + } + c.Ui.Error(fmt.Sprintf( + importCommandMissingResourceFmt, + addr, modulePath, addr.Type, addr.Name, + )) + return 1 } // Load the backend b, err := c.Backend(&BackendOpts{ - Config: conf, + Config: mod.Config(), }) if err != nil { c.Ui.Error(fmt.Sprintf("Failed to load backend: %s", err)) @@ -138,13 +167,7 @@ func (c *ImportCommand) Run(args []string) int { return 1 } - c.Ui.Output(c.Colorize().Color(fmt.Sprintf( - "[reset][green]\n" + - "Import success! The resources imported are shown above. These are\n" + - "now in your Terraform state. Import does not currently generate\n" + - "configuration, so you must do this next. If you do not create configuration\n" + - "for the above resources, then the next `terraform plan` will mark\n" + - "them for destruction."))) + c.Ui.Output(c.Colorize().Color("[reset][green]\n" + importCommandSuccessMsg)) return 0 } @@ -238,3 +261,34 @@ const importCommandResourceModeMsg = `Error: resource address must refer to a ma Data resources cannot be imported. ` + +const importCommandMissingConfigMsg = `Error: no configuration files in this directory. + +"terraform import" can only be run in a Terraform configuration directory. +Create one or more .tf files in this directory to import here. +` + +const importCommandMissingModuleFmt = `Error: %s does not exist in the configuration. + +Please add the configuration for the module before importing resources into it. +` + +const importCommandMissingResourceFmt = `Error: resource address %q does not exist in the configuration. + +Before importing this resource, please create its configuration in %s. For example: + +resource %q %q { + # (resource arguments) +} +` + +const importCommandSuccessMsg = `Import successful! + +The resources that were imported are shown above. These resources are now in +your Terraform state and will henceforth be managed by Terraform. + +Import does not generate configuration, so the next step is to ensure that +the resource configurations match the current (or desired) state of the +imported resources. You can use the output from "terraform plan" to verify that +the configuration is correct and complete. +` diff --git a/command/import_test.go b/command/import_test.go index 324d78616d..da31fe03d8 100644 --- a/command/import_test.go +++ b/command/import_test.go @@ -316,6 +316,66 @@ func TestImport_customProvider(t *testing.T) { testStateOutput(t, statePath, testImportCustomProviderStr) } +func TestImport_missingResourceConfig(t *testing.T) { + defer testChdir(t, testFixturePath("import-missing-resource-config"))() + + statePath := testTempFile(t) + + p := testProvider() + ui := new(cli.MockUi) + c := &ImportCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "test_instance.foo", + "bar", + } + code := c.Run(args) + if code != 1 { + t.Fatalf("import succeeded; expected failure") + } + + msg := ui.ErrorWriter.String() + if want := `resource address "test_instance.foo" does not exist`; !strings.Contains(msg, want) { + t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) + } +} + +func TestImport_missingModuleConfig(t *testing.T) { + defer testChdir(t, testFixturePath("import-missing-resource-config"))() + + statePath := testTempFile(t) + + p := testProvider() + ui := new(cli.MockUi) + c := &ImportCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(p), + Ui: ui, + }, + } + + args := []string{ + "-state", statePath, + "module.baz.test_instance.foo", + "bar", + } + code := c.Run(args) + if code != 1 { + t.Fatalf("import succeeded; expected failure") + } + + msg := ui.ErrorWriter.String() + if want := `module.baz does not exist in the configuration`; !strings.Contains(msg, want) { + t.Errorf("incorrect message\nwant substring: %s\ngot:\n%s", want, msg) + } +} + func TestImport_dataResource(t *testing.T) { defer testChdir(t, testFixturePath("import-missing-resource-config"))() diff --git a/command/test-fixtures/import-provider-aliased/main.tf b/command/test-fixtures/import-provider-aliased/main.tf index 92f563ae8f..9ef6de2ce4 100644 --- a/command/test-fixtures/import-provider-aliased/main.tf +++ b/command/test-fixtures/import-provider-aliased/main.tf @@ -3,3 +3,6 @@ provider "test" { alias = "alias" } + +resource "test_instance" "foo" { +} diff --git a/command/test-fixtures/import-provider-var-default/main.tf b/command/test-fixtures/import-provider-var-default/main.tf index bbe1466ddb..c63b4c063b 100644 --- a/command/test-fixtures/import-provider-var-default/main.tf +++ b/command/test-fixtures/import-provider-var-default/main.tf @@ -3,3 +3,6 @@ variable "foo" {} provider "test" { foo = "${var.foo}" } + +resource "test_instance" "foo" { +} diff --git a/command/test-fixtures/import-provider-var-file/main.tf b/command/test-fixtures/import-provider-var-file/main.tf index bbe1466ddb..c63b4c063b 100644 --- a/command/test-fixtures/import-provider-var-file/main.tf +++ b/command/test-fixtures/import-provider-var-file/main.tf @@ -3,3 +3,6 @@ variable "foo" {} provider "test" { foo = "${var.foo}" } + +resource "test_instance" "foo" { +} diff --git a/command/test-fixtures/import-provider-var/main.tf b/command/test-fixtures/import-provider-var/main.tf index bbe1466ddb..c63b4c063b 100644 --- a/command/test-fixtures/import-provider-var/main.tf +++ b/command/test-fixtures/import-provider-var/main.tf @@ -3,3 +3,6 @@ variable "foo" {} provider "test" { foo = "${var.foo}" } + +resource "test_instance" "foo" { +} diff --git a/command/test-fixtures/import-provider/main.tf b/command/test-fixtures/import-provider/main.tf index dd4a7556cc..943e8b33f4 100644 --- a/command/test-fixtures/import-provider/main.tf +++ b/command/test-fixtures/import-provider/main.tf @@ -1,3 +1,6 @@ provider "test" { foo = "bar" } + +resource "test_instance" "foo" { +} diff --git a/website/source/docs/import/index.html.md b/website/source/docs/import/index.html.md index 88a6964988..d1cd288fe5 100644 --- a/website/source/docs/import/index.html.md +++ b/website/source/docs/import/index.html.md @@ -24,12 +24,10 @@ The current implementation of Terraform import can only import resources into the [state](/docs/state). It does not generate configuration. A future version of Terraform will also generate configuration. -Because of this, the behavior of importing resources into Terraform right now -is that after an import, if you run a `terraform plan`, Terraform views it -as an orphan (a resource with no configuration) and marks it for destruction. -After importing a resource you have to manually write configuration to match -the resource. +Because of this, prior to running `terraform import` it is necessary to write +manually a `resource` configuration block for the resource, to which the +imported object will be attached. While this may seem tedious, it still gives Terraform users an avenue for importing existing resources. A future version of Terraform will fully generate -configuration significantly simplifying this process. +configuration, significantly simplifying this process. diff --git a/website/source/docs/import/usage.html.md b/website/source/docs/import/usage.html.md index 9207e36c4d..9c71494a2b 100644 --- a/website/source/docs/import/usage.html.md +++ b/website/source/docs/import/usage.html.md @@ -15,26 +15,39 @@ you can't yet point Terraform import to an entire collection of resources such as an AWS VPC and import all of it. A future version of Terraform will be able to do this. -Using `terraform import` is simple. An example is shown below: +To import a resource, first write a resource block for it in your +configuration, establishing the name by which it will be known in Terraform: + +``` +resource "aws_instance" "bar" { + # ...instance configuration... +} +``` + +If desired, you can leave the body of the resource block blank for now and +return to fill it in once the instance is imported. + +Now `terraform import` can be run to attach an existing instance to this +resource configuration: ```shell $ terraform import aws_instance.bar i-abcd1234 ``` -The above command imports an AWS instance with the given ID to the -address `aws_instance.bar`. You can also import resources into modules. +The above command imports an AWS instance with the given ID and attaches +it to the name `aws_instance.bar`. You can also import resources into modules. See the [resource addressing](/docs/internals/resource-addressing.html) page for more details on the full range of addresses supported. The ID given is dependent on the resource type being imported. For example, AWS instances use their direct IDs. However, AWS Route53 zones use the -domain name itself. Reference the resource documentation for details on -what the ID it expects is. +domain name itself. Console the resource documentation for details on what +form of ID each resource expects. -As a result of the above command, the resource is put into the state file. -If you run `terraform plan`, you should see Terraform plan your resource -for destruction. You now have to create a matching configuration so that -Terraform doesn't plan a destroy. +As a result of the above command, the resource is recorded in the state file. +You can now run `terraform plan` to see how the configuration compares to +the imported resource, and make any adjustments to the configuration to +align with the current (or desired) state of the imported object. ## Complex Imports @@ -43,7 +56,10 @@ into the state file. An import may also result in a "complex import" where multiple resources are imported. For example, an AWS security group imports an `aws_security_group` but also one `aws_security_group_rule` for each rule. -In this case, the name of the resource is shown as part of the import output. -You'll have to create a configuration for each resource imported. If you want -to rename or otherwise modify the imported resources, the -[state management commands](/docs/commands/state/index.html) should be used. +In this scenario, the secondary resources will not already exist in +configuration, so it is necessary to consult the import output and create +a `resource` block in configuration for each secondary resource. If this is +not done, Terraform will plan to destroy the imported objects on the next run. + +If you want to rename or otherwise modify the imported resources, the +[state management commands](/docs/commands/state/index.html) can be used. From e3401947a68c5f975d1c4d82de657d82b19fd8f8 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 May 2017 15:03:36 -0700 Subject: [PATCH 49/82] plugin/discovery: PluginRequirements can specify SHA256 digests As well as constraining plugins by version number, we also want to be able to pin plugins to use specific executables so that we can detect drift in available plugins between commands. This commit allows such requirements to be specified, but doesn't yet specify any such requirements, nor validate them. --- command/init.go | 2 +- moduledeps/module.go | 14 +++- moduledeps/module_test.go | 4 +- plugin/discovery/meta_set_test.go | 8 +-- plugin/discovery/requirements.go | 95 ++++++++++++++++++++++++--- plugin/discovery/requirements_test.go | 93 ++++++++++++++++++++++++++ plugin/discovery/version.go | 10 +++ plugin/discovery/version_set.go | 3 +- 8 files changed, 210 insertions(+), 19 deletions(-) create mode 100644 plugin/discovery/requirements_test.go diff --git a/command/init.go b/command/init.go index b3d816c31d..db6ec0fe25 100644 --- a/command/init.go +++ b/command/init.go @@ -221,7 +221,7 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { dst := c.pluginDir() for provider, reqd := range missing { - err := c.getProvider(dst, provider, reqd) + err := c.getProvider(dst, provider, reqd.Versions) // TODO: return all errors if err != nil { return err diff --git a/moduledeps/module.go b/moduledeps/module.go index 0e446108fe..d6cbaf5c5f 100644 --- a/moduledeps/module.go +++ b/moduledeps/module.go @@ -104,6 +104,9 @@ func (s sortModules) Swap(i, j int) { // This method only considers the direct requirements of the receiver. // Use AllPluginRequirements to flatten the dependencies for the // entire tree of modules. +// +// Requirements returned by this method include only version constraints, +// and apply no particular SHA256 hash constraint. func (m *Module) PluginRequirements() discovery.PluginRequirements { ret := make(discovery.PluginRequirements) for inst, dep := range m.Providers { @@ -111,12 +114,14 @@ func (m *Module) PluginRequirements() discovery.PluginRequirements { // a PluginRequirements wants keys to be provider *types*, such // as "aws". If there are multiple aliases for the same // provider then we will flatten them into a single requirement - // by using Intersection to merge the version sets. + // by combining their constraint sets. pty := inst.Type() if existing, exists := ret[pty]; exists { - ret[pty] = existing.Append(dep.Constraints) + ret[pty].Versions = existing.Versions.Append(dep.Constraints) } else { - ret[pty] = dep.Constraints + ret[pty] = &discovery.PluginConstraints{ + Versions: dep.Constraints, + } } } return ret @@ -125,6 +130,9 @@ func (m *Module) PluginRequirements() discovery.PluginRequirements { // AllPluginRequirements calls PluginRequirements for the receiver and all // of its descendents, and merges the result into a single PluginRequirements // structure that would satisfy all of the modules together. +// +// Requirements returned by this method include only version constraints, +// and apply no particular SHA256 hash constraint. func (m *Module) AllPluginRequirements() discovery.PluginRequirements { var ret discovery.PluginRequirements m.WalkTree(func(path []string, parent *Module, current *Module) error { diff --git a/moduledeps/module_test.go b/moduledeps/module_test.go index 630f3cd8b7..4cbe0103c8 100644 --- a/moduledeps/module_test.go +++ b/moduledeps/module_test.go @@ -207,10 +207,10 @@ func TestModulePluginRequirements(t *testing.T) { if len(reqd) != 2 { t.Errorf("wrong number of elements in %#v; want 2", reqd) } - if got, want := reqd["foo"].String(), ">=1.0.0,>=2.0.0"; got != want { + if got, want := reqd["foo"].Versions.String(), ">=1.0.0,>=2.0.0"; got != want { t.Errorf("wrong combination of versions for 'foo' %q; want %q", got, want) } - if got, want := reqd["baz"].String(), ">=3.0.0"; got != want { + if got, want := reqd["baz"].Versions.String(), ">=3.0.0"; got != want { t.Errorf("wrong combination of versions for 'baz' %q; want %q", got, want) } } diff --git a/plugin/discovery/meta_set_test.go b/plugin/discovery/meta_set_test.go index 18485f58d0..bd15c5be6d 100644 --- a/plugin/discovery/meta_set_test.go +++ b/plugin/discovery/meta_set_test.go @@ -310,10 +310,10 @@ func TestPluginMetaSetConstrainVersions(t *testing.T) { } byName := s.ConstrainVersions(PluginRequirements{ - "foo": ConstraintStr(">=2.0.0").MustParse(), - "bar": ConstraintStr(">=0.0.0").MustParse(), - "baz": ConstraintStr(">=1.0.0").MustParse(), - "fun": ConstraintStr(">5.0.0").MustParse(), + "foo": &PluginConstraints{Versions: ConstraintStr(">=2.0.0").MustParse()}, + "bar": &PluginConstraints{Versions: ConstraintStr(">=0.0.0").MustParse()}, + "baz": &PluginConstraints{Versions: ConstraintStr(">=1.0.0").MustParse()}, + "fun": &PluginConstraints{Versions: ConstraintStr(">5.0.0").MustParse()}, }) if got, want := len(byName), 3; got != want { t.Errorf("%d keys in map; want %d", got, want) diff --git a/plugin/discovery/requirements.go b/plugin/discovery/requirements.go index 1f58821e45..75430fdd60 100644 --- a/plugin/discovery/requirements.go +++ b/plugin/discovery/requirements.go @@ -1,26 +1,105 @@ package discovery +import ( + "bytes" +) + // PluginRequirements describes a set of plugins (assumed to be of a consistent // kind) that are required to exist and have versions within the given // corresponding sets. -// -// PluginRequirements is a map from plugin name to Constraints. -type PluginRequirements map[string]Constraints +type PluginRequirements map[string]*PluginConstraints + +// PluginConstraints represents an element of PluginRequirements describing +// the constraints for a single plugin. +type PluginConstraints struct { + // Specifies that the plugin's version must be within the given + // constraints. + Versions Constraints + + // If non-nil, the hash of the on-disk plugin executable must exactly + // match the SHA256 hash given here. + SHA256 []byte +} + +// Allows returns true if the given version is within the receiver's version +// constraints. +func (s *PluginConstraints) Allows(v Version) bool { + return s.Versions.Allows(v) +} + +// AcceptsSHA256 returns true if the given executable SHA256 hash is acceptable, +// either because it matches the constraint or because there is no such +// constraint. +func (s *PluginConstraints) AcceptsSHA256(digest []byte) bool { + if s.SHA256 == nil { + return true + } + return bytes.Equal(s.SHA256, digest) +} // Merge takes the contents of the receiver and the other given requirements // object and merges them together into a single requirements structure // that satisfies both sets of requirements. +// +// Note that it doesn't make sense to merge two PluginRequirements with +// differing required plugin SHA256 hashes, since the result will never +// match any plugin. func (r PluginRequirements) Merge(other PluginRequirements) PluginRequirements { ret := make(PluginRequirements) - for n, vs := range r { - ret[n] = vs + for n, c := range r { + ret[n] = &PluginConstraints{ + Versions: Constraints{}.Append(c.Versions), + SHA256: c.SHA256, + } } - for n, vs := range other { + for n, c := range other { if existing, exists := ret[n]; exists { - ret[n] = existing.Append(vs) + ret[n].Versions = ret[n].Versions.Append(c.Versions) + + if existing.SHA256 != nil { + if c.SHA256 != nil && !bytes.Equal(c.SHA256, existing.SHA256) { + // If we've been asked to merge two constraints with + // different SHA256 hashes then we'll produce a dummy value + // that can never match anything. This is a silly edge case + // that no reasonable caller should hit. + ret[n].SHA256 = []byte(invalidProviderHash) + } + } else { + ret[n].SHA256 = c.SHA256 // might still be nil + } } else { - ret[n] = vs + ret[n] = &PluginConstraints{ + Versions: Constraints{}.Append(c.Versions), + SHA256: c.SHA256, + } } } return ret } + +// LockExecutables applies additional constraints to the receiver that +// require plugin executables with specific SHA256 digests. This modifies +// the receiver in-place, since it's intended to be applied after +// version constraints have been resolved. +// +// The given map must include a key for every plugin that is already +// required. If not, any missing keys will cause the corresponding plugin +// to never match, though the direct caller doesn't necessarily need to +// guarantee this as long as the downstream code _applying_ these constraints +// is able to deal with the non-match in some way. +func (r PluginRequirements) LockExecutables(sha256s map[string][]byte) { + for name, cons := range r { + digest := sha256s[name] + + if digest == nil { + // Prevent any match, which will then presumably cause the + // downstream consumer of this requirements to report an error. + cons.SHA256 = []byte(invalidProviderHash) + continue + } + + cons.SHA256 = digest + } +} + +const invalidProviderHash = "" diff --git a/plugin/discovery/requirements_test.go b/plugin/discovery/requirements_test.go new file mode 100644 index 0000000000..b3300fab4b --- /dev/null +++ b/plugin/discovery/requirements_test.go @@ -0,0 +1,93 @@ +package discovery + +import ( + "fmt" + "testing" +) + +func TestPluginConstraintsAllows(t *testing.T) { + tests := []struct { + Constraints *PluginConstraints + Version string + Want bool + }{ + { + &PluginConstraints{ + Versions: AllVersions, + }, + "1.0.0", + true, + }, + { + &PluginConstraints{ + Versions: ConstraintStr(">1.0.0").MustParse(), + }, + "1.0.0", + false, + }, + // This is not an exhaustive test because the callees + // already have plentiful tests of their own. + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + version := VersionStr(test.Version).MustParse() + got := test.Constraints.Allows(version) + if got != test.Want { + t.Logf("looking for %s in %#v", test.Version, test.Constraints) + t.Errorf("wrong result %#v; want %#v", got, test.Want) + } + }) + } +} + +func TestPluginConstraintsAcceptsSHA256(t *testing.T) { + mustUnhex := func(hex string) (ret []byte) { + _, err := fmt.Sscanf(hex, "%x", &ret) + if err != nil { + panic(err) + } + return ret + } + + tests := []struct { + Constraints *PluginConstraints + Digest []byte + Want bool + }{ + { + &PluginConstraints{ + Versions: AllVersions, + SHA256: mustUnhex("0123456789abcdef"), + }, + mustUnhex("0123456789abcdef"), + true, + }, + { + &PluginConstraints{ + Versions: AllVersions, + SHA256: mustUnhex("0123456789abcdef"), + }, + mustUnhex("f00dface"), + false, + }, + { + &PluginConstraints{ + Versions: AllVersions, + SHA256: nil, + }, + mustUnhex("f00dface"), + true, + }, + } + + for i, test := range tests { + t.Run(fmt.Sprintf("%02d", i), func(t *testing.T) { + got := test.Constraints.AcceptsSHA256(test.Digest) + if got != test.Want { + t.Logf("%#v.AcceptsSHA256(%#v)", test.Constraints, test.Digest) + t.Errorf("wrong result %#v; want %#v", got, test.Want) + } + }) + } +} diff --git a/plugin/discovery/version.go b/plugin/discovery/version.go index f2706bd530..50d55635e6 100644 --- a/plugin/discovery/version.go +++ b/plugin/discovery/version.go @@ -19,6 +19,16 @@ func (s VersionStr) Parse() (Version, error) { return Version{raw}, nil } +// MustParse transforms a VersionStr into a Version if it is +// syntactically valid. If it isn't then it panics. +func (s VersionStr) MustParse() Version { + ret, err := s.Parse() + if err != nil { + panic(err) + } + return ret +} + // Version represents a version number that has been parsed from // a semver string and known to be valid. type Version struct { diff --git a/plugin/discovery/version_set.go b/plugin/discovery/version_set.go index e1b0b1e2c1..3ce5796899 100644 --- a/plugin/discovery/version_set.go +++ b/plugin/discovery/version_set.go @@ -43,7 +43,8 @@ func init() { } } -// Allows returns true if the given version is in the receiving set. +// Allows returns true if the given version permitted by the receiving +// constraints set. func (s Constraints) Allows(v Version) bool { return s.raw.Check(v.raw) } From 7d0a98af4653d8b876d16ded3bf192585150f547 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 May 2017 15:15:24 -0700 Subject: [PATCH 50/82] command: provider resolver to also check SHA256 constraints when set In addition to looking for matching versions, the caller can also optionally require a specific executable by its SHA256 digest. --- command/plugins.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/command/plugins.go b/command/plugins.go index dfd168dbde..020e839466 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -33,6 +33,22 @@ func (r *multiVersionProviderResolver) ResolveProviders( for name := range reqd { if metas := candidates[name]; metas != nil { newest := metas.Newest() + + digest, err := newest.SHA256() + if err != nil { + errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err)) + continue + } + if !reqd[name].AcceptsSHA256(digest) { + // This generic error message is intended to avoid troubling + // users with implementation details. The main useful point + // here is that they need to run "terraform init" to + // fix this, which is covered by the UI code reporting these + // error messages. + errs = append(errs, fmt.Errorf("provider.%s: not yet initialized", name)) + continue + } + client := tfplugin.Client(newest) factories[name] = providerFactory(client) } else { From 720670fae77f0af5cde2f71e5eee3155e9072109 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 May 2017 16:31:52 -0700 Subject: [PATCH 51/82] command: helper to manage the provider plugins lock file This is just a JSON file with the SHA256 digests of the plugin executables. --- command/plugins_lock.go | 86 ++++++++++++++++++++++++++++++++++++ command/plugins_lock_test.go | 40 +++++++++++++++++ 2 files changed, 126 insertions(+) create mode 100644 command/plugins_lock.go create mode 100644 command/plugins_lock_test.go diff --git a/command/plugins_lock.go b/command/plugins_lock.go new file mode 100644 index 0000000000..ed2062634e --- /dev/null +++ b/command/plugins_lock.go @@ -0,0 +1,86 @@ +package command + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "log" + "os" + "path/filepath" +) + +func (m *Meta) providerPluginsLock() *pluginSHA256LockFile { + return &pluginSHA256LockFile{ + Filename: filepath.Join(m.pluginDir(), "providers.json"), + } +} + +type pluginSHA256LockFile struct { + Filename string +} + +// Read loads the lock information from the file and returns it. If the file +// cannot be read, an empty map is returned to indicate that _no_ providers +// are acceptable, since the user must run "terraform init" to lock some +// providers before a context can be created. +func (pf *pluginSHA256LockFile) Read() map[string][]byte { + // Returning an empty map is different than nil because it causes + // us to reject all plugins as uninitialized, rather than applying no + // constraints at all. + // + // We don't surface any specific errors here because we want it to all + // roll up into our more-user-friendly error that appears when plugin + // constraint verification fails during context creation. + digests := make(map[string][]byte) + + buf, err := ioutil.ReadFile(pf.Filename) + if err != nil { + // This is expected if the user runs any context-using command before + // running "terraform init". + log.Printf("[INFO] Failed to read plugin lock file %s: %s", pf.Filename, err) + return digests + } + + var strDigests map[string]string + err = json.Unmarshal(buf, &strDigests) + if err != nil { + // This should never happen unless the user directly edits the file. + log.Printf("[WARNING] Plugin lock file %s failed to parse as JSON: %s", pf.Filename, err) + return digests + } + + for name, strDigest := range strDigests { + var digest []byte + _, err := fmt.Sscanf(strDigest, "%x", &digest) + if err == nil { + digests[name] = digest + } else { + // This should never happen unless the user directly edits the file. + log.Printf("[WARNING] Plugin lock file %s has invalid digest for %q", pf.Filename, name) + } + } + + return digests +} + +// Write persists lock information to disk, where it will be retrieved by +// future calls to Read. This entirely replaces any previous lock information, +// so the given map must be comprehensive. +func (pf *pluginSHA256LockFile) Write(digests map[string][]byte) error { + strDigests := map[string]string{} + for name, digest := range digests { + strDigests[name] = fmt.Sprintf("%x", digest) + } + + buf, err := json.MarshalIndent(strDigests, "", " ") + if err != nil { + // should never happen + return fmt.Errorf("failed to serialize plugin lock as JSON: %s", err) + } + + os.MkdirAll( + filepath.Dir(pf.Filename), os.ModePerm, + ) // ignore error since WriteFile below will generate a better one anyway + + return ioutil.WriteFile(pf.Filename, buf, os.ModePerm) +} diff --git a/command/plugins_lock_test.go b/command/plugins_lock_test.go new file mode 100644 index 0000000000..b954c127b0 --- /dev/null +++ b/command/plugins_lock_test.go @@ -0,0 +1,40 @@ +package command + +import ( + "io/ioutil" + "reflect" + "testing" +) + +func TestPluginSHA256LockFile(t *testing.T) { + f, err := ioutil.TempFile("", "tf-pluginsha1lockfile-test-") + if err != nil { + t.Fatalf("failed to create temporary file: %s", err) + } + f.Close() + //defer os.Remove(f.Name()) + t.Logf("working in %s", f.Name()) + + plf := &pluginSHA256LockFile{ + Filename: f.Name(), + } + + // Initially the file is invalid, so we should get an empty map. + digests := plf.Read() + if !reflect.DeepEqual(digests, map[string][]byte{}) { + t.Errorf("wrong initial content %#v; want empty map", digests) + } + + digests = map[string][]byte{ + "test": []byte("hello world"), + } + err = plf.Write(digests) + if err != nil { + t.Fatalf("failed to write lock file: %s", err) + } + + got := plf.Read() + if !reflect.DeepEqual(got, digests) { + t.Errorf("wrong content %#v after write; want %#v", got, digests) + } +} From aa1c64449932668a3f92149f8726864696fcb515 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 May 2017 16:33:26 -0700 Subject: [PATCH 52/82] core: allow setting required plugin hashes on Context When set, this information gets passed on to the provider resolver as part of the requirements information, causing us to reject any plugins that do not match during initialization. --- terraform/context.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/terraform/context.go b/terraform/context.go index e5d4cc1a6f..0302eba4a8 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -63,6 +63,10 @@ type ContextOpts struct { Targets []string Variables map[string]interface{} + // If non-nil, will apply as additional constraints on the provider + // plugins that will be requested from the provider resolver. + ProviderSHA256s map[string][]byte + UIInput UIInput } @@ -180,6 +184,9 @@ func NewContext(opts *ContextOpts) (*Context, error) { var err error deps := ModuleTreeDependencies(opts.Module, state) reqd := deps.AllPluginRequirements() + if opts.ProviderSHA256s != nil { + reqd.LockExecutables(opts.ProviderSHA256s) + } providers, err = resourceProviderFactories(opts.ProviderResolver, reqd) if err != nil { return nil, err From 6ba6508ec9ec2835fa88f18888bd719f993d82bb Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 May 2017 16:36:19 -0700 Subject: [PATCH 53/82] command: pass the locked plugin hashes into ContextOpts By reading our lock file and passing this into the context, we ensure that only the plugins referenced in the lock file can be used. As of this commit there is no way to create that lock file, but that will follow soon as part of "terraform init". We also provide a way to force a particular set of SHA256s. The main use for this is to allow us to persist a set of plugins in the plan and check the same plugins are used during apply, but it may also be useful for automated tests. --- command/meta.go | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/command/meta.go b/command/meta.go index 4e3233362b..c3ba6ee0ac 100644 --- a/command/meta.go +++ b/command/meta.go @@ -50,6 +50,11 @@ type Meta struct { // Override certain behavior for tests within this package testingOverrides *testingOverrides + // Override the set of provider plugin SHA256 digests we expect. + // If this is nil we will instead read from the provider lock file + // when setting up ContextOpts. + forceProviderSHA256s map[string][]byte + //---------------------------------------------------------- // Private: do not set these //---------------------------------------------------------- @@ -244,6 +249,12 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { opts.Provisioners = m.provisionerFactories() } + if m.forceProviderSHA256s != nil { + opts.ProviderSHA256s = m.forceProviderSHA256s + } else { + opts.ProviderSHA256s = m.providerPluginsLock().Read() + } + opts.Meta = &terraform.ContextMeta{ Env: m.Env(), } From 04bcece59c2929e9c0f0682095bc05363c51fd77 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 May 2017 17:29:10 -0700 Subject: [PATCH 54/82] plugin/discovery: handle the -Xn suffix used by auto-installed plugins This is used to mark the plugin protocol version. Currently we actually just ignore this entirely, since only one protocol version exists anyway. Later we will need to add checks here to ensure that we only pay attention to plugins of the right version. --- plugin/discovery/find.go | 6 ++++++ plugin/discovery/find_test.go | 8 ++++++++ 2 files changed, 14 insertions(+) diff --git a/plugin/discovery/find.go b/plugin/discovery/find.go index 923923dccc..e66ef9a844 100644 --- a/plugin/discovery/find.go +++ b/plugin/discovery/find.go @@ -147,6 +147,12 @@ func ResolvePluginPaths(paths []string) PluginMetaSet { version = parts[1] } + // Auto-installed plugins contain an extra name portion representing + // the expected plugin version, which we must trim off. + if dashX := strings.Index(version, "-X"); dashX != -1 { + version = version[:dashX] + } + if _, ok := found[nameVersion{name, version}]; ok { // Skip duplicate versions of the same plugin // (We do this during this step because after this we will be diff --git a/plugin/discovery/find_test.go b/plugin/discovery/find_test.go index 97ae8ed2c6..887a3ad959 100644 --- a/plugin/discovery/find_test.go +++ b/plugin/discovery/find_test.go @@ -53,6 +53,7 @@ func TestResolvePluginPaths(t *testing.T) { "/example/mockos_mockarch/terraform-foo-bar-V0.0.1", "/example/mockos_mockarch/terraform-foo-baz-V0.0.1", "/example/mockos_mockarch/terraform-foo-baz-V1.0.0", + "/example/mockos_mockarch/terraform-foo-baz-V2.0.0-X4", "/example/terraform-foo-bar", "/example/mockos_mockarch/terraform-foo-bar-Vbananas", "/example/mockos_mockarch/terraform-foo-bar-V", @@ -75,6 +76,11 @@ func TestResolvePluginPaths(t *testing.T) { Version: "1.0.0", Path: "/example/mockos_mockarch/terraform-foo-baz-V1.0.0", }, + { + Name: "baz", + Version: "2.0.0", + Path: "/example/mockos_mockarch/terraform-foo-baz-V2.0.0-X4", + }, { Name: "bar", Version: "0.0.0", @@ -92,6 +98,8 @@ func TestResolvePluginPaths(t *testing.T) { }, } + t.Logf("got %#v", got) + if got, want := got.Count(), len(want); got != want { t.Errorf("got %d items; want %d", got, want) } From 032f71f1ff8f54c9eb76a1fe517ab7669504a967 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Wed, 24 May 2017 17:35:46 -0700 Subject: [PATCH 55/82] command: produce provider lock file during "terraform init" Once we've installed the necessary plugins, we'll do one more walk of the available plugins and record the SHA256 hashes of all of the plugins we select in the provider lock file. The file we write here gets read when we're building ContextOpts to initialize the main terraform context, so any command that works with the context will then fail if any of the provider binaries change. --- command/init.go | 23 ++++++++- command/init_test.go | 48 +++++++++++++++++++ command/plugins.go | 24 +++++++--- command/push_test.go | 5 ++ .../init-provider-lock-file/main.tf | 3 ++ .../test-fixtures/init-providers-lock/main.tf | 3 ++ 6 files changed, 98 insertions(+), 8 deletions(-) create mode 100644 command/test-fixtures/init-provider-lock-file/main.tf create mode 100644 command/test-fixtures/init-providers-lock/main.tf diff --git a/command/init.go b/command/init.go index db6ec0fe25..7afeb88304 100644 --- a/command/init.go +++ b/command/init.go @@ -216,8 +216,9 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { return err } + available := c.providerPluginSet() requirements := terraform.ModuleTreeDependencies(mod, state).AllPluginRequirements() - missing := c.missingProviders(requirements) + missing := c.missingPlugins(available, requirements) dst := c.pluginDir() for provider, reqd := range missing { @@ -227,6 +228,26 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { return err } } + + // With all the providers downloaded, we'll generate our lock file + // that ensures the provider binaries remain unchanged until we init + // again. If anything changes, other commands that use providers will + // fail with an error instructing the user to re-run this command. + available = c.providerPluginSet() // re-discover to see newly-installed plugins + chosen := choosePlugins(available, requirements) + digests := map[string][]byte{} + for name, meta := range chosen { + digest, err := meta.SHA256() + if err != nil { + return fmt.Errorf("failed to read provider plugin %s: %s", meta.Path, err) + } + digests[name] = digest + } + err = c.providerPluginsLock().Write(digests) + if err != nil { + return fmt.Errorf("failed to save provider manifest: %s", err) + } + return nil } diff --git a/command/init_test.go b/command/init_test.go index a95a5155b3..f41f44f4e9 100644 --- a/command/init_test.go +++ b/command/init_test.go @@ -1,9 +1,11 @@ package command import ( + "fmt" "io/ioutil" "os" "path/filepath" + "runtime" "strings" "testing" @@ -618,6 +620,52 @@ func TestInit_getProviderMissing(t *testing.T) { } } +func TestInit_providerLockFile(t *testing.T) { + // Create a temporary working directory that is empty + td := tempDir(t) + copy.CopyDir(testFixturePath("init-provider-lock-file"), td) + defer os.RemoveAll(td) + defer testChdir(t, td)() + + getter := &mockGetProvider{ + Providers: map[string][]string{ + "test": []string{"1.2.3"}, + }, + } + + ui := new(cli.MockUi) + c := &InitCommand{ + Meta: Meta{ + testingOverrides: metaOverridesForProvider(testProvider()), + Ui: ui, + }, + getProvider: getter.GetProvider, + } + + args := []string{} + if code := c.Run(args); code != 0 { + t.Fatalf("bad: \n%s", ui.ErrorWriter.String()) + } + + providersLockFile := fmt.Sprintf( + ".terraform/plugins/%s_%s/providers.json", + runtime.GOOS, runtime.GOARCH, + ) + buf, err := ioutil.ReadFile(providersLockFile) + if err != nil { + t.Fatalf("failed to read providers lock file %s: %s", providersLockFile, err) + } + // The hash in here is for the empty files that mockGetProvider produces + wantLockFile := strings.TrimSpace(` +{ + "test": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" +} +`) + if string(buf) != wantLockFile { + t.Errorf("wrong provider lock file contents\ngot: %s\nwant: %s", buf, wantLockFile) + } +} + /* func TestInit_remoteState(t *testing.T) { tmp, cwd := testCwd(t) diff --git a/command/plugins.go b/command/plugins.go index 020e839466..9539ef51df 100644 --- a/command/plugins.go +++ b/command/plugins.go @@ -23,17 +23,27 @@ type multiVersionProviderResolver struct { Available discovery.PluginMetaSet } +func choosePlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) map[string]discovery.PluginMeta { + candidates := avail.ConstrainVersions(reqd) + ret := map[string]discovery.PluginMeta{} + for name, metas := range candidates { + if len(metas) == 0 { + continue + } + ret[name] = metas.Newest() + } + return ret +} + func (r *multiVersionProviderResolver) ResolveProviders( reqd discovery.PluginRequirements, ) (map[string]terraform.ResourceProviderFactory, []error) { factories := make(map[string]terraform.ResourceProviderFactory, len(reqd)) var errs []error - candidates := r.Available.ConstrainVersions(reqd) + chosen := choosePlugins(r.Available, reqd) for name := range reqd { - if metas := candidates[name]; metas != nil { - newest := metas.Newest() - + if newest, available := chosen[name]; available { digest, err := newest.SHA256() if err != nil { errs = append(errs, fmt.Errorf("provider.%s: failed to load plugin to verify its signature: %s", name, err)) @@ -45,7 +55,7 @@ func (r *multiVersionProviderResolver) ResolveProviders( // here is that they need to run "terraform init" to // fix this, which is covered by the UI code reporting these // error messages. - errs = append(errs, fmt.Errorf("provider.%s: not yet initialized", name)) + errs = append(errs, fmt.Errorf("provider.%s: installed but not yet initialized", name)) continue } @@ -108,10 +118,10 @@ func (m *Meta) providerResolver() terraform.ResourceProviderResolver { } // filter the requirements returning only the providers that we can't resolve -func (m *Meta) missingProviders(reqd discovery.PluginRequirements) discovery.PluginRequirements { +func (m *Meta) missingPlugins(avail discovery.PluginMetaSet, reqd discovery.PluginRequirements) discovery.PluginRequirements { missing := make(discovery.PluginRequirements) - candidates := m.providerPluginSet().ConstrainVersions(reqd) + candidates := avail.ConstrainVersions(reqd) for name, versionSet := range reqd { if metas := candidates[name]; metas == nil { diff --git a/command/push_test.go b/command/push_test.go index 473a31843d..ec83abaa39 100644 --- a/command/push_test.go +++ b/command/push_test.go @@ -4,10 +4,12 @@ import ( "archive/tar" "bytes" "compress/gzip" + "fmt" "io" "os" "path/filepath" "reflect" + "runtime" "sort" "strings" "testing" @@ -121,6 +123,9 @@ func TestPush_goodBackendInit(t *testing.T) { // Expected weird behavior, doesn't affect unpackaging ".terraform/", ".terraform/", + ".terraform/plugins/", + fmt.Sprintf(".terraform/plugins/%s_%s/", runtime.GOOS, runtime.GOARCH), + fmt.Sprintf(".terraform/plugins/%s_%s/providers.json", runtime.GOOS, runtime.GOARCH), ".terraform/terraform.tfstate", ".terraform/terraform.tfstate", "main.tf", diff --git a/command/test-fixtures/init-provider-lock-file/main.tf b/command/test-fixtures/init-provider-lock-file/main.tf new file mode 100644 index 0000000000..7eed7c5613 --- /dev/null +++ b/command/test-fixtures/init-provider-lock-file/main.tf @@ -0,0 +1,3 @@ +provider "test" { + version = "1.2.3" +} diff --git a/command/test-fixtures/init-providers-lock/main.tf b/command/test-fixtures/init-providers-lock/main.tf new file mode 100644 index 0000000000..7eed7c5613 --- /dev/null +++ b/command/test-fixtures/init-providers-lock/main.tf @@ -0,0 +1,3 @@ +provider "test" { + version = "1.2.3" +} From fdeb3d929cebc30ffa12fee04babf6e1170f0fad Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 31 May 2017 16:08:56 -0400 Subject: [PATCH 56/82] Add Versions.Sort Sort versions from newest to oldest. --- plugin/discovery/version.go | 11 +++++++++ plugin/discovery/version_test.go | 39 ++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 plugin/discovery/version_test.go diff --git a/plugin/discovery/version.go b/plugin/discovery/version.go index 50d55635e6..dab26b3218 100644 --- a/plugin/discovery/version.go +++ b/plugin/discovery/version.go @@ -1,6 +1,8 @@ package discovery import ( + "sort" + version "github.com/hashicorp/go-version" ) @@ -45,3 +47,12 @@ func (v Version) String() string { func (v Version) NewerThan(other Version) bool { return v.raw.GreaterThan(other.raw) } + +type Versions []Version + +// Sort sorts version from newest to oldest. +func (v Versions) Sort() { + sort.Slice(v, func(i, j int) bool { + return v[i].NewerThan(v[j]) + }) +} diff --git a/plugin/discovery/version_test.go b/plugin/discovery/version_test.go new file mode 100644 index 0000000000..e34647b803 --- /dev/null +++ b/plugin/discovery/version_test.go @@ -0,0 +1,39 @@ +package discovery + +import ( + "reflect" + "testing" +) + +func TestSortVersions(t *testing.T) { + versions := Versions{ + VersionStr("4").MustParse(), + VersionStr("3.1").MustParse(), + VersionStr("1.2").MustParse(), + VersionStr("1.2.3").MustParse(), + VersionStr("2.2.3").MustParse(), + VersionStr("3.2.1").MustParse(), + VersionStr("2.3.2").MustParse(), + } + + expected := []string{ + "4.0.0", + "3.2.1", + "3.1.0", + "2.3.2", + "2.2.3", + "1.2.3", + "1.2.0", + } + + versions.Sort() + + var sorted []string + for _, v := range versions { + sorted = append(sorted, v.String()) + } + + if !reflect.DeepEqual(sorted, expected) { + t.Fatal("versions aren't sorted:", sorted) + } +} From 8ad67991a56070422918738d290048ae2fc079c5 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Wed, 31 May 2017 17:38:55 -0400 Subject: [PATCH 57/82] check protocol version on plugins Verify that the plugin we're requesting has a compatible protocol version. --- plugin/discovery/get.go | 70 ++++++++++++++++++++++++++++++++---- plugin/discovery/get_test.go | 31 ++++++++++------ 2 files changed, 85 insertions(+), 16 deletions(-) diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index bb8afae278..07781ae713 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -7,15 +7,19 @@ import ( "log" "net/http" "runtime" + "strconv" "strings" "golang.org/x/net/html" + cleanhttp "github.com/hashicorp/go-cleanhttp" getter "github.com/hashicorp/go-getter" ) const releasesURL = "https://releases.hashicorp.com/" +var httpClient = cleanhttp.DefaultClient() + // pluginURL generates URLs to lookup the versions of a plugin, or the file path. // // The URL for releases follows the pattern: @@ -69,6 +73,16 @@ func GetProvider(dst, provider string, req Constraints) error { return err } + if len(versions) == 0 { + return fmt.Errorf("no plugins found for provider %q", provider) + } + + versions = filterProtocolVersions(provider, versions) + + if len(versions) == 0 { + return fmt.Errorf("no versions of %q compatible with the plugin ProtocolVersion", provider) + } + version, err := newestVersion(versions, req) if err != nil { return fmt.Errorf("no version of %q available that fulfills constraints %s", provider, req) @@ -80,6 +94,48 @@ func GetProvider(dst, provider string, req Constraints) error { return getter.Get(dst, url) } +// Remove available versions that don't have the correct plugin protocol version. +// TODO: stop checking older versions if the protocol version is too low +func filterProtocolVersions(provider string, versions []Version) []Version { + var compatible []Version + for _, v := range versions { + log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v) + url := providersURL.fileURL(provider, v.String()) + resp, err := httpClient.Head(url) + if err != nil { + log.Printf("[ERROR] error fetching plugin headers: %s", err) + continue + } + + if resp.StatusCode != http.StatusOK { + log.Println("[ERROR] non-200 status fetching plugin headers:", resp.Status) + continue + } + + proto := resp.Header.Get("X-TERRAFORM_PROTOCOL_VERSION") + if proto == "" { + log.Println("[WARNING] missing X-TERRAFORM_PROTOCOL_VERSION from:", url) + continue + } + + protoVersion, err := strconv.Atoi(proto) + if err != nil { + log.Println("[ERROR] invalid ProtocolVersion: %s", proto) + continue + } + + // FIXME: this shouldn't be hardcoded + if protoVersion != 4 { + log.Printf("[INFO] incompatible ProtocolVersion %d from %s version %s", protoVersion, provider, v) + continue + } + + compatible = append(compatible, v) + } + + return compatible +} + var errVersionNotFound = errors.New("version not found") // take the list of available versions for a plugin, and the required @@ -128,11 +184,8 @@ func listProvisionerVersions(name string) ([]Version, error) { } // return a list of the plugin versions at the given URL -// TODO: This doesn't yet take into account plugin protocol version. -// That may have to be checked via an http header via a separate request -// to each plugin file. func listPluginVersions(url string) ([]Version, error) { - resp, err := http.Get(url) + resp, err := httpClient.Get(url) if err != nil { return nil, err } @@ -167,8 +220,12 @@ func listPluginVersions(url string) ([]Version, error) { } f(body) - var versions []Version + return versionsFromNames(names), nil +} +// parse the list of directory names into a sorted list of available versions +func versionsFromNames(names []string) []Version { + var versions []Version for _, name := range names { parts := strings.SplitN(name, "_", 2) if len(parts) == 2 && parts[1] != "" { @@ -183,5 +240,6 @@ func listPluginVersions(url string) ([]Version, error) { } } - return versions, nil + Versions(versions).Sort() + return versions } diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go index 23ba4af6a5..f2385c3b5f 100644 --- a/plugin/discovery/get_test.go +++ b/plugin/discovery/get_test.go @@ -6,13 +6,19 @@ import ( "testing" ) -func TestVersionListing(t *testing.T) { +// lists a constant set of providers, and always returns a protocol version +// equal to the Patch number. +func testReleaseServer() *httptest.Server { handler := http.NewServeMux() handler.HandleFunc("/terraform-providers/terraform-provider-test/", func(w http.ResponseWriter, r *http.Request) { w.Write([]byte(versionList)) }) - server := httptest.NewServer(handler) + return httptest.NewServer(handler) +} + +func TestVersionListing(t *testing.T) { + server := testReleaseServer() defer server.Close() providersURL.releases = server.URL + "/" @@ -22,17 +28,22 @@ func TestVersionListing(t *testing.T) { t.Fatal(err) } - expectedSet := map[string]bool{ - "1.2.4": true, - "1.2.3": true, - "1.2.1": true, + Versions(versions).Sort() + + expected := []string{ + "1.2.4", + "1.2.3", + "1.2.1", } - for _, v := range versions { - if !expectedSet[v.String()] { - t.Fatalf("didn't get version %s in listing", v) + if len(versions) != len(expected) { + t.Fatalf("Received wrong number of versions. expected: %q, got: %q", expected, versions) + } + + for i, v := range versions { + if v.String() != expected[i] { + t.Fatalf("incorrect version: %q, expected %q", v, expected[i]) } - delete(expectedSet, v.String()) } } From 5f053a2e64ddd58f6d50972ba39abf73bece6f2d Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 1 Jun 2017 14:32:33 -0400 Subject: [PATCH 58/82] refactor GetProvider Get provider needs to be provided with the plugin protocol version, because it can't be imported here directly. The plugin url types and methods were confusing; replace them with a few functions to format the urls. --- plugin/discovery/get.go | 94 +++++++++++++++++------------------------ 1 file changed, 38 insertions(+), 56 deletions(-) diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index 07781ae713..c84e7b1bd8 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -16,57 +16,49 @@ import ( getter "github.com/hashicorp/go-getter" ) -const releasesURL = "https://releases.hashicorp.com/" - -var httpClient = cleanhttp.DefaultClient() - -// pluginURL generates URLs to lookup the versions of a plugin, or the file path. +// Releases are located by parsing the html listing from releases.hashicorp.com. // // The URL for releases follows the pattern: // https://releases.hashicorp.com/terraform-providers/terraform-provider-name/ + // terraform-provider-name_/terraform-provider-name___. -type pluginURL struct { - // the base url to search for releases - releases string - // The name prefix common to all plugins of this type. - // This is either `terraform-provider` or `terraform-provisioner`. - baseName string +// +// The plugin protocol version will be saved with the release and returned in +// the header X-TERRAFORM_PROTOCOL_VERSION. + +const providersPath = "/terraform-providers/" +const protocolVersionHeader = "X-TERRAFORM_PROTOCOL_VERSION" + +var releaseHost = "https://releases.hashicorp.com" + +var httpClient = cleanhttp.DefaultClient() + +// Plugins are referred to by the short name, but all URLs and files will use +// the full name prefixed with terraform-- +func providerName(name string) string { + return "terraform-provider-" + name } -// releasesURL returns the top level directory for all plugins of this type -func (p pluginURL) releasesURL() string { - // the top level directory is the plural form of the plugin type - return p.releases + p.baseName + "s" +// providerVersionsURL returns the path to the released versions directory for the provider: +// https://releases.hashicorp.com/terraform-providers/terraform-provider-name/ +func providerVersionsURL(name string) string { + return releaseHost + providersPath + providerName(name) + "/" } -// versionsURL returns the url to the directory to list available versionsURL for this plugin -func (p pluginURL) versionsURL(name string) string { - return fmt.Sprintf("%s/%s-%s", p.releasesURL(), p.baseName, name) -} - -// fileURL returns the full path to a plugin based on the plugin name, -// version, GOOS and GOARCH. -func (p pluginURL) fileURL(name, version string) string { - releasesDir := fmt.Sprintf("%s-%s_%s/", p.baseName, name, version) - fileName := fmt.Sprintf("%s-%s_%s_%s_%s.zip", p.baseName, name, version, runtime.GOOS, runtime.GOARCH) - return fmt.Sprintf("%s/%s/%s", p.versionsURL(name), releasesDir, fileName) -} - -var providersURL = pluginURL{ - releases: releasesURL, - baseName: "terraform-provider", -} - -var provisionersURL = pluginURL{ - releases: releasesURL, - baseName: "terraform-provisioners", +// providerURL returns the full path to the provider file, using the current OS +// and ARCH: +// .../terraform-provider-name_/terraform-provider-name___. +func providerURL(name, version string) string { + versionDir := fmt.Sprintf("%s_%s", providerName(name), version) + fileName := fmt.Sprintf("%s_%s_%s_%s.zip", providerName(name), version, runtime.GOOS, runtime.GOARCH) + u := fmt.Sprintf("%s%s/%s", providerVersionsURL(name), versionDir, fileName) + return u } // GetProvider fetches a provider plugin based on the version constraints, and // copies it to the dst directory. // // TODO: verify checksum and signature -func GetProvider(dst, provider string, req Constraints) error { +func GetProvider(dst, provider string, req Constraints, pluginProtocolVersion uint) error { versions, err := listProviderVersions(provider) // TODO: return multiple errors if err != nil { @@ -77,7 +69,7 @@ func GetProvider(dst, provider string, req Constraints) error { return fmt.Errorf("no plugins found for provider %q", provider) } - versions = filterProtocolVersions(provider, versions) + versions = filterProtocolVersions(provider, versions, pluginProtocolVersion) if len(versions) == 0 { return fmt.Errorf("no versions of %q compatible with the plugin ProtocolVersion", provider) @@ -88,7 +80,7 @@ func GetProvider(dst, provider string, req Constraints) error { return fmt.Errorf("no version of %q available that fulfills constraints %s", provider, req) } - url := providersURL.fileURL(provider, version.String()) + url := providerURL(provider, version.String()) log.Printf("[DEBUG] getting provider %q version %q at %s", provider, version, url) return getter.Get(dst, url) @@ -96,11 +88,11 @@ func GetProvider(dst, provider string, req Constraints) error { // Remove available versions that don't have the correct plugin protocol version. // TODO: stop checking older versions if the protocol version is too low -func filterProtocolVersions(provider string, versions []Version) []Version { +func filterProtocolVersions(provider string, versions []Version, pluginProtocolVersion uint) []Version { var compatible []Version for _, v := range versions { log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v) - url := providersURL.fileURL(provider, v.String()) + url := providerURL(provider, v.String()) resp, err := httpClient.Head(url) if err != nil { log.Printf("[ERROR] error fetching plugin headers: %s", err) @@ -112,20 +104,19 @@ func filterProtocolVersions(provider string, versions []Version) []Version { continue } - proto := resp.Header.Get("X-TERRAFORM_PROTOCOL_VERSION") + proto := resp.Header.Get(protocolVersionHeader) if proto == "" { - log.Println("[WARNING] missing X-TERRAFORM_PROTOCOL_VERSION from:", url) + log.Printf("[WARNING] missing %s from: %s", protocolVersionHeader, url) continue } protoVersion, err := strconv.Atoi(proto) if err != nil { - log.Println("[ERROR] invalid ProtocolVersion: %s", proto) + log.Printf("[ERROR] invalid ProtocolVersion: %s", proto) continue } - // FIXME: this shouldn't be hardcoded - if protoVersion != 4 { + if protoVersion != int(pluginProtocolVersion) { log.Printf("[INFO] incompatible ProtocolVersion %d from %s version %s", protoVersion, provider, v) continue } @@ -167,22 +158,13 @@ func newestVersion(available []Version, required Constraints) (Version, error) { // list the version available for the named plugin func listProviderVersions(name string) ([]Version, error) { - versions, err := listPluginVersions(providersURL.versionsURL(name)) + versions, err := listPluginVersions(providerVersionsURL(name)) if err != nil { return nil, fmt.Errorf("failed to fetch versions for provider %q: %s", name, err) } return versions, nil } -func listProvisionerVersions(name string) ([]Version, error) { - versions, err := listPluginVersions(provisionersURL.versionsURL(name)) - if err != nil { - return nil, fmt.Errorf("failed to fetch versions for provisioner %q: %s", name, err) - } - - return versions, nil -} - // return a list of the plugin versions at the given URL func listPluginVersions(url string) ([]Version, error) { resp, err := httpClient.Get(url) From 48d37131e0fbd772a0306f82cb7735afc602c426 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 1 Jun 2017 14:34:47 -0400 Subject: [PATCH 59/82] more tests for fetching providers Extend the test reslease server to return the protocol version header and a dummy zip file for the provider. Test filtering the plugins by plugin protocol version and add a full GetProvder test. --- plugin/discovery/get_test.go | 113 ++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 8 deletions(-) diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go index f2385c3b5f..10c4d57d72 100644 --- a/plugin/discovery/get_test.go +++ b/plugin/discovery/get_test.go @@ -1,28 +1,78 @@ package discovery import ( + "archive/zip" + "fmt" + "io" + "io/ioutil" "net/http" "net/http/httptest" + "os" + "path/filepath" + "regexp" + "runtime" + "strings" "testing" ) -// lists a constant set of providers, and always returns a protocol version -// equal to the Patch number. +const testProviderFile = "test provider binary" + +// return the directory listing for the "test" provider +func testListingHandler(w http.ResponseWriter, r *http.Request) { + w.Write([]byte(versionList)) +} + +// returns a 200 for a valid provider url, using the patch number for the +// plugin protocol version. +func testHandler(w http.ResponseWriter, r *http.Request) { + if r.URL.Path == "/terraform-providers/terraform-provider-test/" { + testListingHandler(w, r) + return + } + + parts := strings.Split(r.URL.Path, "/") + if len(parts) != 5 { + http.Error(w, "not found", http.StatusNotFound) + return + } + + filename := parts[4] + + reg := regexp.MustCompile(`(terraform-provider-test_(\d).(\d).(\d)_([^_]+)_([^._]+)).zip`) + + fileParts := reg.FindStringSubmatch(filename) + if len(fileParts) != 7 { + http.Error(w, "invalid provider: "+filename, http.StatusNotFound) + return + } + + w.Header().Set(protocolVersionHeader, fileParts[4]) + + // write a dummy file + z := zip.NewWriter(w) + f, err := z.Create(fileParts[1] + "_X" + fileParts[4]) + if err != nil { + panic(err) + } + io.WriteString(f, testProviderFile) + z.Close() +} + func testReleaseServer() *httptest.Server { handler := http.NewServeMux() - handler.HandleFunc("/terraform-providers/terraform-provider-test/", func(w http.ResponseWriter, r *http.Request) { - w.Write([]byte(versionList)) - }) + handler.HandleFunc("/terraform-providers/terraform-provider-test/", testHandler) return httptest.NewServer(handler) } -func TestVersionListing(t *testing.T) { +func TestMain(m *testing.M) { server := testReleaseServer() - defer server.Close() + releaseHost = server.URL - providersURL.releases = server.URL + "/" + os.Exit(m.Run()) +} +func TestVersionListing(t *testing.T) { versions, err := listProviderVersions("test") if err != nil { t.Fatal(err) @@ -82,6 +132,53 @@ func TestNewestVersion(t *testing.T) { } } +func TestFilterProtocolVersions(t *testing.T) { + versions, err := listProviderVersions("test") + if err != nil { + t.Fatal(err) + } + + // use plugin protocl version 3, which should only return version 1.2.3 + compat := filterProtocolVersions("test", versions, 3) + + if len(compat) != 1 || compat[0].String() != "1.2.3" { + t.Fatal("found wrong versions: %q", compat) + } + + compat = filterProtocolVersions("test", versions, 6) + if len(compat) != 0 { + t.Fatal("should be no compatible versions, got: %q", compat) + } +} + +func TestGetProvider(t *testing.T) { + tmpDir, err := ioutil.TempDir("", "tf-plugin") + if err != nil { + t.Fatal(err) + } + + defer os.RemoveAll(tmpDir) + + fileName := fmt.Sprintf("terraform-provider-test_1.2.3_%s_%s_X3", runtime.GOOS, runtime.GOARCH) + + err = GetProvider(tmpDir, "test", AllVersions, 3) + if err != nil { + t.Fatal(err) + } + + dest := filepath.Join(tmpDir, fileName) + f, err := ioutil.ReadFile(dest) + if err != nil { + t.Fatal(err) + } + + // provider should have been unzipped + if string(f) != testProviderFile { + t.Fatalf("test provider contains: %q", f) + } + +} + const versionList = ` From e0f2235f666efcf01d0ac3fa69cf5423592f22bb Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 1 Jun 2017 14:36:30 -0400 Subject: [PATCH 60/82] update init command with new GetProvider signature GetProvider needs the plugin protocol version to be passed in --- command/init.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/command/init.go b/command/init.go index 7afeb88304..ab957ac87a 100644 --- a/command/init.go +++ b/command/init.go @@ -11,6 +11,7 @@ import ( "github.com/hashicorp/terraform/config" "github.com/hashicorp/terraform/config/module" "github.com/hashicorp/terraform/helper/variables" + "github.com/hashicorp/terraform/plugin" "github.com/hashicorp/terraform/plugin/discovery" "github.com/hashicorp/terraform/terraform" ) @@ -24,7 +25,7 @@ type InitCommand struct { // them into the dst directory. // This uses discovery.GetProvider by default, but it provided here as a // way to mock fetching providers for tests. - getProvider func(dst, provider string, req discovery.Constraints) error + getProvider func(dst, provider string, req discovery.Constraints, protoVersion uint) error } func (c *InitCommand) Run(args []string) int { @@ -222,7 +223,7 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { dst := c.pluginDir() for provider, reqd := range missing { - err := c.getProvider(dst, provider, reqd.Versions) + err := c.getProvider(dst, provider, reqd.Versions, plugin.Handshake.ProtocolVersion) // TODO: return all errors if err != nil { return err From dbbafbd43fb924cfe1e6bd0a537195c623a062e7 Mon Sep 17 00:00:00 2001 From: James Bardin Date: Thu, 1 Jun 2017 17:17:14 -0400 Subject: [PATCH 61/82] clean up plugin fetching We can filter the allowed versions and sort them before checking the protocol version, that way we can just return the first one found reducing network requests. --- plugin/discovery/get.go | 109 ++++++++++++++--------------------- plugin/discovery/get_test.go | 62 ++++---------------- 2 files changed, 56 insertions(+), 115 deletions(-) diff --git a/plugin/discovery/get.go b/plugin/discovery/get.go index c84e7b1bd8..2eff52d814 100644 --- a/plugin/discovery/get.go +++ b/plugin/discovery/get.go @@ -69,91 +69,71 @@ func GetProvider(dst, provider string, req Constraints, pluginProtocolVersion ui return fmt.Errorf("no plugins found for provider %q", provider) } - versions = filterProtocolVersions(provider, versions, pluginProtocolVersion) - + versions = allowedVersions(versions, req) if len(versions) == 0 { - return fmt.Errorf("no versions of %q compatible with the plugin ProtocolVersion", provider) - } - - version, err := newestVersion(versions, req) - if err != nil { return fmt.Errorf("no version of %q available that fulfills constraints %s", provider, req) } - url := providerURL(provider, version.String()) + // sort them newest to oldest + Versions(versions).Sort() - log.Printf("[DEBUG] getting provider %q version %q at %s", provider, version, url) - return getter.Get(dst, url) -} - -// Remove available versions that don't have the correct plugin protocol version. -// TODO: stop checking older versions if the protocol version is too low -func filterProtocolVersions(provider string, versions []Version, pluginProtocolVersion uint) []Version { - var compatible []Version + // take the first matching plugin we find for _, v := range versions { - log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v) url := providerURL(provider, v.String()) - resp, err := httpClient.Head(url) - if err != nil { - log.Printf("[ERROR] error fetching plugin headers: %s", err) - continue + log.Printf("[DEBUG] fetching provider info for %s version %s", provider, v) + if checkPlugin(url, pluginProtocolVersion) { + log.Printf("[DEBUG] getting provider %q version %q at %s", provider, v, url) + return getter.Get(dst, url) } - if resp.StatusCode != http.StatusOK { - log.Println("[ERROR] non-200 status fetching plugin headers:", resp.Status) - continue - } - - proto := resp.Header.Get(protocolVersionHeader) - if proto == "" { - log.Printf("[WARNING] missing %s from: %s", protocolVersionHeader, url) - continue - } - - protoVersion, err := strconv.Atoi(proto) - if err != nil { - log.Printf("[ERROR] invalid ProtocolVersion: %s", proto) - continue - } - - if protoVersion != int(pluginProtocolVersion) { - log.Printf("[INFO] incompatible ProtocolVersion %d from %s version %s", protoVersion, provider, v) - continue - } - - compatible = append(compatible, v) + log.Printf("[INFO] incompatible ProtocolVersion for %s version %s", provider, v) } - return compatible + return fmt.Errorf("no versions of %q compatible with the plugin ProtocolVersion", provider) +} + +// Return the plugin version by making a HEAD request to the provided url +func checkPlugin(url string, pluginProtocolVersion uint) bool { + resp, err := httpClient.Head(url) + if err != nil { + log.Printf("[ERROR] error fetching plugin headers: %s", err) + return false + } + + if resp.StatusCode != http.StatusOK { + log.Println("[ERROR] non-200 status fetching plugin headers:", resp.Status) + return false + } + + proto := resp.Header.Get(protocolVersionHeader) + if proto == "" { + log.Printf("[WARNING] missing %s from: %s", protocolVersionHeader, url) + return false + } + + protoVersion, err := strconv.Atoi(proto) + if err != nil { + log.Printf("[ERROR] invalid ProtocolVersion: %s", proto) + return false + } + + return protoVersion == int(pluginProtocolVersion) } var errVersionNotFound = errors.New("version not found") -// take the list of available versions for a plugin, and the required -// Constraints, and return the latest available version that satisfies the -// constraints. -func newestVersion(available []Version, required Constraints) (Version, error) { - var latest Version - found := false +// take the list of available versions for a plugin, and filter out those that +// don't fit the constraints. +func allowedVersions(available []Version, required Constraints) []Version { + var allowed []Version for _, v := range available { if required.Allows(v) { - if !found { - latest = v - found = true - continue - } - - if v.NewerThan(latest) { - latest = v - } + allowed = append(allowed, v) } } - if !found { - return latest, errVersionNotFound - } - return latest, nil + return allowed } // list the version available for the named plugin @@ -222,6 +202,5 @@ func versionsFromNames(names []string) []Version { } } - Versions(versions).Sort() return versions } diff --git a/plugin/discovery/get_test.go b/plugin/discovery/get_test.go index 10c4d57d72..879be9ab27 100644 --- a/plugin/discovery/get_test.go +++ b/plugin/discovery/get_test.go @@ -97,57 +97,13 @@ func TestVersionListing(t *testing.T) { } } -func TestNewestVersion(t *testing.T) { - var available []Version - for _, v := range []string{"1.2.3", "1.2.1", "1.2.4"} { - version, err := VersionStr(v).Parse() - if err != nil { - t.Fatal(err) - } - available = append(available, version) +func TestCheckProtocolVersions(t *testing.T) { + if checkPlugin(providerURL("test", VersionStr("1.2.3").MustParse().String()), 4) { + t.Fatal("protocol version 4 is not compatible") } - reqd, err := ConstraintStr(">1.2.1").Parse() - if err != nil { - t.Fatal(err) - } - - found, err := newestVersion(available, reqd) - if err != nil { - t.Fatal(err) - } - - if found.String() != "1.2.4" { - t.Fatalf("expected newest version 1.2.4, got: %s", found) - } - - reqd, err = ConstraintStr("> 1.2.4").Parse() - if err != nil { - t.Fatal(err) - } - - found, err = newestVersion(available, reqd) - if err == nil { - t.Fatalf("expceted error, got version %s", found) - } -} - -func TestFilterProtocolVersions(t *testing.T) { - versions, err := listProviderVersions("test") - if err != nil { - t.Fatal(err) - } - - // use plugin protocl version 3, which should only return version 1.2.3 - compat := filterProtocolVersions("test", versions, 3) - - if len(compat) != 1 || compat[0].String() != "1.2.3" { - t.Fatal("found wrong versions: %q", compat) - } - - compat = filterProtocolVersions("test", versions, 6) - if len(compat) != 0 { - t.Fatal("should be no compatible versions, got: %q", compat) + if !checkPlugin(providerURL("test", VersionStr("1.2.3").MustParse().String()), 3) { + t.Fatal("protocol version 3 should be compatible") } } @@ -159,13 +115,19 @@ func TestGetProvider(t *testing.T) { defer os.RemoveAll(tmpDir) - fileName := fmt.Sprintf("terraform-provider-test_1.2.3_%s_%s_X3", runtime.GOOS, runtime.GOARCH) + // attempt to use an incompatible protocol version + err = GetProvider(tmpDir, "test", AllVersions, 5) + if err == nil { + t.Fatal("protocol version is incompatible") + } err = GetProvider(tmpDir, "test", AllVersions, 3) if err != nil { t.Fatal(err) } + // we should have version 1.2.3 + fileName := fmt.Sprintf("terraform-provider-test_1.2.3_%s_%s_X3", runtime.GOOS, runtime.GOARCH) dest := filepath.Join(tmpDir, fileName) f, err := ioutil.ReadFile(dest) if err != nil { From f70318097a7fb9d0ca13e655c7a10c62dfd4a700 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 1 Jun 2017 17:51:07 -0700 Subject: [PATCH 62/82] config: fix provider version constraint validation Previously we were using the "semver" library to parse version constraints, but we switched over to go-version and encapsulated it inside our own plugin/discovery package to reduce dependency sprawl in the code. This particular situation was missed when updating references to the new path, which meant that our validation code disagreed with the rest of the code about what is considered a valid version constraint string. By using the correct function, we ensure that we catch early any invalid versions. --- config/config.go | 4 ++-- config/config_test.go | 7 +++++++ .../validate-provider-version-invalid/main.tf | 3 +++ 3 files changed, 12 insertions(+), 2 deletions(-) create mode 100644 config/test-fixtures/validate-provider-version-invalid/main.tf diff --git a/config/config.go b/config/config.go index 0648d6917a..3f756dcf4d 100644 --- a/config/config.go +++ b/config/config.go @@ -8,11 +8,11 @@ import ( "strconv" "strings" - "github.com/blang/semver" "github.com/hashicorp/go-multierror" "github.com/hashicorp/hil" "github.com/hashicorp/hil/ast" "github.com/hashicorp/terraform/helper/hilmapstructure" + "github.com/hashicorp/terraform/plugin/discovery" "github.com/mitchellh/reflectwalk" ) @@ -391,7 +391,7 @@ func (c *Config) Validate() error { } if p.Version != "" { - _, err := semver.ParseRange(p.Version) + _, err := discovery.ConstraintStr(p.Version).Parse() if err != nil { errs = append(errs, fmt.Errorf( "provider.%s: invalid version constraint %q: %s", diff --git a/config/config_test.go b/config/config_test.go index db62e1c54a..edcf36af20 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -626,6 +626,13 @@ func TestConfigValidate_varModuleInvalid(t *testing.T) { } } +func TestConfigValidate_varProviderVersionInvalid(t *testing.T) { + c := testConfig(t, "validate-provider-version-invalid") + if err := c.Validate(); err == nil { + t.Fatal("should not be valid") + } +} + func TestNameRegexp(t *testing.T) { cases := []struct { Input string diff --git a/config/test-fixtures/validate-provider-version-invalid/main.tf b/config/test-fixtures/validate-provider-version-invalid/main.tf new file mode 100644 index 0000000000..9a251a3b2d --- /dev/null +++ b/config/test-fixtures/validate-provider-version-invalid/main.tf @@ -0,0 +1,3 @@ +provider "test" { + version = "bananas!" +} From 4ba20f9c1c909a4d89f0b210037e880a8c363581 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Thu, 1 Jun 2017 17:57:43 -0700 Subject: [PATCH 63/82] command init: show suggested constraints for unconstrained providers When running "terraform init" with providers that are unconstrained, we will now produce information to help the user update configuration to constrain for the particular providers that were chosen, to prevent inadvertently drifting onto a newer major release that might contain breaking changes. A ~> constraint is used here because pinning to a single specific version is expected to create dependency hell when using child modules. By using this constraint mode, which allows minor version upgrades, we avoid the need for users to constantly adjust version constraints across many modules, but make major version upgrades still be opt-in. Any constraint at all in the configuration will prevent the display of these suggestions, so users are free to use stronger or weaker constraints if desired, ignoring the recommendation. --- command/init.go | 46 +++++++++++++++++++++++++++++++++ plugin/discovery/version.go | 8 ++++++ plugin/discovery/version_set.go | 6 +++++ 3 files changed, 60 insertions(+) diff --git a/command/init.go b/command/init.go index ab957ac87a..685083a7eb 100644 --- a/command/init.go +++ b/command/init.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" getter "github.com/hashicorp/go-getter" @@ -187,6 +188,10 @@ func (c *InitCommand) Run(args []string) int { return 1 } + c.Ui.Output(c.Colorize().Color( + "[reset][bold]Initializing provider plugins...", + )) + err = c.getProviders(path, sMgr.State()) if err != nil { c.Ui.Error(fmt.Sprintf( @@ -249,6 +254,37 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { return fmt.Errorf("failed to save provider manifest: %s", err) } + // If any providers have "floating" versions (completely unconstrained) + // we'll suggest the user constrain with a pessimistic constraint to + // avoid implicitly adopting a later major release. + constraintSuggestions := make(map[string]discovery.ConstraintStr) + for name, meta := range chosen { + req := requirements[name] + if req == nil { + // should never happen, but we don't want to crash here, so we'll + // be cautious. + continue + } + + if req.Versions.Unconstrained() { + // meta.Version.MustParse is safe here because our "chosen" metas + // were already filtered for validity of versions. + constraintSuggestions[name] = meta.Version.MustParse().MinorUpgradeConstraintStr() + } + } + if len(constraintSuggestions) != 0 { + names := make([]string, 0, len(constraintSuggestions)) + for name := range constraintSuggestions { + names = append(names, name) + } + sort.Strings(names) + + c.Ui.Output(outputInitProvidersUnconstrained) + for _, name := range names { + c.Ui.Output(fmt.Sprintf("* provider.%s: version = %q", name, constraintSuggestions[name])) + } + } + return nil } @@ -361,3 +397,13 @@ If you ever set or change modules or backend configuration for Terraform, rerun this command to reinitialize your environment. If you forget, other commands will detect it and remind you to do so if necessary. ` + +const outputInitProvidersUnconstrained = ` +The following providers do not have any version constraints in configuration, +so the latest version was installed. + +To prevent automatic upgrades to new major versions that may contain breaking +changes, it is recommended to add version = "..." constraints to the +corresponding provider blocks in configuration, with the constraint strings +suggested below. +` diff --git a/plugin/discovery/version.go b/plugin/discovery/version.go index dab26b3218..587ebc6e9b 100644 --- a/plugin/discovery/version.go +++ b/plugin/discovery/version.go @@ -1,6 +1,7 @@ package discovery import ( + "fmt" "sort" version "github.com/hashicorp/go-version" @@ -48,6 +49,13 @@ func (v Version) NewerThan(other Version) bool { return v.raw.GreaterThan(other.raw) } +// MinorUpgradeConstraintStr returns a ConstraintStr that would permit +// minor upgrades relative to the receiving version. +func (v Version) MinorUpgradeConstraintStr() ConstraintStr { + segments := v.raw.Segments() + return ConstraintStr(fmt.Sprintf("~> %d.%d", segments[0], segments[1])) +} + type Versions []Version // Sort sorts version from newest to oldest. diff --git a/plugin/discovery/version_set.go b/plugin/discovery/version_set.go index 3ce5796899..273aca990a 100644 --- a/plugin/discovery/version_set.go +++ b/plugin/discovery/version_set.go @@ -69,3 +69,9 @@ func (s Constraints) Append(other Constraints) Constraints { func (s Constraints) String() string { return s.raw.String() } + +// Unconstrained returns true if and only if the receiver is an empty +// constraint set. +func (s Constraints) Unconstrained() bool { + return len(s.raw) == 0 +} From 9aae06db97dee3adbc5a277f20ebc62b125cf7c7 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 5 Jun 2017 16:35:53 -0700 Subject: [PATCH 64/82] command: update mockGetProvider.GetProvider for new interface The expected type was changed in the mainline code but the tests were not updated to match. --- command/plugins_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/command/plugins_test.go b/command/plugins_test.go index 74544a3b16..827e587c9f 100644 --- a/command/plugins_test.go +++ b/command/plugins_test.go @@ -22,7 +22,7 @@ func (m mockGetProvider) FileName(provider, version string) string { // GetProvider will check the Providers map to see if it can find a suitable // version, and put an empty file in the dst directory. -func (m mockGetProvider) GetProvider(dst, provider string, req discovery.Constraints) error { +func (m mockGetProvider) GetProvider(dst, provider string, req discovery.Constraints, protoVersion uint) error { versions := m.Providers[provider] if len(versions) == 0 { return fmt.Errorf("provider %q not found", provider) From 4571a16b1568871a93fddc7a6429167894578bc4 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 5 Jun 2017 16:37:08 -0700 Subject: [PATCH 65/82] command: remove Meta.forceProviderSHA256s This was added with the idea of using it to override the SHA256 hashes to match those hypothetically stored in a plan, but we already have a mechanism elsewhere for populating context fields from plan fields, so this is not actually necessary. --- command/meta.go | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/command/meta.go b/command/meta.go index c3ba6ee0ac..dc581d4a38 100644 --- a/command/meta.go +++ b/command/meta.go @@ -50,11 +50,6 @@ type Meta struct { // Override certain behavior for tests within this package testingOverrides *testingOverrides - // Override the set of provider plugin SHA256 digests we expect. - // If this is nil we will instead read from the provider lock file - // when setting up ContextOpts. - forceProviderSHA256s map[string][]byte - //---------------------------------------------------------- // Private: do not set these //---------------------------------------------------------- @@ -249,11 +244,7 @@ func (m *Meta) contextOpts() *terraform.ContextOpts { opts.Provisioners = m.provisionerFactories() } - if m.forceProviderSHA256s != nil { - opts.ProviderSHA256s = m.forceProviderSHA256s - } else { - opts.ProviderSHA256s = m.providerPluginsLock().Read() - } + opts.ProviderSHA256s = m.providerPluginsLock().Read() opts.Meta = &terraform.ContextMeta{ Env: m.Env(), From 1b673746fd0d7a0f97127bf59d1e85b9e99afd62 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Mon, 5 Jun 2017 17:08:02 -0700 Subject: [PATCH 66/82] core: don't allow core or providers to change between plan and apply The information stored in a plan is tightly coupled to the Terraform core and provider plugins that were used to create it, since we have no mechanism to "upgrade" a plan to reflect schema changes and so mismatching versions are likely to lead to the "diffs didn't match during apply" error. To allow us to catch this early and return an error message that _doesn't_ say it's a bug in Terraform, we'll remember the Terraform version and plugin binaries that created a particular plan and then require that those match when loading the plan in order to apply it. The planFormatVersion is increased here so that plan files produced by earlier Terraform versions _without_ this information won't be accepted by this new version, and also that older versions won't try to process plans created by newer versions. --- terraform/context.go | 5 ++++ terraform/context_plan_test.go | 7 ++++++ terraform/plan.go | 30 +++++++++++++++++++++-- terraform/plan_test.go | 45 +++++++++++++++++++++++++++++++++- 4 files changed, 84 insertions(+), 3 deletions(-) diff --git a/terraform/context.go b/terraform/context.go index 0302eba4a8..649af3a7b1 100644 --- a/terraform/context.go +++ b/terraform/context.go @@ -106,6 +106,7 @@ type Context struct { l sync.Mutex // Lock acquired during any task parallelSem Semaphore providerInputConfig map[string]map[string]interface{} + providerSHA256s map[string][]byte runLock sync.Mutex runCond *sync.Cond runContext context.Context @@ -218,6 +219,7 @@ func NewContext(opts *ContextOpts) (*Context, error) { parallelSem: NewSemaphore(par), providerInputConfig: make(map[string]map[string]interface{}), + providerSHA256s: opts.ProviderSHA256s, sh: sh, }, nil } @@ -529,6 +531,9 @@ func (c *Context) Plan() (*Plan, error) { Vars: c.variables, State: c.state, Targets: c.targets, + + TerraformVersion: VersionString(), + ProviderSHA256s: c.providerSHA256s, } var operation walkOperation diff --git a/terraform/context_plan_test.go b/terraform/context_plan_test.go index 7c231063f3..39baa426d6 100644 --- a/terraform/context_plan_test.go +++ b/terraform/context_plan_test.go @@ -22,6 +22,9 @@ func TestContext2Plan_basic(t *testing.T) { "aws": testProviderFuncFixed(p), }, ), + ProviderSHA256s: map[string][]byte{ + "aws": []byte("placeholder"), + }, }) plan, err := ctx.Plan() @@ -33,6 +36,10 @@ func TestContext2Plan_basic(t *testing.T) { t.Fatalf("bad: %#v", plan.Diff.RootModule().Resources) } + if !reflect.DeepEqual(plan.ProviderSHA256s, ctx.providerSHA256s) { + t.Errorf("wrong ProviderSHA256s %#v; want %#v", plan.ProviderSHA256s, ctx.providerSHA256s) + } + actual := strings.TrimSpace(plan.String()) expected := strings.TrimSpace(testTerraformPlanStr) if actual != expected { diff --git a/terraform/plan.go b/terraform/plan.go index ea0884505a..31b26bb684 100644 --- a/terraform/plan.go +++ b/terraform/plan.go @@ -31,6 +31,9 @@ type Plan struct { Vars map[string]interface{} Targets []string + TerraformVersion string + ProviderSHA256s map[string][]byte + // Backend is the backend that this plan should use and store data with. Backend *BackendState @@ -42,17 +45,40 @@ type Plan struct { // The following fields in opts are overridden by the plan: Config, // Diff, State, Variables. func (p *Plan) Context(opts *ContextOpts) (*Context, error) { + var err error + opts, err = p.contextOpts(opts) + if err != nil { + return nil, err + } + return NewContext(opts) +} + +// contextOpts mutates the given base ContextOpts in place to use input +// objects obtained from the receiving plan. +func (p *Plan) contextOpts(base *ContextOpts) (*ContextOpts, error) { + opts := base + opts.Diff = p.Diff opts.Module = p.Module opts.State = p.State opts.Targets = p.Targets + opts.ProviderSHA256s = p.ProviderSHA256s + + thisVersion := VersionString() + if p.TerraformVersion != "" && p.TerraformVersion != thisVersion { + return nil, fmt.Errorf( + "plan was created with a different version of Terraform (created with %s, but running %s)", + p.TerraformVersion, thisVersion, + ) + } + opts.Variables = make(map[string]interface{}) for k, v := range p.Vars { opts.Variables[k] = v } - return NewContext(opts) + return opts, nil } func (p *Plan) String() string { @@ -86,7 +112,7 @@ func (p *Plan) init() { // the ability in the future to change the file format if we want for any // reason. const planFormatMagic = "tfplan" -const planFormatVersion byte = 1 +const planFormatVersion byte = 2 // ReadPlan reads a plan structure out of a reader in the format that // was written by WritePlan. diff --git a/terraform/plan_test.go b/terraform/plan_test.go index 9515efbaaa..4f654f6a08 100644 --- a/terraform/plan_test.go +++ b/terraform/plan_test.go @@ -2,11 +2,54 @@ package terraform import ( "bytes" + "reflect" "strings" - "testing" + + "github.com/hashicorp/terraform/config/module" ) +func TestPlanContextOpts(t *testing.T) { + plan := &Plan{ + Diff: &Diff{ + Modules: []*ModuleDiff{ + { + Path: []string{"test"}, + }, + }, + }, + Module: module.NewTree("test", nil), + State: &State{ + TFVersion: "sigil", + }, + Vars: map[string]interface{}{"foo": "bar"}, + Targets: []string{"baz"}, + + TerraformVersion: VersionString(), + ProviderSHA256s: map[string][]byte{ + "test": []byte("placeholder"), + }, + } + + got, err := plan.contextOpts(&ContextOpts{}) + if err != nil { + t.Fatalf("error creating context: %s", err) + } + + want := &ContextOpts{ + Diff: plan.Diff, + Module: plan.Module, + State: plan.State, + Variables: plan.Vars, + Targets: plan.Targets, + ProviderSHA256s: plan.ProviderSHA256s, + } + + if !reflect.DeepEqual(got, want) { + t.Errorf("wrong result\ngot: %#v\nwant %#v", got, want) + } +} + func TestReadWritePlan(t *testing.T) { plan := &Plan{ Module: testModule(t, "new-good"), From 3c429b362884ddce93ba9bd1b3a436845c2be790 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 2 Jun 2017 16:49:08 -0700 Subject: [PATCH 67/82] command init: show log output for each provider plugin downloaded Each provider plugin will take at least a few seconds to download, so providing feedback about each one should make users feel less like Terraform has hung. Ideally we'd show ongoing progress during the download, but that's not possible without re-working go-getter, so we'll accept this as an interim solution for now. --- command/init.go | 1 + 1 file changed, 1 insertion(+) diff --git a/command/init.go b/command/init.go index 685083a7eb..caea035333 100644 --- a/command/init.go +++ b/command/init.go @@ -228,6 +228,7 @@ func (c *InitCommand) getProviders(path string, state *terraform.State) error { dst := c.pluginDir() for provider, reqd := range missing { + c.Ui.Output(fmt.Sprintf("- downloading plugin for provider %q...", provider)) err := c.getProvider(dst, provider, reqd.Versions, plugin.Handshake.ProtocolVersion) // TODO: return all errors if err != nil { From d9b8c3cac092670aa397876c8d7f81c102c4b347 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 2 Jun 2017 16:50:32 -0700 Subject: [PATCH 68/82] website: "Initialization" section in the getting started guide It's now required to run "terraform init" to get the provider plugins installed, so we need to mention that in the intro guide. --- .../intro/getting-started/build.html.md | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/website/source/intro/getting-started/build.html.md b/website/source/intro/getting-started/build.html.md index b11cd92385..c0bb5e6909 100644 --- a/website/source/intro/getting-started/build.html.md +++ b/website/source/intro/getting-started/build.html.md @@ -112,6 +112,52 @@ is fully documented within our an AMI for Ubuntu, and request a "t2.micro" instance so we qualify under the free tier. +## Initialization + +The first command to run for a new configuration -- or after checking out +an existing configuration from version control -- is `terraform init`, which +initializes various local settings and data that will be used by subsequent +commands. + +In particular, this command will install the plugins for the providers in +use within the configuration, which in this case is just the `aws` provider: + +``` +$ terraform init +Initializing the backend... +Initializing provider plugins... +- downloading plugin for provider "aws"... + +The following providers do not have any version constraints in configuration, +so the latest version was installed. + +To prevent automatic upgrades to new major versions that may contain breaking +changes, it is recommended to add version = "..." constraints to the +corresponding provider blocks in configuration, with the constraint strings +suggested below. + +* provider.aws: version = "~> 1.0" + +Terraform has been successfully initialized! + +You may now begin working with Terraform. Try running "terraform plan" to see +any changes that are required for your infrastructure. All Terraform commands +should now work. + +If you ever set or change modules or backend configuration for Terraform, +rerun this command to reinitialize your environment. If you forget, other +commands will detect it and remind you to do so if necessary. +``` + +The `aws` provider plugin is downloaded and installed in a subdirectory of +the current working directory, along with various other book-keeping files. + +The output specifies which version of the plugin was installed, and suggests +specifying that version in configuration to ensure that running +`terraform init` in future will install a compatible version. This step +is not necessary for following the getting started guide, since this +configuration will be discarded at the end. + ## Execution Plan Next, let's see what Terraform would do if we asked it to From eaf5a621977e8dc83095f5f58e48324e980451d5 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 2 Jun 2017 16:54:30 -0700 Subject: [PATCH 69/82] website: don't recommend to use "terraform init" to clone examples This form of "terraform init" is vestigial at this point and being phased out in 0.10. Something similar may return in a later version for installing modules from a more formal module library, but for now we are advising to use git manually to simplify the UX for "terraform init". --- .../source/intro/examples/index.html.markdown | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/website/source/intro/examples/index.html.markdown b/website/source/intro/examples/index.html.markdown index 3b60a492ca..99df59b11a 100644 --- a/website/source/intro/examples/index.html.markdown +++ b/website/source/intro/examples/index.html.markdown @@ -39,23 +39,19 @@ To use these examples, Terraform must first be installed on your machine. You can install Terraform from the [downloads page](/downloads.html). Once installed, you can use two steps to view and run the examples. -To clone any examples, run `terraform init` with the URL to the example: +To try these examples, first clone them with git as usual: ``` -$ terraform init github.com/hashicorp/terraform/tree/master/examples/aws-two-tier +git clone https://github.com/hashicorp/terraform/examples/aws-two-tier +cd aws-two-tier +``` + +You can then use your own editor to read and browse the configurations. +To try out the example, initialize and then apply: + +``` +$ terraform init ... -``` - -This will put the example files within your working directory. You can then -use your own editor to read and browse the configurations. This command will -not _run_ any code. - -~> If you want to browse the files before downloading them, you can [view -them on GitHub](https://github.com/hashicorp/terraform/tree/master/examples/aws-two-tier). - -If you want to run the example, just run `terraform apply`: - -``` $ terraform apply ... ``` From 766f8e5d64df120edddabb6f3e18f4b406259439 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 2 Jun 2017 16:59:52 -0700 Subject: [PATCH 70/82] command init: remove confusing uses of "environment" in the usage "environment" is a very overloaded term, so here we prefer to use the term "working directory" to talk about a local directory where operations are executed on a given Terraform configuration. --- command/init.go | 12 ++++++------ website/source/docs/commands/init.html.markdown | 15 ++++++++------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/command/init.go b/command/init.go index caea035333..dbdc474dae 100644 --- a/command/init.go +++ b/command/init.go @@ -311,7 +311,7 @@ func (c *InitCommand) Help() string { helpText := ` Usage: terraform init [options] [SOURCE] [PATH] - Initialize a new or existing Terraform environment by creating + Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. This is the first command that should be run for any new or existing @@ -320,9 +320,9 @@ Usage: terraform init [options] [SOURCE] [PATH] control. This command is always safe to run multiple times. Though subsequent runs - may give errors, this command will never blow away your environment or state. - Even so, if you have important information, please back it up prior to - running this command just in case. + may give errors, this command will never delete your configuration or + state. Even so, if you have important information, please back it up prior + to running this command, just in case. If no arguments are given, the configuration in this working directory is initialized. @@ -337,7 +337,7 @@ Usage: terraform init [options] [SOURCE] [PATH] Options: - -backend=true Configure the backend for this environment. + -backend=true Configure the backend for this configuration. -backend-config=path This can be either a path to an HCL file with key/value assignments (same format as terraform.tfvars) or a @@ -395,7 +395,7 @@ any changes that are required for your infrastructure. All Terraform commands should now work. If you ever set or change modules or backend configuration for Terraform, -rerun this command to reinitialize your environment. If you forget, other +rerun this command to reinitialize your working directory. If you forget, other commands will detect it and remind you to do so if necessary. ` diff --git a/website/source/docs/commands/init.html.markdown b/website/source/docs/commands/init.html.markdown index 495763f2c5..6420654d6f 100644 --- a/website/source/docs/commands/init.html.markdown +++ b/website/source/docs/commands/init.html.markdown @@ -8,15 +8,16 @@ description: |- # Command: init -The `terraform init` command is used to initialize a Terraform configuration. -This is the first command that should be run for any new or existing -Terraform configuration. It is safe to run this command multiple times. +The `terraform init` command is used to initialize a working directory +containing Terraform configuration files. This is the first command that should +be run after writing a new Terraform configuration or cloning an existing one +from version control. It is safe to run this command multiple times. ## Usage Usage: `terraform init [options] [SOURCE] [PATH]` -Initialize a new or existing Terraform environment by creating +Initialize a new or existing Terraform working directory by creating initial files, loading any remote state, downloading modules, etc. This is the first command that should be run for any new or existing @@ -25,11 +26,11 @@ necessary to run Terraform that is typically not committed to version control. This command is always safe to run multiple times. Though subsequent runs -may give errors, this command will never blow away your environment or state. +may give errors, this command will never delete your configuration or state. Even so, if you have important information, please back it up prior to running this command just in case. -If no arguments are given, the configuration in this working directory +If no arguments are given, the configuration in the current working directory is initialized. If one or two arguments are given, the first is a SOURCE of a module to @@ -42,7 +43,7 @@ Git history. The command-line flags are all optional. The list of available flags are: -* `-backend=true` - Initialize the [backend](/docs/backends) for this environment. +* `-backend=true` - Initialize the [backend](/docs/backends) for this configuration. * `-backend-config=value` - Value can be a path to an HCL file or a string in the format of 'key=value'. This specifies additional configuration to merge From fb405ff296801846314167ac362d005d31d9d067 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 2 Jun 2017 17:10:11 -0700 Subject: [PATCH 71/82] website: new documentation about provider version configuration --- .../docs/configuration/providers.html.md | 57 ++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-) diff --git a/website/source/docs/configuration/providers.html.md b/website/source/docs/configuration/providers.html.md index 0b277a9121..653bbbfee0 100644 --- a/website/source/docs/configuration/providers.html.md +++ b/website/source/docs/configuration/providers.html.md @@ -33,7 +33,7 @@ A provider configuration looks like the following: provider "aws" { access_key = "foo" secret_key = "bar" - region = "us-east-1" + region = "us-east-1" } ``` @@ -55,6 +55,61 @@ Within the block (the `{ }`) is configuration for the resource. The configuration is dependent on the type, and is documented [for each provider](/docs/providers/index.html). +## Initialization + +Each time a new provider is added to configuration -- either explicitly via +a `provider` block or by adding a resource from that provider -- it's necessary +to initialize that provider before use. Initialization downloads and installs +the provider's plugin and prepares it to be used. + +Provider initialization is one of the actions of `terraform init`. Running +this command will download and initialize any providers that are not already +initialized. + +For more information, see +[the `terraform init` command](/docs/commands/init.html). + +## Provider Versions + +Providers are released on a separate rhythm from Terraform itself, and thus +have their own version numbers. For production use, it is recommended to +constrain the acceptable provider versions via configuration, to ensure that +new versions with breaking changes will not be automatically installed by +`terraform init` in future. + +When `terraform init` is run _without_ provider version constraints, it +prints a suggested version constraint string for each provider: + +``` +The following providers do not have any version constraints in configuration, +so the latest version was installed. + +To prevent automatic upgrades to new major versions that may contain breaking +changes, it is recommended to add version = "..." constraints to the +corresponding provider blocks in configuration, with the constraint strings +suggested below. + +* provider.aws: version = "~> 1.0" +``` + +To constrain the provider version as suggested, add a `version` argument to +the provider configuration block: + +```hcl +provider "aws" { + version = "~> 1.0" + + access_key = "foo" + secret_key = "bar" + region = "us-east-1" +} +``` + +This special argument applies to _all_ providers. +[`terraform providers`](/docs/commands/providers.html) can be used to +view the specified version constraints for all providers used in the +current configuration. + ## Multiple Provider Instances You can define multiple instances of the same provider in order to support From 98ef947ed184a5864fc9a892859ffa6c39048877 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 2 Jun 2017 17:10:32 -0700 Subject: [PATCH 72/82] website: correction of heading and usage in "providers" command docs --- website/source/docs/commands/providers.html.markdown | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/website/source/docs/commands/providers.html.markdown b/website/source/docs/commands/providers.html.markdown index edf71721d0..50bf4170d9 100644 --- a/website/source/docs/commands/providers.html.markdown +++ b/website/source/docs/commands/providers.html.markdown @@ -7,7 +7,7 @@ description: |- in the current configuration. --- -# Command: show +# Command: providers The `terraform providers` command prints information about the providers used in the current configuration. @@ -30,7 +30,7 @@ to understanding why a particular provider is needed. ## Usage -Usage: `terraform show [config-path]` +Usage: `terraform providers [config-path]` Pass an explicit configuration path to override the default of using the current working directory. From 55d5e8a6893332f41e7b8c0bd21776ec407ddf23 Mon Sep 17 00:00:00 2001 From: Martin Atkins Date: Fri, 2 Jun 2017 18:05:18 -0700 Subject: [PATCH 73/82] website: initial v0.10 upgrade guide Further iteration of this will undoubtedly be needed before the final release, but this is a start. --- website/source/layouts/downloads.erb | 3 + .../source/upgrade-guides/0-10.html.markdown | 146 ++++++++++++++++++ 2 files changed, 149 insertions(+) create mode 100644 website/source/upgrade-guides/0-10.html.markdown diff --git a/website/source/layouts/downloads.erb b/website/source/layouts/downloads.erb index ca2005cafc..de1dd703b0 100644 --- a/website/source/layouts/downloads.erb +++ b/website/source/layouts/downloads.erb @@ -8,6 +8,9 @@ > Upgrade Guides