Skip to content

Commit 51adc6f

Browse files
refactor(retrofit2): Upgrade DockerRegistryService client from retrofit to retrofit2
1 parent 0882b52 commit 51adc6f

File tree

7 files changed

+214
-156
lines changed

7 files changed

+214
-156
lines changed

clouddriver-docker/clouddriver-docker.gradle

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ dependencies {
99
implementation "org.springframework.cloud:spring-cloud-context"
1010
implementation "org.apache.groovy:groovy"
1111
implementation "com.google.guava:guava"
12-
implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client"
12+
// implementation "com.jakewharton.retrofit:retrofit1-okhttp3-client"
1313
implementation "com.netflix.spectator:spectator-api"
14-
implementation "com.squareup.retrofit:converter-jackson"
15-
implementation "com.squareup.retrofit:retrofit"
14+
implementation "com.squareup.retrofit2:converter-jackson"
15+
// implementation "com.squareup.retrofit:retrofit"
1616
implementation "org.apache.commons:commons-compress:1.21"
1717
implementation "commons-io:commons-io"
1818
implementation "io.spinnaker.fiat:fiat-api:$fiatVersion"

clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/auth/DockerBearerTokenService.groovy

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,15 @@ import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.Docker
2121
import com.netflix.spinnaker.config.DefaultServiceEndpoint
2222
import com.netflix.spinnaker.kork.client.ServiceClientProvider
2323
import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall
24-
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler
2524
import groovy.util.logging.Slf4j
2625
import org.apache.commons.io.IOUtils
27-
import retrofit.RestAdapter
28-
import retrofit.converter.JacksonConverter
2926
import retrofit2.Call
3027
import retrofit2.http.GET
3128
import retrofit2.http.Header
3229
import retrofit2.http.Headers
3330
import retrofit2.http.Path
3431
import retrofit2.http.Query
3532

36-
import java.nio.charset.Charset
3733
import java.nio.charset.StandardCharsets
3834

3935
@Slf4j

clouddriver-docker/src/main/groovy/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DockerRegistryClient.groovy

Lines changed: 85 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -16,29 +16,36 @@
1616

1717
package com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client
1818

19+
import com.fasterxml.jackson.databind.ObjectMapper
1920
import com.google.gson.Gson
2021
import com.google.gson.GsonBuilder
2122
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.DockerUserAgent
2223
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerToken
2324
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.auth.DockerBearerTokenService
2425
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.DockerRegistryAuthenticationException
2526
import com.netflix.spinnaker.clouddriver.docker.registry.api.v2.exception.DockerRegistryOperationException
27+
import com.netflix.spinnaker.config.DefaultServiceEndpoint
2628
import com.netflix.spinnaker.kork.client.ServiceClientProvider
29+
import com.netflix.spinnaker.kork.retrofit.ErrorHandlingExecutorCallAdapterFactory
30+
import com.netflix.spinnaker.kork.retrofit.Retrofit2SyncCall
2731
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerHttpException
32+
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerNetworkException
2833
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerRetrofitErrorHandler
2934
import com.netflix.spinnaker.kork.retrofit.exceptions.SpinnakerServerException
3035
import groovy.util.logging.Slf4j
36+
import okhttp3.ResponseBody
3137
import org.slf4j.Logger
3238
import org.slf4j.LoggerFactory
33-
import retrofit.RestAdapter
34-
import retrofit.client.Response
3539
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
4249

4350
import java.time.Instant
4451

@@ -177,15 +184,13 @@ class DockerRegistryClient {
177184

178185
this.paginateSize = paginateSize
179186
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())
187192
.build()
188-
.create(DockerRegistryService)
193+
.create(DockerRegistryService);
189194
this.converter = new GsonConverter(new GsonBuilder().create())
190195
this.address = address
191196
this.catalogFile = catalogFile
@@ -245,45 +250,45 @@ class DockerRegistryClient {
245250
@Headers([
246251
"Docker-Distribution-API-Version: registry/2.0"
247252
])
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)
249254

250255
@GET("/v2/{name}/manifests/{reference}")
251256
@Headers([
252257
"Docker-Distribution-API-Version: registry/2.0"
253258
])
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)
255260

256261
@GET("/v2/{name}/manifests/{reference}")
257262
@Headers([
258263
"Docker-Distribution-API-Version: registry/2.0",
259264
"Accept: application/vnd.docker.distribution.manifest.v2+json"
260265
])
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)
262267

