mirror of
https://github.com/opentofu/opentofu.git
synced 2024-12-27 09:21:14 -06:00
6210b1749b
CloseProviderTransformer wasn't using the full provider when creating the graph node, so the Close wasn't actually being called on the provider.
607 lines
16 KiB
Go
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
|
|
}
|