Implement the mu apply
command
This change implements `mu apply`, by driving compilation, evaluation, planning, and then walking the plan and evaluating it. This is the bulk of marapongo/mu#21, except that there's a ton of testing/hardening to perform, in addition to things like progress reporting.
This commit is contained in:
parent
09c01dd942
commit
bfe659017f
7 changed files with 82 additions and 35 deletions
14
cmd/apply.go
14
cmd/apply.go
|
@ -4,9 +4,12 @@ package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
|
"github.com/marapongo/mu/pkg/compiler/errors"
|
||||||
)
|
)
|
||||||
|
|
||||||
func newApplyCmd() *cobra.Command {
|
func newApplyCmd() *cobra.Command {
|
||||||
|
var delete bool
|
||||||
var cmd = &cobra.Command{
|
var cmd = &cobra.Command{
|
||||||
Use: "apply [blueprint] [-- [args]]",
|
Use: "apply [blueprint] [-- [args]]",
|
||||||
Short: "Apply a deployment plan from a Mu blueprint",
|
Short: "Apply a deployment plan from a Mu blueprint",
|
||||||
|
@ -23,10 +26,21 @@ func newApplyCmd() *cobra.Command {
|
||||||
"By default, a blueprint package is loaded from the current directory. Optionally,\n" +
|
"By default, a blueprint package is loaded from the current directory. Optionally,\n" +
|
||||||
"a path to a blueprint elsewhere can be provided as the [blueprint] argument.",
|
"a path to a blueprint elsewhere can be provided as the [blueprint] argument.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
|
if comp, plan := plan(cmd, args, delete); plan != nil {
|
||||||
|
if _, err, _ := plan.Apply(); err != nil {
|
||||||
|
// TODO: we want richer diagnostics in the event that a plan apply fails. For instance, we want to
|
||||||
|
// know precisely what step failed, we want to know whether it was catastrophic, etc. We also
|
||||||
|
// probably want to plumb diag.Sink through apply so it can issue its own rich diagnostics.
|
||||||
|
comp.Diag().Errorf(errors.ErrorPlanApplyFailed, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: options; most importantly, what to compare the blueprint against.
|
// TODO: options; most importantly, what to compare the blueprint against.
|
||||||
|
cmd.PersistentFlags().BoolVar(
|
||||||
|
&delete, "delete", false,
|
||||||
|
"Delete the entirety of the blueprint's resources")
|
||||||
|
|
||||||
return cmd
|
return cmd
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,14 +9,16 @@ import (
|
||||||
|
|
||||||
"github.com/marapongo/mu/pkg/compiler"
|
"github.com/marapongo/mu/pkg/compiler"
|
||||||
"github.com/marapongo/mu/pkg/compiler/core"
|
"github.com/marapongo/mu/pkg/compiler/core"
|
||||||
|
"github.com/marapongo/mu/pkg/compiler/errors"
|
||||||
|
"github.com/marapongo/mu/pkg/diag"
|
||||||
"github.com/marapongo/mu/pkg/graph"
|
"github.com/marapongo/mu/pkg/graph"
|
||||||
|
"github.com/marapongo/mu/pkg/resource"
|
||||||
"github.com/marapongo/mu/pkg/util/cmdutil"
|
"github.com/marapongo/mu/pkg/util/cmdutil"
|
||||||
"github.com/marapongo/mu/pkg/util/contract"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// compile just uses the standard logic to parse arguments, options, and to locate/compile a package. It returns the
|
// compile just uses the standard logic to parse arguments, options, and to locate/compile a package. It returns the
|
||||||
// MuGL graph that is produced, or nil if an error occurred (in which case, we would expect non-0 errors).
|
// MuGL graph that is produced, or nil if an error occurred (in which case, we would expect non-0 errors).
|
||||||
func compile(cmd *cobra.Command, args []string) graph.Graph {
|
func compile(cmd *cobra.Command, args []string) (compiler.Compiler, graph.Graph) {
|
||||||
// If there's a --, we need to separate out the command args from the stack args.
|
// If there's a --, we need to separate out the command args from the stack args.
|
||||||
flags := cmd.Flags()
|
flags := cmd.Flags()
|
||||||
dashdash := flags.ArgsLenAtDash()
|
dashdash := flags.ArgsLenAtDash()
|
||||||
|
@ -26,23 +28,29 @@ func compile(cmd *cobra.Command, args []string) graph.Graph {
|
||||||
args = args[0:dashdash]
|
args = args[0:dashdash]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// A func to lazily allocate a sink to be used if we can't create a compiler.
|
||||||
|
d := func() diag.Sink { return core.DefaultSink("") }
|
||||||
|
|
||||||
// Create a compiler options object and map any flags and arguments to settings on it.
|
// Create a compiler options object and map any flags and arguments to settings on it.
|
||||||
opts := core.DefaultOptions()
|
opts := core.DefaultOptions()
|
||||||
opts.Args = dashdashArgsToMap(packArgs)
|
opts.Args = dashdashArgsToMap(packArgs)
|
||||||
|
|
||||||
// In the case of an argument, load that specific package and new up a compiler based on its base path.
|
// In the case of an argument, load that specific package and new up a compiler based on its base path.
|
||||||
// Otherwise, use the default workspace and package logic (which consults the current working directory).
|
// Otherwise, use the default workspace and package logic (which consults the current working directory).
|
||||||
|
var comp compiler.Compiler
|
||||||
var mugl graph.Graph
|
var mugl graph.Graph
|
||||||
if len(args) == 0 {
|
if len(args) == 0 {
|
||||||
comp, err := compiler.Newwd(opts)
|
var err error
|
||||||
|
comp, err = compiler.Newwd(opts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
contract.Failf("fatal: %v", err)
|
// Create a temporary diagnostics sink so that we can issue an error and bail out.
|
||||||
|
d().Errorf(errors.ErrorCantCreateCompiler, err)
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
mugl = comp.Compile()
|
mugl = comp.Compile()
|
||||||
} else {
|
} else {
|
||||||
fn := args[0]
|
fn := args[0]
|
||||||
if pkg := cmdutil.ReadPackageFromArg(fn); pkg != nil {
|
if pkg := cmdutil.ReadPackageFromArg(fn); pkg != nil {
|
||||||
var comp compiler.Compiler
|
|
||||||
var err error
|
var err error
|
||||||
if fn == "-" {
|
if fn == "-" {
|
||||||
comp, err = compiler.Newwd(opts)
|
comp, err = compiler.Newwd(opts)
|
||||||
|
@ -50,10 +58,41 @@ func compile(cmd *cobra.Command, args []string) graph.Graph {
|
||||||
comp, err = compiler.New(filepath.Dir(fn), opts)
|
comp, err = compiler.New(filepath.Dir(fn), opts)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
contract.Failf("fatal: %v", err)
|
d().Errorf(errors.ErrorCantReadPackage, fn, err)
|
||||||
|
return nil, nil
|
||||||
}
|
}
|
||||||
mugl = comp.CompilePackage(pkg)
|
mugl = comp.CompilePackage(pkg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mugl
|
return comp, mugl
|
||||||
|
}
|
||||||
|
|
||||||
|
// plan just uses the standard logic to parse arguments, options, and to create a plan for a package.
|
||||||
|
func plan(cmd *cobra.Command, args []string, delete bool) (compiler.Compiler, resource.Plan) {
|
||||||
|
// Perform the compilation and, if non-nil is returned, create a plan and print it.
|
||||||
|
comp, mugl := compile(cmd, args)
|
||||||
|
if mugl != nil {
|
||||||
|
// TODO: fetch the old plan for purposes of diffing.
|
||||||
|
rs, err := resource.NewSnapshot(mugl) // create a resource snapshot from the object graph.
|
||||||
|
if err != nil {
|
||||||
|
comp.Diag().Errorf(errors.ErrorCantCreateSnapshot, err)
|
||||||
|
return comp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create a new context for the plan operations.
|
||||||
|
ctx := resource.NewContext()
|
||||||
|
|
||||||
|
var plan resource.Plan
|
||||||
|
if delete {
|
||||||
|
// Generate a plan for deleting the entire snapshot.
|
||||||
|
plan = resource.NewDeletePlan(ctx, rs)
|
||||||
|
} else {
|
||||||
|
// Generate a plan for creating the resources from scratch.
|
||||||
|
plan = resource.NewCreatePlan(ctx, rs)
|
||||||
|
}
|
||||||
|
|
||||||
|
return comp, plan
|
||||||
|
}
|
||||||
|
|
||||||
|
return comp, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -31,7 +31,7 @@ func newEvalCmd() *cobra.Command {
|
||||||
"a path to a blueprint elsewhere can be provided as the [blueprint] argument.",
|
"a path to a blueprint elsewhere can be provided as the [blueprint] argument.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Perform the compilation and, if non-nil is returned, output the graph.
|
// Perform the compilation and, if non-nil is returned, output the graph.
|
||||||
if mugl := compile(cmd, args); mugl != nil {
|
if _, mugl := compile(cmd, args); mugl != nil {
|
||||||
// Serialize that MuGL graph so that it's suitable for printing/serializing.
|
// Serialize that MuGL graph so that it's suitable for printing/serializing.
|
||||||
if dotOutput {
|
if dotOutput {
|
||||||
// Convert the output to a DOT file.
|
// Convert the output to a DOT file.
|
||||||
|
|
24
cmd/plan.go
24
cmd/plan.go
|
@ -5,7 +5,6 @@ package cmd
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"os"
|
|
||||||
"strconv"
|
"strconv"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
@ -33,28 +32,7 @@ func newPlanCmd() *cobra.Command {
|
||||||
"By default, a blueprint package is loaded from the current directory. Optionally,\n" +
|
"By default, a blueprint package is loaded from the current directory. Optionally,\n" +
|
||||||
"a path to a blueprint elsewhere can be provided as the [blueprint] argument.",
|
"a path to a blueprint elsewhere can be provided as the [blueprint] argument.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Perform the compilation and, if non-nil is returned, create a plan and print it.
|
if _, plan := plan(cmd, args, delete); plan != nil {
|
||||||
if mugl := compile(cmd, args); mugl != nil {
|
|
||||||
// TODO: fetch the old plan for purposes of diffing.
|
|
||||||
rs, err := resource.NewSnapshot(mugl) // create a resource snapshot from the object graph.
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(os.Stderr, "fatal: %v\n", err)
|
|
||||||
os.Exit(-1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create a new context for the plan operations.
|
|
||||||
ctx := resource.NewContext()
|
|
||||||
|
|
||||||
var plan resource.Plan
|
|
||||||
if delete {
|
|
||||||
// Generate a plan for deleting the entire snapshot.
|
|
||||||
plan = resource.NewDeletePlan(ctx, rs)
|
|
||||||
} else {
|
|
||||||
// Generate a plan for creating the resources from scratch.
|
|
||||||
plan = resource.NewCreatePlan(ctx, rs)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Finally just pretty-print out the plan.
|
|
||||||
printPlan(plan)
|
printPlan(plan)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -59,10 +59,7 @@ func New(path string, opts *core.Options) (Compiler, error) {
|
||||||
}
|
}
|
||||||
d := opts.Diag
|
d := opts.Diag
|
||||||
if d == nil {
|
if d == nil {
|
||||||
d = diag.DefaultSink(diag.FormatOptions{
|
d = core.DefaultSink(path)
|
||||||
Pwd: path, // ensure output paths are relative to the current path.
|
|
||||||
Colors: true, // turn on colorization of warnings/errors.
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now create a new context to share amongst the compiler and workspace.
|
// Now create a new context to share amongst the compiler and workspace.
|
||||||
|
|
|
@ -20,3 +20,11 @@ type Options struct {
|
||||||
func DefaultOptions() *Options {
|
func DefaultOptions() *Options {
|
||||||
return &Options{}
|
return &Options{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DefaultOptionsSink returns the default preconfigured diagnostics sink.
|
||||||
|
func DefaultSink(path string) diag.Sink {
|
||||||
|
return diag.DefaultSink(diag.FormatOptions{
|
||||||
|
Pwd: path, // ensure output paths are relative to the current path.
|
||||||
|
Colors: true, // turn on colorization of warnings/errors.
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
11
pkg/compiler/errors/planapply.go
Normal file
11
pkg/compiler/errors/planapply.go
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
||||||
|
|
||||||
|
package errors
|
||||||
|
|
||||||
|
// Plan and apply errors are in the [2000,3000) range.
|
||||||
|
var (
|
||||||
|
ErrorCantCreateCompiler = newError(2000, "An error occurred during compiler construction: %v")
|
||||||
|
ErrorCantReadPackage = newError(2001, "An error occurred while reading the package '%v': %v")
|
||||||
|
ErrorCantCreateSnapshot = newError(2002, "Illegal MuGL structure detected; cannot create a snapshot: %v")
|
||||||
|
ErrorPlanApplyFailed = newError(2003, "Plan apply failed: %v")
|
||||||
|
)
|
Loading…
Reference in a new issue