Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@

### Bugs Fixed

- Fixed `ContainerRegistryClient`'s handling of paging for `listRepositoryNames`. ([#47700](https://github.com/Azure/azure-sdk-for-java/pull/47700))

### Other Changes

## 1.2.21 (2025-10-27)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
"AssetsRepo": "Azure/azure-sdk-assets",
"AssetsRepoPrefixPath": "java",
"TagPrefix": "java/containerregistry/azure-containers-containerregistry",
"Tag": "java/containerregistry/azure-containers-containerregistry_543fcee932"
"Tag": "java/containerregistry/azure-containers-containerregistry_67d62f2e16"
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@
import com.azure.core.util.logging.ClientLogger;
import reactor.core.publisher.Mono;

import static com.azure.containers.containerregistry.implementation.UtilsImpl.mapAcrErrorsException;
import static com.azure.core.util.FluxUtil.monoError;
import static com.azure.core.util.FluxUtil.withContext;

Expand Down Expand Up @@ -108,7 +107,7 @@ public String getEndpoint() {
public PagedFlux<String> listRepositoryNames() {
return new PagedFlux<>(
(pageSize) -> withContext(context -> listRepositoryNamesSinglePageAsync(pageSize, context)),
(token, pageSize) -> withContext(context -> listRepositoryNamesNextSinglePageAsync(token, context)));
(token, ignored) -> withContext(context -> listRepositoryNamesNextSinglePageAsync(token, context)));
}

