Skip to content

Commit

Permalink
ACR: consistency review and docs and samples (#33986)
Browse files Browse the repository at this point in the history
* Adding tests, docs, and samples

* Add docs

* default schema version and readme improvements
  • Loading branch information
lmolkova authored Mar 13, 2023
1 parent bf2203a commit ac58928
Show file tree
Hide file tree
Showing 36 changed files with 2,979 additions and 1,895 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@

### Breaking Changes

- Replaced `uploadManifest(OciImageManifest)` method on `ContainerRegistryBlobClient` and `ContainerRegistryBlobAsyncClient` classes
with `uploadManifest(OciImageManifest manifest, String tag)` method.

### Bugs Fixed

### Other Changes
Expand Down
168 changes: 90 additions & 78 deletions sdk/containerregistry/azure-containers-containerregistry/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,23 +61,23 @@ and then include the direct dependency in the dependencies section without the v
```
[//]: # ({x-version-update-end})

### Authenticate the client
### Authenticate clients

The [Azure Identity library][identity] provides easy Azure Active Directory support for authentication.
Note all the below samples assume you have an endpoint, which is the URL including the name of the login server and the `https://` prefix.
More information at [Azure Container Registry portal][container_registry_create_portal]

```java readme-sample-createClient
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
ContainerRegistryClient client = new ContainerRegistryClientBuilder()
ContainerRegistryClient registryClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.credential(credential)
.buildClient();
```

```java readme-sample-createAsyncClient
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
ContainerRegistryAsyncClient client = new ContainerRegistryClientBuilder()
ContainerRegistryAsyncClient registryClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.credential(credential)
.buildAsyncClient();
Expand All @@ -95,13 +95,13 @@ on how to check ARM authentication policy configuration.
`ContainerRegistryAudience` value is specific to the cloud:

```java readme-sample-armTokenPublic
ContainerRegistryClient containerRegistryClient = new ContainerRegistryClientBuilder()
ContainerRegistryClient registryClient = new ContainerRegistryClientBuilder()
.endpoint(getEndpoint())
.credential(credential)
.audience(ContainerRegistryAudience.AZURE_RESOURCE_MANAGER_PUBLIC_CLOUD)
.buildClient();

containerRegistryClient
registryClient
.listRepositoryNames()
.forEach(name -> System.out.println(name));
```
Expand All @@ -113,32 +113,32 @@ To authenticate with a registry in a [National Cloud](https://docs.microsoft.com
- If ACR access token authentication is disabled for yourcontainer Registry resource, you need to configure the audience on the Container Registry client builder.

```java readme-sample-armTokenChina
ContainerRegistryClient containerRegistryClient = new ContainerRegistryClientBuilder()
ContainerRegistryClient registryClient = new ContainerRegistryClientBuilder()
.endpoint(getEndpoint())
.credential(credential)
// only if ACR access tokens are disabled or not supported
.audience(ContainerRegistryAudience.AZURE_RESOURCE_MANAGER_CHINA)
.buildClient();

containerRegistryClient
registryClient
.listRepositoryNames()
.forEach(name -> System.out.println(name));
```

### Anonymous access support
#### Anonymous access support
If the builder is instantiated without any credentials, the SDK creates the service client for the anonymous pull mode.
The user must use this setting on a registry that has been enabled for anonymous pull.
In this mode, the user can only call listRepositoryNames method and its overload. All the other calls will fail.
For more information please read [Anonymous Pull Access](https://docs.microsoft.com/azure/container-registry/container-registry-faq#how-do-i-enable-anonymous-pull-access)

```java readme-sample-createAnonymousAccessClient
ContainerRegistryClient client = new ContainerRegistryClientBuilder()
ContainerRegistryClient registryClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.buildClient();
```

```java readme-sample-createAnonymousAsyncAccessClient
ContainerRegistryAsyncClient client = new ContainerRegistryClientBuilder()
ContainerRegistryAsyncClient registryClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.buildAsyncClient();
```
Expand All @@ -153,36 +153,35 @@ For more information please see [Container Registry Concepts](https://docs.micro

### Sync examples

- [List repository names](#list-repository-names)
- [List tags with anonymous access](#list-tags-with-anonymous-access)
- [Set artifact properties](#set-artifact-properties)
- [Delete images](#delete-images)
- [Upload images](#upload-images)
- [Download images](#download-images)
- [Delete repository with anonymous access throws](#delete-a-repository-with-anonymous-access-throws)
- Registry operations:
- [List repository names](#list-repository-names)
- [List artifact tags with anonymous access](#list-tags-with-anonymous-access)
- [Set artifact properties](#set-artifact-properties)
- [Delete images](#delete-images)
- [Delete repository with anonymous access throws](#delete-a-repository-with-anonymous-access-throws)
- Blob and manifest operations:
- [Upload images](#upload-images)
- [Download images](#download-images)
- [Delete manifest](#delete-manifest)
- [Delete blob](#delete-blob)

### List repository names
### Registry operations

This section contains `ContainerRegistryClient` samples.

#### List repository names

Iterate through the collection of repositories in the registry.

```java readme-sample-listRepositoryNames
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
ContainerRegistryClient client = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.credential(credential)
.buildClient();

client.listRepositoryNames().forEach(repository -> System.out.println(repository));
registryClient.listRepositoryNames().forEach(repository -> System.out.println(repository));
```

### List tags with anonymous access
#### List artifact tags with anonymous access

```java readme-sample-listTagProperties
ContainerRegistryClient anonymousClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.buildClient();

RegistryArtifact image = anonymousClient.getArtifact(repositoryName, digest);

PagedIterable<ArtifactTagProperties> tags = image.listTagProperties();

System.out.printf(String.format("%s has the following aliases:", image.getFullyQualifiedReference()));
Expand All @@ -192,17 +191,10 @@ for (ArtifactTagProperties tag : tags) {
}
```

### Set artifact properties
#### Set artifact properties

```java readme-sample-setArtifactProperties
TokenCredential defaultCredential = new DefaultAzureCredentialBuilder().build();

ContainerRegistryClient client = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.credential(defaultCredential)
.buildClient();

RegistryArtifact image = client.getArtifact(repositoryName, digest);
RegistryArtifact image = registryClient.getArtifact(repositoryName, digest);

image.updateTagProperties(
tag,
Expand All @@ -211,19 +203,13 @@ image.updateTagProperties(
.setDeleteEnabled(false));
```

### Delete Images

```java readme-sample-deleteImages
TokenCredential defaultCredential = new DefaultAzureCredentialBuilder().build();

ContainerRegistryClient client = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.credential(defaultCredential)
.buildClient();
#### Delete Images

```java readme-sample-deleteImages
final int imagesCountToKeep = 3;
for (String repositoryName : client.listRepositoryNames()) {
final ContainerRepository repository = client.getRepository(repositoryName);
for (String repositoryName : registryClient.listRepositoryNames()) {
final ContainerRepository repository = registryClient.getRepository(repositoryName);

// Obtain the images ordered from newest to oldest
PagedIterable<ArtifactManifestProperties> imageManifests =
Expand All @@ -245,16 +231,46 @@ for (String repositoryName : client.listRepositoryNames()) {
}
```

### Upload Images
#### Delete a repository with anonymous access throws

```java readme-sample-uploadImage
```java readme-sample-anonymousClientThrows
final String endpoint = getEndpoint();
final String repositoryName = getRepositoryName();

ContainerRegistryClient anonymousClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.buildClient();

try {
anonymousClient.deleteRepository(repositoryName);
System.out.println("Unexpected Success: Delete is not allowed on anonymous access");
} catch (ClientAuthenticationException ex) {
System.out.println("Expected exception: Delete is not allowed on anonymous access");
}
```

### Blob and manifest operations

This section contains samples for `ContainerRegistryBlobClient` that show how to upload and download images.

First, we need to create blob client.

```java readme-sample-createBlobClient
DefaultAzureCredential credential = new DefaultAzureCredentialBuilder().build();
ContainerRegistryBlobClient blobClient = new ContainerRegistryBlobClientBuilder()
.endpoint(ENDPOINT)
.repository(REPOSITORY)
.endpoint(endpoint)
.credential(credential)
.repository(repository)
.buildClient();
```

#### Upload

BinaryData configContent = BinaryData.fromObject(new ManifestConfig().setProperty("sync client"));
To upload a full image, we need to upload individual layers and configuration. After that we can upload a manifest
which describes an image or artifact and assign it a tag.

```java readme-sample-uploadImage
BinaryData configContent = BinaryData.fromObject(Collections.singletonMap("hello", "world"));

UploadBlobResult configUploadResult = blobClient.uploadBlob(configContent);
System.out.printf("Uploaded config: digest - %s, size - %s\n", configUploadResult.getDigest(), configContent.getLength());
Expand All @@ -277,52 +293,48 @@ OciImageManifest manifest = new OciImageManifest()
.setSizeInBytes(layerContent.getLength())
.setMediaType("application/octet-stream")));

