Unify a bit of command logic, and hoist some failure modes

This commit is contained in:
joeduffy 2017-02-27 14:13:27 -08:00
parent 73babc13a0
commit 371a847eb9
4 changed files with 75 additions and 65 deletions

View file

@ -58,8 +58,44 @@ func sink() diag.Sink {
return snk
}
func initHuskCmd(cmd *cobra.Command, args []string) *huskCmdInfo {
// Create a new context for the plan operations.
ctx := resource.NewContext(sink())
// Read in the name of the husk to use.
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "fatal: missing required husk name\n")
os.Exit(-1)
}
husk := tokens.QName(args[0])
// Read in the deployment information, bailing if an IO error occurs.
dep, old := readHusk(ctx, husk)
if dep == nil {
contract.Assert(!ctx.Diag.Success())
return nil // failure reading the husk information.
}
return &huskCmdInfo{
ctx: ctx,
husk: husk,
dep: dep,
old: old,
args: args[1:],
orig: args,
}
}
type huskCmdInfo struct {
ctx *resource.Context // the resulting context
husk tokens.QName // the husk name
dep *resource.Deployment // the husk's deployment record
old resource.Snapshot // the husk's latest deployment snapshot
args []string // the rest of the args after extracting the husk name
orig []string // the original args before extracting the husk name
}
// create just creates a new husk without deploying anything into it.
func create(cmd *cobra.Command, args []string, husk tokens.QName) {
func create(husk tokens.QName) {
if success := saveHusk(husk, nil, "", false); success {
fmt.Printf("Coconut husk '%v' initialized; ready for deployments (see `coco husk deploy`)\n", husk)
}
@ -121,44 +157,35 @@ type compileResult struct {
}
// plan just uses the standard logic to parse arguments, options, and to create a snapshot and plan.
func plan(cmd *cobra.Command, args []string, husk tokens.QName, delete bool) *planResult {
// Create a new context for the plan operations.
ctx := resource.NewContext(sink())
// Read in the deployment information, bailing if an IO error occurs.
dep, old := readHusk(ctx, husk)
if dep == nil {
contract.Assert(!ctx.Diag.Success())
return nil // failure reading the husk information.
}
func plan(cmd *cobra.Command, info *huskCmdInfo, delete bool) *planResult {
// If deleting, there is no need to create a new snapshot; otherwise, we will need to compile the package.
var new resource.Snapshot
var result *compileResult
if !delete {
// First, compile; if that yields errors or an empty heap, exit early.
if result = compile(cmd, args); result == nil || result.Heap == nil {
if result = compile(cmd, info.args); result == nil || result.Heap == nil {
return nil
}
// Create a resource snapshot from the compiled/evaluated object graph.
var err error
new, err = resource.NewGraphSnapshot(ctx, husk, result.Pkg.Tok, result.C.Ctx().Opts.Args, result.Heap)
new, err = resource.NewGraphSnapshot(
info.ctx, info.husk, result.Pkg.Tok, result.C.Ctx().Opts.Args, result.Heap)
if err != nil {
result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err)
return nil
} else if !ctx.Diag.Success() {
} else if !info.ctx.Diag.Success() {
return nil
}
}
// Generate a plan; this API handles all interesting cases (create, update, delete).
plan := resource.NewPlan(ctx, old, new)
plan := resource.NewPlan(info.ctx, info.old, new)
return &planResult{
compileResult: result,
Ctx: ctx,
Husk: husk,
Old: old,
Ctx: info.ctx,
Husk: info.husk,
Old: info.old,
New: new,
Plan: plan,
}
@ -173,11 +200,11 @@ type planResult struct {
Plan resource.Plan
}
func apply(cmd *cobra.Command, args []string, husk tokens.QName, opts applyOptions) {
if result := plan(cmd, args, husk, opts.Delete); result != nil {
func apply(cmd *cobra.Command, info *huskCmdInfo, opts applyOptions) {
if result := plan(cmd, info, opts.Delete); result != nil {
// If we are doing an empty update, say so.
if result.Plan.Empty() && !opts.Delete {
sink().Infof(diag.Message("nothing to do -- resources are up to date"))
info.ctx.Diag.Infof(diag.Message("nothing to do -- resources are up to date"))
}
// Now based on whether a dry run was specified, or not, either print or perform the planned operations.
@ -186,7 +213,7 @@ func apply(cmd *cobra.Command, args []string, husk tokens.QName, opts applyOptio
if opts.Output == "" || opts.Output == "-" {
printPlan(result.Plan, opts.ShowUnchanged, opts.Summary)
} else {
saveHusk(husk, result.New, opts.Output, true /*overwrite*/)
saveHusk(info.husk, result.New, opts.Output, true /*overwrite*/)
}
} else {
// If show unchanged was requested, print them first.
@ -204,7 +231,7 @@ func apply(cmd *cobra.Command, args []string, husk tokens.QName, opts applyOptio
// 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.
sink().Errorf(errors.ErrorPlanApplyFailed, err)
info.ctx.Diag.Errorf(errors.ErrorPlanApplyFailed, err)
}
// Print out the total number of steps performed (and their kinds), if any succeeded.

View file

@ -3,12 +3,7 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/pulumi/coconut/pkg/tokens"
)
func newHuskDeployCmd() *cobra.Command {
@ -31,20 +26,15 @@ func newHuskDeployCmd() *cobra.Command {
"By default, the Nut to execute is loaded from the current directory. Optionally, an\n" +
"explicit path can be provided using the [nut-file] argument.",
Run: func(cmd *cobra.Command, args []string) {
// Read in the name of the husk to use.
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "fatal: missing required husk name\n")
os.Exit(-1)
if info := initHuskCmd(cmd, args); info != nil {
apply(cmd, info, applyOptions{
Delete: false,
DryRun: dryRun,
ShowUnchanged: showUnchanged,
Summary: summary,
Output: output,
})
}
husk := tokens.QName(args[0])
apply(cmd, args[1:], husk, applyOptions{
Delete: false,
DryRun: dryRun,
ShowUnchanged: showUnchanged,
Summary: summary,
Output: output,
})
},
}

View file

@ -8,8 +8,6 @@ import (
"os"
"github.com/spf13/cobra"
"github.com/pulumi/coconut/pkg/tokens"
)
func newHuskDestroyCmd() *cobra.Command {
@ -28,28 +26,23 @@ func newHuskDestroyCmd() *cobra.Command {
"Warning: although old snapshots can be used to recreate an environment, this command\n" +
"is generally irreversable and should be used with great care.",
Run: func(cmd *cobra.Command, args []string) {
// Read in the name of the husk to use.
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "fatal: missing required husk name\n")
os.Exit(-1)
}
husk := tokens.QName(args[0])
if !yes {
fmt.Printf("This will permanently delete all resources in the '%v' husk!\n", husk)
fmt.Printf("Please confirm that this is what you'd like to do by typing (\"yes\"): ")
reader := bufio.NewReader(os.Stdin)
if line, _ := reader.ReadString('\n'); line != "yes\n" {
fmt.Fprintf(os.Stderr, "Confirmation declined -- exiting without destroying the husk\n")
os.Exit(-1)
if info := initHuskCmd(cmd, args); info != nil {
if !dryRun && !yes {
fmt.Printf("This will permanently delete all resources in the '%v' husk!\n", info.husk)
fmt.Printf("Please confirm that this is what you'd like to do by typing (\"yes\"): ")
reader := bufio.NewReader(os.Stdin)
if line, _ := reader.ReadString('\n'); line != "yes\n" {
fmt.Fprintf(os.Stderr, "Confirmation declined -- exiting without destroying the husk\n")
os.Exit(-1)
}
}
}
apply(cmd, args[1:], husk, applyOptions{
Delete: true,
DryRun: dryRun,
Summary: summary,
})
apply(cmd, info, applyOptions{
Delete: true,
DryRun: dryRun,
Summary: summary,
})
}
},
}

View file

@ -27,7 +27,7 @@ func newHuskInitCmd() *cobra.Command {
}
husk := tokens.QName(args[0])
create(cmd, args[1:], husk)
create(husk)
},
}
}