core: Make provisioner schemas available to plan resource instance nodes

This requires making the "components" object available to the resource
node so it can be used during DynamicExpand. It also involved splitting
the provisioner schema attachment into a separate interface from
GraphNodeProvisionerConsumer so that it can now be handled within
AttachSchemaTransformer, along with all of the other schema attachment
steps.
This commit is contained in:
Martin Atkins 2018-05-24 16:00:26 -07:00
parent 559f65bf3d
commit dd6b171f62
5 changed files with 107 additions and 14 deletions

View File

@ -169,6 +169,7 @@ func (b *PlanGraphBuilder) init() {
b.ConcreteResource = func(a *NodeAbstractResource) dag.Vertex {
return &NodePlannableResource{
NodeAbstractResource: a,
Components: b.Components,
}
}

View File

@ -199,6 +199,9 @@ func (n *NodeAbstractResource) References() []*addrs.Reference {
}
schema := n.ProvisionerSchemas[p.Type]
if schema == nil {
log.Printf("[WARN] no schema for provisioner %q is attached to %s, so provisioner block references cannot be detected", p.Type, n.Name())
}
refs, _ = lang.ReferencesInBlock(p.Config, schema)
result = append(result, refs...)
}
@ -364,8 +367,9 @@ func (n *NodeAbstractResource) ProvisionedBy() []string {
// GraphNodeProvisionerConsumer
func (n *NodeAbstractResource) AttachProvisionerSchema(name string, schema *configschema.Block) {
// FIXME: this isn't called anywehere
panic("unused")
if n.ProvisionerSchemas == nil {
n.ProvisionerSchemas = make(map[string]*configschema.Block)
}
n.ProvisionerSchemas[name] = schema
}

View File

@ -9,6 +9,10 @@ import (
// it is ready to be planned in order to create a diff.
type NodePlannableResource struct {
*NodeAbstractResource
// Components is the component factory to use when performing
// DynamicExpand, to access plugins necessary to build the subgraph.
Components contextComponentFactory
}
var (
@ -85,6 +89,12 @@ func (n *NodePlannableResource) DynamicExpand(ctx EvalContext) (*Graph, error) {
// Targeting
&TargetsTransformer{Targets: n.Targets},
// Schemas must be attached before ReferenceTransformer, so we can
// properly analyze the configuration.
&AttachSchemaTransformer{
Components: n.Components,
},
// Connect references so ordering is correct
&ReferenceTransformer{},

View File

@ -28,22 +28,48 @@ type GraphNodeAttachProviderConfigSchema interface {
AttachProviderConfigSchema(*configschema.Block)
}
// AttachSchemaTransformer finds nodes that implement either
// GraphNodeAttachResourceSchema or GraphNodeAttachProviderConfigSchema, looks up
// the schema for each, and then passes it to a method implemented by the
// node.
// GraphNodeAttachProvisionerSchema is an interface implemented by node types
// that need one or more provisioner schemas attached.
type GraphNodeAttachProvisionerSchema interface {
ProvisionedBy() []string
// SetProvisionerSchema is called during transform for each provisioner
// type returned from ProvisionedBy, providing the configuration schema
// for each provisioner in turn. The implementer should save these for
// later use in evaluating provisioner configuration blocks.
AttachProvisionerSchema(name string, schema *configschema.Block)
}
// AttachSchemaTransformer finds nodes that implement
// GraphNodeAttachResourceSchema, GraphNodeAttachProviderConfigSchema, or
// GraphNodeAttachProvisionerSchema, looks up the needed schemas for each
// and then passes them to a method implemented by the node.
type AttachSchemaTransformer struct {
GraphNodeProvisionerConsumer
Components contextComponentFactory
}
func (t *AttachSchemaTransformer) Transform(g *Graph) error {
if t.Components == nil {
// Should never happen with a reasonable caller, but we'll return a
// proper error here anyway so that we'll fail gracefully.
return fmt.Errorf("AttachSchemaTransformer used with nil Components")
}
err := t.attachProviderSchemas(g)
if err != nil {
return err
}
err = t.attachProvisionerSchemas(g)
if err != nil {
return err
}
return nil
}
func (t *AttachSchemaTransformer) attachProviderSchemas(g *Graph) error {
// First we'll figure out which provider types we need to fetch schemas for.
needProviders := make(map[string]struct{})
for _, v := range g.Vertices() {
@ -150,3 +176,62 @@ func (t *AttachSchemaTransformer) Transform(g *Graph) error {
return nil
}
func (t *AttachSchemaTransformer) attachProvisionerSchemas(g *Graph) error {
// First we'll figure out which provisioners we need to fetch schemas for.
needProvisioners := make(map[string]struct{})
for _, v := range g.Vertices() {
switch tv := v.(type) {
case GraphNodeAttachProvisionerSchema:
names := tv.ProvisionedBy()
log.Printf("[TRACE] AttachSchemaTransformer: %q (%T) provisioned by %s", dag.VertexName(v), v, names)
for _, name := range names {
needProvisioners[name] = struct{}{}
}
}
}
// Now we'll fetch each one. This requires us to temporarily instantiate
// them, though this is not a full bootstrap since we don't yet have
// configuration information; the provisioners will be re-instantiated and
// properly configured during the graph walk.
schemas := make(map[string]*configschema.Block)
for name := range needProvisioners {
log.Printf("[TRACE] AttachSchemaTransformer: retrieving schema for provisioner %q", name)
provisioner, err := t.Components.ResourceProvisioner(name, "early/"+name)
if err != nil {
return fmt.Errorf("failed to instantiate provisioner %q to obtain schema: %s", name, err)
}
schema, err := provisioner.GetConfigSchema()
if err != nil {
return fmt.Errorf("failed to retrieve schema from provisioner %q: %s", name, err)
}
schemas[name] = schema
if closer, ok := provisioner.(ResourceProvisionerCloser); ok {
closer.Close()
}
}
// Finally we'll once again visit all of the vertices and attach to
// them the schemas we found for them.
for _, v := range g.Vertices() {
switch tv := v.(type) {
case GraphNodeAttachProvisionerSchema:
names := tv.ProvisionedBy()
for _, name := range names {
schema := schemas[name]
if schema != nil {
log.Printf("[TRACE] AttachSchemaTransformer: attaching provisioner %q schema to %s", name, dag.VertexName(v))
tv.AttachProvisionerSchema(name, schema)
} else {
log.Printf("[ERROR] AttachSchemaTransformer: No schema available for provisioner %q on %q", name, dag.VertexName(v))
}
}
}
}
return nil
}

View File

@ -5,7 +5,6 @@ import (
"log"
"github.com/hashicorp/terraform/addrs"
"github.com/hashicorp/terraform/config/configschema"
"github.com/hashicorp/go-multierror"
"github.com/hashicorp/terraform/dag"
@ -30,12 +29,6 @@ type GraphNodeCloseProvisioner interface {
// provisioners to use.
type GraphNodeProvisionerConsumer interface {
ProvisionedBy() []string
// SetProvisionerSchema is called during transform for each provisioner
// type returned from ProvisionedBy, providing the configuration schema
// for each provisioner in turn. The implementer should save these for
// later use in evaluating provisioner configuration blocks.
AttachProvisionerSchema(name string, schema *configschema.Block)
}
// ProvisionerTransformer is a GraphTransformer that maps resources to