| package terraform |
| |
| import ( |
| "fmt" |
| "log" |
| |
| "github.com/hashicorp/terraform/addrs" |
| |
| "github.com/hashicorp/go-multierror" |
| "github.com/hashicorp/terraform/dag" |
| ) |
| |
| // GraphNodeProvisioner is an interface that nodes that can be a provisioner |
| // must implement. The ProvisionerName returned is the name of the provisioner |
| // they satisfy. |
| type GraphNodeProvisioner interface { |
| ProvisionerName() string |
| } |
| |
| // GraphNodeCloseProvisioner is an interface that nodes that can be a close |
| // provisioner must implement. The CloseProvisionerName returned is the name |
| // of the provisioner they satisfy. |
| type GraphNodeCloseProvisioner interface { |
| CloseProvisionerName() string |
| } |
| |
| // GraphNodeProvisionerConsumer is an interface that nodes that require |
| // a provisioner must implement. ProvisionedBy must return the names of the |
| // provisioners to use. |
| type GraphNodeProvisionerConsumer interface { |
| ProvisionedBy() []string |
| } |
| |
| // ProvisionerTransformer is a GraphTransformer that maps resources to |
| // provisioners within the graph. This will error if there are any resources |
| // that don't map to proper resources. |
| type ProvisionerTransformer struct{} |
| |
| func (t *ProvisionerTransformer) Transform(g *Graph) error { |
| // Go through the other nodes and match them to provisioners they need |
| var err error |
| m := provisionerVertexMap(g) |
| for _, v := range g.Vertices() { |
| if pv, ok := v.(GraphNodeProvisionerConsumer); ok { |
| for _, p := range pv.ProvisionedBy() { |
| key := provisionerMapKey(p, pv) |
| if m[key] == nil { |
| err = multierror.Append(err, fmt.Errorf( |
| "%s: provisioner %s couldn't be found", |
| dag.VertexName(v), p)) |
| continue |
| } |
| |
| log.Printf("[TRACE] ProvisionerTransformer: %s is provisioned by %s (%q)", dag.VertexName(v), key, dag.VertexName(m[key])) |
| g.Connect(dag.BasicEdge(v, m[key])) |
| } |
| } |
| } |
| |
| return err |
| } |
| |
| // MissingProvisionerTransformer is a GraphTransformer that adds nodes |
| // for missing provisioners into the graph. |
| type MissingProvisionerTransformer struct { |
| // Provisioners is the list of provisioners we support. |
| Provisioners []string |
| } |
| |
| func (t *MissingProvisionerTransformer) Transform(g *Graph) error { |
| // Create a set of our supported provisioners |
| supported := make(map[string]struct{}, len(t.Provisioners)) |
| for _, v := range t.Provisioners { |
| supported[v] = struct{}{} |
| } |
| |
| // Get the map of provisioners we already have in our graph |
| m := provisionerVertexMap(g) |
| |
| // Go through all the provisioner consumers and make sure we add |
| // that provisioner if it is missing. |
| for _, v := range g.Vertices() { |
| pv, ok := v.(GraphNodeProvisionerConsumer) |
| if !ok { |
| continue |
| } |
| |
| // If this node has a subpath, then we use that as a prefix |
| // into our map to check for an existing provider. |
| path := addrs.RootModuleInstance |
| if sp, ok := pv.(GraphNodeSubPath); ok { |
| path = sp.Path() |
| } |
| |
| for _, p := range pv.ProvisionedBy() { |
| // Build the key for storing in the map |
| key := provisionerMapKey(p, pv) |
| |
| if _, ok := m[key]; ok { |
| // This provisioner already exists as a configure node |
| continue |
| } |
| |
| if _, ok := supported[p]; !ok { |
| // If we don't support the provisioner type, we skip it. |
| // Validation later will catch this as an error. |
| continue |
| } |
| |
| // Build the vertex |
| var newV dag.Vertex = &NodeProvisioner{ |
| NameValue: p, |
| PathValue: path, |
| } |
| |
| // Add the missing provisioner node to the graph |
| m[key] = g.Add(newV) |
| log.Printf("[TRACE] MissingProviderTransformer: added implicit provisioner %s, first implied by %s", key, dag.VertexName(v)) |
| } |
| } |
| |
| return nil |
| } |
| |
| // CloseProvisionerTransformer is a GraphTransformer that adds nodes to the |
| // graph that will close open provisioner connections that aren't needed |
| // anymore. A provisioner connection is not needed anymore once all depended |
| // resources in the graph are evaluated. |
| type CloseProvisionerTransformer struct{} |
| |
| func (t *CloseProvisionerTransformer) Transform(g *Graph) error { |
| m := closeProvisionerVertexMap(g) |
| for _, v := range g.Vertices() { |
| if pv, ok := v.(GraphNodeProvisionerConsumer); ok { |
| for _, p := range pv.ProvisionedBy() { |
| source := m[p] |
| |
| if source == nil { |
| // Create a new graphNodeCloseProvisioner and add it to the graph |
| source = &graphNodeCloseProvisioner{ProvisionerNameValue: p} |
| g.Add(source) |
| |
| // Make sure we also add the new graphNodeCloseProvisioner to the map |
| // so we don't create and add any duplicate graphNodeCloseProvisioners. |
| m[p] = source |
| } |
| |
| g.Connect(dag.BasicEdge(source, v)) |
| } |
| } |
| } |
| |
| return nil |
| } |
| |
| // provisionerMapKey is a helper that gives us the key to use for the |
| // maps returned by things such as provisionerVertexMap. |
| func provisionerMapKey(k string, v dag.Vertex) string { |
| pathPrefix := "" |
| if sp, ok := v.(GraphNodeSubPath); ok { |
| pathPrefix = sp.Path().String() + "." |
| } |
| |
| return pathPrefix + k |
| } |
| |
| func provisionerVertexMap(g *Graph) map[string]dag.Vertex { |
| m := make(map[string]dag.Vertex) |
| for _, v := range g.Vertices() { |
| if pv, ok := v.(GraphNodeProvisioner); ok { |
| key := provisionerMapKey(pv.ProvisionerName(), v) |
| m[key] = v |
| } |
| } |
| |
| return m |
| } |
| |
| func closeProvisionerVertexMap(g *Graph) map[string]dag.Vertex { |
| m := make(map[string]dag.Vertex) |
| for _, v := range g.Vertices() { |
| if pv, ok := v.(GraphNodeCloseProvisioner); ok { |
| m[pv.CloseProvisionerName()] = v |
| } |
| } |
| |
| return m |
| } |
| |
| type graphNodeCloseProvisioner struct { |
| ProvisionerNameValue string |
| } |
| |
| func (n *graphNodeCloseProvisioner) Name() string { |
| return fmt.Sprintf("provisioner.%s (close)", n.ProvisionerNameValue) |
| } |
| |
| // GraphNodeEvalable impl. |
| func (n *graphNodeCloseProvisioner) EvalTree() EvalNode { |
| return &EvalCloseProvisioner{Name: n.ProvisionerNameValue} |
| } |
| |
| func (n *graphNodeCloseProvisioner) CloseProvisionerName() string { |
| return n.ProvisionerNameValue |
| } |