16
16
17
17
package com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client
18
18
19
+ import com.fasterxml.jackson.databind.ObjectMapper
19
20
import com.google.gson.Gson
20
21
import com.google.gson.GsonBuilder
21
22
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.DockerUserAgent
22
23
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerToken
23
24
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerTokenService
24
25
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.DockerRegistryAuthenticationException
25
26
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.DockerRegistryOperationException
27
+ import com.netflix.spinnaker.config.DefaultServiceEndpoint
26
28
import com.netflix.spinnaker.kork.client.ServiceClientProvider
29
+ import com.netflix.spinnaker.kork.retrofit.ErrorHandlingExecutorCallAdapterFactory
30
+ import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall
27
31
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException
32
+ import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerNetworkException
28
33
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler
29
34
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException
30
35
import groovy.util.logging.Slf4j
36
+ import okhttp3.ResponseBody
31
37
import org.slf4j.Logger
32
38
import org.slf4j.LoggerFactory
33
- import retrofit.RestAdapter
34
- import retrofit.client.Response
35
39
import retrofit.converter.GsonConverter
36
- import retrofit.converter.JacksonConverter
37
- import retrofit.http.GET
38
- import retrofit.http.Header
39
- import retrofit.http.Headers
40
- import retrofit.http.Path
41
- import retrofit.http.Query
40
+ import retrofit2.Response
41
+ import retrofit2.converter.jackson.JacksonConverterFactory ;
42
+ import retrofit2.Call
43
+ import retrofit2.Retrofit
44
+ import retrofit2.http.GET
45
+ import retrofit2.http.Header
46
+ import retrofit2.http.Headers
47
+ import retrofit2.http.Path
48
+ import retrofit2.http.Query
42
49
43
50
import java.time.Instant
44
51
@@ -177,15 +184,13 @@ class DockerRegistryClient {
177
184
178
185
this . paginateSize = paginateSize
179
186
this . tokenService = new DockerBearerTokenService (serviceClientProvider)
180
-
181
- this . registryService = new RestAdapter.Builder ()
182
- .setEndpoint(address)
183
- .setClient(okClientProvider. provide(address, clientTimeoutMillis, insecureRegistry))
184
- .setConverter(new JacksonConverter ())
185
- .setLogLevel(RestAdapter.LogLevel . NONE )
186
- .setErrorHandler(SpinnakerRetrofitErrorHandler . getInstance())
187
+ this . registryService = new Retrofit.Builder ()
188
+ .baseUrl(address)
189
+ .client(okClientProvider. provide(address, clientTimeoutMillis, insecureRegistry))
190
+ .addCallAdapterFactory(ErrorHandlingExecutorCallAdapterFactory . getInstance())
191
+ .addConverterFactory(JacksonConverterFactory . create())
187
192
.build()
188
- .create(DockerRegistryService )
193
+ .create(DockerRegistryService );
189
194
this . converter = new GsonConverter (new GsonBuilder (). create())
190
195
this . address = address
191
196
this . catalogFile = catalogFile
@@ -245,45 +250,45 @@ class DockerRegistryClient {
245
250
@Headers ([
246
251
" Docker-Distribution-API-Version: registry/2.0"
247
252
])
248
- Response getTags (@Path (value = " repository" , encode = false ) String repository , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
253
+ Call< ResponseBody > getTags (@Path (value = " repository" , encoded = true ) String repository , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
249
254
250
255
@GET (" /v2/{name}/manifests/{reference}" )
251
256
@Headers ([
252
257
" Docker-Distribution-API-Version: registry/2.0"
253
258
])
254
- Response getManifest (@Path (value = " name" , encode = false ) String name , @Path (value = " reference" , encode = false ) String reference , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
259
+ Call< ResponseBody > getManifest (@Path (value = " name" , encoded = true ) String name , @Path (value = " reference" , encoded = true ) String reference , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
255
260
256
261
@GET (" /v2/{name}/manifests/{reference}" )
257
262
@Headers ([
258
263
" Docker-Distribution-API-Version: registry/2.0" ,
259
264
" Accept: application/vnd.docker.distribution.manifest.v2+json"
260
265
])
261
- Response getSchemaV2Manifest (@Path (value = " name" , encode = false ) String name , @Path (value = " reference" , encode = false ) String reference , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
266
+ Call< ResponseBody > getSchemaV2Manifest (@Path (value = " name" , encoded = true ) String name , @Path (value = " reference" , encoded = true ) String reference , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
262
267
263
268
@GET (" /v2/_catalog" )
264
269
@Headers ([
265
270
" Docker-Distribution-API-Version: registry/2.0"
266
271
])
267
- Response getCatalog (@Query (value = " n" ) int paginateSize , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
272
+ Call< ResponseBody > getCatalog (@Query (value = " n" ) int paginateSize , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
268
273
269
274
@GET (" /{path}" )
270
275
@Headers ([
271
276
" Docker-Distribution-API-Version: registry/2.0"
272
277
])
273
- Response get (@Path (value = " path" , encode = false ) String path , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
278
+ Call< ResponseBody > get (@Path (value = " path" , encoded = true ) String path , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
274
279
275
280
@GET (" /v2/" )
276
281
@Headers ([
277
282
" User-Agent: Spinnaker-Clouddriver" ,
278
283
" Docker-Distribution-API-Version: registry/2.0"
279
284
])
280
- Response checkVersion (@Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
285
+ Call< ResponseBody > checkVersion (@Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
281
286
282
287
@GET (" /v2/{repository}/blobs/{digest}" )
283
288
@Headers ([
284
289
" Docker-Distribution-API-Version: registry/2.0"
285
290
])
286
- Response getDigestContent (@Path (value = " repository" , encode = false ) String repository , @Path (value = " digest" , encode = false ) String digest , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
291
+ Call< ResponseBody > getDigestContent (@Path (value = " repository" , encoded = true ) String repository , @Path (value = " digest" , encoded = true ) String digest , @Header (" Authorization" ) String token , @Header (" User-Agent" ) String agent )
287
292
}
288
293
289
294
public String getDigest (String name , String tag ) {
@@ -297,17 +302,31 @@ class DockerRegistryClient {
297
302
298
303
public String getConfigDigest (String name , String tag ) {
299
304
def response = getSchemaV2Manifest(name, tag)
300
- def manifestMap = converter . fromBody (response. body, Map ) as Map
305
+ def manifestMap = convertResponseBody (response. body() , Map )
301
306
return manifestMap?. config?. digest
302
307
}
303
308
304
309
public Map getDigestContent (String name , String digest ) {
305
310
def response = request({
306
- registryService. getDigestContent(name, digest, tokenService. basicAuthHeader, userAgent)
311
+ Retrofit2SyncCall . executeCall( registryService. getDigestContent(name, digest, tokenService. basicAuthHeader, userAgent) )
307
312
}, { token ->
308
- registryService. getDigestContent(name, digest, token, userAgent)
313
+ Retrofit2SyncCall . executeCall( registryService. getDigestContent(name, digest, token, userAgent) )
309
314
}, name)
310
- return converter. fromBody(response. body, Map )
315
+
316
+ return convertResponseBody(response. body(), Map )
317
+ }
318
+
319
+ static def convertResponseBody (ResponseBody responseBody , Class aClass ) {
320
+ if (responseBody == null ) {
321
+ throw new DockerRegistryOperationException (" ResponseBody cannot be null" )
322
+ }
323
+ try {
324
+ def objectMapper = new ObjectMapper ()
325
+ def jsonString = responseBody. string()
326
+ return objectMapper. readValue(jsonString, aClass)
327
+ } catch (Exception e) {
328
+ throw new DockerRegistryOperationException (" Failed to parse ResponseBody : ${ e.message} " , e)
329
+ }
311
330
}
312
331
313
332
private Map tagDateCache = [:]
@@ -317,34 +336,32 @@ class DockerRegistryClient {
317
336
if (tagDateCache. containsKey(key) && tag != ' latest' ){
318
337
return tagDateCache[key]
319
338
}
320
- Map manifest = converter . fromBody (getManifest(name, tag). body, Map )
339
+ Map manifest = convertResponseBody (getManifest(name, tag). body() , Map )
321
340
Instant dateCreated = Instant . parse(new Gson (). fromJson(manifest. history[0 ]. v1Compatibility, Map ). created)
322
341
tagDateCache[key] = dateCreated
323
342
dateCreated
324
343
}
325
344
326
345
private getManifest (String name , String tag ) {
327
346
request({
328
- registryService. getManifest(name, tag, tokenService. basicAuthHeader, userAgent)
347
+ Retrofit2SyncCall . executeCall( registryService. getManifest(name, tag, tokenService. basicAuthHeader, userAgent) )
329
348
}, { token ->
330
- registryService. getManifest(name, tag, token, userAgent)
349
+ Retrofit2SyncCall . executeCall( registryService. getManifest(name, tag, token, userAgent) )
331
350
}, name)
332
351
}
333
352
334
353
private getSchemaV2Manifest (String name , String tag ) {
335
354
request({
336
- registryService. getSchemaV2Manifest(name, tag, tokenService. basicAuthHeader, userAgent)
355
+ Retrofit2SyncCall . executeCall( registryService. getSchemaV2Manifest(name, tag, tokenService. basicAuthHeader, userAgent) )
337
356
}, { token ->
338
- registryService. getSchemaV2Manifest(name, tag, token, userAgent)
357
+ Retrofit2SyncCall . executeCall( registryService. getSchemaV2Manifest(name, tag, token, userAgent) )
339
358
}, name)
340
359
}
341
360
342
- private static String parseLink (retrofit.client.Header header ) {
343
- if (! header. name. equalsIgnoreCase(" link" )) {
344
- return null
345
- }
346
361
347
- def links = header. value. split(" ;" ). collect { it. trim() }
362
+ private static String parseLink (String headerValue ) {
363
+
364
+ def links = headerValue. split(" ;" ). collect { it. trim() }
348
365
349
366
if (! (links. findAll { String tok ->
350
367
tok. replace(" " , " " ). equalsIgnoreCase(" rel=\" next\" " )
@@ -370,23 +387,27 @@ class DockerRegistryClient {
370
387
return link. startsWith(' /' ) ? link. replaceFirst(' /' , ' ' ) : link
371
388
}
372
389
373
- private static String findNextLink (List< retrofit.client.Header > headers ) {
390
+ private static String findNextLink (okhttp3.Headers headers ) {
374
391
if (! headers) {
375
392
return null
376
393
}
377
394
378
- def paths = headers. collect { header ->
379
- parseLink(header)
380
- }. findAll { it }
395
+ def caseInsensitiveHeaders = [:]. withDefault { [] }
396
+ headers. names(). each { name ->
397
+ caseInsensitiveHeaders[name. toLowerCase()] + = headers. values(name)
398
+ }
399
+
400
+ def headerValues = caseInsensitiveHeaders[" link" ]
401
+ headers. values(" link" )
381
402
382
403
// We are at the end of the pagination.
383
- if (! paths || paths . size() == 0 ) {
404
+ if (! headerValues || headerValues . size() == 0 ) {
384
405
return null
385
- } else if (paths . size() > 1 ) {
386
- throw new DockerRegistryOperationException (" Ambiguous number of Link headers provided, the following paths were identified: $p aths " )
406
+ } else if (headerValues . size() > 1 ) {
407
+ throw new DockerRegistryOperationException (" Ambiguous number of Link headers provided, the following paths were identified: $h eaderValues " )
387
408
}
388
409
389
- return paths [0 ]
410
+ return parseLink(headerValues [0 ] as String )
390
411
}
391
412
392
413
/*
@@ -407,19 +428,19 @@ class DockerRegistryClient {
407
428
def response
408
429
try {
409
430
response = request({
410
- path ? registryService. get(path, tokenService. basicAuthHeader, userAgent) :
411
- registryService. getCatalog(paginateSize, tokenService. basicAuthHeader, userAgent)
431
+ path ? Retrofit2SyncCall . executeCall( registryService. get(path, tokenService. basicAuthHeader, userAgent) ) :
432
+ Retrofit2SyncCall . executeCall( registryService. getCatalog(paginateSize, tokenService. basicAuthHeader, userAgent) )
412
433
}, { token ->
413
- path ? registryService. get(path, token, userAgent) :
414
- registryService. getCatalog(paginateSize, token, userAgent)
434
+ path ? Retrofit2SyncCall . executeCall( registryService. get(path, token, userAgent) ) :
435
+ Retrofit2SyncCall . executeCall( registryService. getCatalog(paginateSize, token, userAgent) )
415
436
}, " _catalog" )
416
437
} catch (Exception e) {
417
438
log. warn(" Error encountered during catalog of $path " , e)
418
439
return new DockerRegistryCatalog (repositories : [])
419
440
}
420
441
421
- def nextPath = findNextLink(response?. headers)
422
- def catalog = ( DockerRegistryCatalog ) converter . fromBody( response. body, DockerRegistryCatalog )
442
+ def nextPath = findNextLink(response?. headers() )
443
+ def catalog = convertResponseBody( response. body() , DockerRegistryCatalog )
423
444
424
445
if (repositoriesRegex) {
425
446
catalog. repositories = catalog. repositories. findAll { it ==~ repositoriesRegex }
@@ -434,15 +455,15 @@ class DockerRegistryClient {
434
455
435
456
public DockerRegistryTags getTags (String repository , String path = null ) {
436
457
def response = request({
437
- path ? registryService. get(path, tokenService. basicAuthHeader, userAgent) :
438
- registryService. getTags(repository, tokenService. basicAuthHeader, userAgent)
458
+ path ? Retrofit2SyncCall . executeCall( registryService. get(path, tokenService. basicAuthHeader, userAgent) ) :
459
+ Retrofit2SyncCall . executeCall( registryService. getTags(repository, tokenService. basicAuthHeader, userAgent) )
439
460
}, { token ->
440
- path ? registryService. get(path, token, userAgent) :
441
- registryService. getTags(repository, token, userAgent)
461
+ path ? Retrofit2SyncCall . executeCall( registryService. get(path, token, userAgent) ) :
462
+ Retrofit2SyncCall . executeCall( registryService. getTags(repository, token, userAgent) )
442
463
}, repository)
443
464
444
- def nextPath = findNextLink(response?. headers)
445
- def tags = ( DockerRegistryTags ) converter . fromBody( response. body, DockerRegistryTags )
465
+ def nextPath = findNextLink(response?. headers() )
466
+ def tags = convertResponseBody( response. body() , DockerRegistryTags )
446
467
447
468
if (nextPath) {
448
469
def nextTags = getTags(repository, nextPath)
@@ -465,8 +486,8 @@ class DockerRegistryClient {
465
486
if (! tokenService. basicAuthHeader && error instanceof SpinnakerHttpException && ((SpinnakerHttpException )error). getResponseCode() == 401 ) {
466
487
return
467
488
}
468
- Response response = doCheckV2Availability(tokenService. basicAuthHeader)
469
- if (! response){
489
+ def response = doCheckV2Availability(tokenService. basicAuthHeader)
490
+ if (! response. body() ){
470
491
LOG . error " checkV2Availability" , error
471
492
throw error
472
493
}
@@ -475,27 +496,27 @@ class DockerRegistryClient {
475
496
null
476
497
}
477
498
478
- private Response doCheckV2Availability (String basicAuthHeader = null ) {
499
+ private Response< ResponseBody > doCheckV2Availability (String basicAuthHeader = null ) {
479
500
request({
480
- registryService. checkVersion(basicAuthHeader, userAgent)
501
+ Retrofit2SyncCall . executeCall( registryService. checkVersion(basicAuthHeader, userAgent) )
481
502
}, { token ->
482
- registryService. checkVersion(token, userAgent)
503
+ Retrofit2SyncCall . executeCall( registryService. checkVersion(token, userAgent) )
483
504
}, " v2 version check" )
484
505
}
485
506
486
507
/*
487
508
* Implements token request flow described here https://docs.docker.com/registry/spec/auth/token/
488
509
* The tokenService also caches tokens for us, so it will attempt to use an old token before retrying.
489
510
*/
490
- public Response request (Closure<Response > withoutToken , Closure<Response > withToken , String target ) {
511
+ public Response< ResponseBody > request (Closure<Response< ResponseBody > > withoutToken , Closure<Response< ResponseBody > > withToken , String target ) {
491
512
try {
492
513
DockerBearerToken dockerToken = tokenService. getToken(target)
493
514
String token
494
515
if (dockerToken) {
495
516
token = " Bearer ${ (dockerToken.bearer_token ?: dockerToken.token) ?: dockerToken.access_token} "
496
517
}
497
518
498
- Response response
519
+ Response< ResponseBody > response
499
520
try {
500
521
if (token) {
501
522
response = withToken(token)
0 commit comments