| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118 |
- package space.anyi.docker;
- import org.junit.jupiter.api.Test;
- import java.io.IOException;
- import java.net.ServerSocket;
- import java.net.URI;
- import java.net.http.HttpClient;
- import java.net.http.HttpRequest;
- import java.net.http.HttpResponse;
- import java.time.Duration;
- import java.util.UUID;
- import static org.junit.jupiter.api.Assertions.*;
- /**
- * 镜像推送与 Registry 认证 API 的测试类
- * 注意:需要本机 Docker daemon 运行中(会自动拉取 registry:2 镜像)
- * 测试会启动一个本地私有仓库,结束后自动清理
- */
- class RegistryPushAPITest {
- private String uniqueName(String prefix) {
- return prefix + "-" + UUID.randomUUID().toString().substring(0, 8);
- }
- /**
- * 动态获取一个空闲端口,避免与其他进程冲突
- */
- private int freePort() throws IOException {
- try (ServerSocket socket = new ServerSocket(0)) {
- return socket.getLocalPort();
- }
- }
- /**
- * 测试 docker login:对本地私有仓库认证应返回 Login Succeeded
- */
- @Test
- void authToRegistryReturnsLoginSucceeded() throws IOException {
- RegistryPushAPI api = new RegistryPushAPI();
- int port = freePort();
- String containerId = api.startLocalRegistry(uniqueName("reg"), port);
- String registryAddress = "localhost:" + port;
- String status = api.authToRegistry(registryAddress);
- assertEquals("Login Succeeded", status, "未启用认证的私有仓库应返回 Login Succeeded");
- api.stopAndRemoveRegistryContainer(containerId);
- }
- /**
- * 测试完整闭环:启动仓库 -> 打标签 -> 推送 -> 验证仓库有镜像 -> 删本地 -> 拉回
- */
- @Test
- void pushThenPullRoundTrip() throws IOException, InterruptedException {
- RegistryPushAPI api = new RegistryPushAPI();
- int port = freePort();
- String registryContainerId = api.startLocalRegistry(uniqueName("reg"), port);
- String repository = uniqueName("myapp");
- String tag = "v1";
- String registryAddress = "localhost:" + port;
- String remoteImage = registryAddress + "/" + repository + ":" + tag;
- try {
- // 1. 认证
- assertEquals("Login Succeeded", api.authToRegistry(registryAddress));
- // 2. 打标签并推送
- boolean pushed = api.tagAndPushImage("nginx:latest", registryAddress, repository, tag);
- assertTrue(pushed, "镜像应推送成功");
- // 3. 通过 Registry HTTP API 验证仓库中确实存在该镜像
- // 等价命令: curl http://localhost:5000/v2/_catalog
- HttpResponse<String> catalogResponse = queryRegistry(port);
- String catalogBody = catalogResponse.body();
- assertTrue(catalogBody.contains(repository),
- "仓库目录中应包含推送的镜像,实际: " + catalogBody);
- // 4. 删除本地镜像副本(模拟镜像仅存在于仓库)
- api.removeImage(remoteImage);
- assertFalse(api.checkImageExists(remoteImage), "本地镜像应已被删除");
- // 5. 从仓库拉回并验证
- boolean pulled = api.pullFromRegistry(registryAddress, repository, tag);
- assertTrue(pulled, "镜像应能从仓库拉回");
- assertTrue(api.checkImageExists(remoteImage), "拉回后本地应存在该镜像");
- } finally {
- api.removeImage(remoteImage);
- api.stopAndRemoveRegistryContainer(registryContainerId);
- }
- }
- /**
- * 查询本地仓库目录(Registry HTTP API v2)
- * 等价命令: curl http://localhost:<port>/v2/_catalog
- */
- private HttpResponse<String> queryRegistry(int port) throws IOException, InterruptedException {
- HttpClient client = HttpClient.newBuilder()
- .connectTimeout(Duration.ofSeconds(3))
- .build();
- HttpRequest request = HttpRequest.newBuilder(
- URI.create("http://localhost:" + port + "/v2/_catalog"))
- .GET()
- .build();
- HttpResponse<String> response = null;
- for (int i = 0; i < 10; i++) {
- try {
- response = client.send(request, HttpResponse.BodyHandlers.ofString());
- break;
- } catch (IOException | InterruptedException e) {
- Thread.sleep(1000);
- }
- }
- assertNotNull(response, "本地仓库未能及时响应");
- return response;
- }
- }
|