This change repivots the plan/apply commands slightly. This is largely in preparation for performing deletes and updates of existing environments. The old way was slightly confusing and made things appear more "magical" than they actually are. Namely, different things are needed for different kinds of deployment operations, and trying to present them each underneath a single pair of CLI commands just leads to weird modality and options. The new way is to offer three commands: create, update, and delete. Each does what it says on the tin: create provisions a new environment, update makes resource updates to an existing one, and delete tears down an existing one entirely. The arguments are what make this interesting: create demands a MuPackage to evaluate (producing the new desired state snapshot), update takes *both* an existing snapshot file plus a MuPackage to evaluate (producing the new desired state snapshot to diff against the existing one), and delete merely takes an existing snapshot file and no MuPackage, since all it must do is tear down an existing known environment. Replacing the plan functionality is the --dry-run (-n) flag that may be passed to any of the above commands. This will print out the plan without actually performing any opterations. All commands produce serializable resource files in the MuGL file format, and attempt to do smart things with respect to backups, etc., to support the intended "Git-oriented" workflow of the pure CLI dev experience.
156 lines
4.8 KiB
Go
156 lines
4.8 KiB
Go
// Copyright 2016 Marapongo, Inc. All rights reserved.
|
|
|
|
package resource
|
|
|
|
import (
|
|
"github.com/marapongo/mu/pkg/compiler/core"
|
|
"github.com/marapongo/mu/pkg/tokens"
|
|
"github.com/marapongo/mu/pkg/util/contract"
|
|
)
|
|
|
|
// MuglSnapshot is a serializable, flattened MuGL graph structure, specifically for snapshots. It is very similar to
|
|
// the actual Snapshot interface, except that it flattens and rearranges a few data structures for serializability.
|
|
type MuglSnapshot struct {
|
|
Package tokens.PackageName `json:"package"` // the package which created this graph.
|
|
Args *core.Args `json:"args,omitempty"` // the blueprint args for graph creation.
|
|
Refs *string `json:"refs,omitempty"` // the ref alias, if any (`#ref` by default).
|
|
Vertices *MuglResourceMap `json:"vertices,omitempty"` // a map of monikers to resource vertices.
|
|
}
|
|
|
|
// SnapshotRefTag is the default ref tag for intra-graph edges.
|
|
const SnapshotRefTag = "#ref"
|
|
|
|
// MuglResourceMap is a map of object moniker to the resource vertex for that moniker.
|
|
type MuglResourceMap map[Moniker]*MuglResource
|
|
|
|
// MuglResource is a serializable vertex within a MuGL graph, specifically for resource snapshots.
|
|
type MuglResource struct {
|
|
ID *ID `json:"id,omitempty"` // the provider ID for this resource, if any.
|
|
Type tokens.Type `json:"type"` // this resource's full type token.
|
|
Properties *MuglPropertyMap `json:"properties,omitempty"` // an untyped bag of properties.
|
|
}
|
|
|
|
// MuglPropertyMap is a property map from resource key to the underlying property value.
|
|
type MuglPropertyMap map[PropertyKey]interface{}
|
|
|
|
// SerializeSnapshot turns a snapshot into a MuGL data structure suitable for serialization.
|
|
func SerializeSnapshot(snap Snapshot, reftag string) *MuglSnapshot {
|
|
contract.Assert(snap != nil)
|
|
|
|
// Set the ref to the default `#ref` if empty. Only include it in the serialized output if non-default.
|
|
var refp *string
|
|
if reftag == "" {
|
|
reftag = SnapshotRefTag
|
|
} else {
|
|
refp = &reftag
|
|
}
|
|
|
|
// Serialize all vertices and only include a vertex section if non-empty.
|
|
var vertsp *MuglResourceMap
|
|
verts := make(MuglResourceMap)
|
|
for _, res := range snap.Topsort() {
|
|
m := res.Moniker()
|
|
contract.Assertf(string(m) != "", "Unexpected empty resource moniker")
|
|
contract.Assertf(verts[m] == nil, "Unexpected duplicate resource moniker '%v'", m)
|
|
verts[m] = SerializeResource(res, reftag)
|
|
}
|
|
if len(verts) > 0 {
|
|
vertsp = &verts
|
|
}
|
|
|
|
// Only include the arguments in the output if non-emtpy.
|
|
var argsp *core.Args
|
|
if args := snap.Args(); len(args) > 0 {
|
|
argsp = &args
|
|
}
|
|
|
|
return &MuglSnapshot{
|
|
Package: snap.Pkg(), // TODO: eventually, this should carry version metadata too.
|
|
Args: argsp,
|
|
Refs: refp,
|
|
Vertices: vertsp,
|
|
}
|
|
}
|
|
|
|
// SerializeResource turns a resource into a MuGL data structure suitable for serialization.
|
|
func SerializeResource(res Resource, reftag string) *MuglResource {
|
|
contract.Assert(res != nil)
|
|
|
|
// Only serialize the ID if it is non-empty.
|
|
var idp *ID
|
|
if id := res.ID(); id != ID("") {
|
|
idp = &id
|
|
}
|
|
|
|
// Serialize all properties recursively, and add them if non-empty.
|
|
var props *MuglPropertyMap
|
|
srcprops := res.Properties()
|
|
dstprops := make(MuglPropertyMap)
|
|
for _, key := range StablePropertyKeys(srcprops) {
|
|
if v, use := SerializeProperty(srcprops[key], reftag); use {
|
|
dstprops[key] = v
|
|
}
|
|
}
|
|
if len(dstprops) > 0 {
|
|
props = &dstprops
|
|
}
|
|
|
|
return &MuglResource{
|
|
ID: idp,
|
|
Type: res.Type(),
|
|
Properties: props,
|
|
}
|
|
}
|
|
|
|
// SerializeProperty serializes a resource property value so that it's suitable for serialization.
|
|
func SerializeProperty(prop PropertyValue, reftag string) (interface{}, bool) {
|
|
// Skip nulls.
|
|
if prop.IsNull() {
|
|
return nil, false
|
|
}
|
|
|
|
// For arrays, make sure to recurse.
|
|
if prop.IsArray() {
|
|
var arr []interface{}
|
|
for _, elem := range prop.ArrayValue() {
|
|
if v, use := SerializeProperty(elem, reftag); use {
|
|
arr = append(arr, v)
|
|
}
|
|
}
|
|
if len(arr) > 0 {
|
|
return arr, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// Also for objects, recurse and use naked properties.
|
|
if prop.IsObject() {
|
|
src := prop.ObjectValue()
|
|
dst := make(map[PropertyKey]interface{})
|
|
for _, k := range StablePropertyKeys(src) {
|
|
if v, use := SerializeProperty(src[k], reftag); use {
|
|
dst[k] = v
|
|
}
|
|
}
|
|
if len(dst) > 0 {
|
|
return dst, true
|
|
}
|
|
return nil, false
|
|
}
|
|
|
|
// Morph resources into their equivalent `{ "#ref": "<moniker>" }` form.
|
|
if prop.IsResource() {
|
|
return map[string]string{
|
|
reftag: string(prop.ResourceValue()),
|
|
}, true
|
|
}
|
|
|
|
// All others are returned as-is.
|
|
return prop.V, true
|
|
}
|
|
|
|
// DeserializeSnapshot takes a serialized MuGL snapshot data structure and returns its associated snapshot.
|
|
func DeserializeSnapshot(mugl *MuglSnapshot) Snapshot {
|
|
contract.Failf("MuGL deserialization not yet implemented")
|
|
return nil
|
|
}
|