
client.DockerPackageClient Maven / Gradle / Ivy
/**
* Copyright 2015 Groupon.com
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package client;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import play.libs.ws.WSClient;
import play.libs.ws.WSRequest;
import play.libs.ws.WSResponse;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Spliterator;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;
import javax.inject.Inject;
import javax.inject.Named;
/**
* A client for retreiving information about docker packages.
*
* @author Matthew Hayter (mhayter at groupon dot com)
*/
public class DockerPackageClient extends ClientBase {
/**
* Public constructor.
*
* @param baseUrl the base url of the docker registry
* @param client ws client to use
*/
@Inject
public DockerPackageClient(@Named("DockerRegistryUrl") final String baseUrl, final WSClient client) {
super(baseUrl, client);
}
/**
* Gets all of the packages in the registry.
*
* @return a response with all of the packages
*/
public CompletionStage getAllPackages() {
final CompletionStage> repoNamesPromise = client()
.url(uri("/v1/search").toString())
.get()
.thenApply(searchResponse -> {
// searchResponse = { results: [ {name: "", description: "" }, ... ] }
final Spliterator resultsIter = searchResponse.asJson().get("results").spliterator();
return StreamSupport.stream(resultsIter, false)
.map(jsonResult -> jsonResult.get("name").asText())
.collect(Collectors.toList());
});
final CompletionStage> tagListJsonNodes = repoNamesPromise.thenCompose(repoNames -> {
final List requests = repoNames.stream()
.map(name -> client().url(uri(String.format("/v1/repositories/%s/tags", name)).toString()))
.collect(Collectors.toList());
return concurrentExecute(requests, 5);
});
// Combine the repo names and the tag lists into a Map, and package in a PackageListResponse:
return CompletableFuture.allOf(repoNamesPromise.toCompletableFuture(), tagListJsonNodes.toCompletableFuture())
.thenApply(v -> {
final List repoNames = repoNamesPromise.toCompletableFuture().join();
final List tags = tagListJsonNodes.toCompletableFuture().join();
final Map> packages = Maps.newHashMapWithExpectedSize(repoNames.size());
final Iterator repoNamesIter = repoNames.iterator();
final Iterator tagListsIter = tags.iterator();
while (repoNamesIter.hasNext() && tagListsIter.hasNext()) {
final String nextName = repoNamesIter.next();
final WSResponse tagListResponse = tagListsIter.next();
final List tagList = repoTagListToImages(tagListResponse, nextName);
packages.put(nextName, tagList);
}
return new PackageListResponse(packages);
});
}
private CompletionStage> concurrentExecute(final List reqs, final int concurrency) {
if (concurrency <= 0) {
throw new IllegalArgumentException("concurrency must be >= 1");
}
// Maintains the 'lines' of concurrent requests
final ArrayList> concurrentReqs = Lists.newArrayListWithExpectedSize(concurrency);
for (int i = 0; i < concurrency; i++) {
// Dummy promises
concurrentReqs.add(CompletableFuture.completedFuture(null));
}
int concurrentCount = 0;
final ArrayList> results = new ArrayList<>(reqs.size());
for (final WSRequest next : reqs) {
final int concurrentIndex = concurrentCount % concurrency;
final CompletionStage prev = concurrentReqs.get(concurrentIndex);
final CompletionStage responsePromise = prev.thenCompose(ignored -> next.execute());
results.add(responsePromise);
concurrentReqs.add(concurrentIndex, responsePromise);
concurrentCount += 1;
}
@SuppressWarnings("rawtypes")
final CompletableFuture[] futures = results.stream()
.map(CompletionStage::toCompletableFuture)
.toArray(CompletableFuture[]::new);
return CompletableFuture.allOf(futures)
.thenApply(v -> results.stream()
.map(cs -> cs.toCompletableFuture().join())
.collect(Collectors.toList()));
}
/**
* Convert a response from a docker registry GET tags call into a list of ImageMetadata objects,
* effectively inverting the JSON map.
*
* @param tagsResponse Convertible to JSON which is expected to look like:
* {
* "latest": "9e89cc6f0bc3c38722009fe6857087b486531f9a779a0c17e3ed29dae8f12c4f",
* "0.1.1": "b486531f9a779a0c17e3ed29dae8f12c4f9e89cc6f0bc3c38722009fe6857087"
* }
* @return A list of image's metadata
*/
private List repoTagListToImages(final WSResponse tagsResponse, final String repoName) {
final JsonNode jsonTagToShaMap = tagsResponse.asJson();
final Map> shaToTagList = Maps.newHashMap();
final Iterator> tags = jsonTagToShaMap.fields();
while (tags.hasNext()) {
final Map.Entry tagShaPair = tags.next();
final String tagName = tagShaPair.getKey();
final String sha = tagShaPair.getValue().textValue();
// Add/create the tag list for this SHA
if (shaToTagList.containsKey(sha)) {
shaToTagList.get(sha).add(tagName);
} else {
final LinkedList tagList = new LinkedList<>();
tagList.add(tagName);
shaToTagList.put(sha, tagList);
}
}
final List images = new LinkedList<>();
shaToTagList.forEach((sha, tagList) -> images.add(new ImageMetadata(repoName, sha, tagList)));
return images;
}
/**
* Represents a respons from a docker registry.
*/
public static class PackageListResponse {
/**
* Public constructor.
* @param packages the packages
*/
public PackageListResponse(final Map> packages) {
_packages = packages;
}
public Map> getPackages() {
return _packages;
}
private final Map> _packages;
}
/**
* Represents image metadata.
*/
public static class ImageMetadata {
/**
* Public constructor.
*
* @param repository the name of the repository
* @param id the ide of the image
* @param tags the tags for the image
*/
public ImageMetadata(final String repository, final String id, final List tags) {
_repository = repository;
_id = id;
_tags = tags;
}
public String getName() {
return _repository;
}
public String getId() {
return _id;
}
public List getTags() {
return Collections.unmodifiableList(_tags);
}
private final String _repository;
private final String _id;
private final List _tags;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy