Skip to content

Commit 50754ba

Browse files
committed
onboard gcp conformance test
1 parent 077997e commit 50754ba

File tree

13 files changed

+527
-5
lines changed

13 files changed

+527
-5
lines changed
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
package com.salesforce.multicloudj.registry.client;
2+
3+
import static org.junit.jupiter.api.Assertions.assertFalse;
4+
import static org.junit.jupiter.api.Assertions.assertNotNull;
5+
import static org.junit.jupiter.api.Assertions.assertTrue;
6+
7+
import com.salesforce.multicloudj.common.util.common.TestsUtil;
8+
import com.salesforce.multicloudj.registry.driver.AbstractRegistry;
9+
import com.salesforce.multicloudj.registry.model.Image;
10+
import java.io.InputStream;
11+
import org.junit.jupiter.api.AfterAll;
12+
import org.junit.jupiter.api.AfterEach;
13+
import org.junit.jupiter.api.BeforeAll;
14+
import org.junit.jupiter.api.BeforeEach;
15+
import org.junit.jupiter.api.Test;
16+
import org.junit.jupiter.api.TestInfo;
17+
import org.junit.jupiter.api.TestInstance;
18+
19+
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
20+
public abstract class AbstractRegistryIT {
21+
22+
// Define the Harness interface
23+
public interface Harness extends AutoCloseable {
24+
AbstractRegistry createRegistryDriver();
25+
26+
String getEndpoint();
27+
28+
String getProviderId();
29+
30+
int getPort();
31+
32+
String getTestImageRef();
33+
34+
default String[] getWiremockExtensions() {
35+
return new String[0];
36+
}
37+
}
38+
39+
protected abstract Harness createHarness();
40+
41+
private Harness harness;
42+
protected AbstractRegistry registry;
43+
44+
@BeforeAll
45+
public void initializeWireMockServer() {
46+
harness = createHarness();
47+
TestsUtil.startWireMockServer(
48+
"src/test/resources", harness.getPort(), harness.getWiremockExtensions());
49+
}
50+
51+
@AfterAll
52+
public void shutdownWireMockServer() throws Exception {
53+
TestsUtil.stopWireMockServer();
54+
harness.close();
55+
}
56+
57+
@BeforeEach
58+
public void setupTestEnvironment(TestInfo testInfo) {
59+
String testClassName = testInfo.getTestClass().map(Class::getSimpleName).orElse("Unknown");
60+
String testMethodName =
61+
testInfo.getTestMethod().map(java.lang.reflect.Method::getName).orElse("unknown");
62+
TestsUtil.startWireMockRecording(harness.getEndpoint(), testClassName, testMethodName);
63+
registry = harness.createRegistryDriver();
64+
}
65+
66+
@AfterEach
67+
public void cleanupTestEnvironment() throws Exception {
68+
TestsUtil.stopWireMockRecording();
69+
if (registry != null) {
70+
registry.close();
71+
}
72+
}
73+
74+
@Test
75+
public void testPull() {
76+
String imageRef = harness.getTestImageRef();
77+
Image image = registry.pull(imageRef);
78+
79+
assertNotNull(image, "Pulled image should not be null");
80+
assertNotNull(image.getDigest(), "Image digest should not be null");
81+
assertNotNull(image.getLayers(), "Layers should not be null");
82+
assertFalse(image.getLayers().isEmpty(), "Image should have at least one layer");
83+
}
84+
85+
@Test
86+
public void testExtract() throws Exception {
87+
String imageRef = harness.getTestImageRef();
88+
Image image = registry.pull(imageRef);
89+
assertNotNull(image);
90+
91+
try (InputStream tar = registry.extract(image)) {
92+
assertNotNull(tar, "Extracted tar stream should not be null");
93+
byte[] buffer = new byte[1024];
94+
int totalRead = 0;
95+
int bytesRead;
96+
while ((bytesRead = tar.read(buffer)) != -1) {
97+
totalRead += bytesRead;
98+
}
99+
assertTrue(totalRead > 0, "Extracted tar should contain data");
100+
}
101+
}
102+
}

