Skip to content

Commit

Permalink
Merge pull request #2373 from carolynvs/overwrite-credentials
Browse files Browse the repository at this point in the history
Replace associated credential sets when running bundle
  • Loading branch information
carolynvs authored Sep 26, 2022
2 parents ce94589 + 810ce01 commit 89ad7ed
Show file tree
Hide file tree
Showing 5 changed files with 151 additions and 44 deletions.
31 changes: 31 additions & 0 deletions docs/content/credentials.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,37 @@ where the values can be found.
If you are creating credential sets manually, you can use the [Credential Set Schema]
to validate that you have created it properly.

### Remembering Credentials
Porter remembers the last set of credentials used with an installation, and reuses them when the bundle is executed again.

For example, if Carolyn installs a bundle using her credentials, Porter remembers that Carolyn's credentials are associated with the resulting installation.
Now when Carolyn upgrades the bundle, if credentials are not specified, Porter will reuse the original credentials that the bundle was installed with.
Later Yingrong upgrades the bundle, specifying the shared team credentials in the upgrade command, and now those credentials are associated with the installation instead of Carolyn's personal credentials.

```console
$ porter install tutorial -c carolyn-creds -r ghcr.io/getporter/examples/credentials-tutorial:v0.2.0
# bundle is installed with Carolyn's credentials
$ porter show tutorial
Name: tutorial
Namespace: quickstart
Created: 3 minutes ago
Modified: 7 seconds ago
Bundle:
Repository: ghcr.io/getporter/examples/credentials-tutorial
Version: 0.2.0
Credential Sets:
- carolyn-creds
$ porter upgrade --version 0.3.0
# Carolyn's credentials are used again, since none were specified
$ porter upgrade -c blue-team-creds --version 0.3.1
# Upgrade is run again but this time with the shared blue team credentials
```

[Credential Set Schema]: /src/pkg/schema/credential-set.schema.json

## Runtime
Expand Down
7 changes: 6 additions & 1 deletion pkg/porter/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,12 @@ func (p *TestPorter) SetupIntegrationTest() context.Context {
err = encoding.UnmarshalYaml(ciCredsB, &testCreds)
require.NoError(t, err, "could not unmarshal test credentials %s", ciCredsPath)
err = p.Credentials.UpsertCredentialSet(context.Background(), testCreds)
require.NoError(t, err, "could not save test credentials")
require.NoError(t, err, "could not save test credentials (ci)")

// Make a copy of the creds with a different name so that we can test out switching to different credential sets
testCreds.Name = "ci2"
err = p.Credentials.UpsertCredentialSet(context.Background(), testCreds)
require.NoError(t, err, "could not save test credentials (ci2)")

return ctx
}
Expand Down
29 changes: 7 additions & 22 deletions pkg/porter/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,29 +111,14 @@ func (p *Porter) applyActionOptionsToInstallation(ctx context.Context, i *storag
return err
}

// Record the names of the parameter sets used
i.ParameterSets = append(i.ParameterSets, Unique(i.ParameterSets, opts.ParameterSets...)...)

// Record the names of the credential sets used
i.CredentialSets = append(i.CredentialSets, Unique(i.CredentialSets, opts.CredentialIdentifiers...)...)

return nil
}

