Skip to content
This repository was archived by the owner on Jul 18, 2025. It is now read-only.

Commit 07bb266

Browse files
simonferquelIan Campbell
authored andcommitted
support for sharing registry credentials with invocation images
Adds a new boolean parameter to the CNAB bundles which app generates, `"docker.share-registry-creds"` (exposed as `$DOCKER_SHARE_REGISTRY_CREDS`) Also adds a new credential `docker.registry-creds` which contains a JSON document mapping strings to `github.com/docker/docker/api/types.AuthConfig`. The file must always be present but has content only if `$DOCKER_SHARE_REGISTRY_CREDS` is true (otherwise it is an empty JSON map). The `install` and `upgrade` subcommands gain a new `--with-registry-auth` flag which indicates that registry credentials should be provided to the invocation image. Signed-off-by: Simon Ferquel <simon.ferquel@docker.com> Signed-off-by: Ian Campbell <ijc@docker.com>
1 parent 4b70a4c commit 07bb266

14 files changed

Lines changed: 261 additions & 21 deletions

File tree

cmd/cnab-run/env.go

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package main
22

33
import (
4+
"encoding/json"
5+
"io/ioutil"
46
"os"
57

68
"github.com/docker/app/internal"
@@ -32,9 +34,23 @@ func setupDockerContext() (command.Cli, error) {
3234
if err != nil {
3335
return nil, err
3436
}
35-
return cli, cli.Initialize(&cliflags.ClientOptions{
37+
if err := cli.Initialize(&cliflags.ClientOptions{
3638
Common: &cliflags.CommonOptions{
3739
Context: "cnab",
3840
},
39-
})
41+
}); err != nil {
42+
return nil, err
43+
}
44+
authConfigsJSON, err := ioutil.ReadFile(internal.CredentialRegistryPath)
45+
if err != nil {
46+
return nil, err
47+
}
48+
49+
configFile := cli.ConfigFile()
50+
51+
if err := json.Unmarshal(authConfigsJSON, &configFile.AuthConfigs); err != nil {
52+
return nil, err
53+
}
54+
55+
return cli, nil
4056
}

cmd/cnab-run/install.go

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"encoding/json"
55
"io/ioutil"
66
"os"
7+
"strconv"
78

89
"github.com/deislabs/duffle/pkg/bundle"
910
"github.com/docker/app/internal"
@@ -52,11 +53,15 @@ func installAction(instanceName string) error {
5253
if err := os.Chdir(app.Path); err != nil {
5354
return err
5455
}
56+
sendRegistryAuth, err := strconv.ParseBool(os.Getenv("DOCKER_SHARE_REGISTRY_CREDS"))
57+
if err != nil {
58+
return err
59+
}
5560
// todo: pass registry auth to invocation image
5661
return stack.RunDeploy(cli, getFlagset(orchestrator), rendered, orchestrator, options.Deploy{
5762
Namespace: instanceName,
5863
ResolveImage: swarm.ResolveImageAlways,
59-
SendRegistryAuth: false,
64+
SendRegistryAuth: sendRegistryAuth,
6065
})
6166
}
6267

cmd/cnab-run/main.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ type cnabAction func(string) error
1313
var (
1414
cnabActions = map[string]cnabAction{
1515
"install": installAction,
16-
"upgrade": installAction,
16+
"upgrade": installAction, // upgrade is implemented as reinstall.
1717
"uninstall": uninstallAction,
1818
internal.ActionStatusName: statusAction,
1919
internal.ActionInspectName: inspectAction,

e2e/testdata/simple-bundle.json.golden

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,16 @@
106106
"com.docker.app.render"
107107
]
108108
},
109+
"com.docker.app.share-registry-creds": {
110+
"type": "bool",
111+
"defaultValue": false,
112+
"metadata": {
113+
"description": "Share registry credentials with the invocation image"
114+
},
115+
"destination": {
116+
"env": "DOCKER_SHARE_REGISTRY_CREDS"
117+
}
118+
},
109119
"static_subdir": {
110120
"type": "string",
111121
"defaultValue": "data/static",
@@ -122,6 +132,9 @@
122132
}
123133
},
124134
"credentials": {
135+
"com.docker.app.registry-creds": {
136+
"path": "/cnab/app/registry-creds.json"
137+
},
125138
"docker.context": {
126139
"path": "/cnab/app/context.dockercontext"
127140
}

internal/commands/cnab.go

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package commands
22

33
import (
44
"bytes"
5+
"context"
6+
"encoding/json"
57
"fmt"
68
"io"
79
"io/ioutil"
@@ -23,8 +25,10 @@ import (
2325
"github.com/docker/cli/cli/context/docker"
2426
"github.com/docker/cli/cli/context/store"
2527
"github.com/docker/distribution/reference"
28+
"github.com/docker/docker/api/types"
2629
"github.com/docker/docker/api/types/container"
2730
"github.com/docker/docker/api/types/mount"
31+
"github.com/docker/docker/registry"
2832
"github.com/pkg/errors"
2933
)
3034

@@ -77,6 +81,50 @@ func addDockerCredentials(contextName string, contextStore store.Store) credenti
7781
}
7882
}
7983