registry/registry-gcp/pom.xml

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -62,12 +62,25 @@
6262
<version>1.0-rc7</version>
6363
<scope>provided</scope>
6464
</dependency>
65+
66+
<dependency>
67+
<groupId>org.wiremock</groupId>
68+
<artifactId>wiremock</artifactId>
69+
<version>3.13.2</version>
70+
<scope>test</scope>
71+
</dependency>
6572
<dependency>
66-
<groupId>org.apache.httpcomponents.client5</groupId>
67-
<artifactId>httpclient5</artifactId>
68-
<version>5.2.1</version>
73+
<groupId>com.salesforce.multicloudj</groupId>
74+
<artifactId>registry-client</artifactId>
75+
<type>test-jar</type>
76+
<scope>test</scope>
77+
</dependency>
78+
<dependency>
79+
<groupId>com.salesforce.multicloudj</groupId>
80+
<artifactId>multicloudj-common-gcp</artifactId>
81+
<type>test-jar</type>
82+
<scope>test</scope>
6983
</dependency>
70-
<!-- Test dependencies -->
7184
<dependency>
7285
<groupId>org.junit.jupiter</groupId>
7386
<artifactId>junit-jupiter-api</artifactId>
@@ -94,6 +107,26 @@
94107
</dependencies>
95108
<build>
96109
<plugins>
110+
<plugin>
111+
<groupId>org.apache.maven.plugins</groupId>
112+
<artifactId>maven-failsafe-plugin</artifactId>
113+
<executions>
114+
<execution>
115+
<id>run-integration-tests</id>
116+
<phase>integration-test</phase>
117+
<goals>
118+
<goal>integration-test</goal>
119+
</goals>
120+
</execution>
121+
<execution>
122+
<id>verify-integration-results</id>
123+
<phase>verify</phase>
124+
<goals>
125+
<goal>verify</goal>
126+
</goals>
127+
</execution>
128+
</executions>
129+
</plugin>
97130
<plugin>
98131
<groupId>org.projectlombok</groupId>
99132
<artifactId>lombok-maven-plugin</artifactId>

registry/registry-gcp/src/main/java/com/salesforce/multicloudj/registry/gcp/GcpRegistry.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import java.io.IOException;
1717
import java.util.Collections;
1818
import org.apache.commons.lang3.StringUtils;
19+
import org.apache.http.impl.client.CloseableHttpClient;
1920

