Skip to content

Commit

Permalink
Bring surface area at par with .Net and fix digest computation perfor…
Browse files Browse the repository at this point in the history
…mance. (#27503)

* Bring surface area at part with .Net and fix follow up bugs.

* Incorporate feedback.
  • Loading branch information
pallavit authored Mar 10, 2022
1 parent 66d0952 commit 9575462
Show file tree
Hide file tree
Showing 8 changed files with 219 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
import com.azure.core.http.rest.PagedResponse;
import com.azure.core.http.rest.PagedResponseBase;
import com.azure.core.http.rest.Response;
import com.azure.core.http.rest.ResponseBase;
import com.azure.core.http.rest.SimpleResponse;
import com.azure.core.util.ClientOptions;
import com.azure.core.util.Configuration;
import com.azure.core.util.CoreUtils;
Expand Down Expand Up @@ -66,8 +66,10 @@ public final class UtilsImpl {
public static final String OCI_MANIFEST_MEDIA_TYPE;
public static final String DOCKER_DIGEST_HEADER_NAME;
public static final String CONTAINER_REGISTRY_TRACING_NAMESPACE_VALUE;
private static final ClientLogger LOGGER;

static {
LOGGER = new ClientLogger(UtilsImpl.class);
Map<String, String> properties = CoreUtils.getProperties("azure-containers-containerregistry.properties");
CLIENT_NAME = properties.getOrDefault("name", "UnknownName");
CLIENT_VERSION = properties.getOrDefault("version", "UnknownVersion");
Expand Down Expand Up @@ -191,18 +193,20 @@ public static String computeDigest(ByteBuffer buffer) {
return "sha256:" + byteArrayToHex(digest);

} catch (NoSuchAlgorithmException e) {
// We need to do something better here.
LOGGER.error("SHA-256 conversion failed with" + e);
throw new RuntimeException(e);
}
return null;
}

// TODO: Make this performant. We do not need String.format here and can potentially do simple bit manipulation.
public static String byteArrayToHex(byte[] a) {
StringBuilder sb = new StringBuilder(a.length * 2);
for (byte b: a) {
sb.append(String.format("%02x", b));
private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray();
private static String byteArrayToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return sb.toString();
return new String(hexChars);
}

/**
Expand All @@ -223,11 +227,10 @@ public static <T> Mono<Response<Void>> deleteResponseToSuccess(Response<T> respo
}

static <T> Mono<Response<Void>> getAcceptedDeleteResponse(Response<T> responseT, int statusCode) {
return Mono.just(new ResponseBase<String, Void>(
return Mono.just(new SimpleResponse<Void>(
responseT.getRequest(),
statusCode,
responseT.getHeaders(),
null,
null));
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.containers.containerregistry.models;

import java.util.Objects;

/**
* Options for configuring the download manifest operation.
*/
public final class DownloadManifestOptions {
private final String tag;
private final String digest;

private DownloadManifestOptions(String tag, String digest) {
this.tag = tag;
this.digest = digest;
}

/**
* Instantiate the options class with tag.
* @param tag The tag associated with the manifest.
* @return The DownloadManifestOptions object.
*/
public static DownloadManifestOptions fromTag(String tag) {
Objects.requireNonNull(tag, "tag can't be null");
return new DownloadManifestOptions(tag, null);
}

/**
* Instantiate the options class with tag.
* @param digest The digest associated with the manifest.
* @return The DownloadManifestOptions object.
*/
public static DownloadManifestOptions fromDigest(String digest) {
Objects.requireNonNull(digest, "digest can't be null");
return new DownloadManifestOptions(null, digest);
}

/**
* Digest identifier of the manifest.
* @return The associated digest.
*/
public String getDigest() {
return this.digest;
}

/**
* Tag identifier of the manifest.
* @return The associated tag.
*/
public String getTag() {
return this.tag;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.containers.containerregistry.models;

import com.azure.core.util.BinaryData;

/**
* The result from downloading an OCI manifest from the registry.
*/
public class DownloadManifestResult {
private final String digest;
private final OciManifest manifest;
private final BinaryData manifestStream;

/**
* Instantiate an instance of the DownloadManifestResult object.
* @param digest The digest of the manifest.
* @param manifest The OCIManifest object.
* @param manifestStream The manifest stream.
*/
public DownloadManifestResult(String digest, OciManifest manifest, BinaryData manifestStream) {
this.digest = digest;
this.manifest = manifest;
this.manifestStream = manifestStream;
}

/**
* The manifest's digest, calculated by the registry.
* @return The digest.
*/
public String getDigest() {
return this.digest;
}

/**
* The OCI manifest that was downloaded.
* @return The OCIManifest object.
*/
public OciManifest getManifest() {
return this.manifest;
}

/**
* The manifest stream that was downloaded.
* @return The associated manifest stream.
*/
public BinaryData getManifestStream() {
return this.manifestStream;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

package com.azure.containers.containerregistry.models;

import com.azure.core.util.BinaryData;

import java.util.Objects;

/**
* Options for configuring the upload manifest operation.
*/
public final class UploadManifestOptions {
private String tag;
private final BinaryData manifest;

/**
* Instantiate an instance of upload manifest options with the manifest information.
* @param manifest The manifest that needs to be uploaded.
*/
public UploadManifestOptions(BinaryData manifest) {
Objects.requireNonNull(manifest, "'manifest' can't be null.");
this.manifest = manifest;
}

/**
* Instantiate an instance of upload manifest options with the ocimanifest information.
* @param ociManifest The Oci manifest.
*/
public UploadManifestOptions(OciManifest ociManifest) {
Objects.requireNonNull(ociManifest, "'ociManifest' can't be null.");
this.manifest = BinaryData.fromObject(ociManifest);
}

/**
* A tag to assign to the artifact represented by this manifest.
* @param tag The tag of the manifest.
* @return The UploadManifestOptions object.
*/
public UploadManifestOptions setTag(String tag) {
this.tag = tag;
return this;
}

/**
* The tag assigned to the artifact represented by this manifest.
* @return The tag of the manifest.
*/
public String getTag() {
return this.tag;
}

/**
* The manifest to be uploaded.
* @return The BinaryData representing the manifest.
*/
public BinaryData getManifest() {
return this.manifest;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@

package com.azure.containers.containerregistry.models;

import com.azure.core.util.BinaryData;

/**
* The result from the {@link com.azure.containers.containerregistry.specialized.ContainerRegistryBlobAsyncClient#uploadManifestWithResponse(BinaryData)} operation.
* The result from uploading a manifest.
*/
public final class UploadManifestResult {
private final String digest;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,20 @@
import com.azure.containers.containerregistry.implementation.models.ContainerRegistryBlobsCompleteUploadHeaders;
import com.azure.containers.containerregistry.implementation.models.ManifestWrapper;
import com.azure.containers.containerregistry.models.DownloadBlobResult;
import com.azure.containers.containerregistry.models.DownloadManifestResult;
import com.azure.containers.containerregistry.models.OciManifest;
import com.azure.containers.containerregistry.models.UploadBlobResult;
import com.azure.containers.containerregistry.models.UploadManifestOptions;
import com.azure.containers.containerregistry.models.UploadManifestResult;
import com.azure.core.annotation.ReturnType;
import com.azure.core.annotation.ServiceClient;
import com.azure.core.annotation.ServiceMethod;
import com.azure.core.exception.ClientAuthenticationException;
import com.azure.core.exception.ServiceResponseException;
import com.azure.core.http.HttpHeaders;
import com.azure.core.http.HttpPipeline;
import com.azure.core.http.rest.Response;
import com.azure.core.http.rest.ResponseBase;
import com.azure.core.http.rest.SimpleResponse;
import com.azure.core.util.BinaryData;
import com.azure.core.util.Context;
import com.azure.core.util.FluxUtil;
Expand Down Expand Up @@ -100,7 +102,7 @@ public Mono<UploadManifestResult> uploadManifest(OciManifest manifest) {
return monoError(logger, new NullPointerException("'manifest' can't be null."));
}

return uploadManifest(BinaryData.fromObject(manifest));
return withContext(context -> this.uploadManifestWithResponse(new UploadManifestOptions(manifest), context)).flatMap(FluxUtil::toMono);
}

/**
Expand All @@ -110,18 +112,18 @@ public Mono<UploadManifestResult> uploadManifest(OciManifest manifest) {
* <p>
* Also, the data is read into memory and then an upload operation is performed as a single operation.
* @see <a href="https://github.com/opencontainers/image-spec/blob/main/manifest.md">Oci Manifest Specification</a>
* @param data The manifest that needs to be uploaded.
* @param options The options for the upload manifest operation.
* @return operation result.
* @throws ClientAuthenticationException thrown if the client's credentials do not have access to modify the namespace.
* @throws NullPointerException thrown if the {@code data} is null.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<UploadManifestResult> uploadManifest(BinaryData data) {
if (data == null) {
return monoError(logger, new NullPointerException("'data' can't be null."));
public Mono<UploadManifestResult> uploadManifest(UploadManifestOptions options) {
if (options == null) {
return monoError(logger, new NullPointerException("'options' can't be null."));
}

return withContext(context -> this.uploadManifestWithResponse(data.toByteBuffer(), context)).flatMap(FluxUtil::toMono);
return withContext(context -> this.uploadManifestWithResponse(options, context)).flatMap(FluxUtil::toMono);
}

/**
Expand All @@ -132,29 +134,30 @@ public Mono<UploadManifestResult> uploadManifest(BinaryData data) {
* Also, the data is read into memory and then an upload operation is performed as a single operation.
* @see <a href="https://github.com/opencontainers/image-spec/blob/main/manifest.md">Oci Manifest Specification</a>
*
* @param data The manifest that needs to be uploaded.
* @param options The options for the upload manifest operation.
* @return The rest response containing the operation result.
* @throws ClientAuthenticationException thrown if the client's credentials do not have access to modify the namespace.
* @throws NullPointerException thrown if the {@code data} is null.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<UploadManifestResult>> uploadManifestWithResponse(BinaryData data) {
if (data == null) {
return monoError(logger, new NullPointerException("'data' can't be null."));
public Mono<Response<UploadManifestResult>> uploadManifestWithResponse(UploadManifestOptions options) {
if (options == null) {
return monoError(logger, new NullPointerException("'options' can't be null."));
}

return withContext(context -> this.uploadManifestWithResponse(data.toByteBuffer(), context));
return withContext(context -> this.uploadManifestWithResponse(options, context));
}

Mono<Response<UploadManifestResult>> uploadManifestWithResponse(ByteBuffer data, Context context) {
if (data == null) {
return monoError(logger, new NullPointerException("'data' can't be null."));
Mono<Response<UploadManifestResult>> uploadManifestWithResponse(UploadManifestOptions options, Context context) {
if (options == null) {
return monoError(logger, new NullPointerException("'options' can't be null."));
}

String digest = UtilsImpl.computeDigest(data);
ByteBuffer data = options.getManifest().toByteBuffer();
String tagOrDigest = options.getTag() != null ? options.getTag() : UtilsImpl.computeDigest(data);
return this.registriesImpl.createManifestWithResponseAsync(
repositoryName,
digest,
tagOrDigest,
Flux.just(data),
data.remaining(),
UtilsImpl.OCI_MANIFEST_MEDIA_TYPE,
Expand Down Expand Up @@ -249,7 +252,7 @@ private String trimNextLink(String locationHeader) {
* @throws NullPointerException thrown if the {@code tagOrDigest} is null.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<OciManifest> downloadManifest(String tagOrDigest) {
public Mono<DownloadManifestResult> downloadManifest(String tagOrDigest) {
return this.downloadManifestWithResponse(tagOrDigest).flatMap(FluxUtil::toMono);
}

Expand All @@ -265,11 +268,11 @@ public Mono<OciManifest> downloadManifest(String tagOrDigest) {
* @throws NullPointerException thrown if the {@code tagOrDigest} is null.
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<OciManifest>> downloadManifestWithResponse(String tagOrDigest) {
public Mono<Response<DownloadManifestResult>> downloadManifestWithResponse(String tagOrDigest) {
return withContext(context -> this.downloadManifestWithResponse(tagOrDigest, context));
}

Mono<Response<OciManifest>> downloadManifestWithResponse(String tagOrDigest, Context context) {
Mono<Response<DownloadManifestResult>> downloadManifestWithResponse(String tagOrDigest, Context context) {
if (tagOrDigest == null) {
return monoError(logger, new NullPointerException("'tagOrDigest' can't be null."));
}
Expand All @@ -288,12 +291,11 @@ Mono<Response<OciManifest>> downloadManifestWithResponse(String tagOrDigest, Con
.setLayers(wrapper.getLayers())
.setSchemaVersion(wrapper.getSchemaVersion());

Response<OciManifest> res = new ResponseBase<Void, OciManifest>(
Response<DownloadManifestResult> res = new SimpleResponse<>(
response.getRequest(),
response.getStatusCode(),
response.getHeaders(),
ociManifest,
null);
new DownloadManifestResult(digest, ociManifest, BinaryData.fromObject(ociManifest)));

return Mono.just(res);
} else {
Expand Down Expand Up @@ -338,12 +340,11 @@ Mono<Response<DownloadBlobResult>> downloadBlobWithResponse(String digest, Conte

return BinaryData.fromFlux(streamResponse.getValue())
.flatMap(binaryData -> {
Response<DownloadBlobResult> response = new ResponseBase<HttpHeaders, DownloadBlobResult>(
Response<DownloadBlobResult> response = new SimpleResponse<>(
streamResponse.getRequest(),
streamResponse.getStatusCode(),
streamResponse.getHeaders(),
new DownloadBlobResult(resDigest, binaryData),
null);
new DownloadBlobResult(resDigest, binaryData));

return Mono.just(response);
});
Expand Down
Loading

0 comments on commit 9575462

Please sign in to comment.