Skip to content

Commit 816c723

Browse files
committed
cmd/secret: use new store
Signed-off-by: Alano Terblanche <[email protected]>
1 parent 7145062 commit 816c723

File tree

9 files changed

+109
-178
lines changed

9 files changed

+109
-178
lines changed

cmd/docker-mcp/commands/root.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ func Root(ctx context.Context, cwd string, dockerCli command.Cli) *cobra.Command
4040
HiddenDefaultCmd: true,
4141
},
4242
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
43-
cmd.SetContext(ctx)
4443
if err := plugin.PersistentPreRunE(cmd, args); err != nil {
4544
return err
4645
}
@@ -60,6 +59,7 @@ func Root(ctx context.Context, cwd string, dockerCli command.Cli) *cobra.Command
6059
},
6160
Version: version.Version,
6261
}
62+
cmd.SetContext(ctx)
6363
cmd.SetVersionTemplate("{{.Version}}\n")
6464
cmd.Flags().BoolP("version", "v", false, "Print version information and quit")
6565
cmd.SetHelpTemplate(helpTemplate)

cmd/docker-mcp/commands/secret.go

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"github.com/spf13/cobra"
99

1010
"github.com/docker/mcp-gateway/cmd/docker-mcp/secret-management/secret"
11+
"github.com/docker/mcp-gateway/pkg/desktop"
1112
"github.com/docker/mcp-gateway/pkg/docker"
1213
)
1314

