5
5
"fmt"
6
6
"regexp"
7
7
"slices"
8
+ "sync"
8
9
9
10
"github.com/docker/cli/cli/config/credentials"
10
11
"github.com/docker/cli/cli/config/types"
@@ -24,15 +25,20 @@ type CredentialStore interface {
24
25
Refresh (ctx context.Context , cred Credential ) error
25
26
Remove (ctx context.Context , toolName string ) error
26
27
List (ctx context.Context ) ([]Credential , error )
28
+ RecreateAll (ctx context.Context ) error
27
29
}
28
30
29
31
type Store struct {
30
- credCtxs []string
31
- cfg * config.CLIConfig
32
- program client.ProgramFunc
32
+ credCtxs []string
33
+ cfg * config.CLIConfig
34
+ program client.ProgramFunc
35
+ recreateAllLock sync.RWMutex
33
36
}
34
37
35
- func (s Store ) Get (_ context.Context , toolName string ) (* Credential , bool , error ) {
38
+ func (s * Store ) Get (_ context.Context , toolName string ) (* Credential , bool , error ) {
39
+ s .recreateAllLock .RLock ()
40
+ defer s .recreateAllLock .RUnlock ()
41
+
36
42
if len (s .credCtxs ) > 0 && s .credCtxs [0 ] == AllCredentialContexts {
37
43
return nil , false , fmt .Errorf ("cannot get a credential with context %q" , AllCredentialContexts )
38
44
}
@@ -80,7 +86,10 @@ func (s Store) Get(_ context.Context, toolName string) (*Credential, bool, error
80
86
81
87
// Add adds a new credential to the credential store.
82
88
// Any context set on the credential object will be overwritten with the first context of the credential store.
83
- func (s Store ) Add (_ context.Context , cred Credential ) error {
89
+ func (s * Store ) Add (_ context.Context , cred Credential ) error {
90
+ s .recreateAllLock .RLock ()
91
+ defer s .recreateAllLock .RUnlock ()
92
+
84
93
first := first (s .credCtxs )
85
94
if first == AllCredentialContexts {
86
95
return fmt .Errorf ("cannot add a credential with context %q" , AllCredentialContexts )
@@ -99,7 +108,10 @@ func (s Store) Add(_ context.Context, cred Credential) error {
99
108
}
100
109
101
110
// Refresh updates an existing credential in the credential store.
102
- func (s Store ) Refresh (_ context.Context , cred Credential ) error {
111
+ func (s * Store ) Refresh (_ context.Context , cred Credential ) error {
112
+ s .recreateAllLock .RLock ()
113
+ defer s .recreateAllLock .RUnlock ()
114
+
103
115
if ! slices .Contains (s .credCtxs , cred .Context ) {
104
116
return fmt .Errorf ("context %q not in list of valid contexts for this credential store" , cred .Context )
105
117
}
@@ -115,7 +127,10 @@ func (s Store) Refresh(_ context.Context, cred Credential) error {
115
127
return store .Store (auth )
116
128
}
117
129
118
- func (s Store ) Remove (_ context.Context , toolName string ) error {
130
+ func (s * Store ) Remove (_ context.Context , toolName string ) error {
131
+ s .recreateAllLock .RLock ()
132
+ defer s .recreateAllLock .RUnlock ()
133
+
119
134
first := first (s .credCtxs )
120
135
if len (s .credCtxs ) > 1 || first == AllCredentialContexts {
121
136
return fmt .Errorf ("error: credential deletion is not supported when multiple credential contexts are provided" )
@@ -129,7 +144,10 @@ func (s Store) Remove(_ context.Context, toolName string) error {
129
144
return store .Erase (toolNameWithCtx (toolName , first ))
130
145
}
131
146
132
- func (s Store ) List (_ context.Context ) ([]Credential , error ) {
147
+ func (s * Store ) List (_ context.Context ) ([]Credential , error ) {
148
+ s .recreateAllLock .RLock ()
149
+ defer s .recreateAllLock .RUnlock ()
150
+
133
151
store , err := s .getStore ()
134
152
if err != nil {
135
153
return nil , err
@@ -199,6 +217,55 @@ func (s Store) List(_ context.Context) ([]Credential, error) {
199
217
return maps .Values (credsByName ), nil
200
218
}
201
219
220
+ func (s * Store ) RecreateAll (_ context.Context ) error {
221
+ store , err := s .getStore ()
222
+ if err != nil {
223
+ return err
224
+ }
225
+
226
+ // New credentials might be created after our GetAll, but they will be created with the current encryption configuration,
227
+ // so it's okay that they are skipped by this function.
228
+ s .recreateAllLock .Lock ()
229
+ all , err := store .GetAll ()
230
+ s .recreateAllLock .Unlock ()
231
+ if err != nil {
232
+ return err
233
+ }
234
+
235
+ // Loop through and recreate each individual credential.
236
+ for serverAddress := range all {
237
+ if err := s .recreateCredential (store , serverAddress ); err != nil {
238
+ return err
239
+ }
240
+ }
241
+
242
+ return nil
243
+ }
244
+
245
+ func (s * Store ) recreateCredential (store credentials.Store , serverAddress string ) error {
246
+ s .recreateAllLock .Lock ()
247
+ defer s .recreateAllLock .Unlock ()
248
+
249
+ authConfig , err := store .Get (serverAddress )
250
+ if err != nil {
251
+ if IsCredentialsNotFoundError (err ) {
252
+ // This can happen if the credential was deleted between the GetAll and the Get by another thread.
253
+ return nil
254
+ }
255
+ return err
256
+ }
257
+
258
+ if err := store .Erase (serverAddress ); err != nil {
259
+ return err
260
+ }
261
+
262
+ if err := store .Store (authConfig ); err != nil {
263
+ return err
264
+ }
265
+
266
+ return nil
267
+ }
268
+
202
269
func (s * Store ) getStore () (credentials.Store , error ) {
203
270
if s .program != nil {
204
271
return & toolCredentialStore {
0 commit comments