opentofu/terraform/transform_provider.go
James Bardin 6210b1749b use the full provider name for CloseProvider
CloseProviderTransformer wasn't using the full provider when creating
the graph node, so the Close wasn't actually being called on the
provider.
2018-01-04 15:03:27 -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.Name()]
if closer == nil {
// create a closer for this provider type
closer = &graphNodeCloseProvider{ProviderNameValue: p.Name()}
g.Add(closer)
cpm[p.Name()] = 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 n.ProviderNameValue + " (close)"
}
// 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
}