Skip to content

Commit fe4c28d

Browse files
committed
wip: stub structs and logic for a bundle workflow
* resolve a dependency graph * identify the order of execution * still working on how to represent a workflow of bundles to execute in a way that we can abstract with a driver Signed-off-by: Carolyn Van Slyck <[email protected]>
1 parent d39c729 commit fe4c28d

28 files changed

+1143
-7
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ require (
6060
github.com/spf13/viper v1.8.1
6161
github.com/stretchr/testify v1.7.1
6262
github.com/xeipuuv/gojsonschema v1.2.0
63+
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869
6364
go.mongodb.org/mongo-driver v1.7.1
6465
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.29.0
6566
go.opentelemetry.io/otel v1.7.0

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1578,6 +1578,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
15781578
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
15791579
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
15801580
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
1581+
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8=
1582+
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE=
15811583
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
15821584
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
15831585
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
+109
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package v2
2+
3+
import (
4+
"get.porter.sh/porter/pkg/cnab"
5+
"github.com/yourbasic/graph"
6+
)
7+
8+
// BundleGraph is a directed acyclic graph of a bundle and its dependencies
9+
// (which may be other bundles, or installations) It is used to resolve the
10+
// dependency order in which the bundles must be executed.
11+
type BundleGraph struct {
12+
// nodeKeys is a map from the node key to its index in nodes
13+
nodeKeys map[string]int
14+
nodes []Node
15+
resolver DependencyResolver
16+
}
17+
18+
func NewBundleGraph(resolver DependencyResolver) *BundleGraph {
19+
return &BundleGraph{
20+
nodeKeys: make(map[string]int),
21+
resolver: resolver,
22+
}
23+
}
24+
25+
// RegisterNode adds the specified node to the graph
26+
// returning true if the node is already present.
27+
func (g *BundleGraph) RegisterNode(node Node) bool {
28+
_, exists := g.nodeKeys[node.GetKey()]
29+
if !exists {
30+
nodeIndex := len(g.nodes)
31+
g.nodes = append(g.nodes, node)
32+
g.nodeKeys[node.GetKey()] = nodeIndex
33+
}
34+
return exists
35+
}
36+
37+
func (g *BundleGraph) Sort() ([]Node, bool) {
38+
dag := graph.New(len(g.nodes))
39+
for nodeIndex, node := range g.nodes {
40+
for _, depKey := range node.GetRequires() {
41+
depIndex, ok := g.nodeKeys[depKey]
42+
if !ok {
43+
panic("oops")
44+
}
45+
dag.Add(nodeIndex, depIndex)
46+
}
47+
}
48+
49+
indices, ok := graph.TopSort(dag)
50+
if !ok {
51+
return nil, false
52+
}
53+
54+
// Reverse the sort so that items with no dependencies are listed first
55+
count := len(indices)
56+
results := make([]Node, count)
57+
for i, nodeIndex := range indices {
58+
results[count-i-1] = g.nodes[nodeIndex]
59+
}
60+
return results, true
61+
}
62+
63+
func (g *BundleGraph) GetNode(key string) (Node, bool) {
64+
if nodeIndex, ok := g.nodeKeys[key]; ok {
65+
return g.nodes[nodeIndex], true
66+
}
67+
return nil, false
68+
}
69+
70+
// Node in a BundleGraph.
71+
type Node interface {
72+
GetRequires() []string
73+
GetKey() string
74+
}
75+
76+
var _ Node = BundleNode{}
77+
var _ Node = InstallationNode{}
78+
79+
// BundleNode is a Node in a BundleGraph that represents a dependency on a bundle
80+
// that has not yet been installed.
81+
type BundleNode struct {
82+
Key string
83+
Reference cnab.BundleReference
84+
Requires []string // TODO: we don't need to know this while resolving, find a less confusing way of storing this so it's clear who should set it
85+
}
86+
87+
func (d BundleNode) GetKey() string {
88+
return d.Key
89+
}
90+
91+
func (d BundleNode) GetRequires() []string {
92+
return d.Requires
93+
}
94+
95+
// InstallationNode is a Node in a BundleGraph that represents a dependency on an
96+
// installed bundle (installation).
97+
type InstallationNode struct {
98+
Key string
99+
Namespace string
100+
Name string
101+
}
102+
103+
func (d InstallationNode) GetKey() string {
104+
return d.Key
105+
}
106+
107+
func (d InstallationNode) GetRequires() []string {
108+
return nil
109+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
package v2
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
8+
"github.com/stretchr/testify/require"
9+
)
10+
11+
func TestEngine_DependOnInstallation(t *testing.T) {
12+
/*
13+
A -> B (installation)
14+
A -> C (bundle)
15+
c.parameters.connstr <- B.outputs.connstr
16+
*/
17+
18+
b := InstallationNode{Key: "b"}
19+
c := BundleNode{
20+
Key: "c",
21+
Requires: []string{"b"},
22+
}
23+
a := BundleNode{
24+
Key: "root",
25+
Requires: []string{"b", "c"},
26+
}
27+
28+
g := NewBundleGraph(TestResolver{})
29+
g.RegisterNode(a)
30+
g.RegisterNode(b)
31+
g.RegisterNode(c)
32+
sortedNodes, ok := g.Sort()
33+
require.True(t, ok, "graph should not be cyclic")
34+
35+
gotOrder := make([]string, len(sortedNodes))
36+
for i, node := range sortedNodes {
37+
gotOrder[i] = node.GetKey()
38+
}
39+
wantOrder := []string{
40+
"b",
41+
"c",
42+
"root",
43+
}
44+
assert.Equal(t, wantOrder, gotOrder)
45+
}
46+
47+
/*
48+
✅ need to represent new dependency structure on an extended bundle wrapper
49+
(put in cnab-go later)
50+
51+
need to read a bundle and make a BundleGraph
52+
? how to handle a param that isn't a pure assignment, e.g. connstr: ${bundle.deps.VM.outputs.ip}:${bundle.deps.SVC.outputs.port}
53+
? when are templates evaluated as the graph is executed (for simplicity, first draft no composition / templating)
54+
55+
need to resolve dependencies in the graph
56+
* lookup against existing installations
57+
* lookup against semver tags in registry
58+
* lookup against bundle index? when would we look here? (i.e. preferred/registered implementations of interfaces)
59+
60+
need to turn the sorted nodes into an execution plan
61+
execution plan needs:
62+
* bundle to execute and the installation it will become
63+
* parameters and credentials to pass
64+
* sources:
65+
root parameters/creds
66+
installation outputs
67+
68+
need to write something that can run an execution plan
69+
* knows how to grab sources and pass them into the bundle
70+
*/
+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package v2
2+
3+
import (
4+
"context"
5+
6+
"get.porter.sh/porter/pkg/cache"
7+
"get.porter.sh/porter/pkg/cnab"
8+
)
9+
10+
// BundlePuller can query and pull bundles.
11+
type BundlePuller interface {
12+
// Pull retrieves a bundle definition.
13+
Pull(ctx context.Context, ref cnab.OCIReference) (cache.CachedBundle, error)
14+
15+
// ListTags retrieves all tags defined for a bundle.
16+
ListTags(ctx context.Context, ref cnab.OCIReference) ([]string, error)
17+
}

0 commit comments

Comments
 (0)