private Mono<PagedResponse<String>> listRepositoryNamesSinglePageAsync(Integer pageSize, Context context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ public PagedIterable<String> listRepositoryNames() {
@ServiceMethod(returns = ReturnType.COLLECTION)
public PagedIterable<String> listRepositoryNames(Context context) {
return new PagedIterable<>((pageSize) -> listRepositoryNamesSinglePageSync(pageSize, context),
(token, pageSize) -> listRepositoryNamesNextSinglePageSync(token, context));
(token, ignored) -> listRepositoryNamesNextSinglePageSync(token, context));
}

private PagedResponse<String> listRepositoryNamesSinglePageSync(Integer pageSize, Context context) {
Expand All @@ -134,15 +134,17 @@ private PagedResponse<String> listRepositoryNamesSinglePageSync(Integer pageSize
}

try {
return this.registriesImplClient.getRepositoriesSinglePage(null, pageSize, context);
return UtilsImpl.getPagedResponseWithContinuationToken(
this.registriesImplClient.getRepositoriesSinglePage(null, pageSize, context));
} catch (AcrErrorsException exception) {
throw LOGGER.logExceptionAsError(mapAcrErrorsException(exception));
}
}

private PagedResponse<String> listRepositoryNamesNextSinglePageSync(String nextLink, Context context) {
try {
return this.registriesImplClient.getRepositoriesNextSinglePage(nextLink, context);
return UtilsImpl.getPagedResponseWithContinuationToken(
this.registriesImplClient.getRepositoriesNextSinglePage(nextLink, context));
} catch (AcrErrorsException exception) {
throw LOGGER.logExceptionAsError(mapAcrErrorsException(exception));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -570,7 +570,7 @@ private Mono<String> upload(Flux<ByteBuffer> data, String location, Context cont
return data.flatMapSequential(chunk -> {
BinaryData chunkData = BinaryData.fromByteBuffer(chunk);
return blobsImpl.uploadChunkWithResponseAsync(locationRef.get(), chunkData, chunkData.getLength(), context)
.map(response -> getLocation(response));
.map(UtilsImpl::getLocation);
}, 1, 1).doOnNext(locationRef::set).last();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -380,9 +380,8 @@ public ContainerRegistryContentAsyncClient buildAsyncClient() {
Tracer tracer = createTracer(clientOptions);
HttpPipeline pipeline = getHttpPipeline(tracer);

ContainerRegistryContentAsyncClient client = new ContainerRegistryContentAsyncClient(repositoryName, pipeline,
endpoint, serviceVersion.getVersion(), tracer);
return client;
return new ContainerRegistryContentAsyncClient(repositoryName, pipeline, endpoint, serviceVersion.getVersion(),
tracer);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -316,7 +316,7 @@ public PagedIterable<ArtifactManifestProperties> listManifestProperties(Artifact
private PagedIterable<ArtifactManifestProperties> listManifestPropertiesSync(ArtifactManifestOrder order,
Context context) {
return new PagedIterable<>((pageSize) -> listManifestPropertiesSinglePageSync(pageSize, order, context),
(token, pageSize) -> listManifestPropertiesNextSinglePageSync(token, context));
(token, ignored) -> listManifestPropertiesNextSinglePageSync(token, context));
}

private PagedResponse<ArtifactManifestProperties> listManifestPropertiesSinglePageSync(Integer pageSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import java.net.URL;
import java.util.Objects;

import static com.azure.containers.containerregistry.implementation.UtilsImpl.mapAcrErrorsException;
import static com.azure.core.util.FluxUtil.monoError;
import static com.azure.core.util.FluxUtil.withContext;

Expand Down Expand Up @@ -135,7 +134,7 @@ public String getRegistryEndpoint() {
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<Void>> deleteWithResponse() {
return withContext(context -> deleteWithResponse(context));
return withContext(this::deleteWithResponse);
}

private Mono<Response<Void>> deleteWithResponse(Context context) {
Expand Down Expand Up @@ -237,7 +236,7 @@ public PagedFlux<ArtifactManifestProperties> listManifestProperties() {
public PagedFlux<ArtifactManifestProperties> listManifestProperties(ArtifactManifestOrder order) {
return new PagedFlux<>(
(pageSize) -> withContext(context -> listManifestPropertiesSinglePageAsync(pageSize, order, context)),
(token, pageSize) -> withContext(context -> listManifestPropertiesNextSinglePageAsync(token, context)));
(token, ignored) -> withContext(context -> listManifestPropertiesNextSinglePageAsync(token, context)));
}

private Mono<PagedResponse<ArtifactManifestProperties>> listManifestPropertiesSinglePageAsync(Integer pageSize,
Expand Down Expand Up @@ -282,7 +281,7 @@ private Mono<PagedResponse<ArtifactManifestProperties>> listManifestPropertiesNe
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<ContainerRepositoryProperties>> getPropertiesWithResponse() {
return withContext(context -> this.getPropertiesWithResponse(context));
return withContext(this::getPropertiesWithResponse);
}

private Mono<Response<ContainerRepositoryProperties>> getPropertiesWithResponse(Context context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -611,7 +611,7 @@ public String getRepositoryName() {

private PagedIterable<ArtifactTagProperties> listTagPropertiesSync(ArtifactTagOrder order, Context context) {
return new PagedIterable<>((pageSize) -> listTagPropertiesSinglePageSync(pageSize, order, context),
(token, pageSize) -> listTagPropertiesNextSinglePageSync(token, context));
(token, ignored) -> listTagPropertiesNextSinglePageSync(token, context));
}

private PagedResponse<ArtifactTagProperties> listTagPropertiesSinglePageSync(Integer pageSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ public final class RegistryArtifactAsync {
this.repositoryName = repositoryName;
this.digestMono = isDigest(tagOrDigest)
? Mono.just(tagOrDigest)
: Mono.defer(() -> getTagProperties(tagOrDigest).map(a -> a.getDigest()).cache());
: Mono.defer(() -> getTagProperties(tagOrDigest).map(ArtifactTagProperties::getDigest).cache());
}

/**
Expand All @@ -114,7 +114,7 @@ public final class RegistryArtifactAsync {
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<Void>> deleteWithResponse() {
return withContext(context -> deleteWithResponse(context));
return withContext(this::deleteWithResponse);
}

private Mono<Response<Void>> deleteWithResponse(Context context) {
Expand Down Expand Up @@ -227,7 +227,7 @@ public Mono<Void> deleteTag(String tag) {
*/
@ServiceMethod(returns = ReturnType.SINGLE)
public Mono<Response<ArtifactManifestProperties>> getManifestPropertiesWithResponse() {
return withContext(context -> getManifestPropertiesWithResponse(context));
return withContext(this::getManifestPropertiesWithResponse);
}

private Mono<Response<ArtifactManifestProperties>> getManifestPropertiesWithResponse(Context context) {
Expand Down Expand Up @@ -391,7 +391,7 @@ public PagedFlux<ArtifactTagProperties> listTagProperties() {
public PagedFlux<ArtifactTagProperties> listTagProperties(ArtifactTagOrder order) {
return new PagedFlux<>(
(pageSize) -> withContext(context -> listTagPropertiesSinglePageAsync(pageSize, order, context)),
(token, pageSize) -> withContext(context -> listTagPropertiesNextSinglePageAsync(token, context)));
(token, ignored) -> withContext(context -> listTagPropertiesNextSinglePageAsync(token, context)));
}

private Mono<PagedResponse<ArtifactTagProperties>> listTagPropertiesSinglePageAsync(Integer pageSize,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@
import com.azure.core.util.tracing.Tracer;
import com.azure.core.util.tracing.TracerProvider;

import java.net.MalformedURLException;
import java.net.URL;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
Expand Down Expand Up @@ -89,8 +89,8 @@ public final class UtilsImpl {
public static final HttpHeaderName DOCKER_DIGEST_HEADER_NAME = HttpHeaderName.fromString("docker-content-digest");

public static final String SUPPORTED_MANIFEST_TYPES
= "*/*" + "," + ManifestMediaType.OCI_IMAGE_MANIFEST + "," + ManifestMediaType.DOCKER_MANIFEST
+ ",application/vnd.oci.image.index.v1+json" + ",application/vnd.docker.distribution.manifest.list.v2+json"
= "*/*," + ManifestMediaType.OCI_IMAGE_MANIFEST + "," + ManifestMediaType.DOCKER_MANIFEST
+ ",application/vnd.oci.image.index.v1+json,application/vnd.docker.distribution.manifest.list.v2+json"
+ ",application/vnd.cncf.oras.artifact.manifest.v1+json";

private static final String CONTAINER_REGISTRY_TRACING_NAMESPACE_VALUE = "Microsoft.ContainerRegistry";
Expand Down Expand Up @@ -252,7 +252,7 @@ private static long checkManifestSize(HttpHeaders headers) {
String contentLengthString = headers.getValue(HttpHeaderName.CONTENT_LENGTH);
if (CoreUtils.isNullOrEmpty(contentLengthString)) {
throw LOGGER
.logExceptionAsError(new ServiceResponseException("Response does not include `Content-Length` header"));
.logExceptionAsError(new ServiceResponseException("Response does not include 'Content-Length' header"));
}

try {
Expand Down Expand Up @@ -434,17 +434,6 @@ public static List<ArtifactTagProperties> getTagProperties(List<TagAttributesBas
return artifactTagProperties;
}

public static void validateResponseHeaderDigest(String requestedDigest, HttpHeaders headers) {
String responseHeaderDigest = headers.getValue(DOCKER_DIGEST_HEADER_NAME);
if (!requestedDigest.equals(responseHeaderDigest)) {
throw LOGGER.atError()
.addKeyValue("requestedDigest", requestedDigest)
.addKeyValue("responseDigest", responseHeaderDigest)
.log(new ServiceResponseException(
"The digest in the response header does not match the expected digest."));
}
}

public static <H, T> String getLocation(ResponseBase<H, T> response) {
String locationHeader = response.getHeaders().getValue(HttpHeaderName.LOCATION);
// The location header returned in the nextLink for upload chunk operations starts with a '/'
Expand Down Expand Up @@ -491,9 +480,9 @@ public static boolean isDigest(String tagOrDigest) {

public static String formatFullyQualifiedReference(String endpoint, String repositoryName, String tagOrDigest) {
try {
URL endpointUrl = new URL(endpoint);
return endpointUrl.getHost() + "/" + repositoryName + (isDigest(tagOrDigest) ? "@" : ":") + tagOrDigest;
} catch (MalformedURLException ex) {
URI endpointUri = new URI(endpoint);
return endpointUri.getHost() + "/" + repositoryName + (isDigest(tagOrDigest) ? "@" : ":") + tagOrDigest;
} catch (URISyntaxException ex) {
// This will not happen.
throw LOGGER.logExceptionAsWarning(new IllegalArgumentException("'endpoint' must be a valid URL", ex));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,16 @@
import com.azure.core.test.http.AssertingHttpClientBuilder;
import com.azure.core.util.Context;
import com.azure.identity.AzureAuthorityHosts;
import org.junit.jupiter.api.AfterAll;
import org.junit.jupiter.api.Assumptions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.parallel.Execution;
import org.junit.jupiter.api.parallel.ExecutionMode;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.MethodSource;
import reactor.test.StepVerifier;

import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
Expand All @@ -47,7 +47,6 @@

@Execution(ExecutionMode.SAME_THREAD)
public class ContainerRegistryClientIntegrationTests extends ContainerRegistryClientsTestBase {

private ContainerRegistryAsyncClient registryAsyncClient;
private ContainerRegistryClient registryClient;

Expand All @@ -70,48 +69,50 @@ private ContainerRegistryClient getContainerRegistryClient(HttpClient httpClient
interceptorManager.isPlaybackMode() ? interceptorManager.getPlaybackClient() : httpClient)).buildClient();
}

@BeforeEach
void beforeEach() throws InterruptedException {
importImage(getTestMode(), REGISTRY_NAME, HELLO_WORLD_REPOSITORY_NAME,
Arrays.asList("latest", "v1", "v2", "v3", "v4"), REGISTRY_ENDPOINT);
@BeforeAll
public static void setupSharedResources() {
importImage(REGISTRY_NAME, HELLO_WORLD_REPOSITORY_NAME, Arrays.asList("latest", "v1", "v2", "v3", "v4"),
REGISTRY_ENDPOINT);

importImage(getTestMode(), REGISTRY_NAME, ALPINE_REPOSITORY_NAME,
importImage(REGISTRY_NAME, ALPINE_REPOSITORY_NAME,
Arrays.asList(LATEST_TAG_NAME, V1_TAG_NAME, V2_TAG_NAME, V3_TAG_NAME, V4_TAG_NAME), REGISTRY_ENDPOINT);
}

@AfterAll
public static void cleanupSharedResources() {

}

@ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS)
@MethodSource("getHttpClients")
public void listRepositoryNames(HttpClient httpClient) {
List<String> expected = Arrays.asList(HELLO_WORLD_REPOSITORY_NAME, ALPINE_REPOSITORY_NAME);

registryAsyncClient = getContainerRegistryAsyncClient(httpClient);
registryClient = getContainerRegistryClient(httpClient);

StepVerifier.create(registryAsyncClient.listRepositoryNames())
.recordWith(ArrayList::new)
.thenConsumeWhile(x -> true)
.expectRecordedMatches(this::validateRepositories)
StepVerifier.create(registryAsyncClient.listRepositoryNames().collectList())
.assertNext(repositories -> validateRepositories(repositories, expected))
.verifyComplete();

List<String> repositories = registryClient.listRepositoryNames().stream().collect(Collectors.toList());
validateRepositories(repositories);
validateRepositories(repositories, expected);
}

@ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS)
@MethodSource("getHttpClients")
public void listRepositoryNamesWithPageSize(HttpClient httpClient) {
List<String> expected = Arrays.asList(HELLO_WORLD_REPOSITORY_NAME, ALPINE_REPOSITORY_NAME);

registryAsyncClient = getContainerRegistryAsyncClient(httpClient);
registryClient = getContainerRegistryClient(httpClient);

StepVerifier.create(registryAsyncClient.listRepositoryNames().byPage(PAGESIZE_1))
.recordWith(ArrayList::new)
.thenConsumeWhile(x -> true)
.expectRecordedMatches(this::validateRepositoriesByPage)
StepVerifier.create(registryAsyncClient.listRepositoryNames().byPage(PAGESIZE_1).collectList())
.assertNext(pages -> validateRepositoriesByPage(pages, expected))
.verifyComplete();

ArrayList<String> repositories = new ArrayList<>();
registryClient.listRepositoryNames()
.iterableByPage(PAGESIZE_1)
.forEach(res -> repositories.addAll(res.getValue()));
validateRepositories(repositories);
validateRepositoriesByPage(
registryClient.listRepositoryNames().streamByPage(PAGESIZE_1).collect(Collectors.toList()), expected);
}

@ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS)
Expand All @@ -120,11 +121,9 @@ public void listRepositoryNamesWithInvalidPageSize(HttpClient httpClient) {
registryAsyncClient = getContainerRegistryAsyncClient(httpClient);
registryClient = getContainerRegistryClient(httpClient);

ArrayList<String> repositories = new ArrayList<>();
assertThrows(IllegalArgumentException.class,
() -> registryClient.listRepositoryNames()
.iterableByPage(-1)
.forEach(res -> repositories.addAll(res.getValue())));
() -> registryClient.listRepositoryNames().iterableByPage(-1).forEach(ignored -> {
}));

StepVerifier.create(registryAsyncClient.listRepositoryNames().byPage(-1))
.verifyError(IllegalArgumentException.class);
Expand Down Expand Up @@ -152,7 +151,9 @@ public void getContainerRepository(HttpClient httpClient) {

ContainerRepositoryAsync repositoryAsync = registryAsyncClient.getRepository(HELLO_WORLD_REPOSITORY_NAME);
assertNotNull(repositoryAsync);
StepVerifier.create(repositoryAsync.getProperties()).assertNext(this::validateProperties).verifyComplete();
StepVerifier.create(repositoryAsync.getProperties())
.assertNext(ContainerRegistryClientsTestBase::validateProperties)
.verifyComplete();

ContainerRepository repository = registryClient.getRepository(HELLO_WORLD_REPOSITORY_NAME);
assertNotNull(repository);
Expand All @@ -169,12 +170,12 @@ public void getArtifactRegistry(HttpClient httpClient) {
= registryAsyncClient.getArtifact(HELLO_WORLD_REPOSITORY_NAME, LATEST_TAG_NAME);
assertNotNull(registryArtifactAsync);
StepVerifier.create(registryArtifactAsync.getManifestProperties())
.assertNext(res -> validateManifestProperties(res, true, false))
.assertNext(res -> validateManifestProperties(res, false))
.verifyComplete();

RegistryArtifact registryArtifact = registryClient.getArtifact(HELLO_WORLD_REPOSITORY_NAME, LATEST_TAG_NAME);
assertNotNull(registryArtifact);
validateManifestProperties(registryArtifact.getManifestProperties(), true, false);
validateManifestProperties(registryArtifact.getManifestProperties(), false);
}

@ParameterizedTest(name = DISPLAY_NAME_WITH_ARGUMENTS)
Expand Down Expand Up @@ -202,8 +203,10 @@ public void audienceTest(HttpClient httpClient) {
.audience(ContainerRegistryAudience.AZURE_RESOURCE_MANAGER_PUBLIC_CLOUD)
.buildClient();

List<String> expected = Arrays.asList(HELLO_WORLD_REPOSITORY_NAME, ALPINE_REPOSITORY_NAME);

List<String> repositories = registryClient.listRepositoryNames().stream().collect(Collectors.toList());
validateRepositories(repositories);
validateRepositories(repositories, expected);

ContainerRegistryClient throwableRegistryClient = getContainerRegistryBuilder(httpClient)
.audience(ContainerRegistryAudience.AZURE_RESOURCE_MANAGER_GOVERNMENT)
Expand Down
Loading
Loading