UploadManifestResult manifestResult = blobClient.uploadManifest(new UploadManifestOptions(manifest).setTag("latest"));
UploadManifestResult manifestResult = blobClient.uploadManifest(manifest, "latest");
System.out.printf("Uploaded manifest: digest - %s\n", manifestResult.getDigest());
```

### Download Images
#### Download Images

```java readme-sample-downloadImage
ContainerRegistryBlobClient blobClient = new ContainerRegistryBlobClientBuilder()
.endpoint(ENDPOINT)
.repository(REPOSITORY)
.credential(credential)
.buildClient();
To download a full image, we need to download its manifest and then download individual layers and configuration.

```java readme-sample-downloadImage
DownloadManifestResult manifestResult = blobClient.downloadManifest("latest");

OciImageManifest manifest = manifestResult.asOciManifest();
System.out.printf("Got manifest:\n%s\n\n", PRETTY_PRINT.writeValueAsString(manifest));
System.out.printf("Got manifest:\n%s\n", PRETTY_PRINT.writeValueAsString(manifest));

String configFileName = manifest.getConfig().getDigest() + ".json";
blobClient.downloadStream(manifest.getConfig().getDigest(), createWriteChannel(configFileName));
blobClient.downloadStream(manifest.getConfig().getDigest(), createFileChannel(configFileName));
System.out.printf("Got config: %s\n", configFileName);

for (OciDescriptor layer : manifest.getLayers()) {
blobClient.downloadStream(layer.getDigest(), createWriteChannel(layer.getDigest()));
blobClient.downloadStream(layer.getDigest(), createFileChannel(layer.getDigest()));
System.out.printf("Got layer: %s\n", layer.getDigest());
}
```

#### Delete blob

### Delete a repository with anonymous access throws
```java readme-sample-anonymousClientThrows
final String endpoint = getEndpoint();
final String repositoryName = getRepositoryName();

ContainerRegistryClient anonymousClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.buildClient();
```java readme-sample-deleteBlob
DownloadManifestResult manifestResult = blobClient.downloadManifest("latest");

try {
anonymousClient.deleteRepository(repositoryName);
System.out.println("Unexpected Success: Delete is not allowed on anonymous access");
} catch (ClientAuthenticationException ex) {
System.out.println("Expected exception: Delete is not allowed on anonymous access");
OciImageManifest manifest = manifestResult.asOciManifest();
for (OciDescriptor layer : manifest.getLayers()) {
blobClient.deleteBlob(layer.getDigest());
}
```

#### Delete manifest

```java readme-sample-deleteManifest
DownloadManifestResult manifestResult = blobClient.downloadManifest("latest");
blobClient.deleteManifest(manifestResult.getDigest());
```

## Troubleshooting

See our [troubleshooting guide](https://github.com/Azure/azure-sdk-for-java/blob/main/sdk/containerregistry/azure-containers-containerregistry/TROUBLESHOOTING.md) for details on how to diagnose various failure scenarios.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ Reviewing the HTTP request sent or response received over the wire to/from the A
troubleshooting issues. To enable logging the HTTP request and response payload, the `ContainerRegistryClient` can be configured as shown below:

```java readme-sample-enablehttplogging
ContainerRegistryClient client = new ContainerRegistryClientBuilder()
ContainerRegistryClient registryClient = new ContainerRegistryClientBuilder()
.endpoint(endpoint)
.credential(defaultCredential)
.httpLogOptions(new HttpLogOptions().setLogLevel(HttpLogDetailLevel.BODY_AND_HEADERS))
Expand Down Expand Up @@ -102,7 +102,7 @@ AcrErrorsException: Status code 401, "{"errors":[{"code":"UNAUTHORIZED","message
```

The error indicates that authentication with ARM access token was disabled on accessed Container Registry resource. Check if audience was provided to
Container Registry client builder. When ARM AAD tokens are disabled on the Container Registry resource, audience should not be set.
Container Registry client builder. When ARM AAD tokens are disabled on the Container Registry resource, audience should not be set.
Refer to [ACR CLI reference](https://learn.microsoft.com/cli/azure/acr/config/authentication-as-arm?view=azure-cli-latest) for information on how to
check and configure authentication with ARM tokens.

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public interface UploadManifestResultConstructorAccessor {
}

public interface UploadBlobResultConstructorAccessor {
UploadBlobResult create(String digest);
UploadBlobResult create(String digest, long length);
}

public static void setDownloadBlobResultAccessor(final DownloadBlobAsyncResultConstructorAccessor accessor) {
Expand Down Expand Up @@ -80,7 +80,7 @@ public static DownloadManifestResult createDownloadManifestResult(String digest,
return downloadManifestAccessor.create(digest, mediaType, rawData);
}

public static UploadBlobResult createUploadBlobResult(String digest) {
public static UploadBlobResult createUploadBlobResult(String digest, long length) {
if (uploadBlobResultAccessor == null) {
try {
// it's possible that nobody yet created BlobDownloadAsyncResult, so we'll need to force its static section to run and set accessor.
Expand All @@ -90,7 +90,7 @@ public static UploadBlobResult createUploadBlobResult(String digest) {
}
}
assert uploadBlobResultAccessor != null;
return uploadBlobResultAccessor.create(digest);
return uploadBlobResultAccessor.create(digest, length);
}

public static UploadManifestResult createUploadManifestResult(String digest) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ public final class UtilsImpl {
private static final String HTTP_REST_PROXY_SYNC_PROXY_ENABLE = "com.azure.core.http.restproxy.syncproxy.enable";

public static final HttpHeaderName DOCKER_DIGEST_HEADER_NAME = HttpHeaderName.fromString("docker-content-digest");
// TODO (limolkova) should we send index and list too so that we won't need to change the default later on?
public static final String SUPPORTED_MANIFEST_TYPES = ManifestMediaType.OCI_MANIFEST + "," + ManifestMediaType.DOCKER_MANIFEST;
private static final String CONTAINER_REGISTRY_TRACING_NAMESPACE_VALUE = "Microsoft.ContainerRegistry";
private static final Context CONTEXT_WITH_SYNC = new Context(HTTP_REST_PROXY_SYNC_PROXY_ENABLE, true);
Expand Down
Loading

0 comments on commit ac58928

Please sign in to comment.