263268
@GET("/v2/_catalog")
264269
@Headers([
265270
"Docker-Distribution-API-Version: registry/2.0"
266271
])
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)
268273

269274
@GET("/{path}")
270275
@Headers([
271276
"Docker-Distribution-API-Version: registry/2.0"
272277
])
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)
274279

275280
@GET("/v2/")
276281
@Headers([
277282
"User-Agent: Spinnaker-Clouddriver",
278283
"Docker-Distribution-API-Version: registry/2.0"
279284
])
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)
281286

282287
@GET("/v2/{repository}/blobs/{digest}")
283288
@Headers([
284289
"Docker-Distribution-API-Version: registry/2.0"
285290
])
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)
287292
}
288293

289294
public String getDigest(String name, String tag) {
@@ -297,17 +302,31 @@ class DockerRegistryClient {
297302

298303
public String getConfigDigest(String name, String tag) {
299304
def response = getSchemaV2Manifest(name, tag)
300-
def manifestMap = converter.fromBody(response.body, Map) as Map
305+
def manifestMap = convertResponseBody(response.body(), Map)
301306
return manifestMap?.config?.digest
302307
}
303308

304309
public Map getDigestContent(String name, String digest) {
305310
def response = request({
306-
registryService.getDigestContent(name, digest, tokenService.basicAuthHeader, userAgent)
311+
Retrofit2SyncCall.executeCall(registryService.getDigestContent(name, digest, tokenService.basicAuthHeader, userAgent))
307312
}, { token ->
308-
registryService.getDigestContent(name, digest, token, userAgent)
313+
Retrofit2SyncCall.executeCall(registryService.getDigestContent(name, digest, token, userAgent))
309314
}, 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+
}
311330
}
312331

313332
private Map tagDateCache = [:]
@@ -317,34 +336,32 @@ class DockerRegistryClient {
317336
if(tagDateCache.containsKey(key) && tag !='latest'){
318337
return tagDateCache[key]
319338
}
320-
Map manifest = converter.fromBody(getManifest(name, tag).body, Map)
339+
Map manifest = convertResponseBody(getManifest(name, tag).body(), Map)
321340
Instant dateCreated = Instant.parse(new Gson().fromJson(manifest.history[0].v1Compatibility, Map).created)
322341
tagDateCache[key] = dateCreated
323342
dateCreated
324343
}
325344

326345
private getManifest(String name, String tag) {
327346
request({
328-
registryService.getManifest(name, tag, tokenService.basicAuthHeader, userAgent)
347+
Retrofit2SyncCall.executeCall(registryService.getManifest(name, tag, tokenService.basicAuthHeader, userAgent))
329348
}, { token ->
330-
registryService.getManifest(name, tag, token, userAgent)
349+
Retrofit2SyncCall.executeCall(registryService.getManifest(name, tag, token, userAgent))
331350
}, name)
332351
}
333352

334353
private getSchemaV2Manifest(String name, String tag) {
335354
request({
336-
registryService.getSchemaV2Manifest(name, tag, tokenService.basicAuthHeader, userAgent)
355+
Retrofit2SyncCall.executeCall(registryService.getSchemaV2Manifest(name, tag, tokenService.basicAuthHeader, userAgent))
337356
}, { token ->
338-
registryService.getSchemaV2Manifest(name, tag, token, userAgent)
357+
Retrofit2SyncCall.executeCall(registryService.getSchemaV2Manifest(name, tag, token, userAgent))
339358
}, name)
340359
}
341360

342-
private static String parseLink(retrofit.client.Header header) {
343-
if (!header.name.equalsIgnoreCase("link")) {
344-
return null
345-
}
346361

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() }
348365

349366
if (!(links.findAll { String tok ->
350367
tok.replace(" ", "").equalsIgnoreCase("rel=\"next\"")
@@ -370,23 +387,27 @@ class DockerRegistryClient {
370387
return link.startsWith('/') ? link.replaceFirst('/', '') : link
371388
}
372389

373-
private static String findNextLink(List<retrofit.client.Header> headers) {
390+
private static String findNextLink(okhttp3.Headers headers) {
374391
if (!headers) {
375392
return null
376393
}
377394

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")
381402

382403
// We are at the end of the pagination.
383-
if (!paths || paths.size() == 0) {
404+
if (!headerValues || headerValues.size() == 0) {
384405
return null
385-
} else if (paths.size() > 1) {
386-
throw new DockerRegistryOperationException("Ambiguous number of Link headers provided, the following paths were identified: $paths")
406+
} else if (headerValues.size() > 1) {
407+
throw new DockerRegistryOperationException("Ambiguous number of Link headers provided, the following paths were identified: $headerValues")
387408
}
388409

389-
return paths[0]
410+
return parseLink(headerValues[0] as String)
390411
}
391412

392413
/*
@@ -407,19 +428,19 @@ class DockerRegistryClient {
407428
def response
408429
try {
409430
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))
412433
}, { 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))
415436
}, "_catalog")
416437
} catch (Exception e) {
417438
log.warn("Error encountered during catalog of $path", e)
418439
return new DockerRegistryCatalog(repositories: [])
419440
}
420441

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)
423444

424445
if(repositoriesRegex) {
425446
catalog.repositories = catalog.repositories.findAll { it ==~ repositoriesRegex }
@@ -434,15 +455,15 @@ class DockerRegistryClient {
434455

435456
public DockerRegistryTags getTags(String repository, String path = null) {
436457
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))
439460
}, { 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))
442463
}, repository)
443464

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)
446467

447468
if (nextPath) {
448469
def nextTags = getTags(repository, nextPath)
@@ -465,8 +486,8 @@ class DockerRegistryClient {
465486
if (!tokenService.basicAuthHeader && error instanceof SpinnakerHttpException && ((SpinnakerHttpException)error).getResponseCode() == 401) {
466487
return
467488
}
468-
Response response = doCheckV2Availability(tokenService.basicAuthHeader)
469-
if (!response){
489+
def response = doCheckV2Availability(tokenService.basicAuthHeader)
490+
if (!response.body()){
470491
LOG.error "checkV2Availability", error
471492
throw error
472493
}
@@ -475,27 +496,27 @@ class DockerRegistryClient {
475496
null
476497
}
477498

478-
private Response doCheckV2Availability(String basicAuthHeader = null) {
499+
private Response<ResponseBody> doCheckV2Availability(String basicAuthHeader = null) {
479500
request({
480-
registryService.checkVersion(basicAuthHeader, userAgent)
501+
Retrofit2SyncCall.executeCall(registryService.checkVersion(basicAuthHeader, userAgent))
481502
}, { token ->
482-
registryService.checkVersion(token, userAgent)
503+
Retrofit2SyncCall.executeCall(registryService.checkVersion(token, userAgent))
483504
}, "v2 version check")
484505
}
485506

486507
/*
487508
* Implements token request flow described here https://docs.docker.com/registry/spec/auth/token/
488509
* The tokenService also caches tokens for us, so it will attempt to use an old token before retrying.
489510
*/
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) {
491512
try {
492513
DockerBearerToken dockerToken = tokenService.getToken(target)
493514
String token
494515
if (dockerToken) {
495516
token = "Bearer ${(dockerToken.bearer_token ?: dockerToken.token) ?: dockerToken.access_token}"
496517
}
497518

498-
Response response
519+
Response<ResponseBody> response
499520
try {
500521
if (token) {
501522
response = withToken(token)

clouddriver-docker/src/main/java/com/netflix/spinnaker/clouddriver/docker/registry/api/v2/client/DefaultDockerOkClientProvider.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616
package com.netflix.spinnaker.clouddriver.docker.registry.api.v2.client;
1717

18-
import com.jakewharton.retrofit.Ok3Client;
1918
import com.netflix.spinnaker.clouddriver.docker.registry.security.TrustAllX509TrustManager;
2019
import java.security.KeyManagementException;
2120
import java.security.NoSuchAlgorithmException;
@@ -29,7 +28,7 @@
2928
public class DefaultDockerOkClientProvider implements DockerOkClientProvider {
3029

3130
@Override
32-
public Ok3Client provide(String address, long timeoutMs, boolean insecure) {
31+
public OkHttpClient provide(String address, long timeoutMs, boolean insecure) {
3332
OkHttpClient.Builder clientBuilder =
3433
new OkHttpClient.Builder().readTimeout(timeoutMs, TimeUnit.MILLISECONDS);
3534

@@ -46,6 +45,6 @@ public Ok3Client provide(String address, long timeoutMs, boolean insecure) {
4645
sslContext.getSocketFactory(), (X509TrustManager) trustManagers[0]);
4746
}
4847

49-
return new Ok3Client(clientBuilder.build());
48+
return clientBuilder.build();
5049
}
5150
}

0 commit comments

Comments
 (0)