pulumi/pkg/compiler/parsetree.go
joeduffy 6fb6c2de09 Add cloud target and architecture detection
This change implements most of the cloud target and architecture detection
logic, along with associated verification and a bunch of new error messages.

There are two settings for picking a cloud destination:

* Architecture: this specifies the combination of cloud (e.g., AWS, GCP, etc)
      plus scheduler (e.g., none, Swarm, ECS, etc).

* Target: a named, preconfigured entity that includes both an Architecture and
      an assortment of extra default configuration options.

The general idea here is that you can preconfigure a set of Targets for
named environments like "prod", "stage", etc.  Those can either exist in a
single Mufile, or the Mucluster file if they are shared amongst multiple
Mufiles.  This can be specified at the command line as such:

        $ mu build --target=stage

Furthermore, a given environment may be annointed the default, so that

        $ mu build

selects that environment without needing to say so explicitly.

It is also possible to specify an architecture at the command line for
scenarios where you aren't intending to target an existing named environment.
This is good for "anonymous" testing scenarios or even just running locally:

        $ mu build --arch=aws
        $ mu build --arch=aws:ecs
        $ mu build --arch=local:kubernetes
        $ .. and so on ..

This change does little more than plumb these settings around, verify them,
etc., however it sets us up to actually start dispating to the right backend.
2016-11-17 10:30:37 -08:00

100 lines
3.2 KiB
Go

// Copyright 2016 Marapongo, Inc. All rights reserved.
package compiler
import (
"github.com/blang/semver"
"github.com/golang/glog"
"github.com/marapongo/mu/pkg/ast"
"github.com/marapongo/mu/pkg/compiler/core"
"github.com/marapongo/mu/pkg/diag"
"github.com/marapongo/mu/pkg/errors"
)
// PTAnalyzer knows how to walk and validate parse trees.
type PTAnalyzer interface {
core.Visitor
// Analyze checks the validity of an entire parse tree (starting with a top-level Stack).
Analyze(doc *diag.Document, stack *ast.Stack)
}
// NewPTAnalayzer allocates a new PTAnalyzer associated with the given Compiler.
func NewPTAnalyzer(c Compiler) PTAnalyzer {
return &ptAnalyzer{c: c}
}
type ptAnalyzer struct {
c Compiler
}
func (a *ptAnalyzer) Diag() diag.Sink {
return a.c.Diag()
}
func (a *ptAnalyzer) Analyze(doc *diag.Document, stack *ast.Stack) {
glog.Infof("Parsetree analyzing Mu Stack: %v", stack.Name)
if glog.V(2) {
defer func() {
glog.V(2).Infof("Parsetree analysis for Mu Stack %v completed w/ %v warnings and %v errors",
stack.Name, a.Diag().Warnings(), a.Diag().Errors())
}()
}
// Use an InOrderVisitor to walk the tree in-order; this handles determinism for us.
v := core.NewInOrderVisitor(a, nil)
v.VisitStack(doc, stack)
}
func (a *ptAnalyzer) VisitMetadata(doc *diag.Document, kind string, meta *ast.Metadata) {
// Decorate the AST with contextual information so subsequent passes can operate context-free.
meta.Kind = kind
// Metadata names are required.
if meta.Name == "" {
a.Diag().Errorf(errors.MissingMetadataName.WithDocument(doc), kind)
}
// Metadata versions must be valid semantic versions (and specifically, not ranges). In other words, we need
// a concrete version number like "1.3.9-beta2" and *not* a range like ">1.3.9".
// TODO: should we require a version number?
if meta.Version != "" {
if _, err := semver.Parse(string(meta.Version)); err != nil {
a.Diag().Errorf(errors.IllegalMetadataSemVer.WithDocument(doc), kind, meta.Version)
}
}
}
func (a *ptAnalyzer) VisitStack(doc *diag.Document, stack *ast.Stack) {
}
func (a *ptAnalyzer) VisitParameter(doc *diag.Document, name string, param *ast.Parameter) {
// Decorate the AST with contextual information so subsequent passes can operate context-free.
param.Name = name
}
func (a *ptAnalyzer) VisitDependency(doc *diag.Document, name ast.Name, dep *ast.Dependency) {
// Dependency versions must be valid semantic versions *or* ranges.
// TODO: should we require dependencies to have versions?
ver := *dep
if ver != "" {
if _, err := semver.ParseRange(string(ver)); err != nil {
a.Diag().Errorf(errors.IllegalDependencySemVer.WithDocument(doc), name, ver)
}
}
}
func (a *ptAnalyzer) VisitServices(doc *diag.Document, svcs *ast.Services) {
}
func (a *ptAnalyzer) VisitService(doc *diag.Document, name ast.Name, public bool, svc *ast.Service) {
// Decorate the AST with contextual information so subsequent passes can operate context-free.
svc.Name = name
svc.Public = public
}
func (a *ptAnalyzer) VisitTarget(doc *diag.Document, name string, target *ast.Target) {
// Decorate the AST with contextual information so subsequent passes can operate context-free.
target.Name = name
}