Skip to content

Commit 5fed9dc

Browse files
committed
Implement sql parameter store and credential store
Signed-off-by: robinbraemer <[email protected]>
1 parent 54ccd3d commit 5fed9dc

22 files changed

+833
-416
lines changed

pkg/cnab/provider/helpers.go

+7-12
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,14 @@ import (
44
"context"
55
"testing"
66

7+
"github.com/stretchr/testify/require"
8+
79
"get.porter.sh/porter/pkg/cnab"
810
"get.porter.sh/porter/pkg/config"
911
"get.porter.sh/porter/pkg/portercontext"
1012
"get.porter.sh/porter/pkg/secrets"
1113
"get.porter.sh/porter/pkg/storage"
1214
"get.porter.sh/porter/pkg/test"
13-
"github.com/hashicorp/go-multierror"
14-
"github.com/stretchr/testify/require"
1515
)
1616

1717
const debugDriver = "debug"
@@ -31,6 +31,11 @@ func NewTestRuntime(t *testing.T) *TestRuntime {
3131
tc := config.NewTestConfig(t)
3232
testStorage := storage.NewTestStore(tc)
3333
testSecrets := secrets.NewTestSecretsProvider()
34+
t.Cleanup(func() {
35+
_ = testStorage.Close()
36+
_ = testSecrets.Close()
37+
})
38+
3439
testInstallations := storage.NewTestInstallationProviderFor(tc.TestContext.T, testStorage)
3540
testCredentials := storage.NewTestCredentialProviderFor(tc.TestContext.T, testStorage, testSecrets)
3641
testParameters := storage.NewTestParameterProviderFor(tc.TestContext.T, testStorage, testSecrets)
@@ -49,16 +54,6 @@ func NewTestRuntimeFor(tc *config.TestConfig, testInstallations *storage.TestIns
4954
}
5055
}
5156

52-
func (t *TestRuntime) Close() error {
53-
var bigErr *multierror.Error
54-
55-
bigErr = multierror.Append(bigErr, t.TestInstallations.Close())
56-
bigErr = multierror.Append(bigErr, t.TestCredentials.Close())
57-
bigErr = multierror.Append(bigErr, t.TestParameters.Close())
58-
59-
return bigErr.ErrorOrNil()
60-
}
61-
6257
func (t *TestRuntime) LoadBundle(bundleFile string) (cnab.ExtendedBundle, error) {
6358
return t.Runtime.LoadBundle(bundleFile)
6459
}

pkg/porter/lifecycle.go

+3-14
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"strings"
1010
"unicode"
1111

12+
"github.com/opencontainers/go-digest"
13+
1214
"get.porter.sh/porter/pkg/cache"
1315
"get.porter.sh/porter/pkg/cnab"
1416
"get.porter.sh/porter/pkg/cnab/drivers"
@@ -18,8 +20,6 @@ import (
1820
"get.porter.sh/porter/pkg/secrets"
1921
"get.porter.sh/porter/pkg/storage"
2022
"get.porter.sh/porter/pkg/tracing"
21-
"github.com/opencontainers/go-digest"
22-
"go.mongodb.org/mongo-driver/bson"
2323
)
2424