84+
func shouldPopulateRegistryCreds(parameterValues map[string]interface{}) bool {
85+
v, ok := parameterValues[internal.ParameterShareRegistryCredsName]
86+
if !ok {
87+
return false
88+
}
89+
result, ok := v.(bool)
90+
if !ok {
91+
return false
92+
}
93+
return result
94+
}
95+
96+
func addRegistryCredentials(parameterValues map[string]interface{}, dockerCli command.Cli) credentialSetOpt {
97+
return func(b *bundle.Bundle, creds map[string]string) error {
98+
if _, ok := b.Credentials[internal.CredentialRegistryName]; !ok {
99+
return nil
100+
}
101+
102+
registryCreds := map[string]types.AuthConfig{}
103+
if shouldPopulateRegistryCreds(parameterValues) {
104+
for _, img := range b.Images {
105+
named, err := reference.ParseNormalizedNamed(img.Image)
106+
if err != nil {
107+
return err
108+
}
109+
info, err := registry.ParseRepositoryInfo(named)
110+
if err != nil {
111+
return err
112+
}
113+
key := registry.GetAuthConfigKey(info.Index)
114+
if _, ok := registryCreds[key]; !ok {
115+
registryCreds[key] = command.ResolveAuthConfig(context.Background(), dockerCli, info.Index)
116+
}
117+
}
118+
}
119+
registryCredsJSON, err := json.Marshal(registryCreds)
120+
if err != nil {
121+
return err
122+
}
123+
creds[internal.CredentialRegistryName] = string(registryCredsJSON)
124+
return nil
125+
}
126+
}
127+
80128
func prepareCredentialSet(b *bundle.Bundle, opts ...credentialSetOpt) (map[string]string, error) {
81129
creds := map[string]string{}
82130
for _, op := range opts {

internal/commands/cnab_test.go

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
package commands
22

33
import (
4+
"encoding/json"
45
"testing"
56

7+
"github.com/deislabs/duffle/pkg/bundle"
8+
"github.com/docker/app/internal"
69
"github.com/docker/cli/cli/command"
10+
"github.com/docker/cli/cli/config/configfile"
11+
"github.com/docker/cli/cli/config/types"
712
cliflags "github.com/docker/cli/cli/flags"
813
"gotest.tools/assert"
914
)
@@ -122,3 +127,108 @@ func TestSocketPath(t *testing.T) {
122127
})
123128
}
124129
}
130+
131+
type registryConfigMock struct {
132+
command.Cli
133+
configFile *configfile.ConfigFile
134+
}
135+
136+
func (r *registryConfigMock) ConfigFile() *configfile.ConfigFile {
137+
return r.configFile
138+
}
139+
140+
func TestShareRegistryCreds(t *testing.T) {
141+
cases := []struct {
142+
name string
143+
shareCreds bool
144+
stored map[string]types.AuthConfig
145+
expected map[string]types.AuthConfig
146+
images map[string]bundle.Image
147+
}{
148+
{
149+
name: "no-share",
150+
shareCreds: false,
151+
stored: map[string]types.AuthConfig{
152+
"my-registry.com": {
153+
Username: "test",
154+
Password: "test",
155+
},
156+
},
157+
expected: map[string]types.AuthConfig{},
158+
images: map[string]bundle.Image{
159+
"component1": {
160+
BaseImage: bundle.BaseImage{
161+
Image: "my-registry.com/ns/repo:tag",
162+
},
163+
},
164+
},
165+
},
166+
{
167+
name: "share",
168+
shareCreds: true,
169+
stored: map[string]types.AuthConfig{
170+
"my-registry.com": {
171+
Username: "test",
172+
Password: "test",
173+
},
174+
"my-registry2.com": {
175+
Username: "test",
176+
Password: "test",
177+
},
178+
},
179+
expected: map[string]types.AuthConfig{
180+
"my-registry.com": {
181+
Username: "test",
182+
Password: "test",
183+
}},
184+
images: map[string]bundle.Image{
185+
"component1": {
186+
BaseImage: bundle.BaseImage{
187+
Image: "my-registry.com/ns/repo:tag",
188+
},
189+
},
190+
},
191+
},
192+
{
193+
name: "share-missing",
194+
shareCreds: true,
195+
stored: map[string]types.AuthConfig{
196+
"my-registry2.com": {
197+
Username: "test",
198+
Password: "test",
199+
},
200+
},
201+
expected: map[string]types.AuthConfig{
202+
"my-registry.com": {}},
203+
images: map[string]bundle.Image{
204+
"component1": {
205+
BaseImage: bundle.BaseImage{
206+
Image: "my-registry.com/ns/repo:tag",
207+
},
208+
},
209+
},
210+
},
211+
}
212+
213+
for _, c := range cases {
214+
t.Run(c.name, func(t *testing.T) {
215+
params := map[string]interface{}{
216+
internal.ParameterShareRegistryCredsName: c.shareCreds,
217+
}
218+
creds, err := prepareCredentialSet(
219+
&bundle.Bundle{
220+
Credentials: map[string]bundle.Location{internal.CredentialRegistryName: {}},
221+
Images: c.images,
222+
},
223+
addNamedCredentialSets(nil),
224+
addDockerCredentials("", nil),
225+
addRegistryCredentials(params, &registryConfigMock{configFile: &configfile.ConfigFile{
226+
AuthConfigs: c.stored,
227+
}}))
228+
assert.NilError(t, err)
229+
var result map[string]types.AuthConfig
230+
assert.NilError(t, json.Unmarshal([]byte(creds[internal.CredentialRegistryName]), &result))
231+
assert.DeepEqual(t, c.expected, result)
232+
})
233+
}
234+
}

