janus-idp / backstage-plugins

Plugins for Backstage
https://janus-idp.io
Apache License 2.0
151 stars 150 forks source link

Nexus Plugin: Support Docker Manifest List #813

Closed rmartine-ias closed 2 months ago

rmartine-ias commented 1 year ago

Describe the bug

In addition to the two types of manifests supported, a manifest list can be returned, even when the Accept header forbids this.

(Tested with application/vnd.docker.distribution.manifest.v2+json, application/vnd.docker.distribution.manifest.v1+json;q=0.9 to ensure the */* wasn't causing issues) This behavior from Nexus is contra to what the Docker docs say should happen:

If the manifest being requested uses the new format, and the appropriate media type is not present in an Accept header, the registry will assume that the client cannot handle the manifest as-is, and rewrite it on the fly into the old format. If the object that would otherwise be returned is a manifest list, the registry will look up the appropriate manifest for the amd64 platform and linux OS, rewrite that manifest into the old format if necessary, and return the result to the client.

This is not handled gracefully, and the plugin fails with rawAsset.layers is undefined.

There are several wrinkles that make this not a quick enough fix for me to do right now. First, there needs to be a way to display multiple assets that differ only by architecture. Second, we don't get size info given to us for the underlying image or its layers, only its manifest, so the naive solution ends up looking like this:

Screenshot 2023-09-28 at 2 58 46 PM

Expected Behavior

Annotating a component with a docker image name, where certain versions have multiple architectures, will result in all architecture/OS pairings displayed in the table, with some way to tell them apart. The size column will reflect image size.

What are the steps to reproduce this bug?

  1. Annotate a component with an image name that's multi-arch and in your nexus, like nexus-repository-manager/docker.image-name: bitnami/postgresql
  2. Go to Nexus tab in Backstage
  3. See crash

Versions of software used and environment

Latest plugins, Nexus 3.41.1-01

I probably can't pull the time to work on this soon, as we don't use many multi-arch images.

Very incomplete patch to build on ```patch diff --git a/plugins/nexus-repository-manager/src/api/nexus-repository-manager-api-client/nexus-repository-manager-api-client.ts b/plugins/nexus-repository-manager/src/api/nexus-repository-manager-api-client/nexus-repository-manager-api-client.ts index da98e89..3af0484 100644 --- a/plugins/nexus-repository-manager/src/api/nexus-repository-manager-api-client/nexus-repository-manager-api-client.ts +++ b/plugins/nexus-repository-manager/src/api/nexus-repository-manager-api-client/nexus-repository-manager-api-client.ts @@ -31,8 +31,9 @@ const NEXUS_REPOSITORY_MANAGER_CONFIG = { const DOCKER_MANIFEST_HEADERS = { Accept: [ 'application/vnd.docker.distribution.manifest.v2+json', - 'application/vnd.docker.distribution.manifest.v1+json;q=0.9', - '*/*;q=0.8', + 'application/vnd.docker.distribution.manifest.list.v2+json;q=0.9', + 'application/vnd.docker.distribution.manifest.v1+json;q=0.8', + '*/*;q=0.7', ].join(', '), } as const satisfies HeadersInit; @@ -143,7 +144,30 @@ export class NexusRepositoryManagerApiClient ) ?? [new Promise(() => null)], ); - return assets; + // Split out the manifest list into individual assets + // This needs to be rethought, and probably needs to make network requests + return assets.flatMap(asset => { + if ( + asset?.schemaVersion === 2 && + asset?.mediaType === + 'application/vnd.docker.distribution.manifest.list.v2+json' + ) { + return asset.manifests.map(manifest => { + return { + schemaVersion: 2, + mediaType: manifest.mediaType, + config: { + mediaType: 'unknown', // Media type of underlying image + // note: WRONG, this is the size of thea manifest, not the image + size: manifest.size, + digest: manifest.digest, + }, + layers: [], + } satisfies RawAsset; + }); + } + return asset; + }); } async getComponents(query: SearchServiceQuery) { diff --git a/plugins/nexus-repository-manager/src/components/NexusRepositoryManager/NexusRepositoryManager.tsx b/plugins/nexus-repository-manager/src/components/NexusRepositoryManager/NexusRepositoryManager.tsx index 25ec3f0..099a463 100644 --- a/plugins/nexus-repository-manager/src/components/NexusRepositoryManager/NexusRepositoryManager.tsx +++ b/plugins/nexus-repository-manager/src/components/NexusRepositoryManager/NexusRepositoryManager.tsx @@ -62,6 +62,7 @@ export function NexusRepositoryManager() { // theres only one asset per docker.image-name component // if we want to support multiple repository types // this will probably need to change in the future, + // TODO yes const asset = component.assets?.at(0); const shortHash = getSha256(asset)?.substring(0, 12); diff --git a/plugins/nexus-repository-manager/src/types.ts b/plugins/nexus-repository-manager/src/types.ts index 08a4007..d6d5999 100644 --- a/plugins/nexus-repository-manager/src/types.ts +++ b/plugins/nexus-repository-manager/src/types.ts @@ -9,7 +9,7 @@ export type Annotation = { query?: (str: string) => SearchServiceQuery; }; -export type RawAsset = RawAssetSchema1 | RawAssetSchema2; +export type RawAsset = RawAssetSchema1 | RawAssetSchema2 | RawAssetSchema2List; /** @see {@link https://docs.docker.com/registry/spec/manifest-v2-1/|Image Manifest Version 2, Schema 1} */ export type RawAssetSchema1 = { @@ -48,3 +48,22 @@ export type LayerSchema2 = { size: number; digest: string; }; + +/** @see {@link https://docs.docker.com/registry/spec/manifest-v2-2/|Image Manifest Version 2, Schema 2} */ +export type RawAssetSchema2List = { + schemaVersion: 2; + mediaType: 'application/vnd.docker.distribution.manifest.list.v2+json'; + manifests: ListManifestSchema2[]; +}; + +export type ListManifestSchema2 = { + // This could technically be manifest v1, but we don't support that right now + mediaType: RawAssetSchema2['mediaType']; + size: number; + digest: string; + platform: { + architecture: string; + os: string; + // os.version and os.features not supported as we don't need them + }; +}; diff --git a/plugins/nexus-repository-manager/src/utils/get-sha256/get-sha256.ts b/plugins/nexus-repository-manager/src/utils/get-sha256/get-sha256.ts index 18a4e4d..28ad60d 100644 --- a/plugins/nexus-repository-manager/src/utils/get-sha256/get-sha256.ts +++ b/plugins/nexus-repository-manager/src/utils/get-sha256/get-sha256.ts @@ -5,6 +5,7 @@ export function getSha256(asset: AssetXO | undefined): string { return 'N/A'; } + // TODO manifest list // The checksum should be present with a sha256 if (!asset.checksum || !('sha256' in asset.checksum)) { return 'N/A'; ```
rmartine-ias commented 1 year ago

spotify/techdocs is another good test case, ours has something weird for the mediaType (warranting checking the Content-Type header, maybe?)

rhdh-bot commented 2 months ago

This issue has been closed due to the fact that the Janus community is being sunset.

For future plugin issues, please use https://github.com/backstage/community-plugins/issues

For future showcase issues, please use https://issues.redhat.com/browse/RHIDP

For more information on the sunset, see:

https://janus-idp.io/blog/2024/07/05/future-of-janus-community https://issues.redhat.com/browse/RHIDP-3690 https://issues.redhat.com/browse/RHIDP-1018