fix(oci): remove /v2/ from internal route patterns and make upstream apiPrefix configurable
The OCI handler had /v2/ baked into all regex patterns and Location headers. When basePath was set to /v2 (as in stack.gallery), stripping it removed the prefix that patterns expected, causing all OCI endpoints to 404. Now patterns match on bare paths after basePath stripping, working correctly regardless of the basePath value. Also adds configurable apiPrefix to OCI upstream class (default /v2) for registries behind reverse proxies with custom path prefixes.
This commit is contained in:
@@ -125,12 +125,12 @@ export class OciRegistry extends BaseRegistry {
|
||||
};
|
||||
|
||||
// Route to appropriate handler
|
||||
if (path === '/v2/' || path === '/v2') {
|
||||
if (path === '/' || path === '') {
|
||||
return this.handleVersionCheck();
|
||||
}
|
||||
|
||||
// Manifest operations: /v2/{name}/manifests/{reference}
|
||||
const manifestMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/manifests\/([^\/]+)$/);
|
||||
// Manifest operations: /{name}/manifests/{reference}
|
||||
const manifestMatch = path.match(/^\/([^\/]+(?:\/[^\/]+)*)\/manifests\/([^\/]+)$/);
|
||||
if (manifestMatch) {
|
||||
const [, name, reference] = manifestMatch;
|
||||
// Prefer rawBody for content-addressable operations to preserve exact bytes
|
||||
@@ -138,15 +138,15 @@ export class OciRegistry extends BaseRegistry {
|
||||
return this.handleManifestRequest(context.method, name, reference, token, bodyData, context.headers, actor);
|
||||
}
|
||||
|
||||
// Blob operations: /v2/{name}/blobs/{digest}
|
||||
const blobMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/blobs\/(sha256:[a-f0-9]{64})$/);
|
||||
// Blob operations: /{name}/blobs/{digest}
|
||||
const blobMatch = path.match(/^\/([^\/]+(?:\/[^\/]+)*)\/blobs\/(sha256:[a-f0-9]{64})$/);
|
||||
if (blobMatch) {
|
||||
const [, name, digest] = blobMatch;
|
||||
return this.handleBlobRequest(context.method, name, digest, token, context.headers, actor);
|
||||
}
|
||||
|
||||
// Blob upload operations: /v2/{name}/blobs/uploads/
|
||||
const uploadInitMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/blobs\/uploads\/?$/);
|
||||
// Blob upload operations: /{name}/blobs/uploads/
|
||||
const uploadInitMatch = path.match(/^\/([^\/]+(?:\/[^\/]+)*)\/blobs\/uploads\/?$/);
|
||||
if (uploadInitMatch && context.method === 'POST') {
|
||||
const [, name] = uploadInitMatch;
|
||||
// Prefer rawBody for content-addressable operations to preserve exact bytes
|
||||
@@ -154,22 +154,22 @@ export class OciRegistry extends BaseRegistry {
|
||||
return this.handleUploadInit(name, token, context.query, bodyData);
|
||||
}
|
||||
|
||||
// Blob upload operations: /v2/{name}/blobs/uploads/{uuid}
|
||||
const uploadMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/blobs\/uploads\/([^\/]+)$/);
|
||||
// Blob upload operations: /{name}/blobs/uploads/{uuid}
|
||||
const uploadMatch = path.match(/^\/([^\/]+(?:\/[^\/]+)*)\/blobs\/uploads\/([^\/]+)$/);
|
||||
if (uploadMatch) {
|
||||
const [, name, uploadId] = uploadMatch;
|
||||
return this.handleUploadSession(context.method, uploadId, token, context);
|
||||
}
|
||||
|
||||
// Tags list: /v2/{name}/tags/list
|
||||
const tagsMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/tags\/list$/);
|
||||
// Tags list: /{name}/tags/list
|
||||
const tagsMatch = path.match(/^\/([^\/]+(?:\/[^\/]+)*)\/tags\/list$/);
|
||||
if (tagsMatch) {
|
||||
const [, name] = tagsMatch;
|
||||
return this.handleTagsList(name, token, context.query);
|
||||
}
|
||||
|
||||
// Referrers: /v2/{name}/referrers/{digest}
|
||||
const referrersMatch = path.match(/^\/v2\/([^\/]+(?:\/[^\/]+)*)\/referrers\/(sha256:[a-f0-9]{64})$/);
|
||||
// Referrers: /{name}/referrers/{digest}
|
||||
const referrersMatch = path.match(/^\/([^\/]+(?:\/[^\/]+)*)\/referrers\/(sha256:[a-f0-9]{64})$/);
|
||||
if (referrersMatch) {
|
||||
const [, name, digest] = referrersMatch;
|
||||
return this.handleReferrers(name, digest, token, context.query);
|
||||
@@ -289,7 +289,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
return {
|
||||
status: 201,
|
||||
headers: {
|
||||
'Location': `${this.basePath}/v2/${repository}/blobs/${digest}`,
|
||||
'Location': `${this.basePath}/${repository}/blobs/${digest}`,
|
||||
'Docker-Content-Digest': digest,
|
||||
},
|
||||
body: null,
|
||||
@@ -312,7 +312,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
return {
|
||||
status: 202,
|
||||
headers: {
|
||||
'Location': `${this.basePath}/v2/${repository}/blobs/uploads/${uploadId}`,
|
||||
'Location': `${this.basePath}/${repository}/blobs/uploads/${uploadId}`,
|
||||
'Docker-Upload-UUID': uploadId,
|
||||
},
|
||||
body: null,
|
||||
@@ -527,7 +527,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
return {
|
||||
status: 201,
|
||||
headers: {
|
||||
'Location': `${this.basePath}/v2/${repository}/manifests/${digest}`,
|
||||
'Location': `${this.basePath}/${repository}/manifests/${digest}`,
|
||||
'Docker-Content-Digest': digest,
|
||||
},
|
||||
body: null,
|
||||
@@ -677,7 +677,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
return {
|
||||
status: 202,
|
||||
headers: {
|
||||
'Location': `${this.basePath}/v2/${session.repository}/blobs/uploads/${uploadId}`,
|
||||
'Location': `${this.basePath}/${session.repository}/blobs/uploads/${uploadId}`,
|
||||
'Range': `0-${session.totalSize - 1}`,
|
||||
'Docker-Upload-UUID': uploadId,
|
||||
},
|
||||
@@ -719,7 +719,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
return {
|
||||
status: 201,
|
||||
headers: {
|
||||
'Location': `${this.basePath}/v2/${session.repository}/blobs/${digest}`,
|
||||
'Location': `${this.basePath}/${session.repository}/blobs/${digest}`,
|
||||
'Docker-Content-Digest': digest,
|
||||
},
|
||||
body: null,
|
||||
@@ -739,7 +739,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
return {
|
||||
status: 204,
|
||||
headers: {
|
||||
'Location': `${this.basePath}/v2/${session.repository}/blobs/uploads/${uploadId}`,
|
||||
'Location': `${this.basePath}/${session.repository}/blobs/uploads/${uploadId}`,
|
||||
'Range': session.totalSize > 0 ? `0-${session.totalSize - 1}` : '0-0',
|
||||
'Docker-Upload-UUID': uploadId,
|
||||
},
|
||||
@@ -884,7 +884,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
* Per OCI Distribution Spec, 401 responses MUST include WWW-Authenticate header.
|
||||
*/
|
||||
private createUnauthorizedResponse(repository: string, action: string): IResponse {
|
||||
const realm = this.ociTokens?.realm || `${this.basePath}/v2/token`;
|
||||
const realm = this.ociTokens?.realm || `${this.basePath}/token`;
|
||||
const service = this.ociTokens?.service || 'registry';
|
||||
return {
|
||||
status: 401,
|
||||
@@ -899,7 +899,7 @@ export class OciRegistry extends BaseRegistry {
|
||||
* Create an unauthorized HEAD response (no body per HTTP spec).
|
||||
*/
|
||||
private createUnauthorizedHeadResponse(repository: string, action: string): IResponse {
|
||||
const realm = this.ociTokens?.realm || `${this.basePath}/v2/token`;
|
||||
const realm = this.ociTokens?.realm || `${this.basePath}/token`;
|
||||
const service = this.ociTokens?.service || 'registry';
|
||||
return {
|
||||
status: 401,
|
||||
|
||||
Reference in New Issue
Block a user