Skip to content
This repository has been archived by the owner on Jun 13, 2021. It is now read-only.

Commit

Permalink
Allow the user to specify individual credentials on the command line
Browse files Browse the repository at this point in the history
e.g.

    docker app install --credential name=somevalue bundle.json

Credentials added with `--credential` always come after those added with
`--credential-set` (irrespective of the order on the command line).

A credential specified with `--credential` cannot override any previous
credential, including those specified in a credential set.

The test bnudle used is based on
https://github.com/deislabs/example-bundles/blob/0e8af9a2f1270bd72045a515637a432e74743d5d/example-credentials/bundle.json
But with `cnab/example-credentials:latest` → a digested ref (with the digest I
pulled today)

Signed-off-by: Ian Campbell <ijc@docker.com>
  • Loading branch information
Ian Campbell committed May 3, 2019
1 parent 137c8cd commit b3daa6d
Show file tree
Hide file tree
Showing 10 changed files with 208 additions and 1 deletion.
83 changes: 83 additions & 0 deletions e2e/commands_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
"strings"
"testing"

"github.com/deislabs/duffle/pkg/credentials"
"github.com/docker/app/internal"
"github.com/docker/app/internal/yaml"
"gotest.tools/assert"
Expand Down Expand Up @@ -421,6 +422,88 @@ STATUS
})
}

func TestCredentials(t *testing.T) {
cmd, cleanup := dockerCli.createTestCmd(
WithCredentialSet(t, "default", &credentials.CredentialSet{
Name: "test-creds",
Credentials: []credentials.CredentialStrategy{
{
Name: "secret1",
Source: credentials.Source{
Value: "secret1value",
},
},
{
Name: "secret2",
Source: credentials.Source{
Value: "secret2value",
},
},
},
}),
)
defer cleanup()

bundleJson := golden.Get(t, "credential-install-bundle.json")
tmpDir := fs.NewDir(t, t.Name(),
fs.WithFile("bundle.json", "", fs.WithBytes(bundleJson)),
)
defer tmpDir.Remove()

bundle := tmpDir.Join("bundle.json")

t.Run("missing", func(t *testing.T) {
cmd.Command = dockerCli.Command(
"app", "install",
"--credential", "secret1=foo",
"--name", "missing", bundle,
)
result := icmd.RunCmd(cmd).Assert(t, icmd.Expected{
ExitCode: 1,
Out: icmd.None,
})
golden.Assert(t, result.Stderr(), "credential-install-missing.golden")
})

t.Run("full", func(t *testing.T) {
cmd.Command = dockerCli.Command(
"app", "install",
"--credential", "secret1=foo",
"--credential", "secret2=bar",
"--credential", "secret3=baz",
"--name", "full", bundle,
)
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
golden.Assert(t, result.Stdout(), "credential-install-full.golden")
})

t.Run("mixed", func(t *testing.T) {
cmd.Command = dockerCli.Command(
"app", "install",
"--credential-set", "test-creds",
"--credential", "secret3=xyzzy",
"--name", "mixed", bundle,
)
result := icmd.RunCmd(cmd).Assert(t, icmd.Success)
golden.Assert(t, result.Stdout(), "credential-install-mixed.golden")
})

t.Run("overload", func(t *testing.T) {
cmd.Command = dockerCli.Command(
"app", "install",
"--credential-set", "test-creds",
"--credential", "secret1=overload",
"--credential", "secret3=xyzzy",
"--name", "overload", bundle,
)
result := icmd.RunCmd(cmd).Assert(t, icmd.Expected{
ExitCode: 1,
Out: icmd.None,
})
golden.Assert(t, result.Stderr(), "credential-install-overload.golden")
})
}