func Unique(existings []string, n ...string) []string {
var u []string
old := make(map[string]struct{})

for _, e := range existings {
old[e] = struct{}{}
// Record the names of the parameter and credential sets used if specified. Otherwise, reuse the previously specified sets.
// This should replace previously specified sets so that only what was just specified is used.
if len(opts.ParameterSets) > 0 {
i.ParameterSets = opts.ParameterSets
}

for _, cs := range n {
if _, ok := old[cs]; ok {
continue
}
u = append(u, cs)
if len(opts.CredentialIdentifiers) > 0 {
i.CredentialSets = opts.CredentialIdentifiers
}

return u
return nil
}
106 changes: 89 additions & 17 deletions pkg/porter/install_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
package porter

import (
"context"
"testing"

"get.porter.sh/porter/pkg/cnab"
"get.porter.sh/porter/pkg/portercontext"
"get.porter.sh/porter/pkg/secrets"
"get.porter.sh/porter/pkg/storage"
"github.com/cnabio/cnab-go/bundle"
"github.com/cnabio/cnab-go/bundle/definition"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
Expand Down Expand Up @@ -64,24 +70,90 @@ func TestInstallOptions_validateDriver(t *testing.T) {
}
}

func TestUnique(t *testing.T) {
testcases := []struct {
name string
existing []string
newData []string
expected []string
}{
{"empty existing data", []string{}, []string{"foo", "bar"}, []string{"foo", "bar"}},
{"empty new data", []string{"foo", "bar"}, []string{}, nil},
{"has new data", []string{"foo", "bar"}, []string{"alice"}, []string{"alice"}},
{"has duplicate new data", []string{"foo", "bar"}, []string{"alice", "foo"}, []string{"alice"}},
}
func TestPorter_applyActionOptionsToInstallation(t *testing.T) {
setup := func() (context.Context, *TestPorter, *storage.Installation) {
ctx := context.Background()
p := NewTestPorter(t)

for _, tc := range testcases {
tc := tc
t.Run(tc.name, func(t *testing.T) {
result := Unique(tc.existing, tc.newData...)
require.Equal(t, tc.expected, result)
p.TestParameters.InsertParameterSet(ctx, storage.ParameterSet{
ParameterSetSpec: storage.ParameterSetSpec{
Name: "newps1",
Parameters: []secrets.Strategy{
{Name: "logLevel", Source: secrets.Source{Key: "value", Value: "11"}},
},
},
})

return ctx, p, &storage.Installation{
Bundle: storage.OCIReferenceParts{
Repository: "example.com/mybuns",
Version: "1.0.0",
},
ParameterSets: []string{"oldps1"},
CredentialSets: []string{"oldcs1", "oldcs2"},
}
}

t.Run("replace previous sets", func(t *testing.T) {
ctx, p, inst := setup()

// We should replace the previously used sets since we specified different ones
opts := NewBundleExecutionOptions()
opts.Reference = kahnlatest.String()
opts.bundleRef = &cnab.BundleReference{
Reference: kahnlatest,
Definition: cnab.NewBundle(bundle.Bundle{
Credentials: map[string]bundle.Credential{
"userid": {},
},
Parameters: map[string]bundle.Parameter{
"logLevel": {Definition: "logLevel"},
},
Definitions: map[string]*definition.Schema{
"logLevel": {Type: "string"},
},
}),
}

opts.ParameterSets = []string{"newps1"}
opts.CredentialIdentifiers = []string{"newcs1"}

require.NoError(t, opts.Validate(ctx, nil, p.Porter))
err := p.applyActionOptionsToInstallation(ctx, inst, opts)
require.NoError(t, err, "applyActionOptionsToInstallation failed")

require.Equal(t, opts.ParameterSets, inst.ParameterSets, "expected the installation to replace the credential sets with those specified")
require.Equal(t, opts.CredentialIdentifiers, inst.CredentialSets, "expected the installation to replace the credential sets with those specified")
})

t.Run("reuse previous sets", func(t *testing.T) {
ctx, p, inst := setup()

// We should reuse the previously used sets since we specified different ones
opts := NewBundleExecutionOptions()
opts.Reference = kahnlatest.String()
opts.bundleRef = &cnab.BundleReference{
Reference: kahnlatest,
Definition: cnab.NewBundle(bundle.Bundle{
Credentials: map[string]bundle.Credential{
"userid": {},
},
Parameters: map[string]bundle.Parameter{
"logLevel": {Definition: "logLevel"},
},
Definitions: map[string]*definition.Schema{
"logLevel": {Type: "string"},
},
}),
}
opts.ParameterSets = []string{}
opts.CredentialIdentifiers = []string{}

require.NoError(t, opts.Validate(ctx, nil, p.Porter))
err := p.applyActionOptionsToInstallation(ctx, inst, opts)
require.NoError(t, err, "applyActionOptionsToInstallation failed")

require.Equal(t, []string{"oldps1"}, inst.ParameterSets, "expected the installation to reuse the previous credential sets")
require.Equal(t, []string{"oldcs1", "oldcs2"}, inst.CredentialSets, "expected the installation to reuse the previous credential sets")
})
}
22 changes: 18 additions & 4 deletions tests/integration/dependencies_test.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
//go:build integration
// +build integration

package integration

Expand Down Expand Up @@ -66,7 +65,7 @@ func installWordpressBundle(ctx context.Context, p *porter.TestPorter) (namespac
namespace = p.RandomString(10)
installOpts := porter.NewInstallOptions()
installOpts.Namespace = namespace
installOpts.CredentialIdentifiers = []string{"ci"}
installOpts.CredentialIdentifiers = []string{"ci"} // Use the ci credential set, porter should remember this for later
installOpts.Params = []string{
"wordpress-password=mypassword",
"namespace=" + namespace,
Expand Down Expand Up @@ -137,7 +136,7 @@ func cleanupWordpressBundle(ctx context.Context, p *porter.TestPorter, namespace
func upgradeWordpressBundle(ctx context.Context, p *porter.TestPorter, namespace string) {
upgradeOpts := porter.NewUpgradeOptions()
upgradeOpts.Namespace = namespace
upgradeOpts.CredentialIdentifiers = []string{"ci"}
// do not specify credential sets, porter should reuse what was specified from install
upgradeOpts.Params = []string{
"wordpress-password=mypassword",
"namespace=" + namespace,
Expand All @@ -164,13 +163,18 @@ func upgradeWordpressBundle(ctx context.Context, p *porter.TestPorter, namespace
require.NoError(p.T(), err, "GetLastClaim failed")
assert.Equal(p.T(), cnab.ActionUpgrade, c.Action, "the root bundle wasn't recorded as being upgraded")
assert.Equal(p.T(), cnab.StatusSucceeded, i.Status.ResultStatus, "the root bundle wasn't recorded as being upgraded successfully")

// Check that we are using the original credential set specified during install
require.Len(p.T(), i.CredentialSets, 1, "expected only one credential set associated to the installation")
assert.Equal(p.T(), "ci", i.CredentialSets[0], "expected to use the alternate credential set")
}

func invokeWordpressBundle(ctx context.Context, p *porter.TestPorter, namespace string) {
invokeOpts := porter.NewInvokeOptions()
invokeOpts.Namespace = namespace
invokeOpts.Action = "ping"
invokeOpts.CredentialIdentifiers = []string{"ci"}
// Use a different set of creds to run this rando command
invokeOpts.CredentialIdentifiers = []string{"ci2"}
invokeOpts.Params = []string{
"wordpress-password=mypassword",
"namespace=" + namespace,
Expand All @@ -196,11 +200,16 @@ func invokeWordpressBundle(ctx context.Context, p *porter.TestPorter, namespace
require.NoError(p.T(), err, "GetLastClaim failed")
assert.Equal(p.T(), "ping", c.Action, "the root bundle wasn't recorded as being invoked")
assert.Equal(p.T(), cnab.StatusSucceeded, i.Status.ResultStatus, "the root bundle wasn't recorded as being invoked successfully")

// Check that we are now using the alternate credentials with the bundle
require.Len(p.T(), i.CredentialSets, 1, "expected only one credential set associated to the installation")
assert.Equal(p.T(), "ci2", i.CredentialSets[0], "expected to use the alternate credential set")
}

func uninstallWordpressBundle(ctx context.Context, p *porter.TestPorter, namespace string) {
uninstallOptions := porter.NewUninstallOptions()
uninstallOptions.Namespace = namespace
// Now go back to using the original set of credentials
uninstallOptions.CredentialIdentifiers = []string{"ci"}
uninstallOptions.Params = []string{
"namespace=" + namespace,
Expand All @@ -227,4 +236,9 @@ func uninstallWordpressBundle(ctx context.Context, p *porter.TestPorter, namespa
require.NoError(p.T(), err, "GetLastClaim failed")
assert.Equal(p.T(), cnab.ActionUninstall, c.Action, "the root bundle wasn't recorded as being uninstalled")
assert.Equal(p.T(), cnab.StatusSucceeded, i.Status.ResultStatus, "the root bundle wasn't recorded as being uninstalled successfully")

// Check that we are now using the original credentials with the bundle
require.Len(p.T(), i.CredentialSets, 1, "expected only one credential set associated to the installation")
assert.Equal(p.T(), "ci", i.CredentialSets[0], "expected to use the alternate credential set")

}

0 comments on commit 89ad7ed

Please sign in to comment.