Skip to content

Commit 2fe2c7f

Browse files
committed
wip: test passes with stuff commented out because dry-run prints the wrong format still
Signed-off-by: Carolyn Van Slyck <[email protected]>
1 parent 510954a commit 2fe2c7f

File tree

12 files changed

+212
-46
lines changed

12 files changed

+212
-46
lines changed

pkg/porter/porter.go

+1
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ func NewFor(c *config.Config, store storage.Store, secretStorage secrets.Store)
6464
credStorage := storage.NewCredentialStore(storageManager, secretStorage)
6565
paramStorage := storage.NewParameterStore(storageManager, secretStorage)
6666
sanitizerService := storage.NewSanitizer(paramStorage, secretStorage)
67+
secretStorage.SetPorterStrategy(NewPorterSecretStrategy(installationStorage, sanitizerService))
6768
storageManager.Initialize(sanitizerService) // we have a bit of a dependency problem here that it would be great to figure out eventually
6869

6970
return &Porter{

pkg/porter/porter_strategy.go

+61-6
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ package porter
22

33
import (
44
"context"
5-
"fmt"
6-
"regexp"
5+
6+
"go.mongodb.org/mongo-driver/bson"
77

88
"get.porter.sh/porter/pkg/storage"
99

@@ -16,12 +16,15 @@ import (
1616
// It is not written as a plugin because it is much more straightforward to
1717
// retrieve the data already loaded in the running Porter instance than to start
1818
// another one, load its config and requery the database.
19+
// This should always run in-process within porter and never as an out-of-process plugin.
1920
type PorterSecretStrategy struct {
2021
installations storage.InstallationProvider
22+
sanitizer *storage.Sanitizer
2123
}
2224

23-
// regular expression for parsing a workflow wiring string, such as workflow.jobs.db.outputs.connstr
24-
var workflowWiringRegex = regexp.MustCompile(`workflow\.jobs\.([^\.]+)\.(.+)`)
25+
func NewPorterSecretStrategy(installations storage.InstallationProvider, sanitizer *storage.Sanitizer) PorterSecretStrategy {
26+
return PorterSecretStrategy{installations: installations, sanitizer: sanitizer}
27+
}
2528

2629
func (s PorterSecretStrategy) Resolve(ctx context.Context, keyName string, keyValue string) (string, error) {
2730
ctx, span := tracing.StartSpan(ctx)
@@ -30,23 +33,75 @@ func (s PorterSecretStrategy) Resolve(ctx context.Context, keyName string, keyVa
3033
// 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
3134
// i.e. we can configure a secret strategy and still be able to resolve directly in porter any host values.
3235
if keyName != "porter" {
33-
return "", fmt.Errorf("attempted to resolve secrets of type %s from the porter strategy", keyName)
36+
return "", span.Errorf("attempted to resolve secrets of type %s from the porter strategy", keyName)
3437
}
3538

3639
wiring, err := v2.ParseWorkflowWiring(keyValue)
3740
if err != nil {
38-
return "", fmt.Errorf("invalid workflow wiring was passed to the porter strategy, %s", keyValue)
41+
return "", span.Errorf("invalid workflow wiring was passed to the porter strategy, %s", keyValue)
3942
}
4043

44+
// We support retrieving certain data from Porter's database:
45+
// workflow.WORKFLOWID.jobs.JOBKEY.outputs.OUTPUT
46+
// The WORKFLOWID is set to the current executing workflow by the workflow engine before running the job
47+
// It is not stored in the database and is always set dynamically when the job is run.
48+
4149
// 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
4250
if wiring.Parameter != "" {
4351
// TODO(PEP003): Resolve a parameter from another job that has not run yet
52+
// IS THIS ACTUALLY A PROBLEM? We pass creds/params from the root job, which we need to deal with, but otherwise we only pass outputs from non-root jobs
4453
// 1. Find the workflow definition from the db (need a way to track "current" workflow)
4554
// 2. Grab the job based on the jobid in the workflow wiring
4655
// 3. First check the parameters field for the param, resolve just that if available, otherwise resolve parameter sets and get it from there
4756
// it sure would help if we remembered what params are in each set
57+
58+
return "", nil
4859
} else if wiring.Output != "" {
4960
// TODO(PEP003): Resolve the output from an already executed job
61+
62+
// Lookup the result and run associated with the job run in that workflow
63+
w, err := s.installations.GetWorkflow(ctx, wiring.WorkflowID)
64+
if err != nil {
65+
return "", span.Errorf("error retrieving workflow %s: %w", wiring.WorkflowID, err)
66+
}
67+
68+
// Prepare internal data structures of the workflow
69+
w.Prepare()
70+
71+
// locate the job in the workflow
72+
j, err := w.GetJob(wiring.JobKey)
73+
if err != nil {
74+
return "", span.Errorf("error retrieving job from workflow %s: %w", wiring.WorkflowID)
75+
}
76+
77+
if j.Status.LastResultID == "" {
78+
return "", span.Errorf("error retrieving job status for %s in workflow %s, no result recorded yet", wiring.JobKey, wiring.WorkflowID)
79+
}
80+
81+
outputs, err := s.installations.FindOutputs(ctx, storage.FindOptions{
82+
Sort: []string{"-_id"},
83+
Skip: 0,
84+
Limit: 1,
85+
Filter: bson.M{
86+
"resultId": j.Status.LastResultID,
87+
"name": wiring.Output,
88+
},
89+
})
90+
if err != nil {
91+
// TODO(PEP003): Move a lot of these values into the span attributes instead of in the error message
92+
return "", span.Errorf("error retrieving output %s from result %s for job %s in workflow %s: %w", wiring.Output, j.Status.LastResultID, wiring.JobKey, wiring.WorkflowID)
93+
}
94+
95+
if len(outputs) == 0 {
96+
return "", span.Errorf("no output named %s, found for result %s for job %s in workflow %s", wiring.Output, j.Status.LastResultID, wiring.JobKey, wiring.WorkflowID)
97+
}
98+
99+
output, err := s.sanitizer.RestoreOutput(ctx, outputs[1])
100+
if err != nil {
101+
return "", span.Errorf("error restoring output named %s, found for result %s for job %s in workflow %s", wiring.Output, j.Status.LastResultID, wiring.JobKey, wiring.WorkflowID)
102+
}
103+
104+
return string(output.Value), nil
50105
}
51106

52107
panic("not implemented")

pkg/runtime/runtime_manifest.go

+30-16
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,9 @@ type RuntimeManifest struct {
4545
// do advanced stuff with the manifest, like just read out the yaml for a particular step.
4646
editor *yaml.Editor
4747

48-
// bundles is map of the dependencies bundle definitions, keyed by the alias used in the root manifest
49-
bundles map[string]cnab.ExtendedBundle
48+
// depsv1_bundles is map of the dependencies bundle definitions, keyed by the alias used in the root manifest
49+
// This is only populated for a bundle built for dependencies v1
50+
depsv1_bundles map[string]cnab.ExtendedBundle
5051

5152
steps manifest.Steps
5253
outputs map[string]string
@@ -74,9 +75,12 @@ func (m *RuntimeManifest) Validate() error {
7475
return err
7576
}
7677

77-
err = m.loadDependencyDefinitions()
78-
if err != nil {
79-
return err
78+
fmt.Println("carolyn was here")
79+
if m.bundle.HasDependenciesV1() {
80+
err = m.loadDependencyDefinitions()
81+
if err != nil {
82+
return err
83+
}
8084
}
8185

8286
err = m.setStepsByAction()
@@ -112,7 +116,11 @@ func (m *RuntimeManifest) GetInstallationName() string {
112116
}
113117

114118
func (m *RuntimeManifest) loadDependencyDefinitions() error {
115-
m.bundles = make(map[string]cnab.ExtendedBundle, len(m.Dependencies.Requires))
119+
if !m.bundle.HasDependenciesV1() {
120+
return nil
121+
}
122+
123+
m.depsv1_bundles = make(map[string]cnab.ExtendedBundle, len(m.Dependencies.Requires))
116124
for _, dep := range m.Dependencies.Requires {
117125
bunD, err := GetDependencyDefinition(m.config.Context, dep.Name)
118126
if err != nil {
@@ -124,7 +132,7 @@ func (m *RuntimeManifest) loadDependencyDefinitions() error {
124132
return fmt.Errorf("error unmarshaling bundle definition for dependency %s: %w", dep.Name, err)
125133
}
126134

127-
m.bundles[dep.Name] = cnab.NewBundle(*bun)
135+
m.depsv1_bundles[dep.Name] = cnab.NewBundle(*bun)
128136
}
129137

130138
return nil
@@ -290,15 +298,17 @@ func (m *RuntimeManifest) buildSourceData() (map[string]interface{}, error) {
290298
}
291299

292300
deps := make(map[string]interface{})
293-
bun["dependencies"] = deps
294-
for alias, depB := range m.bundles {
295-
// bundle.dependencies.ALIAS.outputs.NAME
296-
depBun := make(map[string]interface{})
297-
deps[alias] = depBun
301+
if m.bundle.HasDependenciesV1() {
302+
bun["dependencies"] = deps
303+
for alias, depB := range m.depsv1_bundles {
304+
// bundle.dependencies.ALIAS.outputs.NAME
305+
depBun := make(map[string]interface{})
306+
deps[alias] = depBun
298307

299-
depBun["name"] = depB.Name
300-
depBun["version"] = depB.Version
301-
depBun["description"] = depB.Description
308+
depBun["name"] = depB.Name
309+
depBun["version"] = depB.Version
310+
depBun["description"] = depB.Description
311+
}
302312
}
303313

304314
bun["outputs"] = m.outputs
@@ -338,6 +348,10 @@ func (m *RuntimeManifest) buildSourceData() (map[string]interface{}, error) {
338348
for _, s := range sources.ListSourcesByPriority() {
339349
switch ps := s.(type) {
340350
case cnab.DependencyOutputParameterSource:
351+
if m.bundle.HasDependenciesV1() {
352+
return nil, fmt.Errorf("bundle was not built for dependencies v1 but uses a dependency parameter source which is invalid")
353+
}
354+
341355
outRef := manifest.DependencyOutputReference{Dependency: ps.Dependency, Output: ps.OutputName}
342356

343357
// Ignore anything that isn't templated, because that's what we are building the source data for
@@ -362,7 +376,7 @@ func (m *RuntimeManifest) buildSourceData() (map[string]interface{}, error) {
362376
depOutputs[ps.OutputName] = value
363377

364378
// Determine if the dependency's output is defined as sensitive
365-
depB := m.bundles[ps.Dependency]
379+
depB := m.depsv1_bundles[ps.Dependency]
366380
if ok, _ := depB.IsOutputSensitive(ps.OutputName); ok {
367381
m.setSensitiveValue(value)
368382
}

pkg/secrets/helpers.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import inmemory "get.porter.sh/porter/pkg/secrets/plugins/in-memory"
55
var _ Store = &TestSecretsProvider{}
66

77
type TestSecretsProvider struct {
8-
PluginAdapter
8+
*PluginAdapter
99

1010
secrets *inmemory.Store
1111
}

pkg/secrets/plugin_adapter.go

+20-9
Original file line numberDiff line numberDiff line change
@@ -7,36 +7,47 @@ import (
77
"get.porter.sh/porter/pkg/secrets/plugins"
88
)
99

10-
var _ Store = PluginAdapter{}
10+
var _ Store = &PluginAdapter{}
11+
12+
type PorterSecretStrategy interface {
13+
Resolve(ctx context.Context, keyName string, keyValue string) (string, error)
14+
}
1115

1216
// PluginAdapter converts between the low-level plugins.SecretsProtocol and
1317
// the secrets.Store interface.
1418
type PluginAdapter struct {
15-
plugin plugins.SecretsProtocol
19+
plugin plugins.SecretsProtocol
20+
porterPlugin PorterSecretStrategy
21+
}
22+
23+
func (a *PluginAdapter) SetPorterStrategy(strategy PorterSecretStrategy) {
24+
a.porterPlugin = strategy
1625
}
1726

1827
// NewPluginAdapter wraps the specified storage plugin.
19-
func NewPluginAdapter(plugin plugins.SecretsProtocol) PluginAdapter {
20-
return PluginAdapter{plugin: plugin}
28+
func NewPluginAdapter(plugin plugins.SecretsProtocol) *PluginAdapter {
29+
return &PluginAdapter{
30+
plugin: plugin,
31+
}
2132
}
2233

23-
func (a PluginAdapter) Close() error {
34+
func (a *PluginAdapter) Close() error {
2435
if closer, ok := a.plugin.(io.Closer); ok {
2536
return closer.Close()
2637
}
2738
return nil
2839
}
2940

30-
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
41+
func (a *PluginAdapter) Resolve(ctx context.Context, keyName string, keyValue string) (string, error) {
42+
// Intercept requests for Porter to resolve an internal value and run the plugin in-process.
3243
// This supports bundle workflows where we are sourcing data from other runs, e.g. passing a connection string from a dependency to another bundle
3344
if keyName == "porter" {
34-
45+
return a.porterPlugin.Resolve(ctx, keyName, keyValue)
3546
}
3647

3748
return a.plugin.Resolve(ctx, keyName, keyValue)
3849
}
3950

40-
func (a PluginAdapter) Create(ctx context.Context, keyName string, keyValue string, value string) error {
51+
func (a *PluginAdapter) Create(ctx context.Context, keyName string, keyValue string, value string) error {
4152
return a.plugin.Create(ctx, keyName, keyValue, value)
4253
}

pkg/secrets/plugins/porter/store.go

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
package inmemory
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"regexp"
8+
9+
"get.porter.sh/porter/pkg/secrets/plugins"
10+
"get.porter.sh/porter/pkg/storage"
11+
"github.com/cnabio/cnab-go/secrets/host"
12+
)
13+
14+
var _ plugins.SecretsProtocol = &Store{}
15+
16+
// Store implements an in-process plugin for retrieving values from Porter's database
17+
// This never runs in an external process, or even as an internal plugin, because it requires loading all of Porter's config.
18+
type Store struct {
19+
Installations storage.InstallationStore
20+
}
21+
22+
func NewStore() *Store {
23+
return &Store{}
24+
}
25+
26+
var workflowSecretRegexp = regexp.MustCompile(`workflow\.([^.]+)?\.jobs\.([^.]+)?\.outputs\.(.+)`)
27+
28+
func (s *Store) Resolve(ctx context.Context, keyName string, keyValue string) (string, error) {
29+
if keyName == "porter" {
30+
// We support retrieving certain data from Porter's database:
31+
// workflow.WORKFLOWID.jobs.JOBKEY.outputs.OUTPUT
32+
// The WORKFLOWID is set to the current executing workflow by the workflow engine before running the job
33+
// It is not stored in the database and is always set dynamically when the job is run.
34+
35+
matches := workflowSecretRegexp.FindStringSubmatch(keyValue)
36+
if len(matches) != 4 {
37+
return "", fmt.Errorf("invalid porter secret mapping value: expected the format workflow.WORKFLOWID.jobs.JOBKEY.outputs.OUTPUT but got %s", keyValue)
38+
}
39+
40+
// Lookup the result and run associated with the job run in that workflow
41+
42+
}
43+
44+
// Fallback to the host secret plugin
45+
hostStore := host.SecretStore{}
46+
return hostStore.Resolve(keyName, keyValue)
47+
}
48+
49+
func (s *Store) Create(ctx context.Context, keyName string, keyValue string, value string) error {
50+
return errors.New("The porter secrets plugin does not support the create function, because it is for internal use within Porter only. Chek your porter configuration file and make sure that you are using a supported secrets plugin and not the porter secrets plugin directly.")
51+
}

pkg/secrets/secrets.go

+4
Original file line numberDiff line numberDiff line change
@@ -27,4 +27,8 @@ type Store interface {
2727
// - keyName=key, keyValue=conn-string, value=redis://foo
2828
// - keyName=path, keyValue=/tmp/connstring.txt, value=redis://foo
2929
Create(ctx context.Context, keyName string, keyValue string, value string) error
30+
31+
// SetPorterStrategy gives the secret store the ability to resolve the porter secret strategy
32+
// using Porter's database.
33+
SetPorterStrategy(strategy PorterSecretStrategy)
3034
}

pkg/storage/installation_provider.go

+4
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ type InstallationProvider interface {
5555
// GetLastRun returns the last run of an Installation.
5656
GetLastRun(ctx context.Context, namespace string, installation string) (Run, error)
5757

58+
// FindOutputs applies the find operation against outputs collection
59+
// using the specified options.
60+
FindOutputs(ctx context.Context, opts FindOptions) ([]Output, error)
61+
5862
// GetLastOutput returns the most recent value (last) of the specified
5963
// Output associated with the installation.
6064
GetLastOutput(ctx context.Context, namespace string, installation string, name string) (Output, error)

pkg/storage/installation_store.go

+9
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,15 @@ func (s InstallationStore) FindInstallations(ctx context.Context, findOpts FindO
142142
return out, err
143143
}
144144

145+
func (s InstallationStore) FindOutputs(ctx context.Context, findOpts FindOptions) ([]Output, error) {
146+
_, log := tracing.StartSpan(ctx)
147+
defer log.EndSpan()
148+
149+
var out []Output
150+
err := s.store.Find(ctx, CollectionOutputs, findOpts, &out)
151+
return out, err
152+
}
153+
145154
func (s InstallationStore) GetInstallation(ctx context.Context, namespace string, name string) (Installation, error) {
146155
var out Installation
147156

0 commit comments

Comments
 (0)