@@ -28,6 +29,13 @@ func secretCommand(docker docker.Client) *cobra.Command {
2829
Use: "secret",
2930
Short: "Manage secrets",
3031
Example: strings.Trim(setSecretExample, "\n"),
32+
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
33+
err := desktop.CheckHasDockerPass(cmd.Context())
34+
if err != nil {
35+
return err
36+
}
37+
return nil
38+
},
3139
}
3240
cmd.AddCommand(rmSecretCommand())
3341
cmd.AddCommand(listSecretCommand())
@@ -83,9 +91,6 @@ func setSecretCommand() *cobra.Command {
8391
Example: strings.Trim(setSecretExample, "\n"),
8492
Args: cobra.ExactArgs(1),
8593
RunE: func(cmd *cobra.Command, args []string) error {
86-
if !secret.IsValidProvider(opts.Provider) {
87-
return fmt.Errorf("invalid provider: %s", opts.Provider)
88-
}
8994
var s secret.Secret
9095
if isNotImplicitReadFromStdinSyntax(args, *opts) {
9196
va, err := secret.ParseArg(args[0], *opts)
@@ -105,11 +110,12 @@ func setSecretCommand() *cobra.Command {
105110
}
106111
flags := cmd.Flags()
107112
flags.StringVar(&opts.Provider, "provider", "", "Supported: credstore, oauth/<provider>")
113+
flags.MarkDeprecated("provider", "option will be ignored")
108114
return cmd
109115
}
110116

111117
func isNotImplicitReadFromStdinSyntax(args []string, opts secret.SetOpts) bool {
112-
return strings.Contains(args[0], "=") || len(args) > 1 || opts.Provider != ""
118+
return strings.Contains(args[0], "=") || len(args) > 1
113119
}
114120

115121
func exportSecretCommand(docker docker.Client) *cobra.Command {
Lines changed: 33 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -1,125 +1,61 @@
11
package secret
22

33
import (
4+
"bufio"
5+
"bytes"
46
"context"
5-
"io"
67
"os/exec"
7-
"strings"
8-
9-
"github.com/docker/docker-credential-helpers/client"
10-
"github.com/docker/docker-credential-helpers/credentials"
11-
12-
"github.com/docker/mcp-gateway/pkg/desktop"
138
)
149

15-
type CredStoreProvider struct {
16-
credentialHelper credentials.Helper
10+
type CredStoreProvider struct{}
11+
12+
func cmd(ctx context.Context, args ...string) *exec.Cmd {
13+
return exec.CommandContext(ctx, "docker", append([]string{"pass"}, args...)...)
1714
}
1815

1916
func NewCredStoreProvider() *CredStoreProvider {
20-
return &CredStoreProvider{credentialHelper: GetHelper()}
17+
return &CredStoreProvider{}
2118
}
2219

2320
func getSecretKey(secretName string) string {
24-
return "sm_" + secretName
21+
return "docker/mcp/generic/" + secretName
2522
}
2623

27-
func (store *CredStoreProvider) GetSecret(id string) (string, error) {
28-
_, val, err := store.credentialHelper.Get(getSecretKey(id))
24+
func (store *CredStoreProvider) List(ctx context.Context) ([]string, error) {
25+
c := cmd(ctx, "ls")
26+
out, err := c.Output()
2927
if err != nil {
30-
return "", err
31-
}
32-
return val, nil
33-
}
34-
35-
func (store *CredStoreProvider) SetSecret(id string, value string) error {
36-
return store.credentialHelper.Add(&credentials.Credentials{
37-
ServerURL: getSecretKey(id),
38-
Username: "mcp",
39-
Secret: value,
40-
})
41-
}
42-
43-
func (store *CredStoreProvider) DeleteSecret(id string) error {
44-
return store.credentialHelper.Delete(getSecretKey(id))
45-
}
46-
47-
func GetHelper() credentials.Helper {
48-
credentialHelperPath := desktop.Paths().CredentialHelperPath()
49-
return Helper{
50-
program: newShellProgramFunc(credentialHelperPath),
51-
}
52-
}
53-
54-
// newShellProgramFunc creates programs that are executed in a Shell.
55-
func newShellProgramFunc(name string) client.ProgramFunc {
56-
return func(args ...string) client.Program {
57-
return &shell{cmd: exec.CommandContext(context.Background(), name, args...)}
28+
return nil, err
29+
}
30+
scanner := bufio.NewScanner(bytes.NewReader(out))
31+
var secrets []string
32+
for scanner.Scan() {
33+
secret := scanner.Text()
34+
if len(secret) == 0 {
35+
continue
36+
}
37+
secrets = append(secrets, secret)
5838
}
39+
return secrets, nil
5940
}
6041

61-
// shell invokes shell commands to talk with a remote credentials-helper.
62-
type shell struct {
63-
cmd *exec.Cmd
64-
}
65-
66-
// Output returns responses from the remote credentials-helper.
67-
func (s *shell) Output() ([]byte, error) {
68-
return s.cmd.Output()
69-
}
70-
71-
// Input sets the input to send to a remote credentials-helper.
72-
func (s *shell) Input(in io.Reader) {
73-
s.cmd.Stdin = in
74-
}
75-
76-
// Helper wraps credential helper program.
77-
type Helper struct {
78-
// name string
79-
program client.ProgramFunc
80-
}
81-
82-
func (h Helper) List() (map[string]string, error) {
83-
return map[string]string{}, nil
84-
}
85-
86-
// Add stores new credentials.
87-
func (h Helper) Add(creds *credentials.Credentials) error {
88-
username, secret, err := h.Get(creds.ServerURL)
89-
if err != nil && !credentials.IsErrCredentialsNotFound(err) && !isErrDecryption(err) {
90-
return err
91-
}
92-
if username == creds.Username && secret == creds.Secret {
93-
return nil
94-
}
95-
if err := client.Store(h.program, creds); err != nil {
42+
func (store *CredStoreProvider) SetSecret(ctx context.Context, id string, value string) error {
43+
c := cmd(ctx, "set", getSecretKey(id))
44+
in, err := c.StdinPipe()
45+
if err != nil {
9646
return err
9747
}
98-
return nil
99-
}
100-
101-
// Delete removes credentials.
102-
func (h Helper) Delete(serverURL string) error {
103-
if _, _, err := h.Get(serverURL); err != nil {
104-
if credentials.IsErrCredentialsNotFound(err) {
105-
return nil
106-
}
48+
if err := c.Start(); err != nil {
10749
return err
10850
}
109-
return client.Erase(h.program, serverURL)
110-
}
111-
112-
// Get returns the username and secret to use for a given registry server URL.
113-
func (h Helper) Get(serverURL string) (string, string, error) {
114-
creds, err := client.Get(h.program, serverURL)
51+
_, err = in.Write([]byte(value))
11552
if err != nil {
116-
return "", "", err
53+
return err
11754
}
118-
return creds.Username, creds.Secret, nil
55+
return c.Wait()
11956
}
12057

121-
func isErrDecryption(err error) bool {
122-
return err != nil && strings.Contains(err.Error(), "gpg: decryption failed: No secret key")
58+
func (store *CredStoreProvider) DeleteSecret(ctx context.Context, id string) error {
59+
c := cmd(ctx, "rm", getSecretKey(id))
60+
return c.Run()
12361
}
124-
125-
var _ credentials.Helper = Helper{}

cmd/docker-mcp/secret-management/secret/list.go

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,30 @@ import (
66
"fmt"
77

88
"github.com/docker/mcp-gateway/cmd/docker-mcp/secret-management/formatting"
9-
"github.com/docker/mcp-gateway/pkg/desktop"
109
)
1110

1211
type ListOptions struct {
1312
JSON bool
1413
}
1514

15+
// TODO: List needs to query the secrets engine
1616
func List(ctx context.Context, opts ListOptions) error {
17-
l, err := desktop.NewSecretsClient().ListJfsSecrets(ctx)
18-
if err != nil {
19-
return err
17+
var secrets []struct {
18+
Name string
19+
Provider string
2020
}
21+
// fetch secrets from secrets engine
2122

2223
if opts.JSON {
23-
if len(l) == 0 {
24-
l = []desktop.StoredSecret{} // Guarantee empty list (instead of displaying null)
25-
}
26-
jsonData, err := json.MarshalIndent(l, "", " ")
24+
jsonData, err := json.MarshalIndent(secrets, "", " ")
2725
if err != nil {
2826
return err
2927
}
3028
fmt.Println(string(jsonData))
3129
return nil
3230
}
3331
var rows [][]string
34-
for _, v := range l {
32+
for _, v := range secrets {
3533
rows = append(rows, []string{v.Name, v.Provider})
3634
}
3735
formatting.PrettyPrintTable(rows, []int{40, 120})

cmd/docker-mcp/secret-management/secret/rm.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,33 +4,33 @@ import (
44
"context"
55
"errors"
66
"fmt"
7-
8-
"github.com/docker/mcp-gateway/pkg/desktop"
7+
"slices"
98
)
109

1110
type RmOpts struct {
1211
All bool
1312
}
1413

1514
func Remove(ctx context.Context, names []string, opts RmOpts) error {
16-
c := desktop.NewSecretsClient()
17-
if opts.All && len(names) == 0 {
18-
l, err := c.ListJfsSecrets(ctx)
15+
secrets := slices.Clone(names)
16+
p := NewCredStoreProvider()
17+
if opts.All && len(secrets) == 0 {
18+
var err error
19+
secrets, err = p.List(ctx)
1920
if err != nil {
2021
return err
2122
}
22-
for _, secret := range l {
23-
names = append(names, secret.Name)
24-
}
2523
}
24+
2625
var errs []error
27-
for _, name := range names {
28-
if err := c.DeleteJfsSecret(ctx, name); err != nil {
26+
for _, secret := range secrets {
27+
if err := p.DeleteSecret(ctx, secret); err != nil {
2928
errs = append(errs, err)
30-
fmt.Printf("failed removing secret %s\n", name)
29+
fmt.Printf("failed removing secret %s\n", secret)
3130
continue
3231
}
33-
fmt.Printf("removed secret %s\n", name)
32+
fmt.Printf("removed secret %s\n", secret)
3433
}
34+
3535
return errors.Join(errs...)
3636
}

cmd/docker-mcp/secret-management/secret/secret_test.go

Lines changed: 0 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package secret
22

33
import (
4-
"errors"
54
"testing"
65

76
"github.com/stretchr/testify/assert"
@@ -36,28 +35,3 @@ func TestIsDirectValueProvider(t *testing.T) {
3635
assert.True(t, isDirectValueProvider(Credstore))
3736
assert.False(t, isDirectValueProvider("oauth/github"))
3837
}
39-
40-
func TestIsValidProvider(t *testing.T) {
41-
// Valid providers
42-
assert.True(t, IsValidProvider(""))
43-
assert.True(t, IsValidProvider(Credstore))
44-
assert.True(t, IsValidProvider("oauth/github"))
45-
assert.True(t, IsValidProvider("oauth/google"))
46-
47-
// Invalid providers
48-
assert.False(t, IsValidProvider("invalid"))
49-
assert.False(t, IsValidProvider("oauth"))
50-
}
51-
52-
func TestIsErrDecryption(t *testing.T) {
53-
// Test decryption error detection
54-
decryptErr := errors.New("gpg: decryption failed: No secret key")
55-
assert.True(t, isErrDecryption(decryptErr))
56-
57-
// Test other errors
58-
otherErr := errors.New("some other error")
59-
assert.False(t, isErrDecryption(otherErr))
60-
61-
// Test nil
62-
assert.False(t, isErrDecryption(nil))
63-
}

0 commit comments

Comments
 (0)