Skip to content

Commit

Permalink
wip: stub structs and logic for a bundle workflow
Browse files Browse the repository at this point in the history
* 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 <me@carolynvanslyck.com>

wip: porter strategy

Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>
  • Loading branch information
carolynvs committed Dec 24, 2022
1 parent 8bb1e30 commit 5f004f5
Show file tree
Hide file tree
Showing 53 changed files with 2,306 additions and 199 deletions.
3 changes: 3 additions & 0 deletions cmd/porter/installations.go
Original file line number Diff line number Diff line change
Expand Up @@ -139,6 +139,9 @@ You can use the show command to create the initial file:
"Force the bundle to be executed when no changes are detected.")
f.BoolVar(&opts.DryRun, "dry-run", false,
"Evaluate if the bundle would be executed based on the changes in the file.")
f.StringVarP(&opts.RawFormat, "output", "o", "plaintext",
"Specify an output format. Allowed values: plaintext, json, yaml")

return &cmd
}

Expand Down
1 change: 1 addition & 0 deletions docs/content/cli/installations_apply.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ porter installations apply FILE [flags]
--force Force the bundle to be executed when no changes are detected.
-h, --help help for apply
-n, --namespace string Namespace in which the installation is defined. Defaults to the namespace defined in the file.
-o, --output string Specify an output format. Allowed values: plaintext, json, yaml (default "plaintext")
```

### Options inherited from parent commands
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ require (
github.com/spf13/viper v1.10.0
github.com/stretchr/testify v1.8.1
github.com/xeipuuv/gojsonschema v1.2.0
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869
go.mongodb.org/mongo-driver v1.11.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.36.4
go.opentelemetry.io/otel v1.11.2
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,8 @@ github.com/xi2/xz v0.0.0-20171230120015-48954b6210f8/go.mod h1:HUYIGzjTL3rfEspMx
github.com/xiang90/probing v0.0.0-20190116061207-43a291ad63a2/go.mod h1:UETIi67q53MR2AWcXfiuqkDkRtnGDLqkBTpCHuJHxtU=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d h1:splanxYIlg+5LfHAM6xpdFEAYOk8iySO56hMFq6uLyA=
github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA=
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869 h1:7v7L5lsfw4w8iqBBXETukHo4IPltmD+mWoLRYUmeGN8=
github.com/yourbasic/graph v0.0.0-20210606180040-8ecfec1c2869/go.mod h1:Rfzr+sqaDreiCaoQbFCu3sTXxeFq/9kXRuyOoSlGQHE=
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=
Expand Down
4 changes: 2 additions & 2 deletions pkg/cnab/config-adapter/adapter.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,9 +426,9 @@ func (c *ManifestConverter) generateDependencies() (interface{}, string, error)
return deps, cnab.DependenciesV1ExtensionKey, nil
}

func (c *ManifestConverter) generateDependenciesV1() (*depsv1ext.Dependencies, error) {
func (c *ManifestConverter) generateDependenciesV1() *depsv1ext.Dependencies {
if len(c.Manifest.Dependencies.Requires) == 0 {
return nil, nil
return nil
}

deps := &depsv1ext.Dependencies{
Expand Down
129 changes: 129 additions & 0 deletions pkg/cnab/dependencies/v2/bundle_graph.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
package v2

import (
"sort"

"get.porter.sh/porter/pkg/cnab"
depsv2ext "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v2"
"github.com/yourbasic/graph"
)

// BundleGraph is a directed acyclic graph of a bundle and its dependencies
// (which may be other bundles, or installations) It is used to resolve the
// dependency order in which the bundles must be executed.
type BundleGraph struct {
// nodeKeys is a map from the node key to its index in nodes
nodeKeys map[string]int
nodes []Node
}

func NewBundleGraph() *BundleGraph {
return &BundleGraph{
nodeKeys: make(map[string]int),
}
}

// RegisterNode adds the specified node to the graph
// returning true if the node is already present.
func (g *BundleGraph) RegisterNode(node Node) bool {
_, exists := g.nodeKeys[node.GetKey()]
if !exists {
nodeIndex := len(g.nodes)
g.nodes = append(g.nodes, node)
g.nodeKeys[node.GetKey()] = nodeIndex
}
return exists
}

func (g *BundleGraph) Sort() ([]Node, bool) {
dag := graph.New(len(g.nodes))
for nodeIndex, node := range g.nodes {
for _, depKey := range node.GetRequires() {
depIndex, ok := g.nodeKeys[depKey]
if !ok {
panic("oops")
}
dag.Add(nodeIndex, depIndex)
}
}

indices, ok := graph.TopSort(dag)
if !ok {
return nil, false
}

// Reverse the sort so that items with no dependencies are listed first
count := len(indices)
results := make([]Node, count)
for i, nodeIndex := range indices {
results[count-i-1] = g.nodes[nodeIndex]
}
return results, true
}

func (g *BundleGraph) GetNode(key string) (Node, bool) {
if nodeIndex, ok := g.nodeKeys[key]; ok {
return g.nodes[nodeIndex], true
}
return nil, false
}

// Node in a BundleGraph.
type Node interface {
GetRequires() []string
GetKey() string
}

var _ Node = BundleNode{}
var _ Node = InstallationNode{}

// BundleNode is a Node in a BundleGraph that represents a dependency on a bundle
// that has not yet been installed.
type BundleNode struct {
Key string
ParentKey string
Reference cnab.BundleReference
Requires []string

// 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?
Credentials map[string]depsv2ext.DependencySource
Parameters map[string]depsv2ext.DependencySource
}

func (d BundleNode) GetKey() string {
return d.Key
}

func (d BundleNode) GetParentKey() string {
return d.ParentKey
}

func (d BundleNode) GetRequires() []string {
sort.Strings(d.Requires)
return d.Requires
}

func (d BundleNode) IsRoot() bool {
return d.Key == "root"
}

// InstallationNode is a Node in a BundleGraph that represents a dependency on an
// installed bundle (installation).
type InstallationNode struct {
Key string
ParentKey string
Namespace string
Name string
}

func (d InstallationNode) GetKey() string {
return d.Key
}

func (d InstallationNode) GetParentKey() string {
return d.ParentKey
}

func (d InstallationNode) GetRequires() []string {
return nil
}
70 changes: 70 additions & 0 deletions pkg/cnab/dependencies/v2/bundle_graph_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package v2

import (
"testing"

"github.com/stretchr/testify/assert"

"github.com/stretchr/testify/require"
)

func TestEngine_DependOnInstallation(t *testing.T) {
/*
A -> B (installation)
A -> C (bundle)
c.parameters.connstr <- B.outputs.connstr
*/

b := InstallationNode{Key: "b"}
c := BundleNode{
Key: "c",
Requires: []string{"b"},
}
a := BundleNode{
Key: "root",
Requires: []string{"b", "c"},
}

g := NewBundleGraph()
g.RegisterNode(a)
g.RegisterNode(b)
g.RegisterNode(c)
sortedNodes, ok := g.Sort()
require.True(t, ok, "graph should not be cyclic")

gotOrder := make([]string, len(sortedNodes))
for i, node := range sortedNodes {
gotOrder[i] = node.GetKey()
}
wantOrder := []string{
"b",
"c",
"root",
}
assert.Equal(t, wantOrder, gotOrder)
}

/*
✅ need to represent new dependency structure on an extended bundle wrapper
(put in cnab-go later)
need to read a bundle and make a BundleGraph
? how to handle a param that isn't a pure assignment, e.g. connstr: ${bundle.deps.VM.outputs.ip}:${bundle.deps.SVC.outputs.port}
? when are templates evaluated as the graph is executed (for simplicity, first draft no composition / templating)
need to resolve dependencies in the graph
* lookup against existing installations
* lookup against semver tags in registry
* lookup against bundle index? when would we look here? (i.e. preferred/registered implementations of interfaces)
need to turn the sorted nodes into an execution plan
execution plan needs:
* bundle to execute and the installation it will become
* parameters and credentials to pass
* sources:
root parameters/creds
installation outputs
need to write something that can run an execution plan
* knows how to grab sources and pass them into the bundle
*/
17 changes: 17 additions & 0 deletions pkg/cnab/dependencies/v2/bundle_puller.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package v2

import (
"context"

"get.porter.sh/porter/pkg/cache"
"get.porter.sh/porter/pkg/cnab"
)

// BundlePuller can query and pull bundles.
type BundlePuller interface {
// GetBundle retrieves a bundle definition.
GetBundle(ctx context.Context, ref cnab.OCIReference) (cache.CachedBundle, error)

// ListTags retrieves all tags defined for a bundle.
ListTags(ctx context.Context, ref cnab.OCIReference) ([]string, error)
}
Loading

0 comments on commit 5f004f5

Please sign in to comment.