opentofu/terraform/transform_provider.go
James Bardin fc2913d610
Merge pull request #16619 from hashicorp/jbardin/implicit-providers
Allow overriding an implicitly used provider
2017-11-28 17:07:59 -05:00

607 lines
16 KiB
Go

package terraform
import (
"errors"
"fmt"
"log"
"strings"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/config"
"github.com/hashicorp/terraform/config/module"
"github.com/hashicorp/terraform/dag"
)
func TransformProviders(providers []string, concrete ConcreteProviderNodeFunc, mod *module.Tree) GraphTransformer {
return GraphTransformMulti(
// Add providers from the config
&ProviderConfigTransformer{
Module: mod,
Providers: providers,
Concrete: concrete,
},
// Add any remaining missing providers
&MissingProviderTransformer{
Providers: providers,
Concrete: concrete,
},
// Connect the providers
&ProviderTransformer{},
// Remove unused providers and proxies
&PruneProviderTransformer{},
// Connect provider to their parent provider nodes
&ParentProviderTransformer{},
)
}
// GraphNodeProvider is an interface that nodes that can be a provider
// must implement.
// ProviderName returns the name of the provider this satisfies.
// Name returns the full name of the provider in the config.
type GraphNodeProvider interface {
ProviderName() string
Name() string
}
// GraphNodeCloseProvider is an interface that nodes that can be a close
// provider must implement. The CloseProviderName returned is the name of
// the provider they satisfy.
type GraphNodeCloseProvider interface {
CloseProviderName() string
}
// GraphNodeProviderConsumer is an interface that nodes that require
// a provider must implement. ProvidedBy must return the name of the provider
// to use. This may be a provider by type, type.alias or a fully resolved
// provider name
type GraphNodeProviderConsumer interface {
ProvidedBy() string
// Set the resolved provider address for this resource.
SetProvider(string)
}
// ProviderTransformer is a GraphTransformer that maps resources to
// providers within the graph. This will error if there are any resources
// that don't map to proper resources.
type ProviderTransformer struct{}
func (t *ProviderTransformer) Transform(g *Graph) error {
// Go through the other nodes and match them to providers they need
var err error
m := providerVertexMap(g)
for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProviderConsumer); ok {
p := pv.ProvidedBy()
key := providerMapKey(p, pv)
target := m[key]
sp, ok := pv.(GraphNodeSubPath)
if !ok && target == nil {
// no target, and no path to walk up
err = multierror.Append(err, fmt.Errorf(
"%s: provider %s couldn't be found",
dag.VertexName(v), p))
break
}
// if we don't have a provider at this level, walk up the path looking for one
for i := 1; target == nil; i++ {
path := normalizeModulePath(sp.Path())
if len(path) < i {
break
}
key = ResolveProviderName(p, path[:len(path)-i])
target = m[key]
if target != nil {
break
}
}
if target == nil {
err = multierror.Append(err, fmt.Errorf(
"%s: configuration for %s is not present; a provider configuration block is required for all operations",
dag.VertexName(v), p,
))
break
}
// see if this in an inherited provider
if p, ok := target.(*graphNodeProxyProvider); ok {
g.Remove(p)
target = p.Target()
key = target.(GraphNodeProvider).Name()
}
log.Printf("[DEBUG] resource %s using provider %s", dag.VertexName(pv), key)
pv.SetProvider(key)
g.Connect(dag.BasicEdge(v, target))
}
}
return err
}
// CloseProviderTransformer is a GraphTransformer that adds nodes to the
// graph that will close open provider connections that aren't needed anymore.
// A provider connection is not needed anymore once all depended resources
// in the graph are evaluated.
type CloseProviderTransformer struct{}
func (t *CloseProviderTransformer) Transform(g *Graph) error {
pm := providerVertexMap(g)
cpm := make(map[string]*graphNodeCloseProvider)
var err error
for _, v := range pm {
p := v.(GraphNodeProvider)
// get the close provider of this type if we alread created it
closer := cpm[p.ProviderName()]
if closer == nil {
// create a closer for this provider type
closer = &graphNodeCloseProvider{ProviderNameValue: p.ProviderName()}
g.Add(closer)
cpm[p.ProviderName()] = closer
}
// Close node depends on the provider itself
// this is added unconditionally, so it will connect to all instances
// of the provider. Extra edges will be removed by transitive
// reduction.
g.Connect(dag.BasicEdge(closer, p))
// connect all the provider's resources to the close node
for _, s := range g.UpEdges(p).List() {
if _, ok := s.(GraphNodeProviderConsumer); ok {
g.Connect(dag.BasicEdge(closer, s))
}
}
}
return err
}
// MissingProviderTransformer is a GraphTransformer that adds nodes for all
// required providers into the graph. Specifically, it creates provider
// configuration nodes for all the providers that we support. These are pruned
// later during an optimization pass.
type MissingProviderTransformer struct {
// Providers is the list of providers we support.
Providers []string
// Concrete, if set, overrides how the providers are made.
Concrete ConcreteProviderNodeFunc
}
func (t *MissingProviderTransformer) Transform(g *Graph) error {
// Initialize factory
if t.Concrete == nil {
t.Concrete = func(a *NodeAbstractProvider) dag.Vertex {
return a
}
}
var err error
m := providerVertexMap(g)
for _, v := range g.Vertices() {
pv, ok := v.(GraphNodeProviderConsumer)
if !ok {
continue
}
p := pv.ProvidedBy()
// this may be the resolved provider from the state, so we need to get
// the base provider name.
parts := strings.SplitAfter(p, "provider.")
p = parts[len(parts)-1]
key := ResolveProviderName(p, nil)
provider := m[key]
// we already have it
if provider != nil {
continue
}
// we don't implicitly create aliased providers
if strings.Contains(p, ".") {
log.Println("[DEBUG] not adding missing provider alias:", p)
continue
}
log.Println("[DEBUG] adding missing provider:", p)
// create the misisng top-level provider
provider = t.Concrete(&NodeAbstractProvider{
NameValue: p,
}).(dag.Vertex)
m[key] = g.Add(provider)
}
return err
}
// ParentProviderTransformer connects provider nodes to their parents.
//
// This works by finding nodes that are both GraphNodeProviders and
// GraphNodeSubPath. It then connects the providers to their parent
// path. The parent provider is always at the root level.
type ParentProviderTransformer struct{}
func (t *ParentProviderTransformer) Transform(g *Graph) error {
pm := providerVertexMap(g)
for _, v := range g.Vertices() {
// Only care about providers
pn, ok := v.(GraphNodeProvider)
if !ok || pn.ProviderName() == "" {
continue
}
// Also require a subpath, if there is no subpath then we
// can't have a parent.
if pn, ok := v.(GraphNodeSubPath); ok {
if len(normalizeModulePath(pn.Path())) <= 1 {
continue
}
}
// this provider may be disabled, but we can only get it's name from
// the ProviderName string
name := ResolveProviderName(strings.SplitN(pn.ProviderName(), " ", 2)[0], nil)
parent := pm[name]
if parent != nil {
g.Connect(dag.BasicEdge(v, parent))
}
}
return nil
}
// PruneProviderTransformer removes any providers that are not actually used by
// anything, and provider proxies. This avoids the provider being initialized
// and configured. This both saves resources but also avoids errors since
// configuration may imply initialization which may require auth.
type PruneProviderTransformer struct{}
func (t *PruneProviderTransformer) Transform(g *Graph) error {
for _, v := range g.Vertices() {
// We only care about providers
pn, ok := v.(GraphNodeProvider)
if !ok || pn.ProviderName() == "" {
continue
}
// ProxyProviders will have up edges, but we're now done with them in the graph
if _, ok := v.(*graphNodeProxyProvider); ok {
log.Printf("[DEBUG] pruning proxy provider %s", dag.VertexName(v))
g.Remove(v)
}
// Remove providers with no dependencies.
if g.UpEdges(v).Len() == 0 {
log.Printf("[DEBUG] pruning unused provider %s", dag.VertexName(v))
g.Remove(v)
}
}
return nil
}
// providerMapKey is a helper that gives us the key to use for the
// maps returned by things such as providerVertexMap.
func providerMapKey(k string, v dag.Vertex) string {
if strings.Contains(k, "provider.") {
// this is already resolved
return k
}
// we create a dummy provider to
var path []string
if sp, ok := v.(GraphNodeSubPath); ok {
path = normalizeModulePath(sp.Path())
}
return ResolveProviderName(k, path)
}
func providerVertexMap(g *Graph) map[string]dag.Vertex {
m := make(map[string]dag.Vertex)
for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeProvider); ok {
// TODO: The Name may have meta info, like " (disabled)"
name := strings.SplitN(pv.Name(), " ", 2)[0]
m[name] = v
}
}
return m
}
func closeProviderVertexMap(g *Graph) map[string]dag.Vertex {
m := make(map[string]dag.Vertex)
for _, v := range g.Vertices() {
if pv, ok := v.(GraphNodeCloseProvider); ok {
m[pv.CloseProviderName()] = v
}
}
return m
}
type graphNodeCloseProvider struct {
ProviderNameValue string
}
func (n *graphNodeCloseProvider) Name() string {
return fmt.Sprintf("provider.%s (close)", n.ProviderNameValue)
}
// GraphNodeEvalable impl.
func (n *graphNodeCloseProvider) EvalTree() EvalNode {
return CloseProviderEvalTree(n.ProviderNameValue)
}
// GraphNodeDependable impl.
func (n *graphNodeCloseProvider) DependableName() []string {
return []string{n.Name()}
}
func (n *graphNodeCloseProvider) CloseProviderName() string {
return n.ProviderNameValue
}
// GraphNodeDotter impl.
func (n *graphNodeCloseProvider) DotNode(name string, opts *dag.DotOpts) *dag.DotNode {
if !opts.Verbose {
return nil
}
return &dag.DotNode{
Name: name,
Attrs: map[string]string{
"label": n.Name(),
"shape": "diamond",
},
}
}
// RemovableIfNotTargeted
func (n *graphNodeCloseProvider) RemoveIfNotTargeted() bool {
// We need to add this so that this node will be removed if
// it isn't targeted or a dependency of a target.
return true
}
// graphNodeProxyProvider is a GraphNodeProvider implementation that is used to
// store the name and value of a provider node for inheritance between modules.
// These nodes are only used to store the data while loading the provider
// configurations, and are removed after all the resources have been connected
// to their providers.
type graphNodeProxyProvider struct {
nameValue string
path []string
target GraphNodeProvider
}
func (n *graphNodeProxyProvider) ProviderName() string {
return n.Target().ProviderName()
}
func (n *graphNodeProxyProvider) Name() string {
return ResolveProviderName(n.nameValue, n.path)
}
// find the concrete provider instance
func (n *graphNodeProxyProvider) Target() GraphNodeProvider {
switch t := n.target.(type) {
case *graphNodeProxyProvider:
return t.Target()
default:
return n.target
}
}
// ProviderConfigTransformer adds all provider nodes from the configuration and
// attaches the configs.
type ProviderConfigTransformer struct {
Providers []string
Concrete ConcreteProviderNodeFunc
// each provider node is stored here so that the proxy nodes can look up
// their targets by name.
providers map[string]GraphNodeProvider
// record providers that can be overriden with a proxy
proxiable map[string]bool
// Module is the module to add resources from.
Module *module.Tree
}
func (t *ProviderConfigTransformer) Transform(g *Graph) error {
// If no module is given, we don't do anything
if t.Module == nil {
return nil
}
// If the module isn't loaded, that is simply an error
if !t.Module.Loaded() {
return errors.New("module must be loaded for ProviderConfigTransformer")
}
t.providers = make(map[string]GraphNodeProvider)
t.proxiable = make(map[string]bool)
// Start the transformation process
if err := t.transform(g, t.Module); err != nil {
return err
}
// finally attach the configs to the new nodes
return t.attachProviderConfigs(g)
}
func (t *ProviderConfigTransformer) transform(g *Graph, m *module.Tree) error {
// If no config, do nothing
if m == nil {
return nil
}
// Add our resources
if err := t.transformSingle(g, m); err != nil {
return err
}
// Transform all the children.
for _, c := range m.Children() {
if err := t.transform(g, c); err != nil {
return err
}
}
return nil
}
func (t *ProviderConfigTransformer) transformSingle(g *Graph, m *module.Tree) error {
log.Printf("[TRACE] ProviderConfigTransformer: Starting for path: %v", m.Path())
// Get the configuration for this module
conf := m.Config()
// Build the path we're at
path := m.Path()
if len(path) > 0 {
path = append([]string{RootModuleName}, path...)
}
// add all providers from the configuration
for _, p := range conf.ProviderConfigs {
name := p.Name
if p.Alias != "" {
name += "." + p.Alias
}
v := t.Concrete(&NodeAbstractProvider{
NameValue: name,
PathValue: path,
})
// Add it to the graph
g.Add(v)
fullName := ResolveProviderName(name, path)
t.providers[fullName] = v.(GraphNodeProvider)
t.proxiable[fullName] = len(p.RawConfig.RawMap()) == 0
}
// Now replace the provider nodes with proxy nodes if a provider was being
// passed in, and create implicit proxies if there was no config. Any extra
// proxies will be removed in the prune step.
return t.addProxyProviders(g, m)
}
func (t *ProviderConfigTransformer) addProxyProviders(g *Graph, m *module.Tree) error {
path := m.Path()
// can't add proxies at the root
if len(path) == 0 {
return nil
}
parentPath := path[:len(path)-1]
parent := t.Module.Child(parentPath)
if parent == nil {
return nil
}
var parentCfg *config.Module
for _, mod := range parent.Config().Modules {
if mod.Name == m.Name() {
parentCfg = mod
break
}
}
if parentCfg == nil {
// this can't really happen during normal execution.
return fmt.Errorf("parent module config not found for %s", m.Name())
}
// Go through all the providers the parent is passing in, and add proxies to
// the parent provider nodes.
for name, parentName := range parentCfg.Providers {
fullName := ResolveProviderName(name, path)
fullParentName := ResolveProviderName(parentName, parentPath)
parentProvider := t.providers[fullParentName]
if parentProvider == nil {
return fmt.Errorf("missing provider %s", fullParentName)
}
proxy := &graphNodeProxyProvider{
nameValue: name,
path: path,
target: parentProvider,
}
concreteProvider := t.providers[fullName]
// replace the concrete node with the provider passed in
if concreteProvider != nil && t.proxiable[fullName] {
g.Replace(concreteProvider, proxy)
t.providers[fullName] = proxy
continue
}
// aliased providers can't be implicitly passed in
if strings.Contains(name, ".") {
continue
}
// There was no concrete provider, so add this as an implicit provider.
// The extra proxy will be pruned later if it's unused.
g.Add(proxy)
t.providers[fullName] = proxy
}
return nil
}
func (t *ProviderConfigTransformer) attachProviderConfigs(g *Graph) error {
for _, v := range g.Vertices() {
// Only care about GraphNodeAttachProvider implementations
apn, ok := v.(GraphNodeAttachProvider)
if !ok {
continue
}
// Determine what we're looking for
path := normalizeModulePath(apn.Path())[1:]
name := apn.ProviderName()
log.Printf("[TRACE] Attach provider request: %#v %s", path, name)
// Get the configuration.
tree := t.Module.Child(path)
if tree == nil {
continue
}
// Go through the provider configs to find the matching config
for _, p := range tree.Config().ProviderConfigs {
// Build the name, which is "name.alias" if an alias exists
current := p.Name
if p.Alias != "" {
current += "." + p.Alias
}
// If the configs match then attach!
if current == name {
log.Printf("[TRACE] Attaching provider config: %#v", p)
apn.AttachProvider(p)
break
}
}
}
return nil
}