This implements support for arbitrary service types on properties, not just the weakly typed "service". For example, in the AWS stacks, the aws/ec2/route type requires a routeTable, among other things: name: aws/ec2/route properties: routeTable: type: aws/ec2/routeTable This not only binds the definition of such properties, but also the callsites of those creating stacks and supplying values for them. This includes checking for concrete, instantiated, and even base types, so that, for instance, if a custom stack derived from aws/ec2/routeTable using the base property, in the above example it could be supplied as a legal value for the routeTable property.
102 lines
3.3 KiB
Go
102 lines
3.3 KiB
Go
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
package compiler
|
|
|
|
import (
|
|
"github.com/golang/glog"
|
|
|
|
"github.com/marapongo/mu/pkg/ast"
|
|
"github.com/marapongo/mu/pkg/diag"
|
|
"github.com/marapongo/mu/pkg/errors"
|
|
"github.com/marapongo/mu/pkg/util"
|
|
"github.com/marapongo/mu/pkg/workspace"
|
|
)
|
|
|
|
// buildDocumentSema runs the middle semantic analysis phases of the compiler.
|
|
func (c *compiler) buildDocumentSema(w workspace.W, stack *ast.Stack) {
|
|
// Perform semantic analysis on all stacks passes to validate, transform, and/or update the AST.
|
|
b := NewBinder(c)
|
|
c.bindStack(b, w, stack)
|
|
if !c.Diag().Success() {
|
|
return
|
|
}
|
|
}
|
|
|
|
// bindStack performs the two phases of binding plus dependency resolution for the given Stack.
|
|
func (c *compiler) bindStack(b Binder, w workspace.W, stack *ast.Stack) {
|
|
util.Assert(stack != nil)
|
|
|
|
// First prepare the AST for binding.
|
|
refs := b.PrepareStack(stack)
|
|
if !c.Diag().Success() {
|
|
return
|
|
}
|
|
|
|
// Next, resolve all dependencies discovered during this first pass.
|
|
deprefs := make(ast.DependencyRefs)
|
|
for _, ref := range refs {
|
|
// Only resolve dependencies that are currently unknown. This will exlude built-in types that have already
|
|
// been bound to a stack during the first phase of binding. Note that we don't actually parse and perform
|
|
// template substitution here; instead, we remember the document and let the binder do this, since it has
|
|
// all of the information necessary to create a unique Stack per-PropertyBag used to instantiate it.
|
|
if doc := c.resolveDependency(w, stack, ref); doc != nil {
|
|
deprefs[ref] = &ast.StackRef{Ref: ref, Doc: doc}
|
|
}
|
|
}
|
|
if !c.Diag().Success() {
|
|
return
|
|
}
|
|
|
|
// Complete the binding process.
|
|
deps := b.BindStack(stack, deprefs)
|
|
if !c.Diag().Success() {
|
|
return
|
|
}
|
|
|
|
// Now ensure we bind all dependency stacks too.
|
|
for _, dep := range deps {
|
|
c.bindStack(b, w, dep)
|
|
}
|
|
if !c.Diag().Success() {
|
|
return
|
|
}
|
|
|
|
// Finally, now that we know the entire tree, and its transitive closure, is bound, perform final validation.
|
|
b.ValidateStack(stack)
|
|
}
|
|
|
|
// resolveDependency loads up the target dependency from the current workspace using the stack resolution rules.
|
|
func (c *compiler) resolveDependency(w workspace.W, stack *ast.Stack, ref ast.Ref) *diag.Document {
|
|
glog.V(3).Infof("Loading Stack %v dependency %v", stack.Name, ref)
|
|
|
|
// First, see if we've already loaded this dependency (anywhere in any Stacks). If yes, reuse it.
|
|
// TODO: check for version mismatches.
|
|
if doc, exists := c.deps[ref]; exists {
|
|
return doc
|
|
}
|
|
|
|
// There are many places a dependency could come from. Consult the workspace for a list of those paths. It will
|
|
// return a number of them, in preferred order, and we simply probe each one until we find something.
|
|
dep := ref.MustParse()
|
|
for _, loc := range w.DepCandidates(dep) {
|
|
// Try to read this location as a document.
|
|
isMufile := workspace.IsMufile(loc, c.Diag())
|
|
glog.V(5).Infof("Probing for dependency %v at %v: %v", dep, loc, isMufile)
|
|
|
|
if isMufile {
|
|
doc, err := diag.ReadDocument(loc)
|
|
if err != nil {
|
|
c.Diag().Errorf(errors.ErrorCouldNotReadMufile.AtFile(loc), err)
|
|
return nil
|
|
}
|
|
|
|
// Memoize this in the compiler's cache and return it.
|
|
c.deps[ref] = doc
|
|
return doc
|
|
}
|
|
}
|
|
|
|
// If we got to this spot, we could not find the dependency. Issue an error and bail out.
|
|
c.Diag().Errorf(errors.ErrorStackTypeNotFound.At(stack), ref)
|
|
return nil
|
|
}
|