func initializeDockerAppEnvironment(t *testing.T, cmd *icmd.Cmd, tmpDir *fs.Dir, swarm *Container, useBindMount bool) {
cmd.Env = append(cmd.Env, "DOCKER_TARGET_CONTEXT=swarm-target-context")

Expand Down
25 changes: 24 additions & 1 deletion e2e/main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,10 @@ import (
"strings"
"testing"

"github.com/deislabs/duffle/pkg/credentials"
"github.com/docker/app/internal/store"
dockerConfigFile "github.com/docker/cli/cli/config/configfile"
"gotest.tools/assert"
"gotest.tools/icmd"
)

Expand All @@ -29,12 +32,17 @@ type dockerCliCommand struct {
cliPluginDir string
}

func (d dockerCliCommand) createTestCmd() (icmd.Cmd, func()) {
type testCmdOp func(configDir string, config *dockerConfigFile.ConfigFile)

func (d dockerCliCommand) createTestCmd(ops ...testCmdOp) (icmd.Cmd, func()) {
configDir, err := ioutil.TempDir("", "config")
if err != nil {
panic(err)
}
config := dockerConfigFile.ConfigFile{CLIPluginsExtraDirs: []string{d.cliPluginDir}}
for _, op := range ops {
op(configDir, &config)
}
configFile, err := os.Create(filepath.Join(configDir, "config.json"))
if err != nil {
panic(err)
Expand All @@ -54,6 +62,21 @@ func (d dockerCliCommand) Command(args ...string) []string {
return append([]string{d.path}, args...)
}

func WithCredentialSet(t *testing.T, context string, creds *credentials.CredentialSet) testCmdOp {
t.Helper()
return func(configDir string, _ *dockerConfigFile.ConfigFile) {

appstore, err := store.NewApplicationStore(configDir)
assert.NilError(t, err)

credstore, err := appstore.CredentialStore(context)
assert.NilError(t, err)

err = credstore.Store(creds)
assert.NilError(t, err)
}
}

func TestMain(m *testing.M) {
flag.Parse()
if err := os.Chdir(*e2ePath); err != nil {
Expand Down
24 changes: 24 additions & 0 deletions e2e/testdata/credential-install-bundle.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"name": "example-credentials",
"version": "0.0.1",
"schemaVersion": "v1.0.0-WD",
"invocationImages": [
{
"imageType": "docker",
"image": "cnab/example-credentials@sha256:b93f7279bdc9610d4ef275dab5d0a1d19cc613a784e2522977866747090059f4"
}
],
"credentials": {
"secret1": {
"env" :"SECRET_ONE"
},
"secret2": {
"path": "/var/secret_two/data.txt"
},
"secret3": {
"env": "SECRET_THREE",
"path": "/var/secret_three/data.txt"
}
}
}

7 changes: 7 additions & 0 deletions e2e/testdata/credential-install-full.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SECRET_ONE: foo
/var/secret_two/data.txt
bar
SECRET_THREE: baz
/var/secret_three/data.txt
baz
Application "full" installed on context "default"
1 change: 1 addition & 0 deletions e2e/testdata/credential-install-missing.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
bundle requires credential for secret2
7 changes: 7 additions & 0 deletions e2e/testdata/credential-install-mixed.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
SECRET_ONE: secret1value
/var/secret_two/data.txt
secret2value
SECRET_THREE: xyzzy
/var/secret_three/data.txt
xyzzy
Application "mixed" installed on context "default"
1 change: 1 addition & 0 deletions e2e/testdata/credential-install-overload.golden
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
ambiguous credential resolution: "secret1" is already present in base credential sets, cannot merge
27 changes: 27 additions & 0 deletions internal/commands/cnab.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,33 @@ func addNamedCredentialSets(credStore appstore.CredentialStore, namedCredentials
}
}

func parseCommandlineCredential(c string) (string, string, error) {
split := strings.SplitN(c, "=", 2)
if len(split) != 2 || split[0] == "" {
return "", "", errors.Errorf("failed to parse %q as a credential name=value", c)
}
name := split[0]
value := split[1]
return name, value, nil
}

func addCredentials(strcreds []string) credentialSetOpt {
return func(_ *bundle.Bundle, creds credentials.Set) error {
for _, c := range strcreds {
name, value, err := parseCommandlineCredential(c)
if err != nil {
return err
}
if err := creds.Merge(credentials.Set{
name: value,
}); err != nil {
return err
}
}
return nil
}
}

func addDockerCredentials(contextName string, store contextstore.Store) credentialSetOpt {
// docker desktop contexts require some rewriting for being used within a container
store = dockerDesktopAwareStore{Store: store}
Expand Down
31 changes: 31 additions & 0 deletions internal/commands/cnab_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,34 @@ func TestShareRegistryCreds(t *testing.T) {
})
}
}

func TestParseCommandlineCredential(t *testing.T) {
for _, tc := range []struct {
in string
n, v string
err string // either err or n+v are non-""
}{
{in: "", err: `failed to parse "" as a credential name=value`},
{in: "A", err: `failed to parse "A" as a credential name=value`},
{in: "=B", err: `failed to parse "=B" as a credential name=value`},
{in: "A=", n: "A", v: ""},
{in: "A=B", n: "A", v: "B"},
{in: "A==", n: "A", v: "="},
{in: "A=B=C", n: "A", v: "B=C"},
} {
n := tc.in
if n == "" {
n = "«empty»"
}
t.Run(n, func(t *testing.T) {
n, v, err := parseCommandlineCredential(tc.in)
if tc.err != "" {
assert.Error(t, err, tc.err)
} else {
assert.NilError(t, err)
assert.Equal(t, tc.n, n)
assert.Equal(t, tc.v, v)
}
})
}
}
3 changes: 3 additions & 0 deletions internal/commands/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,12 +91,14 @@ func (o *parametersOptions) addFlags(flags *pflag.FlagSet) {
type credentialOptions struct {
targetContext string
credentialsets []string
credentials []string
sendRegistryAuth bool
}

func (o *credentialOptions) addFlags(flags *pflag.FlagSet) {
flags.StringVar(&o.targetContext, "target-context", "", "Context on which the application is installed (default: <current-context>)")
flags.StringArrayVar(&o.credentialsets, "credential-set", []string{}, "Use a YAML file containing a credential set or a credential set present in the credential store")
flags.StringArrayVar(&o.credentials, "credential", nil, "Add a single credential, additive ontop of any --credential-set used")
flags.BoolVar(&o.sendRegistryAuth, "with-registry-auth", false, "Sends registry auth")
}

Expand All @@ -107,6 +109,7 @@ func (o *credentialOptions) SetDefaultTargetContext(dockerCli command.Cli) {
func (o *credentialOptions) CredentialSetOpts(dockerCli command.Cli, credentialStore store.CredentialStore) []credentialSetOpt {
return []credentialSetOpt{
addNamedCredentialSets(credentialStore, o.credentialsets),
addCredentials(o.credentials),
addDockerCredentials(o.targetContext, dockerCli.ContextStore()),
addRegistryCredentials(o.sendRegistryAuth, dockerCli),
}
Expand Down

0 comments on commit b3daa6d

Please sign in to comment.