implement provider inheritence during loading

This implements provider inheritance during config loading, rather than
during graph evaluation. At this point it's much simpler to find the
desired configuration, and once all providers are declared, all the
inheritance code in the graph can be removed.

The inheritance is dome by simply copying the RawConfig from the parent
ProviderConfig into the module. Since this happens before any
evaluation, we record the original interpolation scope in the
ProviderConfig so that it can be properly resolved later on.
This commit is contained in:
James Bardin 2017-10-12 13:43:04 -04:00
parent 29e5a355b9
commit cb0e37a870
11 changed files with 362 additions and 4 deletions

View File

@ -1,11 +1,9 @@
package module
import "github.com/hashicorp/terraform/config"
// Module represents the metadata for a single module.
type Module struct {
Name string
Source string
Version string
Providers []*config.ProviderConfig
Providers map[string]string
}

View File

@ -0,0 +1,13 @@
provider "top" {}
provider "bottom" {
alias = "foo"
value = "from bottom"
}
module "b" {
source = "../c"
providers = {
"bottom" = "bottom.foo"
}
}

View File

@ -0,0 +1,2 @@
# Hello
provider "bottom" {}

View File

@ -0,0 +1,11 @@
provider "top" {
alias = "foo"
value = "from top"
}
module "a" {
source = "./a"
providers = {
"top" = "top.foo"
}
}

View File

@ -0,0 +1,2 @@
resource "bar_resource" "in_grandchild" {}

View File

@ -0,0 +1,9 @@
resource "foo_resource" "in_child" {}
provider "bar" {
value = "from child"
}
module "grandchild" {
source = "./grandchild"
}

View File

@ -0,0 +1,7 @@
provider "foo" {
value = "from root"
}
module "child" {
source = "./child"
}

View File

@ -0,0 +1 @@
resource "foo_instance" "bar" {}

View File

@ -0,0 +1,7 @@
provider "foo" {
value = "from root"
}
module "child" {
source = "./child"
}

View File