internal/commands/install.go

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,6 @@ import (
99
"github.com/deislabs/duffle/pkg/utils/crud"
1010
"github.com/docker/cli/cli"
1111
"github.com/docker/cli/cli/command"
12-
"github.com/pkg/errors"
1312
"github.com/spf13/cobra"
1413
)
1514

@@ -69,9 +68,6 @@ func installCmd(dockerCli command.Cli) *cobra.Command {
6968

7069
func runInstall(dockerCli command.Cli, appname string, opts installOptions) error {
7170
defer muteDockerCli(dockerCli)()
72-
if opts.sendRegistryAuth {
73-
return errors.New("with-registry-auth is not supported at the moment")
74-
}
7571
targetContext := getTargetContext(opts.targetContext, dockerCli.CurrentContext())
7672
bind, err := requiredBindMount(targetContext, opts.orchestrator, dockerCli.ContextStore())
7773
if err != nil {
@@ -102,26 +98,27 @@ func runInstall(dockerCli command.Cli, appname string, opts installOptions) erro
10298
if err != nil {
10399
return err
104100
}
105-
creds, err := prepareCredentialSet(bndl,
106-
addNamedCredentialSets(opts.credentialsets),
107-
addDockerCredentials(targetContext, dockerCli.ContextStore()))
108-
if err != nil {
109-
return err
110-
}
111-
if err := credentials.Validate(creds, bndl.Credentials); err != nil {
112-
return err
113-
}
114-
115101
c.Bundle = bndl
116102

117103
c.Parameters, err = mergeBundleParameters(bndl,
118104
withFileParameters(opts.parametersFiles),
119105
withCommandLineParameters(opts.overrides),
120106
withOrchestratorParameters(opts.orchestrator, opts.kubeNamespace),
107+
withSendRegistryAuth(opts.sendRegistryAuth),
121108
)
122109
if err != nil {
123110
return err
124111
}
112+
creds, err := prepareCredentialSet(bndl,
113+
addNamedCredentialSets(opts.credentialsets),
114+
addDockerCredentials(targetContext, dockerCli.ContextStore()),
115+
addRegistryCredentials(c.Parameters, dockerCli))
116+
if err != nil {
117+
return err
118+
}
119+
if err := credentials.Validate(creds, bndl.Credentials); err != nil {
120+
return err
121+
}
125122

126123
inst := &action.Install{
127124
Driver: driverImpl,

internal/commands/parameters.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,19 @@ func withCommandLineParameters(overrides []string) parameterOperation {
3535
}
3636
}
3737

38+
func withSendRegistryAuth(sendRegistryAuth bool) parameterOperation {
39+
return func(bndl *bundle.Bundle, params map[string]string) error {
40+
if _, ok := bndl.Parameters[internal.ParameterShareRegistryCredsName]; ok {
41+
val := "false"
42+
if sendRegistryAuth {
43+
val = "true"
44+
}
45+
params[internal.ParameterShareRegistryCredsName] = val
46+
}
47+
return nil
48+
}
49+
}
50+
3851
func withOrchestratorParameters(orchestrator string, kubeNamespace string) parameterOperation {
3952
return func(bndl *bundle.Bundle, params map[string]string) error {
4053
if _, ok := bndl.Parameters[internal.ParameterOrchestratorName]; ok {

internal/commands/status.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,8 @@ func runStatus(dockerCli command.Cli, claimName string, opts credentialOptions)
4949
}
5050
creds, err := prepareCredentialSet(c.Bundle,
5151
addNamedCredentialSets(opts.credentialsets),
52-
addDockerCredentials(targetContext, dockerCli.ContextStore()))
52+
addDockerCredentials(targetContext, dockerCli.ContextStore()),
53+
addRegistryCredentials(c.Parameters, dockerCli))
5354
if err != nil {
5455
return err
5556
}

internal/commands/uninstall.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,8 @@ func runUninstall(dockerCli command.Cli, claimName string, opts credentialOption
4848
}
4949
creds, err := prepareCredentialSet(c.Bundle,
5050
addNamedCredentialSets(opts.credentialsets),
51-
addDockerCredentials(targetContext, dockerCli.ContextStore()))
51+
addDockerCredentials(targetContext, dockerCli.ContextStore()),
52+
addRegistryCredentials(c.Parameters, dockerCli))
5253
if err != nil {
5354
return err
5455
}

0 commit comments

Comments
 (0)