@@ -15,8 +15,10 @@ import (
15
15
"k8s.io/kubernetes/pkg/controller"
16
16
"k8s.io/kubernetes/pkg/controller/framework"
17
17
"k8s.io/kubernetes/pkg/credentialprovider"
18
+ "k8s.io/kubernetes/pkg/fields"
18
19
"k8s.io/kubernetes/pkg/registry/secret"
19
20
"k8s.io/kubernetes/pkg/runtime"
21
+ "k8s.io/kubernetes/pkg/types"
20
22
utilruntime "k8s.io/kubernetes/pkg/util/runtime"
21
23
"k8s.io/kubernetes/pkg/util/sets"
22
24
"k8s.io/kubernetes/pkg/util/wait"
@@ -33,6 +35,14 @@ const (
33
35
// ServiceAccountTokenValueAnnotation stores the actual value of the token so that a dockercfg secret can be
34
36
// made without having a value dockerURL
35
37
ServiceAccountTokenValueAnnotation = "openshift.io/token-secret.value"
38
+
39
+ // CreateDockercfgSecretsController is the name of this controller that should be
40
+ // attached to all token secrets this controller create
41
+ CreateDockercfgSecretsController = "openshift.io/create-dockercfg-secrets"
42
+
43
+ // PendingTokenAnnotation contains the name of the token secret that is waiting for the
44
+ // token data population
45
+ PendingTokenAnnotation = "openshift.io/create-dockercfg-secrets.pending-token"
36
46
)
37
47
38
48
// DockercfgControllerOptions contains options for the DockercfgController
@@ -81,8 +91,29 @@ func NewDockercfgController(cl client.Interface, options DockercfgControllerOpti
81
91
},
82
92
},
83
93
)
84
-
85
94
e .serviceAccountCache = NewEtcdMutationCache (serviceAccountCache )
95
+
96
+ tokenSecretSelector := fields .OneTermEqualSelector (api .SecretTypeField , string (api .SecretTypeServiceAccountToken ))
97
+ e .secretCache , e .secretController = framework .NewInformer (
98
+ & cache.ListWatch {
99
+ ListFunc : func (options api.ListOptions ) (runtime.Object , error ) {
100
+ options .FieldSelector = tokenSecretSelector
101
+ return e .client .Secrets (api .NamespaceAll ).List (options )
102
+ },
103
+ WatchFunc : func (options api.ListOptions ) (watch.Interface , error ) {
104
+ options .FieldSelector = tokenSecretSelector
105
+ return e .client .Secrets (api .NamespaceAll ).Watch (options )
106
+ },
107
+ },
108
+ & api.Secret {},
109
+ options .Resync ,
110
+ framework.ResourceEventHandlerFuncs {
111
+ AddFunc : func (cur interface {}) { e .handleTokenSecretUpdate (nil , cur ) },
112
+ UpdateFunc : func (old , cur interface {}) { e .handleTokenSecretUpdate (old , cur ) },
113
+ DeleteFunc : e .handleTokenSecretDelete ,
114
+ },
115
+ )
116
+
86
117
e .syncHandler = e .syncServiceAccount
87
118
88
119
return e
@@ -98,13 +129,80 @@ type DockercfgController struct {
98
129
99
130
serviceAccountCache MutationCache
100
131
serviceAccountController * framework.Controller
132
+ secretCache cache.Store
133
+ secretController * framework.Controller
101
134
102
135
queue workqueue.RateLimitingInterface
103
136
104
137
// syncHandler does the work. It's factored out for unit testing
105
138
syncHandler func (serviceKey string ) error
106
139
}
107
140
141
+ // handleTokenSecretUpdate checks if the service account token secret is populated with
142
+ // token data and triggers re-sync of service account when the data are observed.
143
+ func (e * DockercfgController ) handleTokenSecretUpdate (oldObj , newObj interface {}) {
144
+ secret := newObj .(* api.Secret )
145
+ if secret .Annotations [api .CreatedByAnnotation ] != CreateDockercfgSecretsController {
146
+ return
147
+ }
148
+ isPopulated := len (secret .Data [api .ServiceAccountTokenKey ]) > 0
149
+
150
+ wasPopulated := false
151
+ if oldObj != nil {
152
+ oldSecret := oldObj .(* api.Secret )
153
+ wasPopulated = len (oldSecret .Data [api .ServiceAccountTokenKey ]) > 0
154
+ glog .V (5 ).Infof ("Updating token secret %s/%s" , secret .Namespace , secret .Name )
155
+ } else {
156
+ glog .V (5 ).Infof ("Adding token secret %s/%s" , secret .Namespace , secret .Name )
157
+ }
158
+
159
+ if ! wasPopulated && isPopulated {
160
+ e .enqueueServiceAccountForToken (secret )
161
+ }
162
+ }
163
+
164
+ // handleTokenSecretDelete handles token secrets deletion and re-sync the service account
165
+ // which will cause a token to be re-created.
166
+ func (e * DockercfgController ) handleTokenSecretDelete (obj interface {}) {
167
+ secret , isSecret := obj .(* api.Secret )
168
+ if ! isSecret {
169
+ tombstone , objIsTombstone := obj .(cache.DeletedFinalStateUnknown )
170
+ if ! objIsTombstone {
171
+ glog .V (2 ).Infof ("Expected tombstone object when deleting token, got %v" , obj )
172
+ return
173
+ }
174
+ secret , isSecret = tombstone .Obj .(* api.Secret )
175
+ if ! isSecret {
176
+ glog .V (2 ).Infof ("Expected tombstone object to contain secret, got: %v" , obj )
177
+ return
178
+ }
179
+ }
180
+ if secret .Annotations [api .CreatedByAnnotation ] != CreateDockercfgSecretsController {
181
+ return
182
+ }
183
+ if len (secret .Data [api .ServiceAccountTokenKey ]) > 0 {
184
+ // Let deleted_token_secrets handle deletion of populated tokens
185
+ return
186
+ }
187
+ e .enqueueServiceAccountForToken (secret )
188
+ }
189
+
190
+ func (e * DockercfgController ) enqueueServiceAccountForToken (tokenSecret * api.Secret ) {
191
+ serviceAccount := & api.ServiceAccount {
192
+ ObjectMeta : api.ObjectMeta {
193
+ Name : tokenSecret .Annotations [api .ServiceAccountNameKey ],
194
+ Namespace : tokenSecret .Namespace ,
195
+ UID : types .UID (tokenSecret .Annotations [api .ServiceAccountUIDKey ]),
196
+ },
197
+ }
198
+ key , err := controller .KeyFunc (serviceAccount )
199
+ if err != nil {
200
+ utilruntime .HandleError (fmt .Errorf ("error syncing token secret %s/%s: %v" , tokenSecret .Namespace , tokenSecret .Name , err ))
201
+ return
202
+ }
203
+ e .queue .Add (key )
204
+ }
205
+
108
206
func (e * DockercfgController ) Run (workers int , stopCh <- chan struct {}) {
109
207
defer utilruntime .HandleCrash ()
110
208
@@ -119,6 +217,11 @@ func (e *DockercfgController) Run(workers int, stopCh <-chan struct{}) {
119
217
glog .Infof ("Dockercfg secret controller initialized, starting." )
120
218
121
219
go e .serviceAccountController .Run (stopCh )
220
+ go e .secretController .Run (stopCh )
221
+ for ! e .serviceAccountController .HasSynced () || ! e .secretController .HasSynced () {
222
+ time .Sleep (100 * time .Millisecond )
223
+ }
224
+
122
225
for i := 0 ; i < workers ; i ++ {
123
226
go wait .Until (e .worker , time .Second , stopCh )
124
227
}
@@ -200,7 +303,6 @@ func (e *DockercfgController) SetDockerURLs(newDockerURLs ...string) {
200
303
}
201
304
202
305
func needsDockercfgSecret (serviceAccount * api.ServiceAccount ) bool {
203
-
204
306
mountableDockercfgSecrets , imageDockercfgPullSecrets := getGeneratedDockercfgSecretNames (serviceAccount )
205
307
206
308
// look for an ImagePullSecret in the form
@@ -244,6 +346,8 @@ func (e *DockercfgController) syncServiceAccount(key string) error {
244
346
case foundMountableSecret :
245
347
serviceAccount .ImagePullSecrets = append (serviceAccount .ImagePullSecrets , api.LocalObjectReference {Name : mountableDockercfgSecrets .List ()[0 ]})
246
348
}
349
+ // Clear the pending token annotation when updating
350
+ delete (serviceAccount .Annotations , PendingTokenAnnotation )
247
351
248
352
updatedSA , err := e .client .ServiceAccounts (serviceAccount .Namespace ).Update (serviceAccount )
249
353
if err == nil {
@@ -252,10 +356,14 @@ func (e *DockercfgController) syncServiceAccount(key string) error {
252
356
return err
253
357
}
254
358
255
- dockercfgSecret , err := e .createDockerPullSecret (serviceAccount )
359
+ dockercfgSecret , created , err := e .createDockerPullSecret (serviceAccount )
256
360
if err != nil {
257
361
return err
258
362
}
363
+ if ! created {
364
+ glog .V (5 ).Infof ("The dockercfg secret was not created for service account %s/%s, will retry" , serviceAccount .Namespace , serviceAccount .Name )
365
+ return nil
366
+ }
259
367
260
368
first := true
261
369
err = client .RetryOnConflict (client .DefaultBackoff , func () error {
@@ -281,6 +389,8 @@ func (e *DockercfgController) syncServiceAccount(key string) error {
281
389
282
390
serviceAccount .Secrets = append (serviceAccount .Secrets , api.ObjectReference {Name : dockercfgSecret .Name })
283
391
serviceAccount .ImagePullSecrets = append (serviceAccount .ImagePullSecrets , api.LocalObjectReference {Name : dockercfgSecret .Name })
392
+ // Clear the pending token annotation when updating
393
+ delete (serviceAccount .Annotations , PendingTokenAnnotation )
284
394
285
395
updatedSA , err := e .client .ServiceAccounts (serviceAccount .Namespace ).Update (serviceAccount )
286
396
if err == nil {
@@ -298,62 +408,74 @@ func (e *DockercfgController) syncServiceAccount(key string) error {
298
408
return err
299
409
}
300
410
301
- const (
302
- tokenSecretWaitInterval = 20 * time .Millisecond
303
- tokenSecretWaitTimes = 100
304
- )
305
-
306
411
// createTokenSecret creates a token secret for a given service account. Returns the name of the token
307
- func (e * DockercfgController ) createTokenSecret (serviceAccount * api.ServiceAccount ) (* api.Secret , error ) {
412
+ func (e * DockercfgController ) createTokenSecret (serviceAccount * api.ServiceAccount ) (* api.Secret , bool , error ) {
413
+ pendingTokenName := serviceAccount .Annotations [PendingTokenAnnotation ]
414
+
415
+ // If this service account has no record of a pending token name, record one
416
+ if len (pendingTokenName ) == 0 {
417
+ pendingTokenName = secret .Strategy .GenerateName (osautil .GetTokenSecretNamePrefix (serviceAccount ))
418
+ if serviceAccount .Annotations == nil {
419
+ serviceAccount .Annotations = map [string ]string {}
420
+ }
421
+ serviceAccount .Annotations [PendingTokenAnnotation ] = pendingTokenName
422
+ updatedServiceAccount , err := e .client .ServiceAccounts (serviceAccount .Namespace ).Update (serviceAccount )
423
+ // Conflicts mean we'll get called to sync this service account again
424
+ if kapierrors .IsConflict (err ) {
425
+ return nil , false , nil
426
+ }
427
+ if err != nil {
428
+ return nil , false , err
429
+ }
430
+ serviceAccount = updatedServiceAccount
431
+ }
432
+
433
+ // Return the token from cache
434
+ existingTokenSecretObj , exists , err := e .secretCache .GetByKey (serviceAccount .Namespace + "/" + pendingTokenName )
435
+ if err != nil {
436
+ return nil , false , err
437
+ }
438
+ if exists {
439
+ existingTokenSecret := existingTokenSecretObj .(* api.Secret )
440
+ return existingTokenSecret , len (existingTokenSecret .Data [api .ServiceAccountTokenKey ]) > 0 , nil
441
+ }
442
+
443
+ // Try to create the named pending token
308
444
tokenSecret := & api.Secret {
309
445
ObjectMeta : api.ObjectMeta {
310
- Name : secret . Strategy . GenerateName ( osautil . GetTokenSecretNamePrefix ( serviceAccount )) ,
446
+ Name : pendingTokenName ,
311
447
Namespace : serviceAccount .Namespace ,
312
448
Annotations : map [string ]string {
313
449
api .ServiceAccountNameKey : serviceAccount .Name ,
314
450
api .ServiceAccountUIDKey : string (serviceAccount .UID ),
451
+ api .CreatedByAnnotation : CreateDockercfgSecretsController ,
315
452
},
316
453
},
317
454
Type : api .SecretTypeServiceAccountToken ,
318
455
Data : map [string ][]byte {},
319
456
}
320
457
321
- _ , err := e .client .Secrets (tokenSecret .Namespace ).Create (tokenSecret )
322
- if err != nil {
323
- return nil , err
324
- }
325
-
326
- // now we have to wait for the service account token controller to make this valid
327
- // TODO remove this once we have a create-token endpoint
328
- for i := 0 ; i <= tokenSecretWaitTimes ; i ++ {
329
- liveTokenSecret , err2 := e .client .Secrets (tokenSecret .Namespace ).Get (tokenSecret .Name )
330
- if err2 != nil {
331
- return nil , err2
332
- }
333
-
334
- if len (liveTokenSecret .Data [api .ServiceAccountTokenKey ]) > 0 {
335
- return liveTokenSecret , nil
336
- }
337
-
338
- time .Sleep (wait .Jitter (tokenSecretWaitInterval , 0.0 ))
339
-
458
+ glog .V (4 ).Infof ("Creating token secret %q for service account %s/%s" , tokenSecret .Name , serviceAccount .Namespace , serviceAccount .Name )
459
+ token , err := e .client .Secrets (tokenSecret .Namespace ).Create (tokenSecret )
460
+ // Already exists but not in cache means we'll get an add watch event and resync
461
+ if kapierrors .IsAlreadyExists (err ) {
462
+ return nil , false , nil
340
463
}
341
-
342
- // the token wasn't ever created, attempt deletion
343
- glog .Warningf ("Deleting unfilled token secret %s/%s" , tokenSecret .Namespace , tokenSecret .Name )
344
- if deleteErr := e .client .Secrets (tokenSecret .Namespace ).Delete (tokenSecret .Name ); (deleteErr != nil ) && ! kapierrors .IsNotFound (deleteErr ) {
345
- utilruntime .HandleError (deleteErr )
464
+ if err != nil {
465
+ return nil , false , err
346
466
}
347
- return nil , fmt . Errorf ( " token never generated for %s" , tokenSecret . Name )
467
+ return token , len ( token . Data [ api . ServiceAccountTokenKey ]) > 0 , nil
348
468
}
349
469
350
470
// createDockerPullSecret creates a dockercfg secret based on the token secret
351
- func (e * DockercfgController ) createDockerPullSecret (serviceAccount * api.ServiceAccount ) (* api.Secret , error ) {
352
- glog .V (4 ).Infof ("Creating secret for %s/%s" , serviceAccount .Namespace , serviceAccount .Name )
353
-
354
- tokenSecret , err := e .createTokenSecret (serviceAccount )
471
+ func (e * DockercfgController ) createDockerPullSecret (serviceAccount * api.ServiceAccount ) (* api.Secret , bool , error ) {
472
+ tokenSecret , isPopulated , err := e .createTokenSecret (serviceAccount )
355
473
if err != nil {
356
- return nil , err
474
+ return nil , false , err
475
+ }
476
+ if ! isPopulated {
477
+ glog .V (5 ).Infof ("Token secret for service account %s/%s is not populated yet" , serviceAccount .Namespace , serviceAccount .Name )
478
+ return nil , false , nil
357
479
}
358
480
359
481
dockercfgSecret := & api.Secret {
@@ -370,6 +492,7 @@ func (e *DockercfgController) createDockerPullSecret(serviceAccount *api.Service
370
492
Type : api .SecretTypeDockercfg ,
371
493
Data : map [string ][]byte {},
372
494
}
495
+ glog .V (4 ).Infof ("Creating dockercfg secret %q for service account %s/%s" , dockercfgSecret .Name , serviceAccount .Namespace , serviceAccount .Name )
373
496
374
497
// prevent updating the DockerURL until we've created the secret
375
498
e .dockerURLLock .Lock ()
@@ -385,30 +508,13 @@ func (e *DockercfgController) createDockerPullSecret(serviceAccount *api.Service
385
508
}
386
509
dockercfgContent , err := json .Marshal (& dockercfg )
387
510
if err != nil {
388
- return nil , err
511
+ return nil , false , err
389
512
}
390
513
dockercfgSecret .Data [api .DockerConfigKey ] = dockercfgContent
391
514
392
515
// Save the secret
393
516
createdSecret , err := e .client .Secrets (tokenSecret .Namespace ).Create (dockercfgSecret )
394
- if err != nil {
395
- // Clean up the generated token secret if we're not going to use it
396
- glog .V (2 ).Infof ("deleting unused token secret %s/%s, error creating dockercfgSecret: %v" , tokenSecret .Namespace , tokenSecret .Name , err )
397
- if deleteErr := e .client .Secrets (tokenSecret .Namespace ).Delete (tokenSecret .Name ); (deleteErr != nil ) && ! kapierrors .IsNotFound (deleteErr ) {
398
- utilruntime .HandleError (deleteErr )
399
- }
400
- return nil , err
401
- }
402
-
403
- return createdSecret , err
404
- }
405
-
406
- func getSecretReferences (serviceAccount * api.ServiceAccount ) sets.String {
407
- references := sets .NewString ()
408
- for _ , secret := range serviceAccount .Secrets {
409
- references .Insert (secret .Name )
410
- }
411
- return references
517
+ return createdSecret , err == nil , err
412
518
}
413
519
414
520
func getGeneratedDockercfgSecretNames (serviceAccount * api.ServiceAccount ) (sets.String , sets.String ) {
0 commit comments