@ -190,7 +190,7 @@ func (t *Tree) Load(s getter.Storage, mode GetMode) error {
// modules.
key := fmt.Sprintf("0.root.%s-%s", strings.Join(path, "."), m.Source)
log.Printf("[TRACE] module source %q", m.Source)
log.Printf("[TRACE] module source: %q", m.Source)
// Split out the subdir if we have one.
// Terraform keeps the entire requested tree for now, so that modules can
// reference sibling modules from the same archive or repo.
@ -301,9 +301,145 @@ func (t *Tree) Load(s getter.Storage, mode GetMode) error {
// Set our tree up
t.children = children
// if we're the root module, we can now set the provider inheritance
if len(t.path) == 0 {
t.inheritProviderConfigs(nil)
}
return nil
}
// Once the tree is loaded, we can resolve all provider config inheritance.
//
// This moves the full responsibility of inheritance to the config loader,
// simplifying locating provider configuration during graph evaluation.
// The algorithm is much simpler now too. If there is a provider block without
// a config, we look in the parent's Module block for a provider, and fetch
// that provider's configuration. If that doesn't exist, we assume a default
// empty config. Implicit providers can still inherit their config all the way
// up from the root, so we walk up the tree and copy the first matching
// provider into the module.
func (t *Tree) inheritProviderConfigs(stack []*Tree) {
stack = append(stack, t)
for _, c := range t.children {
c.inheritProviderConfigs(stack)
}
providers := make(map[string]*config.ProviderConfig)
missingProviders := make(map[string]bool)
for _, p := range t.config.ProviderConfigs {
providers[p.FullName()] = p
}
for _, r := range t.config.Resources {
p := r.ProviderFullName()
if _, ok := providers[p]; !(ok || strings.Contains(p, ".")) {
missingProviders[p] = true
}
}
// Search for implicit provider configs
// This adds an empty config is no inherited config is found, so that
// there is always a provider config present.
// This is done in the root module as well, just to set the providers.
for missing := range missingProviders {
// first create an empty provider config
pc := &config.ProviderConfig{
Name: missing,
}
// walk up the stack looking for matching providers
for i := len(stack) - 2; i >= 0; i-- {
pt := stack[i]
var parentProvider *config.ProviderConfig
for _, p := range pt.config.ProviderConfigs {
if p.FullName() == missing {
parentProvider = p
break
}
}
if parentProvider == nil {
continue
}
pc.Scope = pt.Path()
pc.Scope = append([]string{RootName}, pt.path...)
pc.RawConfig = parentProvider.RawConfig
log.Printf("[TRACE] provider %q inheriting config from %q",
strings.Join(append(t.Path(), pc.FullName()), "."),
strings.Join(append(pt.Path(), parentProvider.FullName()), "."),
)
break
}
// always set a provider config
if pc.RawConfig == nil {
pc.RawConfig, _ = config.NewRawConfig(map[string]interface{}{})
}
t.config.ProviderConfigs = append(t.config.ProviderConfigs, pc)
}
// After allowing the empty implicit configs to be created in root, there's nothing left to inherit
if len(stack) == 1 {
return
}
// get our parent's module config block
parent := stack[len(stack)-2]
var parentModule *config.Module
for _, m := range parent.config.Modules {
if m.Name == t.name {
parentModule = m
break
}
}
if parentModule == nil {
panic("can't be a module without a parent module config")
}
// now look for providers that need a config
for p, pc := range providers {
if len(pc.RawConfig.RawMap()) > 0 {
log.Printf("[TRACE] provider %q has a config, continuing", p)
continue
}
// this provider has no config yet, check for one being passed in
parentProviderName, ok := parentModule.Providers[p]
if !ok {
continue
}
var parentProvider *config.ProviderConfig
// there's a config for us in the parent module
for _, pp := range parent.config.ProviderConfigs {
if pp.FullName() == parentProviderName {
parentProvider = pp
break
}
}
if parentProvider == nil {
// no config found, assume defaults
continue
}
// Copy it in, but set an interpolation Scope.
// An interpolation Scope always need to have "root"
pc.Scope = append([]string{RootName}, parent.path...)
pc.RawConfig = parentProvider.RawConfig
log.Printf("[TRACE] provider %q inheriting config from %q",
strings.Join(append(t.Path(), pc.FullName()), "."),
strings.Join(append(parent.Path(), parentProvider.FullName()), "."),
)
}
}
func subdirRecordsPath(dir string) string {
const filename = "module-subdir.json"
// Get the parent directory.

View File

@ -3,6 +3,7 @@ package module
import (
"fmt"
"io/ioutil"
"log"
"os"
"path/filepath"
"reflect"
@ -518,6 +519,177 @@ func TestTreeValidate_unknownModule(t *testing.T) {
}
}
func TestTreeProviders_basic(t *testing.T) {
storage := testStorage(t)
tree := NewTree("", testConfig(t, "basic-parent-providers"))
if err := tree.Load(storage, GetModeGet); err != nil {
t.Fatalf("err: %s", err)
}
var a, b *Tree
for _, child := range tree.Children() {
if child.Name() == "a" {
a = child
}
}
rootProviders := tree.config.ProviderConfigsByFullName()
topRaw := rootProviders["top.foo"]
if a == nil {
t.Fatal("could not find module 'a'")
}
for _, child := range a.Children() {
if child.Name() == "b" {
b = child
}
}
if b == nil {
t.Fatal("could not find module 'b'")
}
aProviders := a.config.ProviderConfigsByFullName()
bottomRaw := aProviders["bottom.foo"]
bProviders := b.config.ProviderConfigsByFullName()
bBottom := bProviders["bottom"]
// compare the configs
// top.foo should have been copied to a.top
aTop := aProviders["top"]
if !reflect.DeepEqual(aTop.RawConfig.RawMap(), topRaw.RawConfig.RawMap()) {
log.Fatalf("expected config %#v, got %#v",
topRaw.RawConfig.RawMap(),
aTop.RawConfig.RawMap(),
)
}
if !reflect.DeepEqual(aTop.Scope, []string{RootName}) {
log.Fatalf(`expected scope for "top": {"root"}, got %#v`, aTop.Scope)
}
if !reflect.DeepEqual(bBottom.RawConfig.RawMap(), bottomRaw.RawConfig.RawMap()) {
t.Fatalf("expected config %#v, got %#v",
bottomRaw.RawConfig.RawMap(),
bBottom.RawConfig.RawMap(),
)
}
if !reflect.DeepEqual(bBottom.Scope, []string{RootName, "a"}) {
t.Fatalf(`expected scope for "bottom": {"root", "a"}, got %#v`, bBottom.Scope)
}
}
func TestTreeProviders_implicit(t *testing.T) {
storage := testStorage(t)
tree := NewTree("", testConfig(t, "implicit-parent-providers"))
if err := tree.Load(storage, GetModeGet); err != nil {
t.Fatalf("err: %s", err)
}
var child *Tree
for _, c := range tree.Children() {
if c.Name() == "child" {
child = c
}
}
if child == nil {
t.Fatal("could not find module 'child'")
}
// child should have inherited foo
providers := child.config.ProviderConfigsByFullName()
foo := providers["foo"]
if foo == nil {
t.Fatal("could not find provider 'foo' in child module")
}
if !reflect.DeepEqual([]string{RootName}, foo.Scope) {
t.Fatalf(`expected foo scope of {"root"}, got %#v`, foo.Scope)
}
expected := map[string]interface{}{
"value": "from root",
}
if !reflect.DeepEqual(expected, foo.RawConfig.RawMap()) {
t.Fatalf(`expected "foo" config %#v, got: %#v`, expected, foo.RawConfig.RawMap())
}
}
func TestTreeProviders_implicitMultiLevel(t *testing.T) {
storage := testStorage(t)
tree := NewTree("", testConfig(t, "implicit-grandparent-providers"))
if err := tree.Load(storage, GetModeGet); err != nil {
t.Fatalf("err: %s", err)
}
var child, grandchild *Tree
for _, c := range tree.Children() {
if c.Name() == "child" {
child = c
}
}
if child == nil {
t.Fatal("could not find module 'child'")
}
for _, c := range child.Children() {
if c.Name() == "grandchild" {
grandchild = c
}
}
if grandchild == nil {
t.Fatal("could not find module 'grandchild'")
}
// child should have inherited foo
providers := child.config.ProviderConfigsByFullName()
foo := providers["foo"]
if foo == nil {
t.Fatal("could not find provider 'foo' in child module")
}
if !reflect.DeepEqual([]string{RootName}, foo.Scope) {
t.Fatalf(`expected foo scope of {"root"}, got %#v`, foo.Scope)
}
expected := map[string]interface{}{
"value": "from root",
}
if !reflect.DeepEqual(expected, foo.RawConfig.RawMap()) {
t.Fatalf(`expected "foo" config %#v, got: %#v`, expected, foo.RawConfig.RawMap())
}
// grandchild should have inherited bar
providers = grandchild.config.ProviderConfigsByFullName()
bar := providers["bar"]
if bar == nil {
t.Fatal("could not find provider 'bar' in grandchild module")
}
if !reflect.DeepEqual([]string{RootName, "child"}, bar.Scope) {
t.Fatalf(`expected bar scope of {"root", "child"}, got %#v`, bar.Scope)
}
expected = map[string]interface{}{
"value": "from child",
}
if !reflect.DeepEqual(expected, bar.RawConfig.RawMap()) {
t.Fatalf(`expected "bar" config %#v, got: %#v`, expected, bar.RawConfig.RawMap())
}
}
const treeLoadStr = `
root
foo (path: foo)