Unify a bit of command logic, and hoist some failure modes
This commit is contained in:
parent
73babc13a0
commit
371a847eb9
4 changed files with 75 additions and 65 deletions
75
cmd/husk.go
75
cmd/husk.go
|
@ -58,8 +58,44 @@ func sink() diag.Sink {
|
||||||
return snk
|
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.
|
// 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 {
|
if success := saveHusk(husk, nil, "", false); success {
|
||||||
fmt.Printf("Coconut husk '%v' initialized; ready for deployments (see `coco husk deploy`)\n", husk)
|
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.
|
// 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 {
|
func plan(cmd *cobra.Command, info *huskCmdInfo, 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.
|
|
||||||
}
|
|
||||||
|
|
||||||
// If deleting, there is no need to create a new snapshot; otherwise, we will need to compile the package.
|
// If deleting, there is no need to create a new snapshot; otherwise, we will need to compile the package.
|
||||||
var new resource.Snapshot
|
var new resource.Snapshot
|
||||||
var result *compileResult
|
var result *compileResult
|
||||||
if !delete {
|
if !delete {
|
||||||
// First, compile; if that yields errors or an empty heap, exit early.
|
// 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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a resource snapshot from the compiled/evaluated object graph.
|
// Create a resource snapshot from the compiled/evaluated object graph.
|
||||||
var err error
|
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 {
|
if err != nil {
|
||||||
result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err)
|
result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err)
|
||||||
return nil
|
return nil
|
||||||
} else if !ctx.Diag.Success() {
|
} else if !info.ctx.Diag.Success() {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate a plan; this API handles all interesting cases (create, update, delete).
|
// 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{
|
return &planResult{
|
||||||
compileResult: result,
|
compileResult: result,
|
||||||
Ctx: ctx,
|
Ctx: info.ctx,
|
||||||
Husk: husk,
|
Husk: info.husk,
|
||||||
Old: old,
|
Old: info.old,
|
||||||
New: new,
|
New: new,
|
||||||
Plan: plan,
|
Plan: plan,
|
||||||
}
|
}
|
||||||
|
@ -173,11 +200,11 @@ type planResult struct {
|
||||||
Plan resource.Plan
|
Plan resource.Plan
|
||||||
}
|
}
|
||||||
|
|
||||||
func apply(cmd *cobra.Command, args []string, husk tokens.QName, opts applyOptions) {
|
func apply(cmd *cobra.Command, info *huskCmdInfo, opts applyOptions) {
|
||||||
if result := plan(cmd, args, husk, opts.Delete); result != nil {
|
if result := plan(cmd, info, opts.Delete); result != nil {
|
||||||
// If we are doing an empty update, say so.
|
// If we are doing an empty update, say so.
|
||||||
if result.Plan.Empty() && !opts.Delete {
|
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.
|
// 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 == "-" {
|
if opts.Output == "" || opts.Output == "-" {
|
||||||
printPlan(result.Plan, opts.ShowUnchanged, opts.Summary)
|
printPlan(result.Plan, opts.ShowUnchanged, opts.Summary)
|
||||||
} else {
|
} else {
|
||||||
saveHusk(husk, result.New, opts.Output, true /*overwrite*/)
|
saveHusk(info.husk, result.New, opts.Output, true /*overwrite*/)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// If show unchanged was requested, print them first.
|
// 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
|
// 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
|
// 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.
|
// 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.
|
// Print out the total number of steps performed (and their kinds), if any succeeded.
|
||||||
|
|
|
@ -3,12 +3,7 @@
|
||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pulumi/coconut/pkg/tokens"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newHuskDeployCmd() *cobra.Command {
|
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" +
|
"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.",
|
"explicit path can be provided using the [nut-file] argument.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Read in the name of the husk to use.
|
if info := initHuskCmd(cmd, args); info != nil {
|
||||||
if len(args) == 0 {
|
apply(cmd, info, applyOptions{
|
||||||
fmt.Fprintf(os.Stderr, "fatal: missing required husk name\n")
|
Delete: false,
|
||||||
os.Exit(-1)
|
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,
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,6 @@ import (
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
|
|
||||||
"github.com/pulumi/coconut/pkg/tokens"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func newHuskDestroyCmd() *cobra.Command {
|
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" +
|
"Warning: although old snapshots can be used to recreate an environment, this command\n" +
|
||||||
"is generally irreversable and should be used with great care.",
|
"is generally irreversable and should be used with great care.",
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: func(cmd *cobra.Command, args []string) {
|
||||||
// Read in the name of the husk to use.
|
if info := initHuskCmd(cmd, args); info != nil {
|
||||||
if len(args) == 0 {
|
if !dryRun && !yes {
|
||||||
fmt.Fprintf(os.Stderr, "fatal: missing required husk name\n")
|
fmt.Printf("This will permanently delete all resources in the '%v' husk!\n", info.husk)
|
||||||
os.Exit(-1)
|
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" {
|
||||||
husk := tokens.QName(args[0])
|
fmt.Fprintf(os.Stderr, "Confirmation declined -- exiting without destroying the husk\n")
|
||||||
if !yes {
|
os.Exit(-1)
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
apply(cmd, args[1:], husk, applyOptions{
|
apply(cmd, info, applyOptions{
|
||||||
Delete: true,
|
Delete: true,
|
||||||
DryRun: dryRun,
|
DryRun: dryRun,
|
||||||
Summary: summary,
|
Summary: summary,
|
||||||
})
|
})
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,7 @@ func newHuskInitCmd() *cobra.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
husk := tokens.QName(args[0])
|
husk := tokens.QName(args[0])
|
||||||
create(cmd, args[1:], husk)
|
create(husk)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue