diff --git a/apply/apply.go b/apply/apply.go index a451b8510..424f8c90a 100644 --- a/apply/apply.go +++ b/apply/apply.go @@ -20,6 +20,7 @@ import ( "github.com/asteris-llc/converge/executor" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/plan" "github.com/asteris-llc/converge/render" "github.com/pkg/errors" @@ -65,10 +66,12 @@ func execPipeline(ctx context.Context, in *graph.Graph, pipelineF MkPipelineF, r var hasErrors error out, err := in.Transform(ctx, - notify.Transform(func(id string, out *graph.Graph) error { + notify.Transform(func(meta *node.Node, out *graph.Graph) error { renderingPlant.Graph = out - pipeline := pipelineF(out, id) - val, pipelineError := pipeline.Exec(out.Get(id)) + pipeline := pipelineF(out, meta.ID) + + val, pipelineError := pipeline.Exec(meta.Value()) + if pipelineError != nil { hasErrors = ErrTreeContainsErrors return pipelineError @@ -82,7 +85,7 @@ func execPipeline(ctx context.Context, in *graph.Graph, pipelineF MkPipelineF, r hasErrors = ErrTreeContainsErrors } - out.Add(id, asResult) + out.Add(meta.WithValue(asResult)) return nil }), ) diff --git a/apply/apply_test.go b/apply/apply_test.go index 39166d6ca..129dec707 100644 --- a/apply/apply_test.go +++ b/apply/apply_test.go @@ -20,6 +20,7 @@ import ( "github.com/asteris-llc/converge/apply" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/faketask" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/plan" @@ -33,7 +34,7 @@ func TestApplyNoOp(t *testing.T) { g := graph.New() task := faketask.Swapper() - g.Add("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: task}) + g.Add(node.New("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: task})) require.NoError(t, g.Validate()) @@ -51,7 +52,7 @@ func TestApplyNoRun(t *testing.T) { g := graph.New() task := faketask.NoOp() - g.Add("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWontChange}, Task: task}) + g.Add(node.New("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWontChange}, Task: task})) require.NoError(t, g.Validate()) @@ -67,8 +68,8 @@ func TestApplyErrorsBelow(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.NoOp()}) - g.Add("root/err", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.Error()}) + g.Add(node.New("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.NoOp()})) + g.Add(node.New("root/err", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.Error()})) g.ConnectParent("root", "root/err") @@ -80,11 +81,15 @@ func TestApplyErrorsBelow(t *testing.T) { out, err := apply.Apply(context.Background(), g) assert.Equal(t, apply.ErrTreeContainsErrors, err) - errNode, ok := out.Get("root/err").(*apply.Result) + errMeta, ok := out.Get("root/err") + require.True(t, ok, `"root/err" was not present in the graph`) + errNode, ok := errMeta.Value().(*apply.Result) require.True(t, ok) assert.EqualError(t, errNode.Error(), "error") - rootNode, ok := out.Get("root").(*apply.Result) + rootMeta, ok := out.Get("root") + require.True(t, ok, `"root" was not present in the graph`) + rootNode, ok := rootMeta.Value().(*apply.Result) require.True(t, ok) assert.EqualError(t, rootNode.Error(), `error in dependency "root/err"`) } @@ -93,7 +98,7 @@ func TestApplyStillChange(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.WillChange()}) + g.Add(node.New("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.WillChange()})) require.NoError(t, g.Validate()) @@ -110,7 +115,7 @@ func TestApplyNilError(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.NilAndError()}) + g.Add(node.New("root", &plan.Result{Status: &resource.Status{Level: resource.StatusWillChange}, Task: faketask.NilAndError()})) require.NoError(t, g.Validate()) @@ -121,12 +126,11 @@ func TestApplyNilError(t *testing.T) { } func getResult(t *testing.T, src *graph.Graph, key string) *apply.Result { - val := src.Get(key) - result, ok := val.(*apply.Result) - if !ok { - t.Logf("needed a %T for %q, got a %T\n", result, key, val) - t.FailNow() - } + meta, ok := src.Get(key) + require.True(t, ok, "%q was not present in the graph", key) + + result, ok := meta.Value().(*apply.Result) + require.True(t, ok, "needed a %T for %q, got a %T", result, key, meta.Value()) return result } diff --git a/apply/pipeline.go b/apply/pipeline.go index 09823c3d8..dbe3c8b0e 100644 --- a/apply/pipeline.go +++ b/apply/pipeline.go @@ -66,7 +66,12 @@ func (g *pipelineGen) DependencyCheck(taskI interface{}) (interface{}, error) { return nil, errors.New("input node is not a task wrapper") } for _, depID := range graph.Targets(g.Graph.DownEdges(g.ID)) { - elem := g.Graph.Get(depID) + meta, ok := g.Graph.Get(depID) + if !ok { + return nil, nil + } + + elem := meta.Value() dep, ok := elem.(executor.Status) if !ok { return nil, fmt.Errorf("apply.DependencyCheck: expected %s to have type executor.Status but got type %T", depID, elem) diff --git a/cmd/apply.go b/cmd/apply.go index 1077bd3d7..dfc677a08 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -21,6 +21,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/rpc" "github.com/asteris-llc/converge/rpc/pb" @@ -115,7 +116,7 @@ real happens.`, slog := flog.WithFields(log.Fields{ "stage": resp.Stage, "run": resp.Run, - "id": resp.Id, + "id": resp.Meta.Id, }) if resp.Run == pb.StatusResponse_STARTED { slog.Info("got status") @@ -126,7 +127,7 @@ real happens.`, if resp.Stage == pb.StatusResponse_APPLY && resp.Run == pb.StatusResponse_FINISHED { details := resp.GetDetails() if details != nil { - g.Add(resp.Id, details.ToPrintable()) + g.Add(node.New(resp.Id, details.ToPrintable())) } } }, diff --git a/cmd/healthcheck.go b/cmd/healthcheck.go index a5966085a..95de842b0 100644 --- a/cmd/healthcheck.go +++ b/cmd/healthcheck.go @@ -21,6 +21,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/rpc" "github.com/asteris-llc/converge/rpc/pb" @@ -116,7 +117,7 @@ not display healthy checks.`, slog := flog.WithFields(log.Fields{ "stage": resp.Stage, "run": resp.Run, - "id": resp.Id, + "id": resp.Meta.Id, }) if resp.Run == pb.StatusResponse_STARTED { slog.Info("got status") @@ -127,7 +128,7 @@ not display healthy checks.`, if resp.Run == pb.StatusResponse_FINISHED { details := resp.GetDetails() if details != nil { - g.Add(resp.Id, details.ToPrintable()) + g.Add(node.New(resp.Id, details.ToPrintable())) } } }, diff --git a/cmd/plan.go b/cmd/plan.go index 7f6140869..9820793e2 100644 --- a/cmd/plan.go +++ b/cmd/plan.go @@ -21,6 +21,7 @@ import ( log "github.com/Sirupsen/logrus" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/rpc" "github.com/asteris-llc/converge/rpc/pb" @@ -115,7 +116,7 @@ can be done separately to see what needs to be changed before execution.`, slog := flog.WithFields(log.Fields{ "stage": resp.Stage, "run": resp.Run, - "id": resp.Id, + "id": resp.Meta.Id, }) if resp.Run == pb.StatusResponse_STARTED { slog.Info("got status") @@ -126,7 +127,7 @@ can be done separately to see what needs to be changed before execution.`, if resp.Run == pb.StatusResponse_FINISHED { details := resp.GetDetails() if details != nil { - g.Add(resp.Id, details.ToPrintable()) + g.Add(node.New(resp.Id, details.ToPrintable())) } } }, diff --git a/graph/graph.go b/graph/graph.go index 56eb0f55f..056a8097f 100644 --- a/graph/graph.go +++ b/graph/graph.go @@ -20,6 +20,7 @@ import ( "strings" "sync" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/hashicorp/go-multierror" "github.com/hashicorp/terraform/dag" @@ -28,10 +29,10 @@ import ( ) // WalkFunc is taken by the walking functions -type WalkFunc func(string, interface{}) error +type WalkFunc func(*node.Node) error // TransformFunc is taken by the transformation functions -type TransformFunc func(string, *Graph) error +type TransformFunc func(*node.Node, *Graph) error type walkerFunc func(context.Context, *Graph, WalkFunc) error @@ -60,12 +61,12 @@ func New() *Graph { } // Add a new value by ID -func (g *Graph) Add(id string, value interface{}) { +func (g *Graph) Add(node *node.Node) { g.innerLock.Lock() defer g.innerLock.Unlock() - g.inner.Add(id) - g.values.Set(id, value) + g.inner.Add(node.ID) + g.values.Set(node.ID, node) } // Remove an existing value by ID @@ -77,14 +78,20 @@ func (g *Graph) Remove(id string) { g.values.Remove(id) } -// Get a value by ID -func (g *Graph) Get(id string) interface{} { - val, _ := g.values.Get(id) - return val +// Get returns the value of the element and a bool indicating if it was +// found. If it was not found the value of the returned element is nil, but a +// valid node will be constructed. +func (g *Graph) Get(id string) (*node.Node, bool) { + raw, ok := g.values.Get(id) + if !ok { + return nil, ok + } + + return raw.(*node.Node), true } // GetParent returns the direct parent vertex of the current node. -func (g *Graph) GetParent(id string) interface{} { +func (g *Graph) GetParent(id string) (*node.Node, bool) { var parentID string for _, edge := range g.UpEdges(id) { switch edge.(type) { @@ -346,7 +353,8 @@ func dependencyWalk(rctx context.Context, g *Graph, cb WalkFunc) error { } logger.WithField("id", id).Debug("executing") - if err := cb(id, g.Get(id)); err != nil { + val, _ := g.Get(id) + if err := cb(val); err != nil { setErr(id, err) } } @@ -420,7 +428,8 @@ func rootFirstWalk(ctx context.Context, g *Graph, cb WalkFunc) error { logger.WithField("id", id).Debug("walking") - if err := cb(id, g.Get(id)); err != nil { + raw, _ := g.Get(id) // we want to call with every value, including nil + if err := cb(raw); err != nil { return err } @@ -449,7 +458,8 @@ func (g *Graph) Copy() *Graph { out := New() for _, v := range g.Vertices() { - out.Add(v, g.Get(v)) + val, _ := g.Get(v) // we don't care if it's nil here, we're doing a direct copy + out.Add(val) } for _, e := range g.inner.Edges() { @@ -503,15 +513,9 @@ func (g *Graph) Vertices() []string { return vertices } -// MaybeGet returns the value of the element and a bool indicating if it was -// found, if it was not found the value of the returned element is nil. -func (g *Graph) MaybeGet(id string) (interface{}, bool) { - return g.values.Get(id) -} - // Contains returns true if the id exists in the map func (g *Graph) Contains(id string) bool { - _, found := g.MaybeGet(id) + _, found := g.Get(id) return found } @@ -550,8 +554,8 @@ func (g *Graph) String() string { func transform(ctx context.Context, source *Graph, walker walkerFunc, cb TransformFunc) (*Graph, error) { dest := source.Copy() - err := walker(ctx, dest, func(id string, _ interface{}) error { - return cb(id, dest) + err := walker(ctx, dest, func(meta *node.Node) error { + return cb(meta, dest) }) if err != nil { return dest, err diff --git a/graph/graph_test.go b/graph/graph_test.go index 36e7c4afa..d7dc7166e 100644 --- a/graph/graph_test.go +++ b/graph/graph_test.go @@ -24,46 +24,35 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestGet(t *testing.T) { - // Get should return the value put into the graph t.Parallel() g := graph.New() - g.Add("one", 1) + g.Add(node.New("one", 1)) - assert.Equal(t, 1, g.Get("one").(int)) -} - -func TestGetNothing(t *testing.T) { - // Get should return nil for a nonexistent node - t.Parallel() - - g := graph.New() - - assert.Nil(t, g.Get("nothing")) -} + t.Run("found", func(t *testing.T) { + val, found := g.Get("one") + assert.Equal(t, 1, val.Value()) + assert.True(t, found) + }) -func TestMaybeGet(t *testing.T) { - t.Parallel() - g := graph.New() - g.Add("one", 1) - val, found := g.MaybeGet("one") - assert.Equal(t, 1, val) - assert.True(t, found) - val, found = g.MaybeGet("two") - assert.Nil(t, val) - assert.False(t, found) + t.Run("not found", func(t *testing.T) { + val, found := g.Get("two") + assert.False(t, found) + assert.Nil(t, val) + }) } func TestContains(t *testing.T) { t.Parallel() g := graph.New() - g.Add("one", 1) + g.Add(node.New("one", 1)) assert.True(t, g.Contains("one")) assert.False(t, g.Contains("two")) } @@ -72,9 +61,9 @@ func BenchmarkAddThenGet(b *testing.B) { g := graph.New() b.RunParallel(func(pb *testing.PB) { for pb.Next() { - id := rand.Int() - g.Add(strconv.Itoa(id), id) - g.Get(strconv.Itoa(id)) + id := strconv.Itoa(rand.Int()) + g.Add(node.New(id, id)) + g.Get(id) } }) } @@ -93,10 +82,11 @@ func TestRemove(t *testing.T) { t.Parallel() g := graph.New() - g.Add("one", 1) + g.Add(node.New("one", 1)) g.Remove("one") - assert.Nil(t, g.Get("one")) + _, ok := g.Get("one") + assert.False(t, ok) } func TestDownEdges(t *testing.T) { @@ -104,8 +94,8 @@ func TestDownEdges(t *testing.T) { t.Parallel() g := graph.New() - g.Add("one", 1) - g.Add("two", 2) + g.Add(node.New("one", 1)) + g.Add(node.New("two", 2)) g.Connect("one", "two") assert.Equal(t, []string{"two"}, graph.Targets(g.DownEdges("one"))) @@ -117,8 +107,8 @@ func TestUpEdges(t *testing.T) { t.Parallel() g := graph.New() - g.Add("one", 1) - g.Add("two", 2) + g.Add(node.New("one", 1)) + g.Add(node.New("two", 2)) g.Connect("one", "two") assert.Equal(t, []string{"one"}, graph.Sources(g.UpEdges("two"))) @@ -129,8 +119,8 @@ func TestDisconnect(t *testing.T) { t.Parallel() g := graph.New() - g.Add("one", 1) - g.Add("two", 2) + g.Add(node.New("one", 1)) + g.Add(node.New("two", 2)) g.Connect("one", "two") g.Disconnect("one", "two") @@ -141,8 +131,8 @@ func TestDescendents(t *testing.T) { t.Parallel() g := graph.New() - g.Add("one", 1) - g.Add("one/two", 2) + g.Add(node.New("one", 1)) + g.Add(node.New("one/two", 2)) g.Connect("one", "one/two") assert.Equal(t, []string{"one/two"}, g.Descendents("one")) @@ -153,9 +143,9 @@ func TestWalkOrder(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", nil) - g.Add("child1", nil) - g.Add("child2", nil) + g.Add(node.New("root", nil)) + g.Add(node.New("child1", nil)) + g.Add(node.New("child2", nil)) g.ConnectParent("root", "child1") g.ConnectParent("root", "child2") @@ -182,10 +172,10 @@ func TestWalkOrderDiamond(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("a", nil) - g.Add("b", nil) - g.Add("c", nil) - g.Add("d", nil) + g.Add(node.New("a", nil)) + g.Add(node.New("b", nil)) + g.Add(node.New("c", nil)) + g.Add(node.New("d", nil)) g.ConnectParent("a", "b") g.ConnectParent("a", "c") @@ -205,11 +195,11 @@ func TestWalkOrderParentDependency(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", 1) - g.Add("dependent", 1) - g.Add("dependency", 1) - g.Add("dependent/child", 1) - g.Add("dependency/child", 1) + g.Add(node.New("root", 1)) + g.Add(node.New("dependent", 1)) + g.Add(node.New("dependency", 1)) + g.Add(node.New("dependent/child", 1)) + g.Add(node.New("dependency/child", 1)) g.ConnectParent("root", "dependent") g.ConnectParent("root", "dependency") @@ -224,11 +214,11 @@ func TestWalkOrderParentDependency(t *testing.T) { require.NoError(t, g.Walk( context.Background(), - func(id string, _ interface{}) error { + func(meta *node.Node) error { exLock.Lock() defer exLock.Unlock() - execution = append(execution, id) + execution = append(execution, meta.ID) return nil }, @@ -251,17 +241,17 @@ func TestWalkOrderParentDependency(t *testing.T) { func TestWalkError(t *testing.T) { g := graph.New() - g.Add("a", nil) - g.Add("b", nil) - g.Add("c", nil) + g.Add(node.New("a", nil)) + g.Add(node.New("b", nil)) + g.Add(node.New("c", nil)) g.ConnectParent("a", "b") g.ConnectParent("b", "c") err := g.Walk( context.Background(), - func(id string, _ interface{}) error { - if id == "c" { + func(meta *node.Node) error { + if meta.ID == "c" { return errors.New("test") } return nil @@ -290,9 +280,9 @@ func TestValidateCycle(t *testing.T) { t.Parallel() g := graph.New() - g.Add("a", nil) - g.Add("b", nil) - g.Add("c", nil) + g.Add(node.New("a", nil)) + g.Add(node.New("b", nil)) + g.Add(node.New("c", nil)) // a is just a root g.Connect("a", "b") @@ -311,7 +301,7 @@ func TestValidateDanglingEdge(t *testing.T) { t.Parallel() g := graph.New() - g.Add("a", nil) + g.Add(node.New("a", nil)) g.Connect("a", "nope") err := g.Validate() @@ -325,19 +315,21 @@ func TestTransform(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("int", 1) + g.Add(node.New("int", 1)) transformed, err := g.Transform( context.Background(), - func(id string, dest *graph.Graph) error { - dest.Add(id, 2) + func(meta *node.Node, dest *graph.Graph) error { + dest.Add(node.New(meta.ID, 2)) return nil }, ) assert.NoError(t, err) - assert.Equal(t, 2, transformed.Get("int").(int)) + meta, ok := transformed.Get("int") + require.True(t, ok, "node was not present in graph") + assert.Equal(t, 2, meta.Value().(int)) } func TestParent(t *testing.T) { @@ -345,15 +337,17 @@ func TestParent(t *testing.T) { t.Parallel() g := graph.New() - g.Add(graph.ID("root"), 1) - g.Add(graph.ID("root", "child"), 2) + g.Add(node.New(graph.ID("root"), 1)) + g.Add(node.New(graph.ID("root", "child"), 2)) g.ConnectParent(graph.ID("root"), graph.ID("root", "child")) require.NoError(t, g.Validate()) - parent := g.GetParent(graph.ID("root", "child")) - assert.Equal(t, g.Get(graph.ID("root")), parent) + actual, ok := g.GetParent(graph.ID("root", "child")) + require.True(t, ok) + should, _ := g.Get(graph.ID("root")) + assert.Equal(t, should, actual) } func TestRootFirstWalk(t *testing.T) { @@ -361,8 +355,8 @@ func TestRootFirstWalk(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", nil) - g.Add("root/child", nil) + g.Add(node.New("root", nil)) + g.Add(node.New("root/child", nil)) g.Connect("root", "root/child") var out []string @@ -370,8 +364,8 @@ func TestRootFirstWalk(t *testing.T) { t, g.RootFirstWalk( context.Background(), - func(id string, _ interface{}) error { - out = append(out, id) + func(meta *node.Node) error { + out = append(out, meta.ID) return nil }, ), @@ -385,9 +379,9 @@ func TestRootFirstWalkSiblingDep(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", nil) - g.Add("root/child", nil) - g.Add("root/sibling", nil) + g.Add(node.New("root", nil)) + g.Add(node.New("root/child", nil)) + g.Add(node.New("root/sibling", nil)) g.Connect("root", "root/child") g.Connect("root", "root/sibling") @@ -398,8 +392,8 @@ func TestRootFirstWalkSiblingDep(t *testing.T) { t, g.RootFirstWalk( context.Background(), - func(id string, _ interface{}) error { - out = append(out, id) + func(meta *node.Node) error { + out = append(out, meta.ID) return nil }, ), @@ -417,19 +411,21 @@ func TestRootFirstTransform(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("int", 1) + g.Add(node.New("int", 1)) transformed, err := g.RootFirstTransform( context.Background(), - func(id string, dest *graph.Graph) error { - dest.Add(id, 2) + func(meta *node.Node, dest *graph.Graph) error { + dest.Add(meta.WithValue(2)) return nil }, ) assert.NoError(t, err) - assert.Equal(t, 2, transformed.Get("int").(int)) + meta, ok := transformed.Get("int") + require.True(t, ok, "\"int\" was not present in graph") + assert.Equal(t, 2, meta.Value().(int)) } func idsInOrderOfExecution(g *graph.Graph) ([]string, error) { @@ -438,11 +434,11 @@ func idsInOrderOfExecution(g *graph.Graph) ([]string, error) { err := g.Walk( context.Background(), - func(id string, _ interface{}) error { + func(meta *node.Node) error { lock.Lock() defer lock.Unlock() - out = append(out, id) + out = append(out, meta.ID) return nil }, diff --git a/graph/merge_duplicates.go b/graph/merge_duplicates.go index 2f772121b..a23d537a8 100644 --- a/graph/merge_duplicates.go +++ b/graph/merge_duplicates.go @@ -19,12 +19,13 @@ import ( "strings" "sync" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/mitchellh/hashstructure" ) // SkipMergeFunc will be used to determine whether or not to merge a node -type SkipMergeFunc func(string) bool +type SkipMergeFunc func(*node.Node) bool // MergeDuplicates removes duplicates in the graph func MergeDuplicates(ctx context.Context, g *Graph, skip SkipMergeFunc) (*Graph, error) { @@ -33,21 +34,24 @@ func MergeDuplicates(ctx context.Context, g *Graph, skip SkipMergeFunc) (*Graph, logger := logging.GetLogger(ctx).WithField("function", "MergeDuplicates") - return g.RootFirstTransform(ctx, func(id string, out *Graph) error { - if id == "root" { // root + return g.RootFirstTransform(ctx, func(meta *node.Node, out *Graph) error { + if meta.ID == "root" { return nil } - if skip(id) { - logger.WithField("id", id).Debug("skipping by request") + if skip(meta) { + logger.WithField("id", meta.ID).Debug("skipping by request") return nil } + if meta.Value() == nil { + return nil // not much use in hashing a nil value + } + lock.Lock() defer lock.Unlock() - value := out.Get(id) - hash, err := hashstructure.Hash(value, nil) + hash, err := hashstructure.Hash(meta.Value(), nil) if err != nil { return err } @@ -55,29 +59,29 @@ func MergeDuplicates(ctx context.Context, g *Graph, skip SkipMergeFunc) (*Graph, // if we haven't seen this value before, register it and return target, ok := values[hash] if !ok { - logger.WithField("id", id).Debug("registering as original") - values[hash] = id + logger.WithField("id", meta.ID).Debug("registering as original") + values[hash] = meta.ID return nil } - logger.WithField("id", target).WithField("duplicate", id).Debug("found duplicate") + logger.WithField("id", target).WithField("duplicate", meta.ID).Debug("found duplicate") // Point all inbound links to value to target instead - for _, src := range Sources(g.UpEdges(id)) { - logger.WithField("src", src).WithField("duplicate", id).WithField("target", target).Debug("re-pointing dependency") - out.Disconnect(src, id) + for _, src := range Sources(g.UpEdges(meta.ID)) { + logger.WithField("src", src).WithField("duplicate", meta.ID).WithField("target", target).Debug("re-pointing dependency") + out.Disconnect(src, meta.ID) out.Connect(src, target) } // Remove children and their edges - for _, child := range g.Descendents(id) { + for _, child := range g.Descendents(meta.ID) { logger.WithField("child", child).Debug("removing child") out.Remove(child) } // Remove value - out.Remove(id) + out.Remove(meta.ID) return nil }) @@ -87,7 +91,7 @@ func MergeDuplicates(ctx context.Context, g *Graph, skip SkipMergeFunc) (*Graph, // TODO: find a better home // SkipModuleAndParams skips trimming modules and params -func SkipModuleAndParams(id string) bool { - base := BaseID(id) +func SkipModuleAndParams(meta *node.Node) bool { + base := BaseID(meta.ID) return strings.HasPrefix(base, "module") || strings.HasPrefix(base, "param") } diff --git a/graph/merge_duplicates_test.go b/graph/merge_duplicates_test.go index f1123dfc0..4f61192f5 100644 --- a/graph/merge_duplicates_test.go +++ b/graph/merge_duplicates_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" @@ -34,10 +35,10 @@ func TestMergeDuplicatesRemovesDuplicates(t *testing.T) { require.NoError(t, err) var nodesLeft int - if transformed.Get("root/first") != nil { + if _, ok := transformed.Get("root/first"); ok { nodesLeft++ } - if transformed.Get("root/one") != nil { + if _, ok := transformed.Get("root/one"); ok { nodesLeft++ } @@ -48,7 +49,7 @@ func TestMergeDuplicatesMigratesDependencies(t *testing.T) { defer logging.HideLogs(t)() g := baseDupGraph() - g.Add("root/two", 2) + g.Add(node.New("root/two", 2)) g.Connect("root", "root/two") g.Connect("root/two", "root/first") @@ -57,7 +58,7 @@ func TestMergeDuplicatesMigratesDependencies(t *testing.T) { // we need to get a result where root/first is removed so we can test // dependency migration. So if root/first still exists, we need to skip - if transformed.Get("root/first") != nil { + if _, ok := transformed.Get("root/first"); ok { t.Logf("retrying test after failing %d times", i) continue } @@ -76,31 +77,32 @@ func TestMergeDuplicatesRemovesChildren(t *testing.T) { g := baseDupGraph() - for _, node := range []string{"root/one", "root/first"} { - g.Add(graph.ID(node, "x"), node) - g.Connect(node, graph.ID(node, "x")) + for _, id := range []string{"root/one", "root/first"} { + g.Add(node.New(graph.ID(id, "x"), id)) + g.Connect(id, graph.ID(id, "x")) } transformed, err := graph.MergeDuplicates(context.Background(), g, neverSkip) require.NoError(t, err) var removed string - if transformed.Get("root/one") == nil { + if _, ok := transformed.Get("root/one"); !ok { removed = "root/one" - } else if transformed.Get("root/first") == nil { + } else if _, ok := transformed.Get("root/first"); !ok { removed = "root/first" } else { assert.FailNow(t, `neither "root/one" nor "root/first" was removed`) } - assert.Nil(t, transformed.Get(graph.ID(removed, "x"))) + _, ok := transformed.Get(graph.ID(removed, "x")) + assert.False(t, ok, "%q was still present", graph.ID(removed, "x")) } func baseDupGraph() *graph.Graph { g := graph.New() - g.Add("root", nil) - g.Add("root/one", 1) - g.Add("root/first", 1) + g.Add(node.New("root", nil)) + g.Add(node.New("root/one", 1)) + g.Add(node.New("root/first", 1)) g.Connect("root", "root/one") g.Connect("root", "root/first") @@ -108,6 +110,6 @@ func baseDupGraph() *graph.Graph { return g } -func neverSkip(string) bool { +func neverSkip(*node.Node) bool { return false } diff --git a/graph/node/node.go b/graph/node/node.go new file mode 100644 index 000000000..e34b93449 --- /dev/null +++ b/graph/node/node.go @@ -0,0 +1,44 @@ +// Copyright © 2016 Asteris, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node + +// Node tracks the metadata associated with a node in the graph +type Node struct { + ID string `json:"id"` + + value interface{} +} + +// New creates a new node +func New(id string, value interface{}) *Node { + return &Node{ + ID: id, + value: value, + } +} + +// Value gets the inner value of this node +func (n *Node) Value() interface{} { + return n.value +} + +// WithValue returns a copy of the node with the new value set +func (n *Node) WithValue(value interface{}) *Node { + copied := new(Node) + *copied = *n + copied.value = value + + return copied +} diff --git a/graph/node/node_test.go b/graph/node/node_test.go new file mode 100644 index 000000000..4426bc334 --- /dev/null +++ b/graph/node/node_test.go @@ -0,0 +1,62 @@ +// Copyright © 2016 Asteris, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package node_test + +import ( + "testing" + + "github.com/asteris-llc/converge/graph/node" + "github.com/stretchr/testify/assert" +) + +// BenchmarkWithValue is mostly for testing the amount of allocations performed +// by node.WithValue +func BenchmarkWithValue(b *testing.B) { + source := node.New("test", 1) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + source.WithValue(i) + } +} + +// TestWithValue tests the functionality of node.WithValue +func TestWithValue(t *testing.T) { + t.Run("base", func(t *testing.T) { + fst := node.New("test", 1) + snd := fst.WithValue(2) + + assert.Equal(t, snd.Value(), 2) + }) + + t.Run("shadowing", func(t *testing.T) { + // TODO: when this grows any pointers, they should be tested here too + t.Run("no b to a", func(t *testing.T) { + fst := node.New("test", 1) + snd := fst.WithValue(2) + snd.ID = "other" + + assert.Equal(t, fst.ID, "test") + }) + + t.Run("no b to a", func(t *testing.T) { + fst := node.New("test", 1) + snd := fst.WithValue(2) + fst.ID = "other" + + assert.Equal(t, snd.ID, "test") + }) + }) +} diff --git a/graph/notifier.go b/graph/notifier.go index b9599f74b..7dbcf30e8 100644 --- a/graph/notifier.go +++ b/graph/notifier.go @@ -14,38 +14,39 @@ package graph -// PreNotifyFunc will be called before execution -type PreNotifyFunc func(string) error +import "github.com/asteris-llc/converge/graph/node" -// PostNotifyFunc will be called after planning -type PostNotifyFunc func(string, interface{}) error +// NotifyFunc will be called before execution +type NotifyFunc func(*node.Node) error // NotifyPre will call a function before walking a node -func NotifyPre(pre PreNotifyFunc, inner TransformFunc) TransformFunc { - return func(id string, g *Graph) error { - if err := pre(id); err != nil { +func NotifyPre(pre NotifyFunc, inner TransformFunc) TransformFunc { + return func(meta *node.Node, g *Graph) error { + if err := pre(meta); err != nil { return err } - return inner(id, g) + return inner(meta, g) } } // NotifyPost will call a function after walking a node -func NotifyPost(post PostNotifyFunc, inner TransformFunc) TransformFunc { - return func(id string, g *Graph) error { - if err := inner(id, g); err != nil { +func NotifyPost(post NotifyFunc, inner TransformFunc) TransformFunc { + return func(meta *node.Node, g *Graph) error { + if err := inner(meta, g); err != nil { return err } - return post(id, g.Get(id)) + meta, _ = g.Get(meta.ID) + + return post(meta) } } // Notifier can wrap a graph transform type Notifier struct { - Pre PreNotifyFunc - Post PostNotifyFunc + Pre NotifyFunc + Post NotifyFunc } // Transform wraps a TransformFunc with this notifier diff --git a/graph/notifier_test.go b/graph/notifier_test.go index b6cda0097..c47eb6d9d 100644 --- a/graph/notifier_test.go +++ b/graph/notifier_test.go @@ -20,16 +20,17 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/stretchr/testify/assert" ) func TestNotifyTransform(t *testing.T) { g := graph.New() - g.Add("root", 1) + g.Add(node.New("root", 1)) - doNothing := func(string, *graph.Graph) error { return nil } - returnError := func(string, *graph.Graph) error { return errors.New("error") } + doNothing := func(*node.Node, *graph.Graph) error { return nil } + returnError := func(*node.Node, *graph.Graph) error { return errors.New("error") } t.Run("pre", func(t *testing.T) { defer logging.HideLogs(t)() @@ -37,7 +38,7 @@ func TestNotifyTransform(t *testing.T) { var ran bool notifier := &graph.Notifier{ - Pre: func(string) error { + Pre: func(*node.Node) error { ran = true return nil }, @@ -57,7 +58,7 @@ func TestNotifyTransform(t *testing.T) { var ran bool notifier := &graph.Notifier{ - Post: func(string, interface{}) error { + Post: func(*node.Node) error { ran = true return nil }, @@ -90,4 +91,32 @@ func TestNotifyTransform(t *testing.T) { }) }) + + t.Run("post gets a fresh node", func(t *testing.T) { + defer logging.HideLogs(t)() + + var finalValue int + notifier := &graph.Notifier{ + Post: func(meta *node.Node) error { + finalValue = meta.Value().(int) + return nil + }, + } + + _, err := g.Transform( + context.Background(), + notifier.Transform(func(meta *node.Node, g *graph.Graph) error { + g.Add(meta.WithValue(2)) + return nil + }), + ) + + assert.NoError(t, err) + assert.Equal( + t, + 2, + finalValue, + "notifier probably didn't get a fresh value after the transform finished", + ) + }) } diff --git a/healthcheck/healthcheck.go b/healthcheck/healthcheck.go index 1d4965fe3..72cb4376a 100644 --- a/healthcheck/healthcheck.go +++ b/healthcheck/healthcheck.go @@ -19,6 +19,7 @@ import ( "errors" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/resource" ) @@ -36,18 +37,26 @@ func CheckGraph(ctx context.Context, in *graph.Graph) (*graph.Graph, error) { // WithNotify is CheckGraph, but with notification features func WithNotify(ctx context.Context, in *graph.Graph, notify *graph.Notifier) (*graph.Graph, error) { - return in.Transform(ctx, - notify.Transform(func(id string, out *graph.Graph) error { - task, err := unboxNode(out.Get(id)) + return in.Transform( + ctx, + notify.Transform(func(meta *node.Node, out *graph.Graph) error { + task, err := unboxNode(meta.Value()) if err != nil { return err } + asCheck, ok := task.(Check) if !ok { return nil } - for _, dep := range out.Dependencies(id) { - depStatus, ok := out.Get(dep).(resource.TaskStatus) + + for _, dep := range out.Dependencies(meta.ID) { + depmeta, ok := out.Get(dep) + if !ok { + continue + } + + depStatus, ok := depmeta.Value().(resource.TaskStatus) if !ok { continue } @@ -58,13 +67,17 @@ func WithNotify(ctx context.Context, in *graph.Graph, notify *graph.Notifier) (* asCheck.FailingDep(dep, depStatus) } } + status, err := asCheck.HealthCheck() if err != nil { return err } - out.Add(id, status) + + out.Add(meta.WithValue(status)) + return nil - })) + }), + ) } // unboxNode will remove a resource.TaskStatus from a plan.Result or apply.Result diff --git a/load/dependencyresolver.go b/load/dependencyresolver.go index c3bdfdbab..e2c47dce9 100644 --- a/load/dependencyresolver.go +++ b/load/dependencyresolver.go @@ -23,6 +23,7 @@ import ( "text/template" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/parse" "github.com/asteris-llc/converge/render/extensions" @@ -37,21 +38,21 @@ func ResolveDependencies(ctx context.Context, g *graph.Graph) (*graph.Graph, err logger := logging.GetLogger(ctx).WithField("function", "ResolveDependencies") logger.Debug("resolving dependencies") - return g.Transform(ctx, func(id string, out *graph.Graph) error { - if id == "root" { // skip root + return g.Transform(ctx, func(meta *node.Node, out *graph.Graph) error { + if meta.ID == "root" { // skip root return nil } - node, ok := out.Get(id).(*parse.Node) + node, ok := meta.Value().(*parse.Node) if !ok { - return fmt.Errorf("ResolveDependencies can only be used on Graphs of *parse.Node. I got %T", out.Get(id)) + return fmt.Errorf("ResolveDependencies can only be used on Graphs of *parse.Node. I got %T", meta.Value()) } depGenerators := []dependencyGenerator{ getDepends, getParams, func(node *parse.Node) (out []string, err error) { - return getXrefs(g, id, node) + return getXrefs(g, meta.ID, node) }, } @@ -64,8 +65,7 @@ func ResolveDependencies(ctx context.Context, g *graph.Graph) (*graph.Graph, err } for _, dep := range deps { - - out.Connect(id, graph.SiblingID(id, dep)) + out.Connect(meta.ID, graph.SiblingID(meta.ID, dep)) } } return nil diff --git a/load/nodes.go b/load/nodes.go index be287e583..109f27037 100644 --- a/load/nodes.go +++ b/load/nodes.go @@ -21,6 +21,7 @@ import ( "github.com/asteris-llc/converge/fetch" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/keystore" "github.com/asteris-llc/converge/parse" @@ -44,7 +45,7 @@ func Nodes(ctx context.Context, root string, verify bool) (*graph.Graph, error) toLoad := []*source{{"root", root, root}} out := graph.New() - out.Add("root", nil) + out.Add(node.New("root", nil)) for len(toLoad) > 0 { select { @@ -89,7 +90,7 @@ func Nodes(ctx context.Context, root string, verify bool) (*graph.Graph, error) for _, resource := range resources { newID := graph.ID(current.Parent, resource.String()) - out.Add(newID, resource) + out.Add(node.New(newID, resource)) out.ConnectParent(current.Parent, newID) if resource.IsModule() { diff --git a/load/nodes_test.go b/load/nodes_test.go index 3ae8e1b0a..4770e9d03 100644 --- a/load/nodes_test.go +++ b/load/nodes_test.go @@ -39,11 +39,16 @@ func TestNodesSourceFile(t *testing.T) { g, err := load.Nodes(context.Background(), "../samples/sourceFile.hcl", false) require.NoError(t, err) - assert.NotNil(t, g.Get("root/param.message")) - assert.NotNil(t, g.Get("root/module.basic")) - assert.NotNil(t, g.Get("root/module.basic/param.message")) - assert.NotNil(t, g.Get("root/module.basic/param.filename")) - assert.NotNil(t, g.Get("root/module.basic/task.render")) + assertPresent := func(id string) { + _, ok := g.Get(id) + assert.True(t, ok, "%q was missing from the graph", id) + } + + assertPresent("root/param.message") + assertPresent("root/module.basic") + assertPresent("root/module.basic/param.message") + assertPresent("root/module.basic/param.filename") + assertPresent("root/module.basic/task.render") basicDeps := graph.Targets(g.DownEdges("root/module.basic")) sort.Strings(basicDeps) diff --git a/load/resource.go b/load/resource.go index 7a53e1c8c..9bce499fc 100644 --- a/load/resource.go +++ b/load/resource.go @@ -19,6 +19,7 @@ import ( "fmt" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/load/registry" "github.com/asteris-llc/converge/parse" @@ -46,34 +47,34 @@ func SetResources(ctx context.Context, g *graph.Graph) (*graph.Graph, error) { logger := logging.GetLogger(ctx).WithField("function", "SetResources") logger.Debug("loading resources") - return g.Transform(ctx, func(id string, out *graph.Graph) error { - if id == "root" { // root + return g.Transform(ctx, func(meta *node.Node, out *graph.Graph) error { + if meta.ID == "root" { return nil } - node, ok := out.Get(id).(*parse.Node) + raw, ok := meta.Value().(*parse.Node) if !ok { - return fmt.Errorf("SetResources can only be used on Graphs of *parse.Node. I got %T", out.Get(id)) + return fmt.Errorf("SetResources can only be used on Graphs of *parse.Node. I got %T", meta.Value()) } - dest, ok := registry.NewByName(node.Kind()) + dest, ok := registry.NewByName(raw.Kind()) if !ok { - return fmt.Errorf("%q is not a valid resource type in %q", node.Kind(), node) + return fmt.Errorf("%q is not a valid resource type in %q", raw.Kind(), raw) } res, ok := dest.(resource.Resource) if !ok { - return fmt.Errorf("%q is not a valid resource, got %T", node.Kind(), dest) + return fmt.Errorf("%q is not a valid resource, got %T", raw.Kind(), dest) } preparer := resource.NewPreparer(res) - err := hcl.DecodeObject(&preparer.Source, node.ObjectItem.Val) + err := hcl.DecodeObject(&preparer.Source, raw.ObjectItem.Val) if err != nil { return err } - out.Add(id, preparer) + out.Add(meta.WithValue(preparer)) return nil }) diff --git a/load/resource_test.go b/load/resource_test.go index d8c658237..95a74477f 100644 --- a/load/resource_test.go +++ b/load/resource_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/load" "github.com/asteris-llc/converge/parse" @@ -41,10 +42,12 @@ task x { ) assert.NoError(t, err) - item := resourced.Get("root/task.x") - preparer, ok := item.(*resource.Preparer) + meta, ok := resourced.Get("root/task.x") + require.True(t, ok, `"root/task.x" was not present in the graph`) + + preparer, ok := meta.Value().(*resource.Preparer) + require.True(t, ok, fmt.Sprintf("preparer was %T, not %T", meta.Value(), preparer)) - require.True(t, ok, fmt.Sprintf("preparer was %T, not %T", item, preparer)) assert.Equal(t, "check", preparer.Source["check"]) assert.Equal(t, "apply", preparer.Source["apply"]) } @@ -63,10 +66,10 @@ func getResourcesGraph(t *testing.T, content []byte) (*graph.Graph, error) { require.NoError(t, err) g := graph.New() - g.Add("root", nil) + g.Add(node.New("root", nil)) for _, resource := range resources { id := graph.ID("root", resource.String()) - g.Add(id, resource) + g.Add(node.New(id, resource)) g.Connect("root", id) } require.NoError(t, g.Validate()) diff --git a/plan/pipeline.go b/plan/pipeline.go index 0ed217fd0..a8ba64a61 100644 --- a/plan/pipeline.go +++ b/plan/pipeline.go @@ -71,10 +71,14 @@ func (g *pipelineGen) DependencyCheck(taskI interface{}) (interface{}, error) { return nil, errors.New("input node is not a task wrapper") } for _, depID := range graph.Targets(g.Graph.DownEdges(g.ID)) { - elem := g.Graph.Get(depID) - dep, ok := elem.(executor.Status) + meta, ok := g.Graph.Get(depID) if !ok { - return nil, fmt.Errorf("expected executor.Status but got %T", elem) + return nil, nil + } + + dep, ok := meta.Value().(executor.Status) + if !ok { + return nil, fmt.Errorf("expected executor.Status but got %T", meta.Value()) } if err := dep.Error(); err != nil { errResult := &Result{ diff --git a/plan/plan.go b/plan/plan.go index 36a05a4d3..c3cd0e74d 100644 --- a/plan/plan.go +++ b/plan/plan.go @@ -20,6 +20,7 @@ import ( "fmt" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/render" ) @@ -41,10 +42,12 @@ func WithNotify(ctx context.Context, in *graph.Graph, notify *graph.Notifier) (* } out, err := in.Transform(ctx, - notify.Transform(func(id string, out *graph.Graph) error { + notify.Transform(func(meta *node.Node, out *graph.Graph) error { renderingPlant.Graph = out - pipeline := Pipeline(out, id, renderingPlant) - val, pipelineErr := pipeline.Exec(out.Get(id)) + + pipeline := Pipeline(out, meta.ID, renderingPlant) + + val, pipelineErr := pipeline.Exec(meta.Value()) if pipelineErr != nil { fmt.Printf("pipeline returned Right %v\n", val) return pipelineErr @@ -60,7 +63,8 @@ func WithNotify(ctx context.Context, in *graph.Graph, notify *graph.Notifier) (* hasErrors = ErrTreeContainsErrors } - out.Add(id, asResult) + out.Add(meta.WithValue(asResult)) + return nil }), ) diff --git a/plan/plan_test.go b/plan/plan_test.go index 9e81ecbd9..482f42471 100644 --- a/plan/plan_test.go +++ b/plan/plan_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/faketask" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/plan" @@ -31,7 +32,7 @@ func TestPlanNoOp(t *testing.T) { g := graph.New() task := faketask.NoOp() - g.Add("root", task) + g.Add(node.New("root", task)) require.NoError(t, g.Validate()) @@ -49,7 +50,7 @@ func TestPlanNoOp(t *testing.T) { func TestPlanNilAndError(t *testing.T) { g := graph.New() task := faketask.NilAndError() - g.Add("root", task) + g.Add(node.New("root", task)) // test that running this results in an appropriate result planned, err := plan.Plan(context.Background(), g) @@ -65,8 +66,8 @@ func TestPlanErrorsBelow(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", faketask.NoOp()) - g.Add("root/err", faketask.Error()) + g.Add(node.New("root", faketask.NoOp())) + g.Add(node.New("root/err", faketask.Error())) g.Connect("root", "root/err") @@ -78,22 +79,25 @@ func TestPlanErrorsBelow(t *testing.T) { out, err := plan.Plan(context.Background(), g) assert.Equal(t, plan.ErrTreeContainsErrors, err) - errNode, ok := out.Get("root/err").(*plan.Result) + errMeta, ok := out.Get("root/err") + require.True(t, ok) + errNode, ok := errMeta.Value().(*plan.Result) require.True(t, ok) assert.Error(t, errNode.Error()) - rootNode, ok := out.Get("root").(*plan.Result) + rootMeta, ok := out.Get("root") + require.True(t, ok) + rootNode, ok := rootMeta.Value().(*plan.Result) require.True(t, ok) assert.EqualError(t, rootNode.Error(), `error in dependency "root/err"`) } func getResult(t *testing.T, src *graph.Graph, key string) *plan.Result { - val := src.Get(key) - result, ok := val.(*plan.Result) - if !ok { - t.Logf("needed a %T for %q, got a %T\n", result, key, val) - t.FailNow() - } + meta, ok := src.Get(key) + require.True(t, ok, "%q was not present in the graph", key) + + result, ok := meta.Value().(*plan.Result) + require.True(t, ok, "needed a %T for %q, got a %T", result, key, meta.Value()) return result } diff --git a/prettyprinters/graphviz/graphviz.go b/prettyprinters/graphviz/graphviz.go index 4f51d177e..974ab5737 100644 --- a/prettyprinters/graphviz/graphviz.go +++ b/prettyprinters/graphviz/graphviz.go @@ -130,7 +130,13 @@ func New(opts Options, provider PrintProvider) *Printer { // MarkNode will call SubgraphMarker() on the print provider to determine // whether the current node is the beginning of a subgraph. func (p *Printer) MarkNode(g *graph.Graph, id string) *pp.SubgraphMarker { - entity := GraphEntity{Name: id, Value: g.Get(id)} + var val interface{} + if meta, ok := g.Get(id); ok { + val = meta.Value() + } + + entity := GraphEntity{Name: id, Value: val} + sgState := p.printProvider.SubgraphMarker(entity) subgraphID := p.clusterIndex switch sgState { @@ -155,8 +161,12 @@ func (p *Printer) MarkNode(g *graph.Graph, id string) *pp.SubgraphMarker { // VertexGetProperties() on the associated print provider. It will return a // visible Renderable IFF the underlying VertexID is renderable. func (p *Printer) DrawNode(g *graph.Graph, id string) (pp.Renderable, error) { - graphValue := g.Get(id) - graphEntity := GraphEntity{id, graphValue} + var val interface{} + if meta, ok := g.Get(id); ok { + val = meta.Value() + } + + graphEntity := GraphEntity{id, val} vertexID, err := p.printProvider.VertexGetID(graphEntity) if err != nil || !vertexID.Visible() { return pp.HiddenString(), err @@ -183,8 +193,19 @@ func (p *Printer) DrawNode(g *graph.Graph, id string) (pp.Renderable, error) { // DrawEdge prints edge data in a fashion similar to DrawNode. It will return a // visible Renderable IFF the source and destination vertices are visible. func (p *Printer) DrawEdge(g *graph.Graph, id1, id2 string) (pp.Renderable, error) { - sourceEntity := GraphEntity{id1, g.Get(id1)} - destEntity := GraphEntity{id2, g.Get(id2)} + var srcVal, destVal interface{} + + if src, ok := g.Get(id1); ok { + srcVal = src.Value() + } + + if dest, ok := g.Get(id2); ok { + destVal = dest.Value() + } + + sourceEntity := GraphEntity{id1, srcVal} + destEntity := GraphEntity{id2, destVal} + sourceVertex, err := p.printProvider.VertexGetID(sourceEntity) if err != nil { return pp.HiddenString(), err diff --git a/prettyprinters/graphviz/graphviz_test.go b/prettyprinters/graphviz/graphviz_test.go index fe45b85cc..d00b95d91 100644 --- a/prettyprinters/graphviz/graphviz_test.go +++ b/prettyprinters/graphviz/graphviz_test.go @@ -21,6 +21,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" pp "github.com/asteris-llc/converge/prettyprinters" "github.com/asteris-llc/converge/prettyprinters/graphviz" @@ -57,7 +58,7 @@ func Test_DrawNode_SetsNodeNameToVertexID(t *testing.T) { provider.On("SubgraphMarker", mock.Anything).Return(graphviz.SubgraphMarkerNOP) provider.On("VertexGetProperties", mock.Anything).Return(make(graphviz.PropertySet)) g := graph.New() - g.Add("test", nil) + g.Add(node.New("test", nil)) printer := graphviz.New(graphviz.DefaultOptions(), provider) dotCode, _ := printer.DrawNode(g, "test") actual := getDotNodeID(dotCode) @@ -72,7 +73,7 @@ func Test_DrawNode_WhenVertexIDReturnsError_ReturnsError(t *testing.T) { provider.On("SubgraphMarker", mock.Anything).Return(graphviz.SubgraphMarkerNOP) provider.On("VertexGetProperties", mock.Anything).Return(make(graphviz.PropertySet)) g := graph.New() - g.Add("test", nil) + g.Add(node.New("test", nil)) printer := graphviz.New(graphviz.DefaultOptions(), provider) _, actualErr := printer.DrawNode(g, "test") assert.Equal(t, err, actualErr) @@ -86,7 +87,7 @@ func Test_DrawNode_SetsLabelToVertexLabel(t *testing.T) { provider.On("SubgraphMarker", mock.Anything).Return(graphviz.SubgraphMarkerNOP) provider.On("VertexGetProperties", mock.Anything).Return(make(graphviz.PropertySet)) g := graph.New() - g.Add("test", nil) + g.Add(node.New("test", nil)) printer := graphviz.New(graphviz.DefaultOptions(), provider) dotCode, _ := printer.DrawNode(g, "test") actual := getDotNodeLabel(dotCode) @@ -101,7 +102,7 @@ func Test_DrawNode_WhenVertexLabelReturnsError_ReturnsError(t *testing.T) { provider.On("SubgraphMarker", mock.Anything).Return(graphviz.SubgraphMarkerNOP) provider.On("VertexGetProperties", mock.Anything).Return(make(graphviz.PropertySet)) g := graph.New() - g.Add("test", nil) + g.Add(node.New("test", nil)) printer := graphviz.New(graphviz.DefaultOptions(), provider) _, actualErr := printer.DrawNode(g, "test") assert.Equal(t, err, actualErr) @@ -118,7 +119,7 @@ func Test_DrawNode_WhenAdditionalAttributes_AddsAttributesTo(t *testing.T) { provider.On("SubgraphMarker", mock.Anything).Return(graphviz.SubgraphMarkerNOP) provider.On("VertexGetProperties", mock.Anything).Return(expectedAttrs) g := graph.New() - g.Add("test", nil) + g.Add(node.New("test", nil)) printer := graphviz.New(graphviz.DefaultOptions(), provider) dotCode, _ := printer.DrawNode(g, "test") actualAttrs := getDotAttributes(dotCode) @@ -421,9 +422,9 @@ func compareAttrMap(a, b map[string]string) bool { func testGraph() *graph.Graph { g := graph.New() - g.Add("root", nil) - g.Add("child1", nil) - g.Add("child2", nil) + g.Add(node.New("root", nil)) + g.Add(node.New("child1", nil)) + g.Add(node.New("child2", nil)) g.Connect("root", "child1") g.Connect("root", "child2") return g @@ -431,9 +432,9 @@ func testGraph() *graph.Graph { func edgeTestGraph() *graph.Graph { g := graph.New() - g.Add("A", "A") - g.Add("B", "B") - g.Add("C", "C") + g.Add(node.New("A", "A")) + g.Add(node.New("B", "B")) + g.Add(node.New("C", "C")) g.Connect("A", "B") g.Connect("A", "C") return g diff --git a/prettyprinters/health/health.go b/prettyprinters/health/health.go index 8a93c8723..bef4513c6 100644 --- a/prettyprinters/health/health.go +++ b/prettyprinters/health/health.go @@ -67,7 +67,13 @@ func (p *Printer) FinishPP(g *graph.Graph) (pp.Renderable, error) { if vertex == root { continue } - status, ok := g.Get(vertex).(*resource.HealthStatus) + + meta, ok := g.Get(vertex) + if !ok { + continue + } + + status, ok := meta.Value().(*resource.HealthStatus) if !ok { continue } @@ -138,17 +144,21 @@ func (p *Printer) DrawNode(g *graph.Graph, id string) (pp.Renderable, error) { return pp.HiddenString(), err } - node := g.Get(id) - healthStatus, ok := node.(*resource.HealthStatus) + meta, ok := g.Get(id) + if !ok { + // not a node, deferring to the human printer + return p.Printer.DrawNode(g, id) + } + + healthStatus, ok := meta.Value().(*resource.HealthStatus) if !ok { - fmt.Printf("%s is not a health node, deferring to the human printer\n", id) + // not a health node, deferring to the human printer return p.Printer.DrawNode(g, id) } tmpl, err := p.getDrawTemplate(healthStatus) if err != nil { - fmt.Println("template generation error") return pp.HiddenString(), err } diff --git a/prettyprinters/human/human.go b/prettyprinters/human/human.go index 57af45008..6b9092a82 100644 --- a/prettyprinters/human/human.go +++ b/prettyprinters/human/human.go @@ -81,7 +81,12 @@ func (p *Printer) FinishPP(g *graph.Graph) (pp.Renderable, error) { }{} for _, id := range g.Vertices() { - printable, ok := g.Get(id).(Printable) + meta, ok := g.Get(id) + if !ok { + continue + } + + printable, ok := meta.Value().(Printable) if !ok { continue } @@ -106,7 +111,12 @@ func (p *Printer) FinishPP(g *graph.Graph) (pp.Renderable, error) { // DrawNode containing a result func (p *Printer) DrawNode(g *graph.Graph, id string) (pp.Renderable, error) { - printable, ok := g.Get(id).(Printable) + meta, ok := g.Get(id) + if !ok { + return pp.HiddenString(), nil + } + + printable, ok := meta.Value().(Printable) if !ok { return pp.HiddenString(), errors.New("cannot print values that don't implement Printable") } diff --git a/prettyprinters/human/human_test.go b/prettyprinters/human/human_test.go index 0f9edc3c3..97b158e81 100644 --- a/prettyprinters/human/human_test.go +++ b/prettyprinters/human/human_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" pp "github.com/asteris-llc/converge/prettyprinters" "github.com/asteris-llc/converge/prettyprinters/human" "github.com/asteris-llc/converge/resource" @@ -42,7 +43,7 @@ func TestSatisfiesInterface(t *testing.T) { func testFinishPP(t *testing.T, in Printable, out string) { g := graph.New() - g.Add("root", in) + g.Add(node.New("root", in)) printer := human.New() printer.InitColors() @@ -86,7 +87,7 @@ func testDrawNodes(t *testing.T, in Printable, out string) { func testDrawNodesCustomPrinter(t *testing.T, h *human.Printer, id string, in Printable, out string) { g := graph.New() - g.Add(id, in) + g.Add(node.New(id, in)) str, err := h.DrawNode(g, id) @@ -104,7 +105,7 @@ func benchmarkDrawNodes(in Printable) { func benchmarkDrawNodesCustomPrinter(h *human.Printer, id string, in Printable) { g := graph.New() - g.Add(id, in) + g.Add(node.New(id, in)) h.DrawNode(g, id) } diff --git a/prettyprinters/jsonl/jsonl.go b/prettyprinters/jsonl/jsonl.go index 86b112655..9a7e26535 100644 --- a/prettyprinters/jsonl/jsonl.go +++ b/prettyprinters/jsonl/jsonl.go @@ -18,13 +18,15 @@ import ( "encoding/json" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" pp "github.com/asteris-llc/converge/prettyprinters" ) // Node is the serializable type for graph nodes type Node struct { Kind string `json:"kind"` - ID string `json:"id"` + ID string `json:"id"` // TODO: preserved for compat, remove in 0.4.0 + Meta *node.Node `json:"meta"` Value interface{} `json:"value"` } @@ -40,7 +42,17 @@ type Printer struct{} // DrawNode prints a node in JSONL format func (j *Printer) DrawNode(graph *graph.Graph, nodeID string) (pp.Renderable, error) { - out, err := json.Marshal(&Node{Kind: "node", ID: nodeID, Value: graph.Get(nodeID)}) + meta, ok := graph.Get(nodeID) + if !ok { + return pp.HiddenString(), nil + } + + out, err := json.Marshal(&Node{ + Kind: "node", + ID: meta.ID, // TODO: preserved for compat, remove in 0.4.0 + Meta: meta, + Value: meta.Value(), + }) return pp.VisibleString(string(out) + "\n"), err } diff --git a/prettyprinters/jsonl/jsonl_test.go b/prettyprinters/jsonl/jsonl_test.go index 0f56716c0..8f029903e 100644 --- a/prettyprinters/jsonl/jsonl_test.go +++ b/prettyprinters/jsonl/jsonl_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" pp "github.com/asteris-llc/converge/prettyprinters" "github.com/asteris-llc/converge/prettyprinters/jsonl" "github.com/stretchr/testify/assert" @@ -34,13 +35,13 @@ func TestSatisfiesInterface(t *testing.T) { func TestDrawNode(t *testing.T) { g := graph.New() - g.Add("x", 1) + g.Add(node.New("x", 1)) printer := new(jsonl.Printer) out, err := printer.DrawNode(g, "x") assert.NoError(t, err) - assert.Equal(t, `{"kind":"node","id":"x","value":1}`+"\n", fmt.Sprint(out)) + assert.Equal(t, `{"kind":"node","id":"x","meta":{"id":"x"},"value":1}`+"\n", fmt.Sprint(out)) } func TestDrawEdge(t *testing.T) { diff --git a/prettyprinters/prettyprinter_test.go b/prettyprinters/prettyprinter_test.go index 79a79db8f..4f1fd8237 100644 --- a/prettyprinters/prettyprinter_test.go +++ b/prettyprinters/prettyprinter_test.go @@ -20,6 +20,7 @@ import ( "os" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/load" "github.com/asteris-llc/converge/prettyprinters" "github.com/asteris-llc/converge/prettyprinters/graphviz" @@ -80,29 +81,29 @@ func Example_generateAGraphFromAFileOnDisk() { func Example_createACustomPrintProvider() { g := graph.New() - g.Add(graph.ID("a"), 1) - g.Add(graph.ID("a", "b"), 2) - g.Add(graph.ID("a", "c"), 3) - g.Add(graph.ID("a", "c", "d"), 4) - g.Add(graph.ID("a", "c", "e"), 5) - g.Add(graph.ID("a", "b", "f"), 6) - g.Add(graph.ID("a", "b", "g"), 7) - - g.Add(graph.ID("a", "c", "d", "h"), 8) - g.Add(graph.ID("a", "c", "d", "i"), 9) - g.Add(graph.ID("a", "c", "d", "j"), 10) - - g.Add(graph.ID("a", "c", "e", "k"), 11) - g.Add(graph.ID("a", "c", "e", "l"), 12) - g.Add(graph.ID("a", "c", "e", "m"), 13) - - g.Add(graph.ID("a", "b", "f", "n"), 14) - g.Add(graph.ID("a", "b", "f", "o"), 15) - g.Add(graph.ID("a", "b", "f", "p"), 16) - - g.Add(graph.ID("a", "b", "g", "q"), 17) - g.Add(graph.ID("a", "b", "g", "r"), 18) - g.Add(graph.ID("a", "b", "g", "s"), 19) + g.Add(node.New(graph.ID("a"), 1)) + g.Add(node.New(graph.ID("a", "b"), 2)) + g.Add(node.New(graph.ID("a", "c"), 3)) + g.Add(node.New(graph.ID("a", "c", "d"), 4)) + g.Add(node.New(graph.ID("a", "c", "e"), 5)) + g.Add(node.New(graph.ID("a", "b", "f"), 6)) + g.Add(node.New(graph.ID("a", "b", "g"), 7)) + + g.Add(node.New(graph.ID("a", "c", "d", "h"), 8)) + g.Add(node.New(graph.ID("a", "c", "d", "i"), 9)) + g.Add(node.New(graph.ID("a", "c", "d", "j"), 10)) + + g.Add(node.New(graph.ID("a", "c", "e", "k"), 11)) + g.Add(node.New(graph.ID("a", "c", "e", "l"), 12)) + g.Add(node.New(graph.ID("a", "c", "e", "m"), 13)) + + g.Add(node.New(graph.ID("a", "b", "f", "n"), 14)) + g.Add(node.New(graph.ID("a", "b", "f", "o"), 15)) + g.Add(node.New(graph.ID("a", "b", "f", "p"), 16)) + + g.Add(node.New(graph.ID("a", "b", "g", "q"), 17)) + g.Add(node.New(graph.ID("a", "b", "g", "r"), 18)) + g.Add(node.New(graph.ID("a", "b", "g", "s"), 19)) g.Connect(graph.ID("a"), graph.ID("a", "b")) g.Connect(graph.ID("a"), graph.ID("a", "c")) @@ -200,9 +201,9 @@ func (p NumberProvider) SubgraphMarker(e graphviz.GraphEntity) graphviz.Subgraph func createTestGraph() *graph.Graph { g := graph.New() - g.Add(graph.ID("a"), 1) - g.Add(graph.ID("a", "b"), 2) - g.Add(graph.ID("a", "c"), 3) + g.Add(node.New(graph.ID("a"), 1)) + g.Add(node.New(graph.ID("a", "b"), 2)) + g.Add(node.New(graph.ID("a", "c"), 3)) g.Connect(graph.ID("a"), graph.ID("a", "b")) g.Connect(graph.ID("a"), graph.ID("a", "c")) return g diff --git a/prettyprinters/subgraph_printer.go b/prettyprinters/subgraph_printer.go index d00fe2d7c..8b8ff6e2a 100644 --- a/prettyprinters/subgraph_printer.go +++ b/prettyprinters/subgraph_printer.go @@ -20,6 +20,7 @@ import ( "errors" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" ) // Subgraph are treated as a semi-lattice with the root graph as the ⊥ (join) @@ -91,17 +92,17 @@ func (p Printer) loadSubgraphs(ctx context.Context, g *graph.Graph, subgraphs Su return } - g.RootFirstWalk(ctx, func(id string, val interface{}) error { - if sgMarker := printer.MarkNode(g, id); sgMarker != nil { + g.RootFirstWalk(ctx, func(meta *node.Node) error { + if sgMarker := printer.MarkNode(g, meta.ID); sgMarker != nil { if sgMarker.Start { - addNodeToSubgraph(subgraphs, sgMarker.SubgraphID, id) + addNodeToSubgraph(subgraphs, sgMarker.SubgraphID, meta.ID) } else { - thisSubgraph := getSubgraphByID(subgraphs, graph.ParentID(id)) - setSubgraphEndNode(subgraphs, thisSubgraph, id) + thisSubgraph := getSubgraphByID(subgraphs, graph.ParentID(meta.ID)) + setSubgraphEndNode(subgraphs, thisSubgraph, meta.ID) } } else { - subgraphID := getSubgraphByID(subgraphs, graph.ParentID(id)) - addNodeToSubgraph(subgraphs, subgraphID, id) + subgraphID := getSubgraphByID(subgraphs, graph.ParentID(meta.ID)) + addNodeToSubgraph(subgraphs, subgraphID, meta.ID) } return nil }) diff --git a/render/factory.go b/render/factory.go index f3ff7390f..40cbcd6b5 100644 --- a/render/factory.go +++ b/render/factory.go @@ -95,7 +95,12 @@ func getParamOverrides(gFunc func() *graph.Graph, id string) (ValueThunk, bool) f := func() (string, bool, error) { return "", false, nil } if strings.HasPrefix(name, "param") { f = func() (string, bool, error) { - parentTask, ok := resource.ResolveTask(gFunc().GetParent(id)) + parentMeta, ok := gFunc().GetParent(id) + if !ok { + return "", false, fmt.Errorf("%q was missing from the graph", id) + } + + parentTask, ok := resource.ResolveTask(parentMeta.Value()) if !ok { return "", false, fmt.Errorf("parent node is not a valid task type") } diff --git a/render/preprocessor/preprocessor_test.go b/render/preprocessor/preprocessor_test.go index a5c8075b6..b40bbeead 100644 --- a/render/preprocessor/preprocessor_test.go +++ b/render/preprocessor/preprocessor_test.go @@ -19,6 +19,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/render/preprocessor" "github.com/stretchr/testify/assert" ) @@ -78,10 +79,10 @@ func TestVertexSplit(t *testing.T) { t.Run("TestVertexSplitWhenMatchingSubstringReturnsPrefixAndRest", func(t *testing.T) { s := "a.b.c.d.e" g := graph.New() - g.Add("a", "a") - g.Add("a.b", "a.b.") - g.Add("a.c.d.", "a.c.d") - g.Add("a.b.c", "a.b.c") + g.Add(node.New("a", "a")) + g.Add(node.New("a.b", "a.b.")) + g.Add(node.New("a.c.d.", "a.c.d")) + g.Add(node.New("a.b.c", "a.b.c")) pfx, rest, found := preprocessor.VertexSplit(g, s) assert.Equal(t, "a.b.c", pfx) assert.Equal(t, "d.e", rest) @@ -91,7 +92,7 @@ func TestVertexSplit(t *testing.T) { t.Run("TestVertexSplitWhenExactMatchReturnsPrefix", func(t *testing.T) { s := "a.b.c" g := graph.New() - g.Add("a.b.c", "a.b.c") + g.Add(node.New("a.b.c", "a.b.c")) pfx, rest, found := preprocessor.VertexSplit(g, s) assert.Equal(t, "a.b.c", pfx) assert.Equal(t, "", rest) @@ -101,7 +102,7 @@ func TestVertexSplit(t *testing.T) { t.Run("TestVertexSplitWhenNoMatchReturnsRest", func(t *testing.T) { s := "x.y.z" g := graph.New() - g.Add("a.b.c", "a.b.c") + g.Add(node.New("a.b.c", "a.b.c")) pfx, rest, found := preprocessor.VertexSplit(g, s) assert.Equal(t, "", pfx) assert.Equal(t, "x.y.z", rest) diff --git a/render/render.go b/render/render.go index e9d84bc43..190d5e1c1 100644 --- a/render/render.go +++ b/render/render.go @@ -22,6 +22,7 @@ import ( "github.com/asteris-llc/converge/executor" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/fakerenderer" "github.com/asteris-llc/converge/resource" "github.com/asteris-llc/converge/resource/module" @@ -37,13 +38,15 @@ func Render(ctx context.Context, g *graph.Graph, top Values) (*graph.Graph, erro if err != nil { return nil, err } - return g.RootFirstTransform(ctx, func(id string, out *graph.Graph) error { - pipeline := Pipeline(out, id, renderingPlant, top) - value, err := pipeline.Exec(out.Get(id)) + return g.RootFirstTransform(ctx, func(meta *node.Node, out *graph.Graph) error { + pipeline := Pipeline(out, meta.ID, renderingPlant, top) + + value, err := pipeline.Exec(meta.Value()) if err != nil { return err } - out.Add(id, value) + + out.Add(meta.WithValue(value)) renderingPlant.Graph = out return nil }) diff --git a/render/render_test.go b/render/render_test.go index 833f88992..1399031db 100644 --- a/render/render_test.go +++ b/render/render_test.go @@ -20,6 +20,7 @@ import ( "testing" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/helpers/logging" "github.com/asteris-llc/converge/render" "github.com/asteris-llc/converge/resource" @@ -33,7 +34,7 @@ func TestRenderSingleNode(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add( + g.Add(node.New( "root/file.content.x", resource.NewPreparerWithSource( new(content.Preparer), @@ -42,15 +43,16 @@ func TestRenderSingleNode(t *testing.T) { "content": "{{2}}", }, ), - ) + )) rendered, err := render.Render(context.Background(), g, render.Values{}) assert.NoError(t, err) - node := rendered.Get("root/file.content.x") + meta, ok := rendered.Get("root/file.content.x") + assert.True(t, ok, `"root/file.content.x" was missing from the graph`) - wrapper, ok := node.(*resource.TaskWrapper) - require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was %T", wrapper, node)) + wrapper, ok := meta.Value().(*resource.TaskWrapper) + require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was %T", wrapper, meta.Value())) fileContent, ok := wrapper.Task.(*content.Content) require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was %T", fileContent, wrapper.Task)) @@ -63,23 +65,23 @@ func TestRenderParam(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", nil) + g.Add(node.New("root", nil)) - g.Add( + g.Add(node.New( "root/file.content.x", resource.NewPreparerWithSource( new(content.Preparer), map[string]interface{}{"destination": "{{param `destination`}}"}, ), - ) + )) - g.Add( + g.Add(node.New( "root/param.destination", resource.NewPreparerWithSource( new(param.Preparer), map[string]interface{}{"default": "1"}, ), - ) + )) g.ConnectParent("root", "root/file.content.x") g.ConnectParent("root", "root/param.destination") @@ -88,13 +90,14 @@ func TestRenderParam(t *testing.T) { rendered, err := render.Render(context.Background(), g, render.Values{}) require.NoError(t, err) - node := rendered.Get("root/file.content.x") + meta, ok := rendered.Get("root/file.content.x") + assert.True(t, ok, `"root/file.content.x" was missing from the graph`) - wrapper, ok := node.(*resource.TaskWrapper) - require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was %T", wrapper, node)) + wrapper, ok := meta.Value().(*resource.TaskWrapper) + require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was %T", wrapper, meta.Value())) fileContent, ok := wrapper.Task.(*content.Content) - require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was %T", fileContent, node)) + require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was %T", fileContent, wrapper.Task)) assert.Equal(t, "1", fileContent.Destination) } @@ -103,21 +106,21 @@ func TestRenderValues(t *testing.T) { defer logging.HideLogs(t)() g := graph.New() - g.Add("root", nil) - g.Add( + g.Add(node.New("root", nil)) + g.Add(node.New( "root/file.content.x", resource.NewPreparerWithSource( new(content.Preparer), map[string]interface{}{"destination": "{{param `destination`}}"}, ), - ) - g.Add( + )) + g.Add(node.New( "root/param.destination", resource.NewPreparerWithSource( new(param.Preparer), map[string]interface{}{"default": "1"}, ), - ) + )) g.ConnectParent("root", "root/file.content.x") g.ConnectParent("root", "root/param.destination") @@ -126,10 +129,11 @@ func TestRenderValues(t *testing.T) { rendered, err := render.Render(context.Background(), g, render.Values{"destination": 2}) require.NoError(t, err) - node := rendered.Get("root/file.content.x") + meta, ok := rendered.Get("root/file.content.x") + assert.True(t, ok, `"root/file.content.x" was missing from the graph`) - wrapper, ok := node.(*resource.TaskWrapper) - require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was a %T", wrapper, node)) + wrapper, ok := meta.Value().(*resource.TaskWrapper) + require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was a %T", wrapper, meta.Value())) content, ok := wrapper.Task.(*content.Content) require.True(t, ok, fmt.Sprintf("expected root to be a %T, but it was a %T", content, wrapper.Task)) diff --git a/render/renderer.go b/render/renderer.go index 02679326e..c5e23d165 100644 --- a/render/renderer.go +++ b/render/renderer.go @@ -17,9 +17,9 @@ package render import ( "errors" "fmt" - "log" "reflect" + log "github.com/Sirupsen/logrus" "github.com/asteris-llc/converge/graph" "github.com/asteris-llc/converge/render/extensions" "github.com/asteris-llc/converge/render/preprocessor" @@ -123,8 +123,12 @@ func (r *Renderer) paramMap(name string) (map[string]interface{}, error) { } func (r *Renderer) paramRawValue(name string) (interface{}, error) { - task, ok := resource.ResolveTask(r.Graph().Get(graph.SiblingID(r.ID, "param."+name))) + sibling, ok := r.Graph().Get(graph.SiblingID(r.ID, "param."+name)) + if !ok { + return "", errors.New("param not found") + } + task, ok := resource.ResolveTask(sibling.Value()) if task == nil || !ok { return "", errors.New("param not found") } @@ -154,17 +158,20 @@ func (r *Renderer) lookup(name string) (string, error) { return "", fmt.Errorf("%s does not resolve to a valid node", fqgn) } - if _, isThunk := g.Get(vertexName).(*PrepareThunk); isThunk { - log.Println("[INFO] node is unresolvable by proxy-reference to ", vertexName) + meta, ok := g.Get(vertexName) + if !ok { + return "", fmt.Errorf("%s is empty", vertexName) + } + + if _, isThunk := meta.Value().(*PrepareThunk); isThunk { + log.WithField("proxy-reference", vertexName).Warn("node is unresolvable") r.resolverErr = true return "", ErrUnresolvable{} } - val, ok := resource.ResolveTask(g.Get(vertexName)) - + val, ok := resource.ResolveTask(meta.Value()) if !ok { - p := g.Get(vertexName) - return "", fmt.Errorf("%s is not a valid task node (type: %T)", vertexName, p) + return "", fmt.Errorf("%s is not a valid task node (type: %T)", vertexName, meta.Value()) } result, err := preprocessor.EvalTerms(val, preprocessor.SplitTerms(terms)...) diff --git a/rpc/executor.go b/rpc/executor.go index e32aadd91..2f8a15370 100644 --- a/rpc/executor.go +++ b/rpc/executor.go @@ -22,6 +22,7 @@ import ( "github.com/asteris-llc/converge/apply" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/healthcheck" "github.com/asteris-llc/converge/plan" "github.com/asteris-llc/converge/prettyprinters/human" @@ -74,17 +75,18 @@ func (e *executor) sendMeta(ctx context.Context, g *graph.Graph, stream statusRe func (e *executor) stageNotifier(stage pb.StatusResponse_Stage, stream statusResponseStream) *graph.Notifier { return &graph.Notifier{ - Pre: func(id string) error { + Pre: func(meta *node.Node) error { return stream.Send(&pb.StatusResponse{ - Id: id, + Id: meta.ID, // TODO: deprecated, remove in 0.4.0 Stage: stage, Run: pb.StatusResponse_STARTED, + Meta: pb.MetaFromNode(meta), }) }, - Post: func(id string, r interface{}) error { + Post: func(meta *node.Node) error { response := statusResponseFromPrintable( - id, - r.(human.Printable), + meta, + meta.Value().(human.Printable), stage, pb.StatusResponse_FINISHED, ) diff --git a/rpc/grapher.go b/rpc/grapher.go index 5358c27b4..fa7562988 100644 --- a/rpc/grapher.go +++ b/rpc/grapher.go @@ -46,10 +46,14 @@ func (g *grapher) Graph(in *pb.LoadRequest, stream pb.Grapher_GraphServer) error } for _, vertex := range loaded.Vertices() { - node, err := resolveVertex(vertex, loaded.Get(vertex)) + var val interface{} + if meta, ok := loaded.Get(vertex); ok { + val = meta.Value() + } + node, err := resolveVertex(vertex, val) if err != nil { - return errors.Wrapf(err, "%T is an unknown vertex type", loaded.Get(vertex)) + return errors.Wrapf(err, "%T is an unknown vertex type", val) } kind, ok := registry.NameForType(node) diff --git a/rpc/grapherclient.go b/rpc/grapherclient.go index ee3d14184..e74d6f937 100644 --- a/rpc/grapherclient.go +++ b/rpc/grapherclient.go @@ -19,6 +19,7 @@ import ( "io" "github.com/asteris-llc/converge/graph" + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/rpc/pb" "github.com/pkg/errors" "google.golang.org/grpc" @@ -62,7 +63,7 @@ func (gc *GrapherClient) Graph(ctx context.Context, loc *pb.LoadRequest, opts .. } if vertex := container.GetVertex(); vertex != nil { - g.Add(vertex.Id, vertex) + g.Add(node.New(vertex.Id, vertex)) } else if edge := container.GetEdge(); edge != nil { var parent bool for _, attr := range edge.Attributes { diff --git a/rpc/pb/root.pb.go b/rpc/pb/root.pb.go index ec8182516..85fe5ed8d 100644 --- a/rpc/pb/root.pb.go +++ b/rpc/pb/root.pb.go @@ -117,10 +117,13 @@ func (*ContentResponse) ProtoMessage() {} func (*ContentResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} } type StatusResponse struct { + // TODO: preserve for compat but will stop working in 0.4.0. This has moved to + // meta.id Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` Stage StatusResponse_Stage `protobuf:"varint,2,opt,name=stage,enum=pb.StatusResponse_Stage" json:"stage,omitempty"` Run StatusResponse_Run `protobuf:"varint,3,opt,name=run,enum=pb.StatusResponse_Run" json:"run,omitempty"` Details *StatusResponse_Details `protobuf:"bytes,4,opt,name=details" json:"details,omitempty"` + Meta *StatusResponse_Meta `protobuf:"bytes,5,opt,name=meta" json:"meta,omitempty"` } func (m *StatusResponse) Reset() { *m = StatusResponse{} } @@ -135,6 +138,13 @@ func (m *StatusResponse) GetDetails() *StatusResponse_Details { return nil } +func (m *StatusResponse) GetMeta() *StatusResponse_Meta { + if m != nil { + return m.Meta + } + return nil +} + // the informational message, if present type StatusResponse_Details struct { Messages []string `protobuf:"bytes,1,rep,name=messages" json:"messages,omitempty"` @@ -155,6 +165,15 @@ func (m *StatusResponse_Details) GetChanges() map[string]*DiffResponse { return nil } +type StatusResponse_Meta struct { + Id string `protobuf:"bytes,1,opt,name=id" json:"id,omitempty"` +} + +func (m *StatusResponse_Meta) Reset() { *m = StatusResponse_Meta{} } +func (m *StatusResponse_Meta) String() string { return proto.CompactTextString(m) } +func (*StatusResponse_Meta) ProtoMessage() {} +func (*StatusResponse_Meta) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2, 1} } + type DiffResponse struct { Original string `protobuf:"bytes,1,opt,name=original" json:"original,omitempty"` Current string `protobuf:"bytes,2,opt,name=current" json:"current,omitempty"` @@ -317,6 +336,7 @@ func init() { proto.RegisterType((*ContentResponse)(nil), "pb.ContentResponse") proto.RegisterType((*StatusResponse)(nil), "pb.StatusResponse") proto.RegisterType((*StatusResponse_Details)(nil), "pb.StatusResponse.Details") + proto.RegisterType((*StatusResponse_Meta)(nil), "pb.StatusResponse.Meta") proto.RegisterType((*DiffResponse)(nil), "pb.DiffResponse") proto.RegisterType((*GraphComponent)(nil), "pb.GraphComponent") proto.RegisterType((*GraphComponent_Vertex)(nil), "pb.GraphComponent.Vertex") @@ -745,61 +765,63 @@ var _Grapher_serviceDesc = grpc.ServiceDesc{ func init() { proto.RegisterFile("root.proto", fileDescriptor0) } var fileDescriptor0 = []byte{ - // 881 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x95, 0xdf, 0x6e, 0xe3, 0x44, - 0x14, 0xc6, 0x6b, 0x27, 0x69, 0x92, 0x93, 0x28, 0x0d, 0xb3, 0xbb, 0x25, 0xeb, 0x45, 0x6c, 0xe4, - 0x8b, 0xdd, 0x50, 0x84, 0x03, 0x2e, 0x48, 0x68, 0xa5, 0x15, 0xca, 0x26, 0x6e, 0x53, 0xa9, 0x44, - 0x91, 0xd3, 0x45, 0xe2, 0x8f, 0x40, 0x13, 0x7b, 0xea, 0x58, 0x75, 0x66, 0xcc, 0x78, 0x5c, 0x6d, - 0x84, 0xb8, 0xe1, 0x12, 0x2e, 0xb9, 0xe6, 0x61, 0x78, 0x01, 0x6e, 0x78, 0x05, 0x2e, 0x79, 0x08, - 0x34, 0x63, 0xbb, 0xb8, 0x69, 0x2a, 0xed, 0x9d, 0xcf, 0xcc, 0x77, 0x7e, 0x73, 0xe6, 0x3b, 0x27, - 0x13, 0x00, 0xce, 0x98, 0xb0, 0x62, 0xce, 0x04, 0x43, 0x7a, 0xbc, 0x34, 0xde, 0x0b, 0x18, 0x0b, - 0x22, 0x32, 0xc4, 0x71, 0x38, 0xc4, 0x94, 0x32, 0x81, 0x45, 0xc8, 0x68, 0x92, 0x29, 0x8c, 0x27, - 0xf9, 0xae, 0x8a, 0x96, 0xe9, 0xe5, 0x90, 0xac, 0x63, 0xb1, 0xc9, 0x36, 0xcd, 0x3f, 0x35, 0x68, - 0x9d, 0x33, 0xec, 0xbb, 0xe4, 0xc7, 0x94, 0x24, 0x02, 0x19, 0xd0, 0x88, 0x98, 0xa7, 0xf2, 0x7b, - 0x5a, 0x5f, 0x1b, 0x34, 0xdd, 0x9b, 0x18, 0x7d, 0x01, 0x10, 0x63, 0x8e, 0xd7, 0x44, 0x10, 0x9e, - 0xf4, 0xf4, 0x7e, 0x65, 0xd0, 0xb2, 0x9f, 0x5a, 0xf1, 0xd2, 0x2a, 0x01, 0xac, 0xf9, 0x8d, 0xc2, - 0xa1, 0x82, 0x6f, 0xdc, 0x52, 0x0a, 0x3a, 0x84, 0xfd, 0x6b, 0xc2, 0xc3, 0xcb, 0x4d, 0xaf, 0xd2, - 0xd7, 0x06, 0x0d, 0x37, 0x8f, 0x8c, 0x97, 0x70, 0xb0, 0x95, 0x86, 0xba, 0x50, 0xb9, 0x22, 0x9b, - 0xbc, 0x04, 0xf9, 0x89, 0x1e, 0x42, 0xed, 0x1a, 0x47, 0x29, 0xe9, 0xe9, 0x6a, 0x2d, 0x0b, 0x5e, - 0xe8, 0x9f, 0x6b, 0xe6, 0x87, 0x70, 0x30, 0x66, 0x54, 0x10, 0x2a, 0x5c, 0x92, 0xc4, 0x8c, 0x26, - 0x04, 0xf5, 0xa0, 0xee, 0x65, 0x4b, 0x39, 0xa2, 0x08, 0xcd, 0xdf, 0xaa, 0xd0, 0x59, 0x08, 0x2c, - 0xd2, 0xe4, 0x46, 0xdc, 0x01, 0x3d, 0xf4, 0x73, 0x9d, 0x1e, 0xfa, 0xc8, 0x82, 0x5a, 0x22, 0x70, - 0x90, 0x9d, 0xd4, 0xb1, 0x7b, 0xf2, 0x8a, 0xb7, 0x53, 0x64, 0x18, 0x10, 0x37, 0x93, 0xa1, 0x01, - 0x54, 0x78, 0x4a, 0xd5, 0x9d, 0x3a, 0xf6, 0xe1, 0x0e, 0xb5, 0x9b, 0x52, 0x57, 0x4a, 0xd0, 0xa7, - 0x50, 0xf7, 0x89, 0xc0, 0x61, 0x94, 0xf4, 0xaa, 0x7d, 0x6d, 0xd0, 0xb2, 0x8d, 0x1d, 0xea, 0x49, - 0xa6, 0x70, 0x0b, 0xa9, 0xf1, 0xaf, 0x06, 0xf5, 0x7c, 0x51, 0xf6, 0x67, 0x4d, 0x92, 0x04, 0x07, - 0x24, 0xe9, 0x69, 0xfd, 0x8a, 0xec, 0x4f, 0x11, 0xa3, 0x11, 0xd4, 0xbd, 0x15, 0xa6, 0x72, 0x2b, - 0x6b, 0xce, 0xf3, 0xfb, 0xe9, 0xd6, 0x38, 0x53, 0x66, 0x4d, 0x2a, 0xf2, 0xd0, 0xfb, 0x00, 0x2b, - 0x9c, 0xe4, 0x7b, 0x79, 0x97, 0x4a, 0x2b, 0xb2, 0x09, 0x84, 0x73, 0xc6, 0x55, 0xf9, 0x4d, 0x37, - 0x0b, 0x8c, 0x73, 0x68, 0x97, 0x71, 0x3b, 0x9a, 0xf7, 0xac, 0xdc, 0xbc, 0x96, 0xdd, 0x95, 0x85, - 0x4d, 0xc2, 0xcb, 0xcb, 0xa2, 0xac, 0x72, 0x3b, 0x8f, 0xa1, 0xa6, 0xec, 0x45, 0x8f, 0xe0, 0x9d, - 0xd7, 0xb3, 0xc5, 0xdc, 0x19, 0x9f, 0x9d, 0x9c, 0x39, 0x93, 0x1f, 0x16, 0x17, 0xa3, 0x53, 0xa7, - 0xbb, 0x87, 0x1a, 0x50, 0x9d, 0x9f, 0x8f, 0x66, 0x5d, 0x0d, 0x35, 0xa1, 0x36, 0x9a, 0xcf, 0xcf, - 0xbf, 0xee, 0xea, 0xe6, 0x67, 0x50, 0x71, 0x53, 0x8a, 0x1e, 0xc0, 0x41, 0x39, 0xc5, 0x7d, 0x3d, - 0xeb, 0xee, 0xa1, 0x16, 0xd4, 0x17, 0x17, 0x23, 0xf7, 0xc2, 0x99, 0x74, 0x35, 0xd4, 0x86, 0xc6, - 0xc9, 0xd9, 0xec, 0x6c, 0x31, 0x75, 0x26, 0x5d, 0xdd, 0xfc, 0x1e, 0xda, 0xe5, 0x32, 0xa4, 0xbd, - 0x8c, 0x87, 0x41, 0x48, 0x71, 0x54, 0x8c, 0x7f, 0x11, 0xab, 0x99, 0x4a, 0x39, 0x97, 0x33, 0xa5, - 0xe7, 0x33, 0x95, 0x85, 0x6a, 0xe7, 0x96, 0x65, 0x45, 0x68, 0xfe, 0xa1, 0x43, 0xe7, 0x94, 0xe3, - 0x78, 0x35, 0x66, 0xeb, 0x98, 0x51, 0x29, 0x3e, 0x56, 0x3f, 0x02, 0x41, 0xde, 0xa8, 0x03, 0x5a, - 0xf6, 0x63, 0xe9, 0xc5, 0x6d, 0x8d, 0xf5, 0x95, 0x12, 0x4c, 0xf7, 0xdc, 0x5c, 0x8a, 0x3e, 0x82, - 0x2a, 0xf1, 0x83, 0xc2, 0xbe, 0x77, 0x77, 0xa4, 0x38, 0x7e, 0x40, 0xa6, 0x7b, 0xae, 0x92, 0x19, - 0x27, 0xb0, 0x9f, 0x21, 0xee, 0xcc, 0x36, 0x82, 0xea, 0x55, 0x48, 0xfd, 0xfc, 0x06, 0xea, 0x5b, - 0x96, 0x5f, 0x4c, 0xa5, 0x2c, 0xbf, 0xfd, 0xff, 0xe4, 0xb9, 0x50, 0x95, 0x5c, 0xf9, 0xc3, 0x4d, - 0x58, 0xca, 0x3d, 0x92, 0x93, 0xf2, 0x48, 0xd2, 0x7c, 0x92, 0x14, 0x7e, 0xa8, 0x6f, 0x39, 0x42, - 0x58, 0x08, 0x1e, 0x2e, 0x53, 0xa1, 0xfc, 0x90, 0x33, 0x5a, 0x5a, 0x79, 0xd5, 0x82, 0xa6, 0x57, - 0x54, 0x6d, 0xff, 0xaa, 0x43, 0xc3, 0x79, 0x43, 0xbc, 0x54, 0x30, 0x8e, 0xbe, 0x83, 0xd6, 0x94, - 0xe0, 0x48, 0xac, 0xc6, 0x2b, 0xe2, 0x5d, 0xa1, 0x83, 0xad, 0xa7, 0xc5, 0x40, 0x77, 0xc7, 0xd9, - 0x7c, 0xf6, 0xcb, 0xdf, 0xff, 0xfc, 0xae, 0xf7, 0xcd, 0x27, 0xea, 0xf1, 0xbb, 0xfe, 0x64, 0xb8, - 0xc6, 0xde, 0x2a, 0xa4, 0x64, 0xb8, 0x52, 0x24, 0x4f, 0x92, 0x5e, 0x68, 0x47, 0x1f, 0x6b, 0x68, - 0x06, 0xd5, 0x79, 0x84, 0xe9, 0xdb, 0x61, 0x9f, 0x2a, 0xec, 0x63, 0xf3, 0xe1, 0x36, 0x36, 0x8e, - 0x30, 0xcd, 0x78, 0x73, 0xa8, 0x8d, 0xe2, 0x38, 0xda, 0xbc, 0x1d, 0xb0, 0xaf, 0x80, 0x86, 0xf9, - 0x68, 0x1b, 0x88, 0x25, 0x43, 0x11, 0xed, 0xbf, 0x34, 0x68, 0xbb, 0x24, 0xb3, 0x76, 0xca, 0x12, - 0x81, 0xbe, 0x81, 0xe6, 0x29, 0x11, 0xaf, 0x42, 0x8a, 0xf9, 0x06, 0x1d, 0x5a, 0xd9, 0x3b, 0x6e, - 0x15, 0xef, 0xb8, 0xe5, 0xc8, 0x77, 0xdc, 0x78, 0x20, 0x4f, 0xdb, 0x7a, 0xff, 0x8a, 0xe3, 0x50, - 0xaf, 0x38, 0x8e, 0xe7, 0xdc, 0x64, 0xb8, 0xcc, 0x70, 0x4b, 0xc5, 0xfe, 0x92, 0xf9, 0x69, 0x44, - 0xee, 0x5e, 0x61, 0x27, 0x74, 0xa8, 0xa0, 0x1f, 0xa0, 0xe7, 0x77, 0xa1, 0x6b, 0xc5, 0x49, 0x86, - 0x3f, 0x15, 0x7f, 0x16, 0x2f, 0x8f, 0x8e, 0x7e, 0xb6, 0xbf, 0x85, 0xba, 0x9a, 0x52, 0xc2, 0xa5, - 0x5b, 0xea, 0xf3, 0x1e, 0xb7, 0x6e, 0x0f, 0xf3, 0xfd, 0x6e, 0x05, 0x52, 0xa7, 0xdc, 0x5a, 0xee, - 0x2b, 0x1f, 0x8e, 0xff, 0x0b, 0x00, 0x00, 0xff, 0xff, 0x7a, 0x12, 0x34, 0x27, 0x0d, 0x07, 0x00, - 0x00, + // 916 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x8c, 0x95, 0x4d, 0x6f, 0x1a, 0x47, + 0x18, 0xc7, 0xbd, 0x0b, 0x18, 0x78, 0x40, 0x98, 0x4e, 0x12, 0x67, 0xb3, 0xa9, 0x1a, 0xb4, 0x87, + 0x84, 0x3a, 0xea, 0xd2, 0xe2, 0x56, 0xaa, 0x22, 0x45, 0x15, 0x06, 0x6c, 0x2c, 0x39, 0x08, 0x0d, + 0x4e, 0xa5, 0xbe, 0xa8, 0xd5, 0x00, 0x63, 0x58, 0x79, 0x99, 0xd9, 0xce, 0xce, 0x5a, 0x41, 0x55, + 0x2f, 0x3d, 0xf6, 0xda, 0x73, 0x3f, 0x4c, 0xbf, 0x40, 0x2f, 0xed, 0x47, 0xe8, 0xb1, 0x1f, 0xa2, + 0x9a, 0x99, 0x5d, 0x07, 0x63, 0x2c, 0xf9, 0xb6, 0xcf, 0xcc, 0xff, 0xf9, 0xcd, 0xf3, 0xb6, 0x33, + 0x00, 0x82, 0x73, 0xe9, 0x47, 0x82, 0x4b, 0x8e, 0xec, 0x68, 0xe2, 0x7e, 0x38, 0xe7, 0x7c, 0x1e, + 0xd2, 0x16, 0x89, 0x82, 0x16, 0x61, 0x8c, 0x4b, 0x22, 0x03, 0xce, 0x62, 0xa3, 0x70, 0x9f, 0xa6, + 0xbb, 0xda, 0x9a, 0x24, 0x17, 0x2d, 0xba, 0x8c, 0xe4, 0xca, 0x6c, 0x7a, 0x7f, 0x5a, 0x50, 0x39, + 0xe3, 0x64, 0x86, 0xe9, 0x4f, 0x09, 0x8d, 0x25, 0x72, 0xa1, 0x14, 0xf2, 0xa9, 0xf6, 0x77, 0xac, + 0x86, 0xd5, 0x2c, 0xe3, 0x6b, 0x1b, 0x7d, 0x05, 0x10, 0x11, 0x41, 0x96, 0x54, 0x52, 0x11, 0x3b, + 0x76, 0x23, 0xd7, 0xac, 0xb4, 0x9f, 0xf9, 0xd1, 0xc4, 0x5f, 0x03, 0xf8, 0xa3, 0x6b, 0x45, 0x9f, + 0x49, 0xb1, 0xc2, 0x6b, 0x2e, 0x68, 0x1f, 0x76, 0xaf, 0xa8, 0x08, 0x2e, 0x56, 0x4e, 0xae, 0x61, + 0x35, 0x4b, 0x38, 0xb5, 0xdc, 0xd7, 0xb0, 0xb7, 0xe1, 0x86, 0xea, 0x90, 0xbb, 0xa4, 0xab, 0x34, + 0x04, 0xf5, 0x89, 0x1e, 0x42, 0xe1, 0x8a, 0x84, 0x09, 0x75, 0x6c, 0xbd, 0x66, 0x8c, 0x57, 0xf6, + 0x97, 0x96, 0xf7, 0x12, 0xf6, 0xba, 0x9c, 0x49, 0xca, 0x24, 0xa6, 0x71, 0xc4, 0x59, 0x4c, 0x91, + 0x03, 0xc5, 0xa9, 0x59, 0x4a, 0x11, 0x99, 0xe9, 0xfd, 0x93, 0x87, 0xda, 0x58, 0x12, 0x99, 0xc4, + 0xd7, 0x62, 0x04, 0x76, 0x30, 0x33, 0xba, 0x23, 0xdb, 0xb1, 0xb0, 0x1d, 0xcc, 0x90, 0x0f, 0x85, + 0x58, 0x92, 0xb9, 0x39, 0xad, 0xd6, 0x76, 0x54, 0x9a, 0x37, 0xdd, 0x94, 0x39, 0xa7, 0xd8, 0xc8, + 0x50, 0x13, 0x72, 0x22, 0x61, 0x3a, 0xaf, 0x5a, 0x7b, 0x7f, 0x8b, 0x1a, 0x27, 0x0c, 0x2b, 0x09, + 0xfa, 0x1c, 0x8a, 0x33, 0x2a, 0x49, 0x10, 0xc6, 0x4e, 0xbe, 0x61, 0x35, 0x2b, 0x6d, 0x77, 0x8b, + 0xba, 0x67, 0x14, 0x38, 0x93, 0xa2, 0x97, 0x90, 0x5f, 0x52, 0x49, 0x9c, 0x82, 0x76, 0x79, 0xbc, + 0xc5, 0xe5, 0x0d, 0x95, 0x04, 0x6b, 0x91, 0xfb, 0x9f, 0x05, 0xc5, 0x94, 0xa0, 0x1a, 0xba, 0xa4, + 0x71, 0x4c, 0xe6, 0x34, 0x76, 0xac, 0x46, 0x4e, 0x35, 0x34, 0xb3, 0x51, 0x07, 0x8a, 0xd3, 0x05, + 0x61, 0x6a, 0xcb, 0x74, 0xf3, 0xc5, 0xdd, 0xa1, 0xf8, 0x5d, 0xa3, 0x34, 0x5d, 0xcd, 0xfc, 0xd0, + 0x47, 0x00, 0x0b, 0x12, 0xa7, 0x7b, 0x69, 0x5b, 0xd7, 0x56, 0x54, 0xd7, 0xa8, 0x10, 0x5c, 0xe8, + 0x5c, 0xcb, 0xd8, 0x18, 0xee, 0x19, 0x54, 0xd7, 0x71, 0x5b, 0xba, 0xfd, 0x7c, 0xbd, 0xdb, 0x95, + 0x76, 0x5d, 0x05, 0xd6, 0x0b, 0x2e, 0x2e, 0xb2, 0xb0, 0xd6, 0xfa, 0xef, 0xee, 0x43, 0x5e, 0x25, + 0x8f, 0x6a, 0xef, 0xfb, 0xa8, 0x7a, 0xe8, 0x1d, 0x42, 0x41, 0xf7, 0x08, 0x3d, 0x82, 0x0f, 0xde, + 0x0e, 0xc7, 0xa3, 0x7e, 0xf7, 0xf4, 0xf8, 0xb4, 0xdf, 0xfb, 0x71, 0x7c, 0xde, 0x39, 0xe9, 0xd7, + 0x77, 0x50, 0x09, 0xf2, 0xa3, 0xb3, 0xce, 0xb0, 0x6e, 0xa1, 0x32, 0x14, 0x3a, 0xa3, 0xd1, 0xd9, + 0x37, 0x75, 0xdb, 0xfb, 0x02, 0x72, 0x38, 0x61, 0xe8, 0x01, 0xec, 0xad, 0xbb, 0xe0, 0xb7, 0xc3, + 0xfa, 0x0e, 0xaa, 0x40, 0x71, 0x7c, 0xde, 0xc1, 0xe7, 0xfd, 0x5e, 0xdd, 0x42, 0x55, 0x28, 0x1d, + 0x9f, 0x0e, 0x4f, 0xc7, 0x83, 0x7e, 0xaf, 0x6e, 0x7b, 0x3f, 0x40, 0x75, 0x3d, 0x3c, 0x55, 0x76, + 0x2e, 0x82, 0x79, 0xc0, 0x48, 0x98, 0xfd, 0x47, 0x99, 0xad, 0x87, 0x33, 0x11, 0x42, 0x0d, 0xa7, + 0x9d, 0x0e, 0xa7, 0x31, 0xf5, 0xce, 0x8d, 0x52, 0x66, 0xa6, 0xf7, 0x87, 0x0d, 0xb5, 0x13, 0x41, + 0xa2, 0x45, 0x97, 0x2f, 0x23, 0xce, 0x94, 0xf8, 0x50, 0xff, 0x4d, 0x92, 0xbe, 0xd3, 0x07, 0x54, + 0xda, 0x4f, 0x54, 0x8d, 0x6e, 0x6a, 0xfc, 0xaf, 0xb5, 0x60, 0xb0, 0x83, 0x53, 0x29, 0xfa, 0x04, + 0xf2, 0x74, 0x36, 0xcf, 0xca, 0xfa, 0x78, 0x8b, 0x4b, 0x7f, 0x36, 0xa7, 0x83, 0x1d, 0xac, 0x65, + 0xee, 0x31, 0xec, 0x1a, 0xc4, 0x66, 0x71, 0x11, 0x82, 0xfc, 0x65, 0xc0, 0x66, 0x69, 0x06, 0xfa, + 0x5b, 0x85, 0x9f, 0x8d, 0xb6, 0x0a, 0xbf, 0x7a, 0x3d, 0xbe, 0x2e, 0x86, 0xbc, 0xe2, 0xaa, 0x1b, + 0x20, 0xe6, 0x89, 0x98, 0xd2, 0x94, 0x94, 0x5a, 0x8a, 0x36, 0xa3, 0x71, 0x56, 0x0f, 0xfd, 0xad, + 0x46, 0x8b, 0x48, 0x29, 0x82, 0x49, 0x22, 0x75, 0x3d, 0xd4, 0xec, 0xae, 0xad, 0x1c, 0x55, 0xa0, + 0x3c, 0xcd, 0xa2, 0x6e, 0xff, 0x66, 0x43, 0xa9, 0xff, 0x8e, 0x4e, 0x13, 0xc9, 0x05, 0xfa, 0x1e, + 0x2a, 0x03, 0x4a, 0x42, 0xb9, 0xe8, 0x2e, 0xe8, 0xf4, 0x12, 0xed, 0x6d, 0xdc, 0x51, 0x2e, 0xba, + 0x3d, 0xe6, 0xde, 0xf3, 0x5f, 0xff, 0xfe, 0xf7, 0x77, 0xbb, 0xe1, 0x3d, 0xd5, 0xb7, 0xe8, 0xd5, + 0x67, 0xad, 0x25, 0x99, 0x2e, 0x02, 0x46, 0x5b, 0x0b, 0x4d, 0x9a, 0x2a, 0xd2, 0x2b, 0xeb, 0xe0, + 0x53, 0x0b, 0x0d, 0x21, 0x3f, 0x0a, 0x09, 0xbb, 0x1f, 0xf6, 0x99, 0xc6, 0x3e, 0xf1, 0x1e, 0x6e, + 0x62, 0xa3, 0x90, 0x30, 0xc3, 0x1b, 0x41, 0xa1, 0x13, 0x45, 0xe1, 0xea, 0x7e, 0xc0, 0x86, 0x06, + 0xba, 0xde, 0xa3, 0x4d, 0x20, 0x51, 0x0c, 0x4d, 0x6c, 0xff, 0x65, 0x41, 0x15, 0x53, 0x53, 0xda, + 0x01, 0x8f, 0x25, 0xfa, 0x16, 0xca, 0x27, 0x54, 0x1e, 0x05, 0x8c, 0x88, 0x15, 0xda, 0xf7, 0xcd, + 0x83, 0xe0, 0x67, 0x0f, 0x82, 0xdf, 0x57, 0x0f, 0x82, 0xfb, 0x40, 0x9d, 0xb6, 0x71, 0x91, 0x66, + 0xc7, 0x21, 0x27, 0x3b, 0x4e, 0xa4, 0xdc, 0xb8, 0x35, 0x31, 0xb8, 0x89, 0x66, 0xbf, 0xe1, 0xb3, + 0x24, 0xa4, 0xb7, 0x53, 0xd8, 0x0a, 0x6d, 0x69, 0xe8, 0xc7, 0xe8, 0xc5, 0x6d, 0xe8, 0x52, 0x73, + 0xe2, 0xd6, 0xcf, 0xd9, 0xab, 0xf3, 0xfa, 0xe0, 0xe0, 0x97, 0xf6, 0x77, 0x50, 0xd4, 0x53, 0x4a, + 0x85, 0xaa, 0x96, 0xfe, 0xbc, 0xa3, 0x5a, 0x37, 0x87, 0xf9, 0xee, 0x6a, 0xcd, 0x95, 0x4e, 0x57, + 0x6b, 0xb2, 0xab, 0xeb, 0x70, 0xf8, 0x7f, 0x00, 0x00, 0x00, 0xff, 0xff, 0xfb, 0x97, 0x78, 0x6d, + 0x56, 0x07, 0x00, 0x00, } diff --git a/rpc/pb/root.proto b/rpc/pb/root.proto index 12089d99a..262838809 100644 --- a/rpc/pb/root.proto +++ b/rpc/pb/root.proto @@ -16,7 +16,9 @@ message ContentResponse { } message StatusResponse { - string id = 1; + // TODO: preserve for compat but will stop working in 0.4.0. This has moved to + // meta.id + string id = 1 [deprecated=true]; // the stage from which this status response is being sent enum Stage { @@ -42,6 +44,11 @@ message StatusResponse { string error = 4; } Details details = 4; + + message Meta { + string id = 1; + } + Meta meta = 5; } message DiffResponse { diff --git a/rpc/pb/root.swagger.json b/rpc/pb/root.swagger.json index d80191e7a..4b7b5d96c 100644 --- a/rpc/pb/root.swagger.json +++ b/rpc/pb/root.swagger.json @@ -68,6 +68,33 @@ ] } }, + "/api/v1/machine/healthcheck": { + "post": { + "summary": "Healthcheck a module given by the location", + "operationId": "HealthCheck", + "responses": { + "200": { + "description": "(streaming responses)", + "schema": { + "$ref": "#/definitions/pbStatusResponse" + } + } + }, + "parameters": [ + { + "name": "body", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/pbLoadRequest" + } + } + ], + "tags": [ + "Executor" + ] + } + }, "/api/v1/machine/plan": { "post": { "summary": "Plan out the execution of a module given by the location", @@ -206,6 +233,15 @@ }, "title": "the informational message, if present" }, + "StatusResponseMeta": { + "type": "object", + "properties": { + "id": { + "type": "string", + "format": "string" + } + } + }, "StatusResponseRun": { "type": "string", "enum": [ @@ -276,6 +312,10 @@ "type": "string", "format": "string" } + }, + "verify": { + "type": "boolean", + "format": "boolean" } } }, @@ -287,7 +327,11 @@ }, "id": { "type": "string", - "format": "string" + "format": "string", + "title": "TODO: preserve for compat but will stop working in 0.4.0. This has moved to\nmeta.id" + }, + "meta": { + "$ref": "#/definitions/StatusResponseMeta" }, "run": { "$ref": "#/definitions/StatusResponseRun" diff --git a/rpc/pb/statusresponse_details.go b/rpc/pb/statusresponse_details.go new file mode 100644 index 000000000..c32bc4c04 --- /dev/null +++ b/rpc/pb/statusresponse_details.go @@ -0,0 +1,24 @@ +// Copyright © 2016 Asteris, LLC +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package pb + +import "github.com/asteris-llc/converge/graph/node" + +// MetaFromNode transfers metadata from a node to a Meta object +func MetaFromNode(meta *node.Node) *StatusResponse_Meta { + return &StatusResponse_Meta{ + Id: meta.ID, + } +} diff --git a/rpc/statusresponse.go b/rpc/statusresponse.go index c9b69f927..5ca99e397 100644 --- a/rpc/statusresponse.go +++ b/rpc/statusresponse.go @@ -15,15 +15,17 @@ package rpc import ( + "github.com/asteris-llc/converge/graph/node" "github.com/asteris-llc/converge/prettyprinters/human" "github.com/asteris-llc/converge/rpc/pb" ) -func statusResponseFromPrintable(id string, p human.Printable, stage pb.StatusResponse_Stage, run pb.StatusResponse_Run) *pb.StatusResponse { +func statusResponseFromPrintable(meta *node.Node, p human.Printable, stage pb.StatusResponse_Stage, run pb.StatusResponse_Run) *pb.StatusResponse { resp := &pb.StatusResponse{ - Id: id, + Id: meta.ID, // TODO: deprecated, remove in 0.4.0 Stage: stage, Run: run, + Meta: pb.MetaFromNode(meta), Details: &pb.StatusResponse_Details{ Messages: p.Messages(),