2021
/**
2122
* GCP Artifact Registry implementation.
@@ -46,9 +47,15 @@ public GcpRegistry() {
4647
}
4748

4849
public GcpRegistry(Builder builder) {
50+
this(builder, null);
51+
}
52+
53+
public GcpRegistry(Builder builder, CloseableHttpClient httpClient) {
4954
super(builder);
5055
this.ociClient =
51-
registryEndpoint != null ? new OciRegistryClient(registryEndpoint, this) : null;
56+
registryEndpoint != null
57+
? new OciRegistryClient(registryEndpoint, this, httpClient)
58+
: null;
5259
}
5360

5461
@Override
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.salesforce.multicloudj.registry.gcp;
2+
3+
import static com.salesforce.multicloudj.common.util.common.TestsUtil.WIREMOCK_HOST;
4+
5+
import com.salesforce.multicloudj.common.gcp.GcpConstants;
6+
import com.salesforce.multicloudj.registry.client.AbstractRegistryIT;
7+
import com.salesforce.multicloudj.registry.driver.AbstractRegistry;
8+
import com.salesforce.multicloudj.sts.model.CredentialsOverrider;
9+
import com.salesforce.multicloudj.sts.model.CredentialsType;
10+
import com.salesforce.multicloudj.sts.model.StsCredentials;
11+
import java.security.KeyManagementException;
12+
import java.security.NoSuchAlgorithmException;
13+
import java.security.SecureRandom;
14+
import java.security.cert.X509Certificate;
15+
import java.util.concurrent.ThreadLocalRandom;
16+
import javax.net.ssl.SSLContext;
17+
import javax.net.ssl.TrustManager;
18+
import javax.net.ssl.X509TrustManager;
19+
import org.apache.http.HttpHost;
20+
import org.apache.http.conn.ssl.NoopHostnameVerifier;
21+
import org.apache.http.impl.client.CloseableHttpClient;
22+
import org.apache.http.impl.client.HttpClients;
23+
24+
public class GcpRegistryIT extends AbstractRegistryIT {
25+
26+
private static final String ENDPOINT = "https://us-central1-docker.pkg.dev";
27+
private static final String TEST_IMAGE_REF =
28+
"substrate-sdk-gcp-poc1/test-repo/hello-world:latest";
29+
30+
@Override
31+
protected Harness createHarness() {
32+
return new HarnessImpl();
33+
}
34+
35+
public static class HarnessImpl implements Harness {
36+
int port = ThreadLocalRandom.current().nextInt(1000, 10000);
37+
38+
@Override
39+
public AbstractRegistry createRegistryDriver() {
40+
boolean isRecordingEnabled = System.getProperty("record") != null;
41+
42+
CloseableHttpClient ociHttpClient = createProxyHttpClient(port);
43+
44+
GcpRegistry.Builder builder =
45+
(GcpRegistry.Builder)
46+
new GcpRegistry.Builder().withRegistryEndpoint(ENDPOINT);
47+
48+
if (isRecordingEnabled) {
49+
return new GcpRegistry(builder, ociHttpClient);
50+
}
51+
52+
CredentialsOverrider overrider =
53+
new CredentialsOverrider.Builder(CredentialsType.SESSION)
54+
.withSessionCredentials(
55+
new StsCredentials("mock-key-id", "mock-secret", "mock-gcp-oauth2-token"))
56+
.build();
57+
builder.withCredentialsOverrider(overrider);
58+
59+
return new GcpRegistry(builder, ociHttpClient);
60+
}
61+
62+
@Override
63+
public String getEndpoint() {
64+
return ENDPOINT;
65+
}
66+
67+
@Override
68+
public String getProviderId() {
69+
return GcpConstants.PROVIDER_ID;
70+
}
71+
72+
@Override
73+
public int getPort() {
74+
return port;
75+
}
76+
77+
@Override
78+
public String getTestImageRef() {
79+
return TEST_IMAGE_REF;
80+
}
81+
82+
@Override
83+
public void close() {}
84+
}
85+
86+
/**
87+
* Creates an Apache HttpClient that routes all traffic through the WireMock proxy with trust-all
88+
* SSL configuration.
89+
*/
90+
static CloseableHttpClient createProxyHttpClient(int port) {
91+
TrustManager[] trustAllCerts = {
92+
new X509TrustManager() {
93+
@Override
94+
public void checkClientTrusted(X509Certificate[] certs, String authType) {}
95+
96+
@Override
97+
public void checkServerTrusted(X509Certificate[] certs, String authType) {}
98+
99+
@Override
100+
public X509Certificate[] getAcceptedIssuers() {
101+
return new X509Certificate[0];
102+
}
103+
}
104+
};
105+
106+
try {
107+
SSLContext sslContext = SSLContext.getInstance("TLS");
108+
sslContext.init(null, trustAllCerts, new SecureRandom());
109+
110+
return HttpClients.custom()
111+
.setProxy(new HttpHost(WIREMOCK_HOST, port + 1))
112+
.setSSLContext(sslContext)
113+
.setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
114+
.build();
115+
} catch (NoSuchAlgorithmException | KeyManagementException e) {
116+
throw new RuntimeException("Failed to create proxy HTTP client", e);
117+
}
118+
}
119+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"id" : "93474093-ecb1-4555-8c99-93cb405ecc92",
3+
"name" : "GcpRegistryIT_testExtract-GET-0",
4+
"request" : {
5+
"url" : "/artifacts-downloads/namespaces/substrate-sdk-gcp-poc1/repositories/test-repo/downloads/Y6KPFvg_QT5fwwnT",
6+
"method" : "GET"
7+
},
8+
"response" : {
9+
"status" : 200,
10+
"base64Body" : "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",
11+
"headers" : {
12+
"X-Goog-Hash" : "crc32c=gFxM7w==",
13+
"Accept-Ranges" : "bytes",
14+
"Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000",
15+
"Server" : "UploadServer",
16+
"Cache-Control" : "private, max-age=0",
17+
"X-GUploader-UploadID" : "AGQBYWzR2vEr--BaozWeyEkw91q_fIEoc5SqME3Y6or5qgaSCJ8uJpijljtmqnw4XMBafJC7bHA5fXU",
18+
"Expires" : "Mon, 09 Mar 2026 21:38:54 GMT",
19+
"Date" : "Mon, 09 Mar 2026 21:38:54 GMT",
20+
"Content-Type" : "application/octet-stream"
21+
}
22+
},
23+
"uuid" : "93474093-ecb1-4555-8c99-93cb405ecc92",
24+
"persistent" : true,
25+
"insertionIndex" : 1
26+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
{
2+
"id" : "9923eef2-3bf8-4a22-a507-af81043a3166",
3+
"name" : "GcpRegistryIT_testExtract-GET-1",
4+
"request" : {
5+
"url" : "/v2/substrate-sdk-gcp-poc1/test-repo/hello-world/blobs/sha256:198f93fd5094f85a71f793fb8d8f481294d75fb80e6190abb4c6fad2b052a6b6",
6+
"method" : "GET"
7+
},
8+
"response" : {
9+
"status" : 302,
10+
"body" : "<a href=\"/artifacts-downloads/namespaces/substrate-sdk-gcp-poc1/repositories/test-repo/downloads/Y6KPFvg_QT5fwwnT\">Found</a>.\n\n",
11+
"headers" : {
12+
"X-Frame-Options" : "SAMEORIGIN",
13+
"Docker-Distribution-Api-Version" : "registry/2.0",
14+
"Alt-Svc" : "h3=\":443\"; ma=2592000,h3-29=\":443\"; ma=2592000",
15+
"X-Content-Type-Options" : "nosniff",
16+
"X-Xss-Protection" : "0",
17+
"Date" : "Mon, 09 Mar 2026 21:38:54 GMT",
18+
"Location" : "/artifacts-downloads/namespaces/substrate-sdk-gcp-poc1/repositories/test-repo/downloads/Y6KPFvg_QT5fwwnT",
19+
"Content-Type" : "text/html; charset=utf-8"
20+
}
21+
},
22+
"uuid" : "9923eef2-3bf8-4a22-a507-af81043a3166",
23+
"persistent" : true,
24+
"insertionIndex" : 2
25+
}

0 commit comments

Comments
 (0)