Merge pull request #531 from ijc/individual-credentials
Allow the user to specify individual credentials on the command line
This commit is contained in:
Коммит
f2fbcae871
|
@ -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"
|
||||
|
@ -447,6 +448,90 @@ 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",
|
||||
// secret2 deliberately omitted.
|
||||
"--credential", "secret3=baz",
|
||||
"--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")
|
||||
|
||||
|
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
@ -36,14 +39,21 @@ func (d dockerCliCommand) createTestCmd(ops ...ConfigFileOperator) (icmd.Cmd, fu
|
|||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
config := dockerConfigFile.ConfigFile{CLIPluginsExtraDirs: []string{d.cliPluginDir}}
|
||||
configFilePath := filepath.Join(configDir, "config.json")
|
||||
config := dockerConfigFile.ConfigFile{
|
||||
CLIPluginsExtraDirs: []string{
|
||||
d.cliPluginDir,
|
||||
},
|
||||
Filename: configFilePath,
|
||||
}
|
||||
for _, op := range ops {
|
||||
op(&config)
|
||||
}
|
||||
configFile, err := os.Create(filepath.Join(configDir, "config.json"))
|
||||
configFile, err := os.Create(configFilePath)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
defer configFile.Close()
|
||||
err = json.NewEncoder(configFile).Encode(config)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
|
@ -59,6 +69,21 @@ func (d dockerCliCommand) Command(args ...string) []string {
|
|||
return append([]string{d.path}, args...)
|
||||
}
|
||||
|
||||
func withCredentialSet(t *testing.T, context string, creds *credentials.CredentialSet) ConfigFileOperator {
|
||||
t.Helper()
|
||||
return func(config *dockerConfigFile.ConfigFile) {
|
||||
configDir := filepath.Dir(config.Filename)
|
||||
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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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"
|
|
@ -0,0 +1 @@
|
|||
bundle requires credential for secret2
|
|
@ -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"
|
|
@ -0,0 +1 @@
|
|||
ambiguous credential resolution: "secret1" is already present in base credential sets, cannot merge
|
|
@ -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}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,12 +103,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")
|
||||
}
|
||||
|
||||
|
@ -119,6 +121,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),
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче