Ensure configuration round-trips in Huskfiles
This commit is contained in:
parent
c77329129a
commit
6a2edc9159
|
@ -34,16 +34,17 @@ func newEvalCmd() *cobra.Command {
|
|||
// Perform the compilation and, if non-nil is returned, output the graph.
|
||||
if result := compile(cmd, args, nil); result != nil {
|
||||
// Serialize that evaluation graph so that it's suitable for printing/serializing.
|
||||
g := result.Heap.G
|
||||
if dotOutput {
|
||||
// Convert the output to a DOT file.
|
||||
if err := dotconv.Print(result.Heap.G, os.Stdout); err != nil {
|
||||
if err := dotconv.Print(g, os.Stdout); err != nil {
|
||||
fmt.Fprintf(os.Stderr, "error: failed to write DOT file to output: %v\n", err)
|
||||
os.Exit(-1)
|
||||
}
|
||||
} else {
|
||||
// Just print a very basic, yet (hopefully) aesthetically pleasinge, ascii-ization of the graph.
|
||||
shown := make(map[graph.Vertex]bool)
|
||||
for _, root := range result.Heap.G.Objs() {
|
||||
for _, root := range g.Objs() {
|
||||
printVertex(root.ToObj(), shown, "")
|
||||
}
|
||||
}
|
||||
|
|
114
cmd/husk.go
114
cmd/husk.go
|
@ -70,37 +70,39 @@ func initHuskCmd(cmd *cobra.Command, args []string) *huskCmdInfo {
|
|||
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 {
|
||||
name := tokens.QName(args[0])
|
||||
huskfile, husk, old := readHusk(ctx, name)
|
||||
if husk == 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,
|
||||
Ctx: ctx,
|
||||
Husk: husk,
|
||||
Huskfile: huskfile,
|
||||
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
|
||||
Ctx *resource.Context // the resulting context
|
||||
Husk *resource.Husk // the husk information
|
||||
Huskfile *resource.Huskfile // the full serialized huskfile from which this came.
|
||||
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(husk tokens.QName) {
|
||||
// create just creates a new empty husk without deploying anything into it.
|
||||
// TODO: add the ability to configure the husk at the command line.
|
||||
func create(name tokens.QName) {
|
||||
husk := &resource.Husk{Name: name}
|
||||
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", name)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -149,7 +151,7 @@ func prepareCompiler(cmd *cobra.Command, args []string) (compiler.Compiler, *pac
|
|||
|
||||
// compile just uses the standard logic to parse arguments, options, and to locate/compile a package. It returns the
|
||||
// CocoGL 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, config *resource.ConfigMap) *compileResult {
|
||||
func compile(cmd *cobra.Command, args []string, config resource.ConfigMap) *compileResult {
|
||||
// Prepare the compiler info and, provided it succeeds, perform the compilation.
|
||||
if comp, pkg := prepareCompiler(cmd, args); comp != nil {
|
||||
// Create the preexec hook if the config map is non-nil.
|
||||
|
@ -208,29 +210,27 @@ func plan(cmd *cobra.Command, info *huskCmdInfo, delete bool) *planResult {
|
|||
var result *compileResult
|
||||
if !delete {
|
||||
// First, compile; if that yields errors or an empty heap, exit early.
|
||||
if result = compile(cmd, info.args, info.dep.Config); result == nil || result.Heap == nil {
|
||||
if result = compile(cmd, info.Args, info.Husk.Config); result == nil || result.Heap == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Create a resource snapshot from the compiled/evaluated object graph.
|
||||
var err error
|
||||
new, err = resource.NewGraphSnapshot(
|
||||
info.ctx, info.husk, result.Pkg.Tok, result.C.Ctx().Opts.Args, result.Heap)
|
||||
info.Ctx, info.Husk.Name, result.Pkg.Tok, result.C.Ctx().Opts.Args, result.Heap)
|
||||
if err != nil {
|
||||
result.C.Diag().Errorf(errors.ErrorCantCreateSnapshot, err)
|
||||
return nil
|
||||
} else if !info.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(info.ctx, info.old, new)
|
||||
plan := resource.NewPlan(info.Ctx, info.Old, new)
|
||||
return &planResult{
|
||||
compileResult: result,
|
||||
Ctx: info.ctx,
|
||||
Husk: info.husk,
|
||||
Old: info.old,
|
||||
Info: info,
|
||||
New: new,
|
||||
Plan: plan,
|
||||
}
|
||||
|
@ -238,18 +238,17 @@ func plan(cmd *cobra.Command, info *huskCmdInfo, delete bool) *planResult {
|
|||
|
||||
type planResult struct {
|
||||
*compileResult
|
||||
Ctx *resource.Context
|
||||
Husk tokens.QName // the husk name.
|
||||
Info *huskCmdInfo // plan command information.
|
||||
Old resource.Snapshot // the existing snapshot (if any).
|
||||
New resource.Snapshot // the new snapshot for this plan (if any).
|
||||
Plan resource.Plan
|
||||
Plan resource.Plan // the plan created by this command.
|
||||
}
|
||||
|
||||
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 {
|
||||
info.ctx.Diag.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.
|
||||
|
@ -258,7 +257,7 @@ func apply(cmd *cobra.Command, info *huskCmdInfo, opts applyOptions) {
|
|||
if opts.Output == "" || opts.Output == "-" {
|
||||
printPlan(result, opts)
|
||||
} else {
|
||||
saveHusk(info.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, along with a header.
|
||||
|
@ -275,7 +274,7 @@ func apply(cmd *cobra.Command, info *huskCmdInfo, opts applyOptions) {
|
|||
// 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.
|
||||
info.ctx.Diag.Errorf(errors.ErrorPlanApplyFailed, err)
|
||||
info.Ctx.Diag.Errorf(errors.ErrorPlanApplyFailed, err)
|
||||
}
|
||||
|
||||
// Print out the total number of steps performed (and their kinds), the duration, and any summary info.
|
||||
|
@ -291,12 +290,13 @@ func apply(cmd *cobra.Command, info *huskCmdInfo, opts applyOptions) {
|
|||
|
||||
// Now save the updated snapshot to the specified output file, if any, or the standard location otherwise.
|
||||
// Note that if a failure has occurred, the Apply routine above will have returned a safe checkpoint.
|
||||
saveHusk(result.Husk, checkpoint, opts.Output, true /*overwrite*/)
|
||||
husk := result.Info.Husk
|
||||
saveHusk(husk, checkpoint, opts.Output, true /*overwrite*/)
|
||||
|
||||
// If a deletion was requested, remove the husk; but only if no error has occurred!
|
||||
if err == nil && opts.Delete {
|
||||
deleteHusk(result.Husk)
|
||||
fmt.Printf("Coconut husk '%v' has been destroyed!\n", result.Husk)
|
||||
deleteHusk(husk)
|
||||
fmt.Printf("Coconut husk '%v' has been destroyed!\n", husk.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -311,41 +311,41 @@ func backupHusk(file string) {
|
|||
}
|
||||
|
||||
// deleteHusk removes an existing snapshot file, leaving behind a backup.
|
||||
func deleteHusk(husk tokens.QName) {
|
||||
contract.Require(husk != "", "husk")
|
||||
func deleteHusk(husk *resource.Husk) {
|
||||
contract.Require(husk != nil, "husk")
|
||||
// Just make a backup of the file and don't write out anything new.
|
||||
file := workspace.HuskPath(husk)
|
||||
file := workspace.HuskPath(husk.Name)
|
||||
backupHusk(file)
|
||||
}
|
||||
|
||||
// readHusk reads in an existing snapshot file, issuing an error and returning nil if something goes awry.
|
||||
func readHusk(ctx *resource.Context, husk tokens.QName) (*resource.Deployment, resource.Snapshot) {
|
||||
contract.Require(husk != "", "husk")
|
||||
file := workspace.HuskPath(husk)
|
||||
func readHusk(ctx *resource.Context, name tokens.QName) (*resource.Huskfile, *resource.Husk, resource.Snapshot) {
|
||||
contract.Require(name != "", "name")
|
||||
file := workspace.HuskPath(name)
|
||||
|
||||
// Detect the encoding of the file so we can do our initial unmarshaling.
|
||||
m, ext := encoding.Detect(file)
|
||||
if m == nil {
|
||||
ctx.Diag.Errorf(errors.ErrorIllegalMarkupExtension, ext)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Now read the whole file into a byte blob.
|
||||
b, err := ioutil.ReadFile(file)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
ctx.Diag.Errorf(errors.ErrorInvalidHuskName, husk)
|
||||
ctx.Diag.Errorf(errors.ErrorInvalidHuskName, name)
|
||||
} else {
|
||||
ctx.Diag.Errorf(errors.ErrorIO, err)
|
||||
}
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Unmarshal the contents into a deployment structure.
|
||||
var dep resource.Deployment
|
||||
if err = m.Unmarshal(b, &dep); err != nil {
|
||||
// Unmarshal the contents into a huskfile deployment structure.
|
||||
var huskfile resource.Huskfile
|
||||
if err = m.Unmarshal(b, &huskfile); err != nil {
|
||||
ctx.Diag.Errorf(errors.ErrorCantReadDeployment, file, err)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
|
||||
// Next, use the mapping infrastructure to validate the contents.
|
||||
|
@ -353,7 +353,7 @@ func readHusk(ctx *resource.Context, husk tokens.QName) (*resource.Deployment, r
|
|||
var obj mapper.Object
|
||||
if err = m.Unmarshal(b, &obj); err != nil {
|
||||
ctx.Diag.Errorf(errors.ErrorCantReadDeployment, file, err)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
} else {
|
||||
if obj["latest"] != nil {
|
||||
if latest, islatest := obj["latest"].(map[string]interface{}); islatest {
|
||||
|
@ -361,21 +361,23 @@ func readHusk(ctx *resource.Context, husk tokens.QName) (*resource.Deployment, r
|
|||
}
|
||||
}
|
||||
md := mapper.New(nil)
|
||||
var ignore resource.Deployment // just for errors.
|
||||
var ignore resource.Huskfile // just for errors.
|
||||
if err = md.Decode(obj, &ignore); err != nil {
|
||||
ctx.Diag.Errorf(errors.ErrorCantReadDeployment, file, err)
|
||||
return nil, nil
|
||||
return nil, nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return &dep, resource.DeserializeDeployment(ctx, &dep)
|
||||
husk, snap := resource.DeserializeHuskfile(ctx, &huskfile)
|
||||
contract.Assert(husk != nil)
|
||||
return &huskfile, husk, snap
|
||||
}
|
||||
|
||||
// saveHusk saves a new snapshot at the given location, backing up any existing ones.
|
||||
func saveHusk(husk tokens.QName, snap resource.Snapshot, file string, existok bool) bool {
|
||||
contract.Require(husk != "", "husk")
|
||||
func saveHusk(husk *resource.Husk, snap resource.Snapshot, file string, existok bool) bool {
|
||||
contract.Require(husk != nil, "husk")
|
||||
if file == "" {
|
||||
file = workspace.HuskPath(husk)
|
||||
file = workspace.HuskPath(husk.Name)
|
||||
}
|
||||
|
||||
// Make a serializable CocoGL data structure and then use the encoder to encode it.
|
||||
|
@ -387,7 +389,7 @@ func saveHusk(husk tokens.QName, snap resource.Snapshot, file string, existok bo
|
|||
if filepath.Ext(file) == "" {
|
||||
file = file + ext
|
||||
}
|
||||
dep := resource.SerializeDeployment(husk, snap, "")
|
||||
dep := resource.SerializeHuskfile(husk, snap, "")
|
||||
b, err := m.Marshal(dep)
|
||||
if err != nil {
|
||||
sink().Errorf(errors.ErrorIO, err)
|
||||
|
|
|
@ -28,7 +28,7 @@ func newHuskDestroyCmd() *cobra.Command {
|
|||
Run: func(cmd *cobra.Command, args []string) {
|
||||
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("This will permanently delete all resources in the '%v' husk!\n", info.Husk.Name)
|
||||
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" {
|
||||
|
|
|
@ -39,17 +39,17 @@ func newHuskLsCmd() *cobra.Command {
|
|||
}
|
||||
|
||||
// Skip files without valid extensions (e.g., *.bak files).
|
||||
huskfile := file.Name()
|
||||
ext := filepath.Ext(huskfile)
|
||||
huskfn := file.Name()
|
||||
ext := filepath.Ext(huskfn)
|
||||
if _, has := encoding.Marshalers[ext]; !has {
|
||||
continue
|
||||
}
|
||||
|
||||
// Create a new context and read in the husk information.
|
||||
husk := tokens.QName(huskfile[:len(huskfile)-len(ext)])
|
||||
name := tokens.QName(huskfn[:len(huskfn)-len(ext)])
|
||||
ctx := resource.NewContext(sink())
|
||||
dep, old := readHusk(ctx, husk)
|
||||
if dep == nil {
|
||||
huskfile, husk, old := readHusk(ctx, name)
|
||||
if husk == nil {
|
||||
contract.Assert(!ctx.Diag.Success())
|
||||
continue // failure reading the husk information.
|
||||
}
|
||||
|
@ -57,13 +57,13 @@ func newHuskLsCmd() *cobra.Command {
|
|||
// Now print out the name, last deployment time (if any), and resources (if any).
|
||||
lastDeploy := "n/a"
|
||||
resourceCount := "n/a"
|
||||
if dep.Latest != nil {
|
||||
lastDeploy = dep.Latest.Time.String()
|
||||
if huskfile.Latest != nil {
|
||||
lastDeploy = huskfile.Latest.Time.String()
|
||||
}
|
||||
if old != nil {
|
||||
resourceCount = strconv.Itoa(len(old.Resources()))
|
||||
}
|
||||
fmt.Printf("%-20s %-48s %-12s\n", dep.Husk, lastDeploy, resourceCount)
|
||||
fmt.Printf("%-20s %-48s %-12s\n", husk.Name, lastDeploy, resourceCount)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
|
|
@ -163,6 +163,7 @@ func (c *compiler) CompilePackage(pkg *pack.Package, preexec Preexec) (*symbols.
|
|||
// Create a fresh evaluator; if there are pre-exec hooks, run them now.
|
||||
e := eval.New(b.Ctx(), gg)
|
||||
if preexec != nil {
|
||||
glog.V(7).Infof("Invoking compiler preexec routine")
|
||||
preexec(b.Ctx(), pkgsym, e)
|
||||
}
|
||||
if !c.Diag().Success() {
|
||||
|
|
|
@ -41,7 +41,9 @@ func (cfg *ConfigMap) ConfigApplier(vars map[tokens.Token]*rt.Object) compiler.P
|
|||
// we are accessing module and class members, this routine will also trigger the relevant initialization routines.
|
||||
func (cfg *ConfigMap) ApplyConfig(ctx *binder.Context, pkg *symbols.Package,
|
||||
e eval.Interpreter) map[tokens.Token]*rt.Object {
|
||||
// Keep track of all variables applied:
|
||||
glog.V(5).Infof("Applying configuration values for package '%v'", pkg)
|
||||
|
||||
// Track all configuration variables that get set, for diagnostics and plumbing.
|
||||
vars := make(map[tokens.Token]*rt.Object)
|
||||
|
||||
if cfg != nil {
|
||||
|
|
|
@ -13,13 +13,6 @@ import (
|
|||
"github.com/pulumi/coconut/pkg/util/contract"
|
||||
)
|
||||
|
||||
// Deployment is a serialized deployment target plus a record of the latest deployment.
|
||||
type Deployment struct {
|
||||
Husk tokens.QName `json:"husk"` // the target environment name.
|
||||
Config *ConfigMap `json:"config,omitempty"` // optional configuration key/values.
|
||||
Latest *DeploymentRecord `json:"latest,omitempty"` // the latest/current deployment record.
|
||||
}
|
||||
|
||||
// DeploymentRecord is a serializable, flattened CocoGL graph structure, representing a deployment. It is similar
|
||||
// to the actual Snapshot interface, except that it flattens and rearranges a few data structures for serializability.
|
||||
// Over time, we also expect this to gather more information about deployments themselves.
|
||||
|
@ -44,19 +37,6 @@ type ResourceDeployment struct {
|
|||
// DeployedPropertyMap is a property map from resource key to the underlying property value.
|
||||
type DeployedPropertyMap map[string]interface{}
|
||||
|
||||
// SerializeDeployment turns a snapshot into a CocoGL data structure suitable for serialization.
|
||||
func SerializeDeployment(husk tokens.QName, snap Snapshot, reftag string) *Deployment {
|
||||
// If snap is nil, that's okay, we will just create an empty deployment; otherwise, serialize the whole snapshot.
|
||||
var latest *DeploymentRecord
|
||||
if snap != nil {
|
||||
latest = serializeDeploymentRecord(snap, reftag)
|
||||
}
|
||||
return &Deployment{
|
||||
Husk: husk,
|
||||
Latest: latest,
|
||||
}
|
||||
}
|
||||
|
||||
func serializeDeploymentRecord(snap Snapshot, reftag string) *DeploymentRecord {
|
||||
// Initialize the reftag if needed, and only serialize if overridden.
|
||||
var refp *string
|
||||
|
@ -167,53 +147,6 @@ func serializeProperty(prop PropertyValue, reftag string) (interface{}, bool) {
|
|||
return prop.V, true
|
||||
}
|
||||
|
||||
// DeserializeDeploymentRecord takes a serialized deployment record and returns its associated snapshot.
|
||||
func DeserializeDeployment(ctx *Context, ser *Deployment) Snapshot {
|
||||
latest := ser.Latest
|
||||
if latest == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Determine the reftag to use.
|
||||
var reftag string
|
||||
if latest.Reftag == nil {
|
||||
reftag = DefaultDeploymentReftag
|
||||
} else {
|
||||
reftag = *latest.Reftag
|
||||
}
|
||||
|
||||
// For every serialized resource vertex, create a ResourceDeployment out of it.
|
||||
var resources []Resource
|
||||
if latest.Resources != nil {
|
||||
for _, kvp := range latest.Resources.Iter() {
|
||||
// Deserialize the resources, if they exist.
|
||||
res := kvp.Value
|
||||
var props PropertyMap
|
||||
if res.Properties == nil {
|
||||
props = make(PropertyMap)
|
||||
} else {
|
||||
props = deserializeProperties(*res.Properties, reftag)
|
||||
}
|
||||
|
||||
// And now just produce a resource object using the information available.
|
||||
var id ID
|
||||
if res.ID != nil {
|
||||
id = *res.ID
|
||||
}
|
||||
|
||||
resources = append(resources, NewResource(id, kvp.Key, res.Type, props))
|
||||
}
|
||||
}
|
||||
|
||||
// If the args are non-nil, use them.
|
||||
var args core.Args
|
||||
if latest.Args != nil {
|
||||
args = *latest.Args
|
||||
}
|
||||
|
||||
return NewSnapshot(ctx, ser.Husk, latest.Package, args, resources)
|
||||
}
|
||||
|
||||
func deserializeProperties(props DeployedPropertyMap, reftag string) PropertyMap {
|
||||
result := make(PropertyMap)
|
||||
for k, prop := range props {
|
||||
|
|
100
pkg/resource/husk.go
Normal file
100
pkg/resource/husk.go
Normal file
|
@ -0,0 +1,100 @@
|
|||
// Copyright 2016 Pulumi, Inc. All rights reserved.
|
||||
|
||||
package resource
|
||||
|
||||
import (
|
||||
"github.com/pulumi/coconut/pkg/compiler/core"
|
||||
"github.com/pulumi/coconut/pkg/tokens"
|
||||
"github.com/pulumi/coconut/pkg/util/contract"
|
||||
)
|
||||
|
||||
// Husk represents information about a deployment target.
|
||||
type Husk struct {
|
||||
Name tokens.QName // the target environment name.
|
||||
Config ConfigMap // optional configuration key/values.
|
||||
}
|
||||
|
||||
// Huskfile is a serialized deployment target plus a record of the latest deployment.
|
||||
type Huskfile struct {
|
||||
Husk tokens.QName `json:"husk"` // the target environment name.
|
||||
Config *ConfigMap `json:"config,omitempty"` // optional configuration key/values.
|
||||
Latest *DeploymentRecord `json:"latest,omitempty"` // the latest/current deployment record.
|
||||
}
|
||||
|
||||
// SerializeHuskfile turns a snapshot into a CocoGL data structure suitable for serialization.
|
||||
func SerializeHuskfile(husk *Husk, snap Snapshot, reftag string) *Huskfile {
|
||||
contract.Requiref(husk != nil, "husk", "!= nil")
|
||||
|
||||
// If snap is nil, that's okay, we will just create an empty deployment; otherwise, serialize the whole snapshot.
|
||||
var latest *DeploymentRecord
|
||||
if snap != nil {
|
||||
latest = serializeDeploymentRecord(snap, reftag)
|
||||
}
|
||||
|
||||
var config *ConfigMap
|
||||
if husk.Config != nil {
|
||||
config = &husk.Config
|
||||
}
|
||||
|
||||
return &Huskfile{
|
||||
Husk: husk.Name,
|
||||
Config: config,
|
||||
Latest: latest,
|
||||
}
|
||||
}
|
||||
|
||||
// DeserializeDeployment takes a serialized deployment record and returns its associated snapshot.
|
||||
func DeserializeHuskfile(ctx *Context, huskfile *Huskfile) (*Husk, Snapshot) {
|
||||
contract.Require(ctx != nil, "ctx")
|
||||
contract.Require(huskfile != nil, "huskfile")
|
||||
|
||||
var snap Snapshot
|
||||
name := huskfile.Husk
|
||||
if latest := huskfile.Latest; latest != nil {
|
||||
// Determine the reftag to use.
|
||||
var reftag string
|
||||
if latest.Reftag == nil {
|
||||
reftag = DefaultDeploymentReftag
|
||||
} else {
|
||||
reftag = *latest.Reftag
|
||||
}
|
||||
|
||||
// For every serialized resource vertex, create a ResourceDeployment out of it.
|
||||
var resources []Resource
|
||||
if latest.Resources != nil {
|
||||
for _, kvp := range latest.Resources.Iter() {
|
||||
// Deserialize the resources, if they exist.
|
||||
res := kvp.Value
|
||||
var props PropertyMap
|
||||
if res.Properties == nil {
|
||||
props = make(PropertyMap)
|
||||
} else {
|
||||
props = deserializeProperties(*res.Properties, reftag)
|
||||
}
|
||||
|
||||
// And now just produce a resource object using the information available.
|
||||
var id ID
|
||||
if res.ID != nil {
|
||||
id = *res.ID
|
||||
}
|
||||
|
||||
resources = append(resources, NewResource(id, kvp.Key, res.Type, props))
|
||||
}
|
||||
}
|
||||
|
||||
// If the args are non-nil, use them.
|
||||
var args core.Args
|
||||
if latest.Args != nil {
|
||||
args = *latest.Args
|
||||
}
|
||||
|
||||
snap = NewSnapshot(ctx, name, latest.Package, args, resources)
|
||||
}
|
||||
|
||||
// Create husk and snapshot objects to return.
|
||||
husk := &Husk{Name: name}
|
||||
if huskfile.Config != nil {
|
||||
husk.Config = *huskfile.Config
|
||||
}
|
||||
return husk, snap
|
||||
}
|
|
@ -61,7 +61,7 @@ func NewPlan(ctx *Context, old Snapshot, new Snapshot) Plan {
|
|||
|
||||
type plan struct {
|
||||
ctx *Context // this plan's context.
|
||||
husk tokens.QName // the husk/namespace target being deployed into.
|
||||
ns tokens.QName // the husk/namespace target being deployed into.
|
||||
pkg tokens.Package // the package from which this snapshot came.
|
||||
args core.Args // the arguments used to compile this package.
|
||||
first *step // the first step to take.
|
||||
|
@ -159,14 +159,14 @@ func (p *plan) checkpoint(resources []Resource) Snapshot {
|
|||
tops = append(tops, topvert.Data().(Resource))
|
||||
}
|
||||
glog.V(7).Infof("Checkpointing plan application: %v total resources", len(tops))
|
||||
return NewSnapshot(p.ctx, p.husk, p.pkg, p.args, tops)
|
||||
return NewSnapshot(p.ctx, p.ns, p.pkg, p.args, tops)
|
||||
}
|
||||
|
||||
// newPlan handles all three cases: (1) a creation plan from a new snapshot when old doesn't exist (nil), (2) an update
|
||||
// plan when both old and new exist, and (3) a deletion plan when old exists, but not new.
|
||||
func newPlan(ctx *Context, old Snapshot, new Snapshot) *plan {
|
||||
// These variables are read from either snapshot (preferred new, since it may have updated args).
|
||||
var husk tokens.QName
|
||||
var ns tokens.QName
|
||||
var pkg tokens.Package
|
||||
var args core.Args
|
||||
|
||||
|
@ -175,7 +175,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) *plan {
|
|||
if old != nil {
|
||||
oldres = old.Resources()
|
||||
if new == nil {
|
||||
husk = old.Husk()
|
||||
ns = old.Namespace()
|
||||
pkg = old.Pkg()
|
||||
args = old.Args()
|
||||
}
|
||||
|
@ -183,7 +183,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) *plan {
|
|||
var newres []Resource
|
||||
if new != nil {
|
||||
newres = new.Resources()
|
||||
husk = new.Husk()
|
||||
ns = new.Namespace()
|
||||
pkg = new.Pkg()
|
||||
args = new.Args()
|
||||
}
|
||||
|
@ -221,7 +221,7 @@ func newPlan(ctx *Context, old Snapshot, new Snapshot) *plan {
|
|||
// Keep track of vertices for our later graph operations.
|
||||
p := &plan{
|
||||
ctx: ctx,
|
||||
husk: husk,
|
||||
ns: ns,
|
||||
pkg: pkg,
|
||||
args: args,
|
||||
}
|
||||
|
|
|
@ -19,7 +19,7 @@ import (
|
|||
// or apply an infrastructure deployment plan in order to make reality match the snapshot state.
|
||||
type Snapshot interface {
|
||||
Ctx() *Context // fetches the context for this snapshot.
|
||||
Husk() tokens.QName // the husk/namespace target being deployed into.
|
||||
Namespace() tokens.QName // the husk/namespace target being deployed into.
|
||||
Pkg() tokens.Package // the package from which this snapshot came.
|
||||
Args() core.Args // the arguments used to compile this package.
|
||||
Resources() []Resource // a topologically sorted list of resources (based on dependencies).
|
||||
|
@ -29,15 +29,15 @@ type Snapshot interface {
|
|||
}
|
||||
|
||||
// NewSnapshot creates a snapshot from the given arguments. The resources must be in topologically sorted order.
|
||||
func NewSnapshot(ctx *Context, husk tokens.QName, pkg tokens.Package,
|
||||
func NewSnapshot(ctx *Context, ns tokens.QName, pkg tokens.Package,
|
||||
args core.Args, resources []Resource) Snapshot {
|
||||
return &snapshot{ctx, husk, pkg, args, resources}
|
||||
return &snapshot{ctx, ns, pkg, args, resources}
|
||||
}
|
||||
|
||||
// NewGraphSnapshot takes an object graph and produces a resource snapshot from it. It understands how to name
|
||||
// resources based on their position within the graph and how to identify and record dependencies. This function can
|
||||
// fail dynamically if the input graph did not satisfy the preconditions for resource graphs (like that it is a DAG).
|
||||
func NewGraphSnapshot(ctx *Context, husk tokens.QName, pkg tokens.Package,
|
||||
func NewGraphSnapshot(ctx *Context, ns tokens.QName, pkg tokens.Package,
|
||||
args core.Args, heap *heapstate.Heap) (Snapshot, error) {
|
||||
// Topologically sort the entire heapstate (in dependency order) and extract just the resource objects.
|
||||
resobjs, err := topsort(ctx, heap.G)
|
||||
|
@ -47,27 +47,27 @@ func NewGraphSnapshot(ctx *Context, husk tokens.QName, pkg tokens.Package,
|
|||
|
||||
// Next, name all resources, create their monikers and objects, and maps that we will use. Note that we must do
|
||||
// this in DAG order (guaranteed by our topological sort above), so that referenced monikers are available.
|
||||
resources, err := createResources(ctx, husk, heap, resobjs)
|
||||
resources, err := createResources(ctx, ns, heap, resobjs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return NewSnapshot(ctx, husk, pkg, args, resources), nil
|
||||
return NewSnapshot(ctx, ns, pkg, args, resources), nil
|
||||
}
|
||||
|
||||
type snapshot struct {
|
||||
ctx *Context // the context shared by all operations in this snapshot.
|
||||
husk tokens.QName // the husk/namespace target being deployed into.
|
||||
ns tokens.QName // the namespace target being deployed into.
|
||||
pkg tokens.Package // the package from which this snapshot came.
|
||||
args core.Args // the arguments used to compile this package.
|
||||
resources []Resource // the topologically sorted linearized list of resources.
|
||||
}
|
||||
|
||||
func (s *snapshot) Ctx() *Context { return s.ctx }
|
||||
func (s *snapshot) Husk() tokens.QName { return s.husk }
|
||||
func (s *snapshot) Pkg() tokens.Package { return s.pkg }
|
||||
func (s *snapshot) Args() core.Args { return s.args }
|
||||
func (s *snapshot) Resources() []Resource { return s.resources }
|
||||
func (s *snapshot) Ctx() *Context { return s.ctx }
|
||||
func (s *snapshot) Namespace() tokens.QName { return s.ns }
|
||||
func (s *snapshot) Pkg() tokens.Package { return s.pkg }
|
||||
func (s *snapshot) Args() core.Args { return s.args }
|
||||
func (s *snapshot) Resources() []Resource { return s.resources }
|
||||
|
||||
func (s *snapshot) ResourceByID(id ID, t tokens.Type) Resource {
|
||||
contract.Failf("TODO: not yet implemented")
|
||||
|
|
Loading…
Reference in a new issue