Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit bb1397e

Browse files
committedNov 14, 2022
wip: porter strategy
Signed-off-by: Carolyn Van Slyck <me@carolynvanslyck.com>
1 parent 89f0c59 commit bb1397e

File tree

7 files changed

+214
-27
lines changed

7 files changed

+214
-27
lines changed
 

‎pkg/cnab/extensions/dependencies/v2/types.go

+44-2
Original file line numberDiff line numberDiff line change
@@ -65,13 +65,14 @@ type DependencySource struct {
6565
Output string `json:"output,omitempty" mapstructure:"output,omitempty"`
6666
}
6767

68+
var dependencySourceWiringRegex = regexp.MustCompile(`bundle(\.dependencies\.([^.]+))?\.([^.]+)\.(.+)`)
69+
6870
// ParseDependencySource identifies the components specified in a wiring string.
6971
func ParseDependencySource(value string) (DependencySource, error) {
7072
// TODO(PEP003): At build time, check if a dependency source was defined with templating and error out
7173
// e.g. ${bundle.parameters.foo} should be bundle.parameters.foo
7274

73-
regex := regexp.MustCompile(`bundle(\.dependencies\.([^.]+))?\.([^.]+)\.(.+)`)
74-
matches := regex.FindStringSubmatch(value)
75+
matches := dependencySourceWiringRegex.FindStringSubmatch(value)
7576

7677
// If it doesn't match our wiring syntax, assume that it is a hard coded value
7778
if matches == nil || len(matches) < 5 {
@@ -162,6 +163,47 @@ func (s DependencySource) WiringSuffix() string {
162163
return s.Value
163164
}
164165

166+
type WorkflowWiring struct {
167+
WorkflowID string
168+
JobKey string
169+
Parameter string
170+
Credential string
171+
Output string
172+
}
173+
174+
var workflowWiringRegex = regexp.MustCompile(`workflow\.([^\.]+)\.jobs\.([^\.]+)\.([^\.]+)\.(.+)`)
175+
176+
func ParseWorkflowWiring(value string) (WorkflowWiring, error) {
177+
matches := workflowWiringRegex.FindStringSubmatch(value)
178+
if len(matches) < 5 {
179+
return WorkflowWiring{}, fmt.Errorf("invalid workflow wiring was passed to the porter strategy, %s", value)
180+
}
181+
182+
// the first group is the entire match, we don't care about it
183+
workflowID := matches[1]
184+
jobKey := matches[2]
185+
dataType := matches[3] // e.g. parameters, credentials or outputs
186+
dataKey := matches[4] // e.g. the name of the param/cred/output
187+
188+
wiring := WorkflowWiring{
189+
WorkflowID: workflowID,
190+
JobKey: jobKey,
191+
}
192+
193+
switch dataType {
194+
case "parameters":
195+
wiring.Parameter = dataKey
196+
case "credentials":
197+
wiring.Credential = dataKey
198+
case "outputs":
199+
wiring.Output = dataKey
200+
default:
201+
return WorkflowWiring{}, fmt.Errorf("invalid workflow wiring was passed to the porter strategy, %s", value)
202+
}
203+
204+
return wiring, nil
205+
}
206+
165207
type DependencyInstallation struct {
166208
Labels map[string]string `json:"labels,omitempty" mapstructure:"labels,omitempty"`
167209
Criteria *InstallationCriteria `json:"criteria,omitempty" mapstructure:"criteria,omitempty"`

‎pkg/cnab/extensions/dependencies/v2/types_test.go

+66-2
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
func TestDependencySource(t *testing.T) {
1111
t.Parallel()
1212

13+
jobKey := "1"
1314
testcases := []struct {
1415
name string
1516
bundleWiring string
@@ -89,8 +90,71 @@ func TestDependencySource(t *testing.T) {
8990
require.Equal(t, tc.bundleWiring, gotBundleWiring, "incorrect bundle wiring was returned")
9091

9192
// Check that we can convert to a workflow wiring form
92-
gotWorkflowWiring := gotSource.AsWorkflowWiring("1")
93-
require.Equal(t, tc.wantWorkflowWiring, gotWorkflowWiring, "incorrect workflow wiring was returned")
93+
gotWorkflowWiringValue := gotSource.AsWorkflowWiring(jobKey)
94+
require.Equal(t, tc.wantWorkflowWiring, gotWorkflowWiringValue, "incorrect workflow wiring string value was returned")
95+
} else {
96+
tests.RequireErrorContains(t, err, tc.wantErr)
97+
}
98+
})
99+
}
100+
}
101+
102+
func TestParseWorkflowWiring(t *testing.T) {
103+
t.Parallel()
104+
105+
testcases := []struct {
106+
name string
107+
wiringStr string
108+
wantWorkflowWiring WorkflowWiring
109+
wantErr string
110+
}{
111+
{ // Check that we can still pass hard-coded values in a workflow
112+
name: "value not supported",
113+
wiringStr: "11",
114+
wantErr: "invalid workflow wiring",
115+
},
116+
{
117+
name: "parameter",
118+
wiringStr: "workflow.abc123.jobs.myjerb.parameters.logLevel",
119+
wantWorkflowWiring: WorkflowWiring{
120+
WorkflowID: "abc123",
121+
JobKey: "myjerb",
122+
Parameter: "logLevel",
123+
},
124+
},
125+
{
126+
name: "credential",
127+
wiringStr: "workflow.myworkflow.jobs.root.credentials.kubeconfig",
128+
wantWorkflowWiring: WorkflowWiring{
129+
WorkflowID: "myworkflow",
130+
JobKey: "root",
131+
Credential: "kubeconfig",
132+
},
133+
},
134+
{
135+
name: "output",
136+
wiringStr: "workflow.abc123.jobs.mydb.outputs.connstr",
137+
wantWorkflowWiring: WorkflowWiring{
138+
WorkflowID: "abc123",
139+
JobKey: "mydb",
140+
Output: "connstr",
141+
},
142+
},
143+
{
144+
name: "dependencies not allowed",
145+
wiringStr: "workflow.abc123.jobs.root.dependencies.mydb.outputs.connstr",
146+
wantErr: "invalid workflow wiring",
147+
},
148+
}
149+
150+
for _, tc := range testcases {
151+
tc := tc
152+
t.Run(tc.name, func(t *testing.T) {
153+
t.Parallel()
154+
155+
gotWiring, err := ParseWorkflowWiring(tc.wiringStr)
156+
if tc.wantErr == "" {
157+
require.Equal(t, tc.wantWorkflowWiring, gotWiring, "incorrect WorkflowWiring was parsed")
94158
} else {
95159
tests.RequireErrorContains(t, err, tc.wantErr)
96160
}

‎pkg/porter/porter_strategy.go

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package porter
2+
3+
import (
4+
"context"
5+
"fmt"
6+
"regexp"
7+
8+
"get.porter.sh/porter/pkg/storage"
9+
10+
v2 "get.porter.sh/porter/pkg/cnab/extensions/dependencies/v2"
11+
"get.porter.sh/porter/pkg/tracing"
12+
)
13+
14+
// PorterSecretStrategy knows how to resolve specially formatted wiring strings
15+
// such as workflow.jobs.db.outputs.connstr from Porter instead of from a plugin.
16+
// It is not written as a plugin because it is much more straightforward to
17+
// retrieve the data already loaded in the running Porter instance than to start
18+
// another one, load its config and requery the database.
19+
type PorterSecretStrategy struct {
20+
installations storage.InstallationProvider
21+
}
22+
23+
// regular expression for parsing a workflow wiring string, such as workflow.jobs.db.outputs.connstr
24+
var workflowWiringRegex = regexp.MustCompile(`workflow\.jobs\.([^\.]+)\.(.+)`)
25+
26+
func (s PorterSecretStrategy) Resolve(ctx context.Context, keyName string, keyValue string) (string, error) {
27+
ctx, span := tracing.StartSpan(ctx)
28+
defer span.EndSpan()
29+
30+
// TODO(PEP003): It would be great when we configure this strategy that we also do host, so that host secret resolution isn't deferred to the plugins
31+
// i.e. we can configure a secret strategy and still be able to resolve directly in porter any host values.
32+
if keyName != "porter" {
33+
return "", fmt.Errorf("attempted to resolve secrets of type %s from the porter strategy", keyName)
34+
}
35+
36+
wiring, err := v2.ParseWorkflowWiring(keyValue)
37+
if err != nil {
38+
return "", fmt.Errorf("invalid workflow wiring was passed to the porter strategy, %s", keyValue)
39+
}
40+
41+
// TODO(PEP003): How do we want to re-resolve credentials passed to the root bundle? They aren't recorded so it's not a simple lookup
42+
if wiring.Parameter != "" {
43+
// TODO(PEP003): Resolve a parameter from another job that has not run yet
44+
// 1. Find the workflow definition from the db (need a way to track "current" workflow)
45+
// 2. Grab the job based on the jobid in the workflow wiring
46+
// 3. First check the parameters field for the param, resolve just that if available, otherwise resolve parameter sets and get it from there
47+
// it sure would help if we remembered what params are in each set
48+
} else if wiring.Output != "" {
49+
// TODO(PEP003): Resolve the output from an already executed job
50+
}
51+
52+
panic("not implemented")
53+
}

‎pkg/porter/workflow_engine.go

+8-7
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ func (t Engine) CreateWorkflow(ctx context.Context, opts CreateWorkflowOptions)
7272
}
7373

7474
// Now build job definitions for each node in the graph
75-
jobs := make(map[string]storage.Job, len(nodes))
75+
jobs := make(map[string]*storage.Job, len(nodes))
7676
for _, node := range nodes {
7777
switch tn := node.(type) {
7878
case depsv2.BundleNode:
@@ -117,7 +117,7 @@ func (t Engine) CreateWorkflow(ctx context.Context, opts CreateWorkflowOptions)
117117
}
118118
}
119119

120-
jobs[tn.Key] = storage.Job{
120+
jobs[tn.Key] = &storage.Job{
121121
Action: cnab.ActionInstall, // TODO(PEP003): eventually this needs to support all actions
122122
Installation: inst,
123123
Depends: requiredJobs,
@@ -211,8 +211,8 @@ func (t Engine) executeStage(ctx context.Context, w storage.Workflow, stageIndex
211211
return fmt.Errorf("could not sort jobs in stage")
212212
}
213213

214-
availableJobs := make(chan storage.Job, len(s.Jobs))
215-
completedJobs := make(chan storage.Job, len(s.Jobs))
214+
availableJobs := make(chan *storage.Job, len(s.Jobs))
215+
completedJobs := make(chan *storage.Job, len(s.Jobs))
216216

217217
// Default the number of parallel jobs to the number of CPUs
218218
// This gives us 1 CPU per invocation image.
@@ -235,6 +235,7 @@ func (t Engine) executeStage(ctx context.Context, w storage.Workflow, stageIndex
235235
return
236236
case job := <-availableJobs:
237237
jobsInProgress.Go(func() error {
238+
// TODO(PEP003) why do we have to look this up again?
238239
return t.executeJob(ctx, s.Jobs[job.Key], completedJobs)
239240
})
240241
}
@@ -283,15 +284,15 @@ func (t Engine) executeStage(ctx context.Context, w storage.Workflow, stageIndex
283284
return err
284285
}
285286

286-
func (t Engine) queueAvailableJobs(ctx context.Context, s storage.Stage, sortedNodes []depsv2.Node, availableJobs chan storage.Job) bool {
287+
func (t Engine) queueAvailableJobs(ctx context.Context, s storage.Stage, sortedNodes []depsv2.Node, availableJobs chan *storage.Job) bool {
287288
// Walk through the graph in sorted order (bottom up)
288289
// if the node's dependencies are all successful, schedule it
289290
// as soon as it's not schedule, stop looking because none of the remainder will be either
290291
var i int
291292
for i = 0; i < len(sortedNodes); i++ {
292293
node := sortedNodes[i]
293294

294-
job := node.(storage.Job)
295+
job := node.(*storage.Job)
295296
switch job.Status.Status {
296297
case cnab.StatusFailed:
297298
// stop scheduling more jobs
@@ -320,7 +321,7 @@ func (t Engine) queueAvailableJobs(ctx context.Context, s storage.Stage, sortedN
320321
return i >= len(sortedNodes)
321322
}
322323

323-
func (t Engine) executeJob(ctx context.Context, j storage.Job, jobs chan storage.Job) error {
324+
func (t Engine) executeJob(ctx context.Context, j *storage.Job, jobs chan *storage.Job) error {
324325
ctx, span := tracing.StartSpan(ctx, tracing.ObjectAttribute("job", j))
325326
defer span.EndSpan()
326327

‎pkg/secrets/plugin_adapter.go

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,12 @@ func (a PluginAdapter) Close() error {
2828
}
2929

3030
func (a PluginAdapter) Resolve(ctx context.Context, keyName string, keyValue string) (string, error) {
31+
// Instead of calling out to a plugin, resolve the value from Porter's database
32+
// This supports bundle workflows where we are sourcing data from other runs, e.g. passing a connection string from a dependency to another bundle
33+
if keyName == "porter" {
34+
35+
}
36+
3137
return a.plugin.Resolve(ctx, keyName, keyValue)
3238
}
3339

‎pkg/storage/workflow.go

+31-10
Original file line numberDiff line numberDiff line change
@@ -27,27 +27,38 @@ type WorkflowSpec struct {
2727
MaxParallel int `json:"maxParallel"`
2828

2929
// DebugMode tweaks how the workflow is run to make it easier to debug
30-
DebugMode bool `json:"DebugMode"`
30+
DebugMode bool `json:"debugMode"`
3131
}
3232

3333
// TODO(PEP003): Figure out what needs to be persisted, and how to persist multiple or continued runs
3434
type WorkflowStatus struct {
3535
}
3636

3737
// Prepare updates the internal data representation of the workflow before running it.
38-
func (w Workflow) Prepare() {
39-
for _, stage := range w.Stages {
40-
for jobKey, job := range stage.Jobs {
41-
job.Key = jobKey
42-
stage.Jobs[jobKey] = job
43-
}
38+
func (w *Workflow) Prepare() {
39+
// Assign an id to the workflow
40+
w.ID = cnab.NewULID()
41+
42+
// Update any workflow wiring to use the workflow id?
43+
44+
for _, s := range w.Stages {
45+
s.Prepare(w.ID)
4446
}
4547
}
4648

4749
// Stage represents a set of jobs that should run, possibly in parallel.
4850
type Stage struct {
4951
// Jobs is the set of bundles to execute, keyed by the job name.
50-
Jobs map[string]Job `json:"jobs"`
52+
Jobs map[string]*Job `json:"jobs"`
53+
}
54+
55+
func (s *Stage) Prepare(workflowID string) {
56+
// Update the jobs so that they know their job key (since they won't be used within the larger workflow, but as independent jobs)
57+
for jobKey, job := range s.Jobs {
58+
job.Prepare(workflowID, jobKey)
59+
s.Jobs[jobKey] = job
60+
}
61+
5162
}
5263

5364
// Job represents the execution of a bundle.
@@ -70,14 +81,24 @@ type Job struct {
7081
Status JobStatus `json:"status,omitempty"`
7182
}
7283

73-
func (j Job) GetRequires() []string {
84+
func (j *Job) GetRequires() []string {
7485
return j.Depends
7586
}
7687

77-
func (j Job) GetKey() string {
88+
func (j *Job) GetKey() string {
7889
return j.Key
7990
}
8091

92+
func (j *Job) Prepare(workflowId string, jobKey string) {
93+
j.Key = jobKey
94+
for _, param := range j.Installation.Parameters.Parameters {
95+
if param.Source.Key != "porter" {
96+
continue
97+
}
98+
99+
}
100+
}
101+
81102
type JobStatus struct {
82103
Status string `json:"status"`
83104
Message string `json:"message"`

‎tests/integration/testdata/workflow/mybuns.yaml

+6-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
schemaVersion: 1.0.0-alpha.1
22
name: "apply mybuns.yaml"
3-
maxparallel: 1
4-
debugmode: false
3+
maxParallel: 1
4+
debugMode: false
55
stages:
66
- jobs:
77
root:
88
action: install
99
installation:
10-
schemaversion: 1.0.2
10+
schemaVersion: 1.0.2
1111
name: mybuns
1212
namespace: dev
1313
bundle:
@@ -28,7 +28,7 @@ stages:
2828
root/db:
2929
action: install
3030
installation:
31-
schemaversion: 1.0.2
31+
schemaVersion: 1.0.2
3232
name: mybuns/db
3333
namespace: dev
3434
bundle:
@@ -37,5 +37,5 @@ stages:
3737
tag: v0.1.0
3838
parameters:
3939
- name: database
40-
source:
41-
value: bigdb
40+
source:
41+
value: bigdb

0 commit comments

Comments
 (0)
Please sign in to comment.