Merge pull request #531 from ijc/individual-credentials

Allow the user to specify individual credentials on the command line
This commit is contained in:
Jean-Christophe Sirot 2019-05-17 10:59:54 +02:00 коммит произвёл GitHub
Родитель b953958352 9ece4d778f
Коммит f2fbcae871
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
10 изменённых файлов: 213 добавлений и 2 удалений

Просмотреть файл

@ -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 {

24
e2e/testdata/credential-install-bundle.json поставляемый Normal file
Просмотреть файл

@ -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
e2e/testdata/credential-install-full.golden поставляемый Normal file
Просмотреть файл

@ -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
e2e/testdata/credential-install-missing.golden поставляемый Normal file
Просмотреть файл

@ -0,0 +1 @@
bundle requires credential for secret2

7
e2e/testdata/credential-install-mixed.golden поставляемый Normal file
Просмотреть файл

@ -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
e2e/testdata/credential-install-overload.golden поставляемый Normal file
Просмотреть файл

@ -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),
}