pulumi/cmd/pack_eval.go

130 lines
4.3 KiB
Go
Raw Normal View History

// Copyright 2017 Pulumi, Inc. All rights reserved.
package cmd
import (
"fmt"
"os"
"strings"
"github.com/spf13/cobra"
2017-02-25 16:25:33 +01:00
"github.com/pulumi/coconut/pkg/compiler/core"
"github.com/pulumi/coconut/pkg/eval/heapstate"
"github.com/pulumi/coconut/pkg/graph"
"github.com/pulumi/coconut/pkg/graph/dotconv"
"github.com/pulumi/coconut/pkg/resource"
2017-02-25 16:25:33 +01:00
"github.com/pulumi/coconut/pkg/tokens"
)
func newPackEvalCmd() *cobra.Command {
var configEnv string
var dotOutput bool
var cmd = &cobra.Command{
Use: "eval [package] [-- [args]]",
Short: "Evaluate a package and print the resulting objects",
Long: "Evaluate a package and print the resulting objects\n" +
"\n" +
"A graph is a topologically sorted directed-acyclic-graph (DAG), representing a\n" +
"collection of resources that may be used in a deployment operation like plan or apply.\n" +
"This graph is produced by evaluating the contents of a blueprint package, and does not\n" +
2017-02-25 16:25:33 +01:00
"actually perform any updates to the target environment.\n" +
"\n" +
"By default, a blueprint package is loaded from the current directory. Optionally,\n" +
"a path to a package elsewhere can be provided as the [package] argument.",
Run: runFunc(func(cmd *cobra.Command, args []string) error {
// If a configuration environment was requested, load it.
var config resource.ConfigMap
if configEnv != "" {
envInfo, err := initEnvCmdName(tokens.QName(configEnv), args)
if err != nil {
return err
}
config = envInfo.Env.Config
}
// Perform the compilation and, if non-nil is returned, output the graph.
if result := compile(cmd, args, config); result != nil && result.Heap != nil && result.Heap.G != nil {
// Serialize that evaluation graph so that it's suitable for printing/serializing.
if dotOutput {
// Convert the output to a DOT file.
if err := dotconv.Print(result.Heap.G, os.Stdout); err != nil {
return fmt.Errorf("failed to write DOT file to output: %v", err)
}
} 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() {
Implement updates This change is a first whack at implementing updates. Creation and deletion plans are pretty straightforward; we just take a single graph, topologically sort it, and perform the operations in the right order. For creation, this is in dependency order (things that are depended upon must be created before dependents); for deletion, this is in reverse-dependency order (things that depend on others must be deleted before dependencies). These are just special cases of the more general idea of performing DAG operations in dependency order. Updates must work in terms of this more general notion. For example: * It is an error to delete a resource while another refers to it; thus, resources are deleted after deleting dependents, or after updating dependent properties that reference the resource to new values. * It is an error to depend on a create a resource before it is created; thus, resources must be created before dependents are created, and/or before updates to existing resource properties that would cause them to refer to the new resource. Of course, all of this is tangled up in a graph of dependencies. As a result, we must create a DAG of the dependencies between creates, updates, and deletes, and then topologically sort this DAG, in order to determine the proper order of update operations. To do this, we slightly generalize the existing graph infrastructure, while also specializing two kinds of graphs; the existing one becomes a heapstate.ObjectGraph, while this new one is resource.planGraph (internal).
2017-02-23 23:56:23 +01:00
printVertex(root.ToObj(), shown, "")
}
}
}
return nil
}),
}
cmd.PersistentFlags().StringVar(
&configEnv, "config-env", "",
"Apply configuration from the specified environment before evaluating the package")
cmd.PersistentFlags().BoolVar(
&dotOutput, "dot", false,
"Output the graph as a DOT digraph (graph description language)")
return cmd
}
// printVertex just pretty-prints a graph. The output is not serializable, it's just for display purposes.
// TODO: option to print properties.
// TODO: full serializability, including a DOT file option.
Implement updates This change is a first whack at implementing updates. Creation and deletion plans are pretty straightforward; we just take a single graph, topologically sort it, and perform the operations in the right order. For creation, this is in dependency order (things that are depended upon must be created before dependents); for deletion, this is in reverse-dependency order (things that depend on others must be deleted before dependencies). These are just special cases of the more general idea of performing DAG operations in dependency order. Updates must work in terms of this more general notion. For example: * It is an error to delete a resource while another refers to it; thus, resources are deleted after deleting dependents, or after updating dependent properties that reference the resource to new values. * It is an error to depend on a create a resource before it is created; thus, resources must be created before dependents are created, and/or before updates to existing resource properties that would cause them to refer to the new resource. Of course, all of this is tangled up in a graph of dependencies. As a result, we must create a DAG of the dependencies between creates, updates, and deletes, and then topologically sort this DAG, in order to determine the proper order of update operations. To do this, we slightly generalize the existing graph infrastructure, while also specializing two kinds of graphs; the existing one becomes a heapstate.ObjectGraph, while this new one is resource.planGraph (internal).
2017-02-23 23:56:23 +01:00
func printVertex(v *heapstate.ObjectVertex, shown map[graph.Vertex]bool, indent string) {
s := v.Obj().Type()
if shown[v] {
fmt.Printf("%v%v: <cycle...>\n", indent, s)
} else {
shown[v] = true // prevent cycles.
fmt.Printf("%v%v:\n", indent, s)
Implement updates This change is a first whack at implementing updates. Creation and deletion plans are pretty straightforward; we just take a single graph, topologically sort it, and perform the operations in the right order. For creation, this is in dependency order (things that are depended upon must be created before dependents); for deletion, this is in reverse-dependency order (things that depend on others must be deleted before dependencies). These are just special cases of the more general idea of performing DAG operations in dependency order. Updates must work in terms of this more general notion. For example: * It is an error to delete a resource while another refers to it; thus, resources are deleted after deleting dependents, or after updating dependent properties that reference the resource to new values. * It is an error to depend on a create a resource before it is created; thus, resources must be created before dependents are created, and/or before updates to existing resource properties that would cause them to refer to the new resource. Of course, all of this is tangled up in a graph of dependencies. As a result, we must create a DAG of the dependencies between creates, updates, and deletes, and then topologically sort this DAG, in order to determine the proper order of update operations. To do this, we slightly generalize the existing graph infrastructure, while also specializing two kinds of graphs; the existing one becomes a heapstate.ObjectGraph, while this new one is resource.planGraph (internal).
2017-02-23 23:56:23 +01:00
for _, out := range v.OutObjs() {
printVertex(out.ToObj(), shown, indent+" -> ")
}
}
}
// dashdashArgsToMap is a simple args parser that places incoming key/value pairs into a map. These are then used
// during package compilation as inputs to the main entrypoint function.
// TODO: this is fairly rudimentary; we eventually want to support arrays, maps, and complex types.
func dashdashArgsToMap(args []string) core.Args {
mapped := make(core.Args)
for i := 0; i < len(args); i++ {
arg := args[i]
// Eat - or -- at the start.
if arg[0] == '-' {
arg = arg[1:]
if arg[0] == '-' {
arg = arg[1:]
}
}
// Now find a k=v, and split the k/v part.
if eq := strings.IndexByte(arg, '='); eq != -1 {
// For --k=v, simply store v underneath k's entry.
mapped[tokens.Name(arg[:eq])] = arg[eq+1:]
} else {
if i+1 < len(args) && args[i+1][0] != '-' {
// If the next arg doesn't start with '-' (i.e., another flag) use its value.
mapped[tokens.Name(arg)] = args[i+1]
i++
} else if arg[0:3] == "no-" {
// For --no-k style args, strip off the no- prefix and store false underneath k.
mapped[tokens.Name(arg[3:])] = false
} else {
// For all other --k args, assume this is a boolean flag, and set the value of k to true.
mapped[tokens.Name(arg)] = true
}
}
}
return mapped
}