2525
// BundleAction is an interface that defines a method for supplying
@@ -456,18 +456,7 @@ func (p *Porter) createRun(ctx context.Context, bundleRef cnab.BundleReference,
456456
for _, csName := range currentRun.CredentialSets {
457457
var cs storage.CredentialSet
458458
// Try to get the creds in the local namespace first, fallback to the global creds
459-
query := storage.FindOptions{
460-
Sort: []string{"-namespace"},
461-
Filter: bson.M{
462-
"name": csName,
463-
"$or": []bson.M{
464-
{"namespace": ""},
465-
{"namespace": currentRun.Namespace},
466-
},
467-
},
468-
}
469-
store := p.Credentials.GetDataStore()
470-
err := store.FindOne(ctx, storage.CollectionCredentials, query, &cs)
459+
cs, err := p.Credentials.FindCredentialSet(ctx, currentRun.Namespace, csName)
471460
if err != nil {
472461
return storage.Run{}, span.Errorf("could not find credential set named %s in the %s namespace or global namespace: %w", csName, inst.Namespace, err)
473462
}

pkg/porter/parameters.go

+7-20
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ import (
1111
"strings"
1212
"time"
1313

14+
dtprinter "github.com/carolynvs/datetime-printer"
15+
"github.com/cnabio/cnab-go/bundle"
16+
"github.com/cnabio/cnab-go/bundle/definition"
17+
"github.com/olekukonko/tablewriter"
18+
1419
"get.porter.sh/porter/pkg/cnab"
1520
"get.porter.sh/porter/pkg/editor"
1621
"get.porter.sh/porter/pkg/encoding"
@@ -19,11 +24,6 @@ import (
1924
"get.porter.sh/porter/pkg/secrets"
2025
"get.porter.sh/porter/pkg/storage"
2126
"get.porter.sh/porter/pkg/tracing"
22-
dtprinter "github.com/carolynvs/datetime-printer"
23-
"github.com/cnabio/cnab-go/bundle"
24-
"github.com/cnabio/cnab-go/bundle/definition"
25-
"github.com/olekukonko/tablewriter"
26-
"go.mongodb.org/mongo-driver/bson"
2727
)
2828

2929
// ParameterShowOptions represent options for Porter's parameter show command
@@ -361,21 +361,8 @@ func (p *Porter) loadParameterSets(ctx context.Context, bun cnab.ExtendedBundle,
361361
resolvedParameters := secrets.Set{}
362362

363363
for _, name := range params {
364-
// Try to get the params in the local namespace first, fallback to the global creds
365-
query := storage.FindOptions{
366-
Sort: []string{"-namespace"},
367-
Filter: bson.M{
368-
"name": name,
369-
"$or": []bson.M{
370-
{"namespace": ""},
371-
{"namespace": namespace},
372-
},
373-
},
374-
}
375-
store := p.Parameters.GetDataStore()
376-
377-
var pset storage.ParameterSet
378-
err := store.FindOne(ctx, storage.CollectionParameters, query, &pset)
364+
// Try to get the params in the local namespace first, fallback to the global params
365+
pset, err := p.Parameters.FindParameterSet(ctx, name, namespace)
379366
if err != nil {
380367
return nil, err
381368
}

pkg/secrets/strategy.go

+15
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package secrets
22

33
import (
4+
"database/sql/driver"
45
"encoding/json"
56
"errors"
67
"fmt"
@@ -128,3 +129,17 @@ func (l StrategyList) Swap(i, j int) {
128129
func (l StrategyList) Len() int {
129130
return len(l)
130131
}
132+
133+
// Scan implements the sql.Scanner interface for StrategyList.
134+
func (l *StrategyList) Scan(value interface{}) error {
135+
bytes, ok := value.([]byte)
136+
if !ok {
137+
return fmt.Errorf("unsupported Scan, storing driver.Value type %T into type *secrets.StrategyList", value)
138+
}
139+
return json.Unmarshal(bytes, l)
140+
}
141+
142+
// Value implements the driver.Valuer interface for StrategyList.
143+
func (l StrategyList) Value() (driver.Value, error) {
144+
return json.Marshal(l)
145+
}

pkg/storage/credential_store.go

+19-46
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,12 @@ package storage
22

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

88
"get.porter.sh/porter/pkg/secrets"
99
hostSecrets "get.porter.sh/porter/pkg/secrets/plugins/host"
1010
"get.porter.sh/porter/pkg/tracing"
11-
"github.com/cnabio/cnab-go/secrets/host"
12-
"github.com/hashicorp/go-multierror"
1311
)
1412

1513
var _ CredentialSetProvider = &CredentialStore{}
@@ -52,58 +50,33 @@ func EnsureCredentialIndices(ctx context.Context, store Store) error {
5250
return span.Error(err)
5351
}
5452

55-
func (s CredentialStore) GetDataStore() Store {
56-
return s.Documents
53+
func (s CredentialStore) FindCredentialSet(ctx context.Context, namespace string, name string) (CredentialSet, error) {
54+
var out CredentialSet
55+
// Try to get the creds in the local namespace first, fallback to the global creds
56+
query := FindOptions{
57+
Sort: []string{"-namespace"},
58+
Filter: bson.M{
59+
"name": name,
60+
"$or": []bson.M{
61+
{"namespace": ""},
62+
{"namespace": namespace},
63+
},
64+
},
65+
}
66+
err := s.Documents.FindOne(ctx, CollectionCredentials, query, &out)
67+
return out, err
5768
}
5869

5970
/*
6071
Secrets
6172
*/
6273

6374
func (s CredentialStore) ResolveAll(ctx context.Context, creds CredentialSet) (secrets.Set, error) {
64-
resolvedCreds := make(secrets.Set)
65-
var resolveErrors error
66-
67-
for _, cred := range creds.Credentials {
68-
var value string
69-
var err error
70-
if isHandledByHostPlugin(cred.Source.Strategy) {
71-
value, err = s.HostSecrets.Resolve(ctx, cred.Source.Strategy, cred.Source.Hint)
72-
} else {
73-
value, err = s.Secrets.Resolve(ctx, cred.Source.Strategy, cred.Source.Hint)
74-
}
75-
if err != nil {
76-
resolveErrors = multierror.Append(resolveErrors, fmt.Errorf("unable to resolve credential %s.%s from %s %s: %w", creds.Name, cred.Name, cred.Source.Strategy, cred.Source.Hint, err))
77-
}
78-
79-
resolvedCreds[cred.Name] = value
80-
}
81-
82-
return resolvedCreds, resolveErrors
75+
return resolveAll(ctx, creds.Credentials, s.HostSecrets, s.Secrets, creds.Name, "credential")
8376
}
8477

8578
func (s CredentialStore) Validate(ctx context.Context, creds CredentialSet) error {
86-
validSources := []string{secrets.SourceSecret, host.SourceValue, host.SourceEnv, host.SourcePath, host.SourceCommand}
87-
var errors error
88-
89-
for _, cs := range creds.Credentials {
90-
valid := false
91-
for _, validSource := range validSources {
92-
if cs.Source.Strategy == validSource {
93-
valid = true
94-
break
95-
}
96-
}
97-
if !valid {
98-
errors = multierror.Append(errors, fmt.Errorf(
99-
"%s is not a valid source. Valid sources are: %s",
100-
cs.Source.Strategy,
101-
strings.Join(validSources, ", "),
102-
))
103-
}
104-
}
105-
106-
return errors
79+
return validate(creds.Credentials)
10780
}
10881

10982
/*

pkg/storage/credential_store_sql.go

+135
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
package storage
2+
3+
import (
4+
"context"
5+
"errors"
6+
7+
"gorm.io/gorm"
8+
9+
"get.porter.sh/porter/pkg/secrets"
10+
hostSecrets "get.porter.sh/porter/pkg/secrets/plugins/host"
11+
"get.porter.sh/porter/pkg/tracing"
12+
)
13+
14+
var _ CredentialSetProvider = (*CredentialStoreSQL)(nil)
15+
16+
// CredentialStoreSQL is a wrapper around Porter's datastore
17+
// providing typed access and additional business logic around
18+
// credential sets, usually referred to as "credentials" as a shorthand.
19+
type CredentialStoreSQL struct {
20+
db *gorm.DB
21+
Secrets secrets.Store
22+
HostSecrets hostSecrets.Store
23+
}
24+
25+
func NewCredentialStoreSQL(db *gorm.DB, secrets secrets.Store) *CredentialStoreSQL {
26+
return &CredentialStoreSQL{
27+
db: db,
28+
Secrets: secrets,
29+
HostSecrets: hostSecrets.NewStore(),
30+
}
31+
}
32+
33+
/*
34+
Secrets
35+
*/
36+
37+
func (s CredentialStoreSQL) ResolveAll(ctx context.Context, creds CredentialSet) (secrets.Set, error) {
38+
return resolveAll(ctx, creds.Credentials, s.HostSecrets, s.Secrets, creds.Name, "credential")
39+
}
40+
41+
func (s CredentialStoreSQL) Validate(ctx context.Context, creds CredentialSet) error {
42+
return validate(creds.Credentials)
43+
}
44+
45+
/*
46+
Document Storage
47+
*/
48+
49+
func (s CredentialStoreSQL) InsertCredentialSet(ctx context.Context, creds CredentialSet) error {
50+
creds.SchemaVersion = DefaultCredentialSetSchemaVersion
51+
return s.db.WithContext(ctx).Create(&creds).Error
52+
}
53+
54+
func (s CredentialStoreSQL) ListCredentialSets(ctx context.Context, listOptions ListOptions) ([]CredentialSet, error) {
55+
_, log := tracing.StartSpan(ctx)
56+
defer log.EndSpan()
57+
58+
var out []CredentialSet
59+
60+
// If no filters are provided, return an empty list
61+
if listOptions.Namespace == "" && listOptions.Name == "" && len(listOptions.Labels) == 0 {
62+
return out, nil
63+
}
64+
65+
query := s.db.WithContext(ctx).Order("namespace ASC, name ASC")
66+
67+
if listOptions.Namespace != "" && listOptions.Namespace != "*" {
68+
query = query.Where("namespace = ?", listOptions.Namespace)
69+
}
70+
71+
if listOptions.Name != "" {
72+
query = query.Where("name LIKE ?", "%"+listOptions.Name+"%")
73+
}
74+
75+
// Filter by Labels
76+
if len(listOptions.Labels) > 0 {
77+
for key, value := range listOptions.Labels {
78+
query = query.Where("labels->? = ?", key, value)
79+
}
80+
}
81+
82+
if listOptions.Skip > 0 {
83+
query = query.Offset(int(listOptions.Skip))
84+
}
85+
86+
if listOptions.Limit > 0 {
87+
query = query.Limit(int(listOptions.Limit))
88+
}
89+
90+
err := query.Find(&out).Error
91+
if err != nil {
92+
return nil, log.Error(err)
93+
}
94+
return out, err
95+
}
96+
97+
func (s CredentialStoreSQL) GetCredentialSet(ctx context.Context, namespace string, name string) (CredentialSet, error) {
98+
var out CredentialSet
99+
err := s.db.WithContext(ctx).
100+
Where("namespace = ? AND name = ?", namespace, name).
101+
First(&out).Error
102+
if errors.Is(err, gorm.ErrRecordNotFound) {
103+
return CredentialSet{}, ErrNotFound{Collection: "credentials"}
104+
}
105+
return out, err
106+
}
107+
func (s CredentialStoreSQL) FindCredentialSet(ctx context.Context, namespace string, name string) (CredentialSet, error) {
108+
var out CredentialSet
109+
110+
query := s.db.WithContext(ctx).
111+
Where("name = ?", name).
112+
Where("(namespace = ? OR namespace = '' OR namespace IS NULL)", namespace).
113+
Order("namespace DESC") // DESC ensures that the namespace is prioritized over the empty or null
114+
115+
err := query.First(&out).Error
116+
if errors.Is(err, gorm.ErrRecordNotFound) {
117+
return CredentialSet{}, ErrNotFound{Collection: "credentials"}
118+
}
119+
return out, err
120+
}
121+
122+
func (s CredentialStoreSQL) UpdateCredentialSet(ctx context.Context, creds CredentialSet) error {
123+
creds.SchemaVersion = DefaultCredentialSetSchemaVersion
124+
return s.db.WithContext(ctx).
125+
Where("namespace = ? AND name = ?", creds.Namespace, creds.Name).
126+
Save(&creds).Error
127+
}
128+
129+
func (s CredentialStoreSQL) UpsertCredentialSet(ctx context.Context, creds CredentialSet) error {
130+
creds.SchemaVersion = DefaultCredentialSetSchemaVersion
131+
return s.db.WithContext(ctx).Save(&creds).Error
132+
}
133+
func (s CredentialStoreSQL) RemoveCredentialSet(ctx context.Context, namespace string, name string) error {
134+
return s.db.WithContext(ctx).Where("namespace = ? AND name = ?", namespace, name).Delete(&CredentialSet{}).Error
135+
}

0 commit comments

Comments
 (0)