Skip to content

Commit 5f004f5

Browse files
committed
wip: stub structs and logic for a bundle workflow
* resolve a dependency graph * identify the order of execution * create new call path to execute bundles from a workflow (i.e. jobs) * Add secret strategy for resolving data from the workflow * Support workflow wiring Signed-off-by: Carolyn Van Slyck <[email protected]> wip: porter strategy Signed-off-by: Carolyn Van Slyck <[email protected]>
1 parent 8bb1e30 commit 5f004f5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

53 files changed

+2306
-199
lines changed

cmd/porter/installations.go

+3
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,9 @@ You can use the show command to create the initial file:
139139
"Force the bundle to be executed when no changes are detected.")
140140
f.BoolVar(&opts.DryRun, "dry-run", false,
141141
"Evaluate if the bundle would be executed based on the changes in the file.")
142+
f.StringVarP(&opts.RawFormat, "output", "o", "plaintext",
143+
"Specify an output format. Allowed values: plaintext, json, yaml")
144+
142145
return &cmd
143146
}
144147

docs/content/cli/installations_apply.md

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ porter installations apply FILE [flags]
3737
--force Force the bundle to be executed when no changes are detected.
3838
-h, --help help for apply
3939
-n, --namespace string Namespace in which the installation is defined. Defaults to the namespace defined in the file.
40+
-o, --output string Specify an output format. Allowed values: plaintext, json, yaml (default "plaintext")
4041
```
4142

4243
### Options inherited from parent commands

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ require (
6060
github.com/spf13/viper v1.10.0
6161
github.com/stretchr/testify v1.8.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.11.0
6465
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4
6566
go.opentelemetry.io/otel v1.11.2

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -1591,6 +1591,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
15911591
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
15921592
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
15931593
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
1594+
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8=
1595+
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE=
15941596
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
15951597
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
15961598
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

pkg/cnab/config-adapter/adapter.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -426,9 +426,9 @@ func (c *ManifestConverter) generateDependencies() (interface{}, string, error)
426426
return deps, cnab.DependenciesV1ExtensionKey, nil
427427
}
428428

429-
func (c *ManifestConverter) generateDependenciesV1() (*depsv1ext.Dependencies, error) {
429+
func (c *ManifestConverter) generateDependenciesV1() *depsv1ext.Dependencies {
430430
if len(c.Manifest.Dependencies.Requires) == 0 {
431-
return nil, nil
431+
return nil
432432
}
433433

434434
deps := &depsv1ext.Dependencies{
+129
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
package v2
2+
3+
import (
4+
"sort"
5+
6+
"get.porter.sh/porter/pkg/cnab"
7+
depsv2ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v2"
8+
"github.com/yourbasic/graph"
9+
)
10+
11+
// BundleGraph is a directed acyclic graph of a bundle and its dependencies
12+
// (which may be other bundles, or installations) It is used to resolve the
13+
// dependency order in which the bundles must be executed.
14+
type BundleGraph struct {
15+
// nodeKeys is a map from the node key to its index in nodes
16+
nodeKeys map[string]int
17+
nodes []Node
18+
}
19+
20+
func NewBundleGraph() *BundleGraph {
21+
return &BundleGraph{
22+
nodeKeys: make(map[string]int),
23+
}
24+
}
25+
26+
// RegisterNode adds the specified node to the graph
27+
// returning true if the node is already present.
28+
func (g *BundleGraph) RegisterNode(node Node) bool {
29+
_, exists := g.nodeKeys[node.GetKey()]
30+
if !exists {
31+
nodeIndex := len(g.nodes)
32+
g.nodes = append(g.nodes, node)
33+
g.nodeKeys[node.GetKey()] = nodeIndex
34+
}
35+
return exists
36+
}
37+
38+
func (g *BundleGraph) Sort() ([]Node, bool) {
39+
dag := graph.New(len(g.nodes))
40+
for nodeIndex, node := range g.nodes {
41+
for _, depKey := range node.GetRequires() {
42+
depIndex, ok := g.nodeKeys[depKey]
43+
if !ok {
44+
panic("oops")
45+
}
46+
dag.Add(nodeIndex, depIndex)
47+
}
48+
}
49+
50+
indices, ok := graph.TopSort(dag)
51+
if !ok {
52+
return nil, false
53+
}
54+
55+
// Reverse the sort so that items with no dependencies are listed first
56+
count := len(indices)
57+
results := make([]Node, count)
58+
for i, nodeIndex := range indices {
59+
results[count-i-1] = g.nodes[nodeIndex]
60+
}
61+
return results, true
62+
}
63+
64+
func (g *BundleGraph) GetNode(key string) (Node, bool) {
65+
if nodeIndex, ok := g.nodeKeys[key]; ok {
66+
return g.nodes[nodeIndex], true
67+
}
68+
return nil, false
69+
}
70+
71+
// Node in a BundleGraph.
72+
type Node interface {
73+
GetRequires() []string
74+
GetKey() string
75+
}
76+
77+
var _ Node = BundleNode{}
78+
var _ Node = InstallationNode{}
79+
80+
// BundleNode is a Node in a BundleGraph that represents a dependency on a bundle
81+
// that has not yet been installed.
82+
type BundleNode struct {
83+
Key string
84+
ParentKey string
85+
Reference cnab.BundleReference
86+
Requires []string
87+
88+
// TODO(PEP003): DO we need to store this? Can we do it somewhere else or hold a reference to the dep and add more to the Node interface?
89+
Credentials map[string]depsv2ext.DependencySource
90+
Parameters map[string]depsv2ext.DependencySource
91+
}
92+
93+
func (d BundleNode) GetKey() string {
94+
return d.Key
95+
}
96+
97+
func (d BundleNode) GetParentKey() string {
98+
return d.ParentKey
99+
}
100+
101+
func (d BundleNode) GetRequires() []string {
102+
sort.Strings(d.Requires)
103+
return d.Requires
104+
}
105+
106+
func (d BundleNode) IsRoot() bool {
107+
return d.Key == "root"
108+
}
109+
110+
// InstallationNode is a Node in a BundleGraph that represents a dependency on an
111+
// installed bundle (installation).
112+
type InstallationNode struct {
113+
Key string
114+
ParentKey string
115+
Namespace string
116+
Name string
117+
}
118+
119+
func (d InstallationNode) GetKey() string {
120+
return d.Key
121+
}
122+
123+
func (d InstallationNode) GetParentKey() string {
124+
return d.ParentKey
125+
}
126+
127+
func (d InstallationNode) GetRequires() []string {
128+
return nil
129+
}
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()
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+
// GetBundle retrieves a bundle definition.
13+
GetBundle(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)