2016-11-15 20:30:34 +01:00
|
|
|
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
|
|
|
|
package compiler
|
|
|
|
|
|
|
|
import (
|
2016-11-16 17:19:26 +01:00
|
|
|
"io/ioutil"
|
|
|
|
"os"
|
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2016-11-15 20:30:34 +01:00
|
|
|
"github.com/golang/glog"
|
|
|
|
|
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 19:30:37 +01:00
|
|
|
"github.com/marapongo/mu/pkg/ast"
|
|
|
|
"github.com/marapongo/mu/pkg/compiler/clouds"
|
2016-11-17 17:52:54 +01:00
|
|
|
"github.com/marapongo/mu/pkg/compiler/core"
|
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 19:30:37 +01:00
|
|
|
"github.com/marapongo/mu/pkg/compiler/schedulers"
|
2016-11-15 20:30:34 +01:00
|
|
|
"github.com/marapongo/mu/pkg/diag"
|
|
|
|
"github.com/marapongo/mu/pkg/errors"
|
|
|
|
"github.com/marapongo/mu/pkg/workspace"
|
|
|
|
)
|
|
|
|
|
|
|
|
// Compiler provides an interface into the many phases of the Mu compilation process.
|
|
|
|
type Compiler interface {
|
2016-11-17 17:52:54 +01:00
|
|
|
core.Phase
|
2016-11-16 18:29:44 +01:00
|
|
|
|
2016-11-16 02:42:22 +01:00
|
|
|
// Context returns the current compiler context.
|
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 19:30:37 +01:00
|
|
|
Context() *core.Context
|
2016-11-16 02:42:22 +01:00
|
|
|
|
2016-11-15 20:30:34 +01:00
|
|
|
// Build detects and compiles inputs from the given location, storing build artifacts in the given destination.
|
|
|
|
Build(inp string, outp string)
|
2016-11-17 22:08:20 +01:00
|
|
|
// BuildJSON uses the given JSON-based Mufile directly, and stores build artifacts in the given destination.
|
|
|
|
BuildJSON(mufile []byte, outp string)
|
|
|
|
// BuildYAML uses the given YAML-based Mufile directly, and stores build artifacts in the given destination.
|
|
|
|
BuildYAML(mufile []byte, outp string)
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// compiler is the canonical implementation of the Mu compiler.
|
|
|
|
type compiler struct {
|
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 19:30:37 +01:00
|
|
|
ctx *core.Context
|
2016-11-15 20:30:34 +01:00
|
|
|
opts Options
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewCompiler creates a new instance of the Mu compiler, with the given initialization settings.
|
|
|
|
func NewCompiler(opts Options) Compiler {
|
2016-11-16 02:42:22 +01:00
|
|
|
return &compiler{
|
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 19:30:37 +01:00
|
|
|
ctx: &core.Context{},
|
2016-11-16 02:42:22 +01:00
|
|
|
opts: opts,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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 19:30:37 +01:00
|
|
|
func (c *compiler) Context() *core.Context {
|
2016-11-16 02:42:22 +01:00
|
|
|
return c.ctx
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
2016-11-16 01:30:10 +01:00
|
|
|
func (c *compiler) Diag() diag.Sink {
|
|
|
|
return c.opts.Diag
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compiler) Build(inp string, outp string) {
|
2016-11-16 01:30:10 +01:00
|
|
|
glog.Infof("Building target '%v' (out='%v')", inp, outp)
|
2016-11-17 22:08:20 +01:00
|
|
|
|
|
|
|
// First find the root of the current package based on the location of its Mufile.
|
|
|
|
mufile := c.detectMufile(inp)
|
|
|
|
if mufile == "" {
|
|
|
|
c.Diag().Errorf(errors.MissingMufile, inp)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read in the contents of the document and make it available to subsequent stages.
|
|
|
|
doc, err := diag.ReadDocument(mufile)
|
|
|
|
if err != nil {
|
|
|
|
c.Diag().Errorf(errors.CouldNotReadMufile.WithFile(mufile), err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
c.buildDocument(doc, outp)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compiler) BuildJSON(mufile []byte, outp string) {
|
|
|
|
glog.Infof("Building in-memory JSON file (bytes=%v out='%v')", len(mufile), outp)
|
|
|
|
c.buildDocument(&diag.Document{File: workspace.MufileBase + ".json", Body: mufile}, outp)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compiler) BuildYAML(mufile []byte, outp string) {
|
|
|
|
glog.Infof("Building in-memory YAML file (bytes=%v out='%v')", len(mufile), outp)
|
|
|
|
c.buildDocument(&diag.Document{File: workspace.MufileBase + ".yaml", Body: mufile}, outp)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (c *compiler) buildDocument(doc *diag.Document, outp string) {
|
|
|
|
glog.Infof("Building doc '%v' (bytes=%v out='%v')", doc.File, len(doc.Body), outp)
|
2016-11-16 01:30:10 +01:00
|
|
|
if glog.V(2) {
|
|
|
|
defer func() {
|
2016-11-17 22:08:20 +01:00
|
|
|
glog.V(2).Infof("Building doc '%v' completed w/ %v warnings and %v errors",
|
|
|
|
doc.File, c.Diag().Warnings(), c.Diag().Errors())
|
2016-11-16 01:30:10 +01:00
|
|
|
}()
|
|
|
|
}
|
2016-11-15 20:30:34 +01:00
|
|
|
|
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 19:30:37 +01:00
|
|
|
// Perform the front-end passes to generate a stack AST.
|
2016-11-17 22:08:20 +01:00
|
|
|
stack, ok := c.parseStack(doc)
|
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 19:30:37 +01:00
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// Perform the semantic analysis passes to validate, transform, and/or update the AST.
|
|
|
|
stack, ok = c.analyzeStack(doc, stack)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2016-11-17 22:08:20 +01:00
|
|
|
if !c.opts.SkipCodegen {
|
|
|
|
// Figure out which cloud architecture we will be targeting during code-gen.
|
|
|
|
target, arch, ok := c.discoverTargetArch(doc, stack)
|
|
|
|
if !ok {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
if glog.V(2) {
|
|
|
|
tname := "n/a"
|
|
|
|
if target != nil {
|
|
|
|
tname = target.Name
|
|
|
|
}
|
|
|
|
glog.V(2).Infof("Stack %v targets target=%v cloud=%v", stack.Name, tname, arch)
|
|
|
|
}
|
2016-11-15 20:30:34 +01:00
|
|
|
|
2016-11-17 22:08:20 +01:00
|
|
|
// TODO: lower the ASTs to the target backend's representation, emit it.
|
|
|
|
// TODO: delta generation, deployment, etc.
|
2016-11-16 02:42:22 +01:00
|
|
|
}
|
2016-11-17 22:08:20 +01:00
|
|
|
}
|
2016-11-16 02:42:22 +01:00
|
|
|
|
2016-11-17 22:08:20 +01:00
|
|
|
// loadAndParseStack takes a Mufile document, parses and validates it, and returns a stack AST. If anything goes wrong
|
|
|
|
// during this process, the number of errors will be non-zero, and the bool will be false.
|
|
|
|
func (c *compiler) parseStack(doc *diag.Document) (*ast.Stack, bool) {
|
2016-11-15 20:30:34 +01:00
|
|
|
// To build the Mu package, first parse the input file.
|
|
|
|
p := NewParser(c)
|
2016-11-16 02:42:22 +01:00
|
|
|
stack := p.Parse(doc)
|
|
|
|
if p.Diag().Errors() > 0 {
|
2016-11-16 18:29:44 +01:00
|
|
|
// If any errors happened during parsing, exit.
|
2016-11-17 22:08:20 +01:00
|
|
|
return stack, false
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
|
|
|
|
2016-11-16 02:42:22 +01:00
|
|
|
// Do a pass over the parse tree to ensure that all is well.
|
|
|
|
ptAnalyzer := NewPTAnalyzer(c)
|
|
|
|
ptAnalyzer.Analyze(doc, stack)
|
|
|
|
if p.Diag().Errors() > 0 {
|
2016-11-16 18:29:44 +01:00
|
|
|
// If any errors happened during parse tree analysis, exit.
|
2016-11-17 22:08:20 +01:00
|
|
|
return stack, false
|
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 19:30:37 +01:00
|
|
|
}
|
|
|
|
|
2016-11-17 22:08:20 +01:00
|
|
|
return stack, true
|
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 19:30:37 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// discoverTargetArch uses a variety of mechanisms to discover the target architecture, returning it. If no
|
|
|
|
// architecture was discovered, an error is issued, and the bool return will be false.
|
|
|
|
func (c *compiler) discoverTargetArch(doc *diag.Document, stack *ast.Stack) (*ast.Target, Arch, bool) {
|
|
|
|
// Target and architectures settings may come from one of three places, in order of search preference:
|
|
|
|
// 1) command line arguments.
|
|
|
|
// 2) settings specific to this stack.
|
|
|
|
// 3) cluster-wide settings in a Mucluster file.
|
|
|
|
// In other words, 1 overrides 2 which overrides 3.
|
|
|
|
arch := c.opts.Arch
|
|
|
|
|
|
|
|
// If a target was specified, look it up and load up its options.
|
|
|
|
var target *ast.Target
|
|
|
|
if c.opts.Target != "" {
|
|
|
|
// First, check the stack to see if it has a targets section.
|
|
|
|
if t, exists := stack.Targets[c.opts.Target]; exists {
|
|
|
|
target = &t
|
|
|
|
} else {
|
|
|
|
// If that didn't work, see if there's a clusters file we can consult.
|
|
|
|
// TODO: support Mucluster files.
|
|
|
|
c.Diag().Errorf(errors.CloudTargetNotFound.WithDocument(doc), c.opts.Target)
|
|
|
|
return target, arch, false
|
|
|
|
}
|
2016-11-16 18:29:44 +01:00
|
|
|
}
|
|
|
|
|
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 19:30:37 +01:00
|
|
|
// If no target was specified or discovered yet, see if there is a default one to use.
|
|
|
|
if target == nil {
|
|
|
|
for _, t := range stack.Targets {
|
|
|
|
if t.Default {
|
|
|
|
target = &t
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if target == nil {
|
|
|
|
// If no target was found, and we don't have an architecture, error out.
|
|
|
|
if arch.Cloud == clouds.NoArch {
|
2016-11-17 22:08:20 +01:00
|
|
|
c.Diag().Errorf(errors.MissingTarget.WithDocument(doc))
|
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 19:30:37 +01:00
|
|
|
return target, arch, false
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// If a target was found, go ahead and extract and validate the target architecture.
|
|
|
|
a, ok := c.getTargetArch(doc, target, arch)
|
|
|
|
if !ok {
|
|
|
|
return target, arch, false
|
|
|
|
}
|
|
|
|
arch = a
|
|
|
|
}
|
|
|
|
|
|
|
|
return target, arch, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// getTargetArch gets and validates the architecture from an existing target.
|
|
|
|
func (c *compiler) getTargetArch(doc *diag.Document, target *ast.Target, existing Arch) (Arch, bool) {
|
|
|
|
targetCloud := existing.Cloud
|
|
|
|
targetScheduler := existing.Scheduler
|
|
|
|
|
|
|
|
// If specified, look up the target's architecture settings.
|
|
|
|
if target.Cloud != "" {
|
|
|
|
tc, ok := clouds.ArchMap[target.Cloud]
|
|
|
|
if !ok {
|
|
|
|
c.Diag().Errorf(errors.UnrecognizedCloudArch.WithDocument(doc), target.Cloud)
|
|
|
|
return existing, false
|
|
|
|
}
|
|
|
|
targetCloud = tc
|
|
|
|
}
|
|
|
|
if target.Scheduler != "" {
|
|
|
|
ts, ok := schedulers.ArchMap[target.Scheduler]
|
|
|
|
if !ok {
|
|
|
|
c.Diag().Errorf(errors.UnrecognizedSchedulerArch.WithDocument(doc), target.Scheduler)
|
|
|
|
return existing, false
|
|
|
|
}
|
|
|
|
targetScheduler = ts
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure there aren't any conflicts, comparing compiler options to target settings.
|
|
|
|
tarch := Arch{targetCloud, targetScheduler}
|
|
|
|
if targetCloud != existing.Cloud && existing.Cloud != clouds.NoArch {
|
|
|
|
c.Diag().Errorf(errors.ConflictingTargetArchSelection.WithDocument(doc), existing, target.Name, tarch)
|
|
|
|
return tarch, false
|
|
|
|
}
|
|
|
|
if targetScheduler != existing.Scheduler && existing.Scheduler != schedulers.NoArch {
|
|
|
|
c.Diag().Errorf(errors.ConflictingTargetArchSelection.WithDocument(doc), existing, target.Name, tarch)
|
|
|
|
return tarch, false
|
|
|
|
}
|
|
|
|
|
|
|
|
return tarch, true
|
|
|
|
}
|
|
|
|
|
|
|
|
// analyzeStack performs semantic analysis on a stack -- validating, transforming, and/or updating it -- and then
|
|
|
|
// returns the result. If a problem occurs, errors will have been emitted, and the bool return will be false.
|
|
|
|
func (c *compiler) analyzeStack(doc *diag.Document, stack *ast.Stack) (*ast.Stack, bool) {
|
2016-11-16 18:29:44 +01:00
|
|
|
// TODO: load dependencies.
|
|
|
|
|
|
|
|
binder := NewBinder(c)
|
|
|
|
binder.Bind(doc, stack)
|
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 19:30:37 +01:00
|
|
|
if c.Diag().Errors() > 0 {
|
2016-11-16 18:29:44 +01:00
|
|
|
// If any errors happened during binding, exit.
|
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 19:30:37 +01:00
|
|
|
return stack, false
|
2016-11-16 02:42:22 +01:00
|
|
|
}
|
2016-11-15 20:30:34 +01:00
|
|
|
|
2016-11-16 18:29:44 +01:00
|
|
|
// TODO: perform semantic analysis on the bound tree.
|
2016-11-17 16:00:52 +01:00
|
|
|
|
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 19:30:37 +01:00
|
|
|
return stack, true
|
2016-11-15 20:30:34 +01:00
|
|
|
}
|
2016-11-16 17:19:26 +01:00
|
|
|
|
|
|
|
// detectMufile locates the closest Mufile-looking file from the given path, searching "upwards" in the directory
|
|
|
|
// hierarchy. If no Mufile is found, an empty path is returned.
|
|
|
|
func (c *compiler) detectMufile(from string) string {
|
|
|
|
abs, err := filepath.Abs(from)
|
|
|
|
if err != nil {
|
|
|
|
glog.Fatalf("An IO error occurred while searching for a Mufile: %v", err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// It's possible the target is already the file we seek; if so, return right away.
|
|
|
|
if c.isMufile(abs) {
|
|
|
|
return abs
|
|
|
|
}
|
|
|
|
|
|
|
|
curr := abs
|
|
|
|
for {
|
|
|
|
stop := false
|
|
|
|
|
|
|
|
// If the target is a directory, enumerate its files, checking each to see if it's a Mufile.
|
|
|
|
files, err := ioutil.ReadDir(curr)
|
|
|
|
if err != nil {
|
|
|
|
glog.Fatalf("An IO error occurred while searching for a Mufile: %v", err)
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
for _, file := range files {
|
|
|
|
name := file.Name()
|
|
|
|
path := filepath.Join(curr, name)
|
|
|
|
if c.isMufile(path) {
|
|
|
|
return path
|
|
|
|
} else if name == workspace.Muspace {
|
|
|
|
// If we hit a .muspace file, stop looking.
|
|
|
|
stop = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we encountered a stop condition, break out of the loop.
|
|
|
|
if stop {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
|
|
|
|
// If neither succeeded, keep looking in our parent directory.
|
|
|
|
curr = filepath.Dir(curr)
|
|
|
|
if os.IsPathSeparator(curr[len(curr)-1]) {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// isMufile returns true if the path references what appears to be a valid Mufile.
|
|
|
|
func (c *compiler) isMufile(path string) bool {
|
|
|
|
info, err := os.Stat(path)
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Directories can't be Mufiles.
|
|
|
|
if info.IsDir() {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Ensure the base name is expected.
|
|
|
|
name := info.Name()
|
|
|
|
ext := filepath.Ext(name)
|
|
|
|
base := strings.TrimSuffix(name, ext)
|
|
|
|
if base != workspace.MufileBase {
|
|
|
|
if strings.EqualFold(base, workspace.MufileBase) {
|
|
|
|
// If the strings aren't equal, but case-insensitively match, issue a warning.
|
|
|
|
c.Diag().Warningf(errors.WarnIllegalMufileCasing.WithFile(name))
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check all supported extensions.
|
|
|
|
for _, mufileExt := range workspace.MufileExts {
|
|
|
|
if name == workspace.MufileBase+mufileExt {
|
|
|
|
return true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we got here, it means the base name matched, but not the extension. Warn and return.
|
|
|
|
c.Diag().Warningf(errors.WarnIllegalMufileExt.WithFile(name), ext)
|
|
|
|
return false
|
|
|
|
}
|