From a072b887049ffb3dcbd8deb5fc16204d6605f18b Mon Sep 17 00:00:00 2001 From: "Brandon Waterloo [MSFT]" <36966225+bwateratmsft@users.noreply.github.com> Date: Tue, 24 Oct 2023 09:17:02 -0400 Subject: [PATCH] Expose method to get image manifest (#213) --- package-lock.json | 2 +- .../vscode-docker-registries/package.json | 2 +- .../GitHub/GitHubRegistryDataProvider.ts | 7 +- .../RegistryV2/RegistryV2DataProvider.ts | 64 +++++++++++++------ 4 files changed, 50 insertions(+), 25 deletions(-) diff --git a/package-lock.json b/package-lock.json index 92658cf7..83fab5d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2609,7 +2609,7 @@ }, "packages/vscode-docker-registries": { "name": "@microsoft/vscode-docker-registries", - "version": "0.1.5", + "version": "0.1.6", "license": "See LICENSE in the project root for license information.", "dependencies": { "dayjs": "^1.11.7", diff --git a/packages/vscode-docker-registries/package.json b/packages/vscode-docker-registries/package.json index b76c40fe..a173e713 100644 --- a/packages/vscode-docker-registries/package.json +++ b/packages/vscode-docker-registries/package.json @@ -1,7 +1,7 @@ { "name": "@microsoft/vscode-docker-registries", "author": "Microsoft Corporation", - "version": "0.1.5", + "version": "0.1.6", "description": "Extensibility model for contributing registry providers to the Docker extension for Visual Studio Code", "license": "See LICENSE in the project root for license information.", "repository": { diff --git a/packages/vscode-docker-registries/src/clients/GitHub/GitHubRegistryDataProvider.ts b/packages/vscode-docker-registries/src/clients/GitHub/GitHubRegistryDataProvider.ts index 0bb8696e..e20ab497 100644 --- a/packages/vscode-docker-registries/src/clients/GitHub/GitHubRegistryDataProvider.ts +++ b/packages/vscode-docker-registries/src/clients/GitHub/GitHubRegistryDataProvider.ts @@ -5,7 +5,7 @@ import * as vscode from 'vscode'; import { BasicOAuthProvider } from '../../auth/BasicOAuthProvider'; -import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2RegistryRoot, V2Repository } from '../RegistryV2/RegistryV2DataProvider'; +import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2RegistryRoot, V2Repository, V2Tag } from '../RegistryV2/RegistryV2DataProvider'; import { registryV2Request } from '../RegistryV2/registryV2Request'; import { AuthenticationProvider } from '../../contracts/AuthenticationProvider'; import { httpRequest } from '../../utils/httpRequest'; @@ -131,8 +131,9 @@ export class GitHubRegistryDataProvider extends RegistryV2DataProvider { return this.authenticationProvider; } - protected override async getTagCreatedDate(repository: V2Repository, tag: string): Promise { - const tagRequestUrl = repository.baseUrl.with({ path: `v2/${repository.label}/manifests/${tag}` }); + protected override async getTagCreatedDate(item: V2Tag): Promise { + const repository = item.parent as V2Repository; + const tagRequestUrl = repository.baseUrl.with({ path: `v2/${repository.label}/manifests/${item.label}` }); const tagDetailResponse = await registryV2Request({ authenticationProvider: this.getAuthenticationProvider(repository), diff --git a/packages/vscode-docker-registries/src/clients/RegistryV2/RegistryV2DataProvider.ts b/packages/vscode-docker-registries/src/clients/RegistryV2/RegistryV2DataProvider.ts index 492bdf89..980a693c 100644 --- a/packages/vscode-docker-registries/src/clients/RegistryV2/RegistryV2DataProvider.ts +++ b/packages/vscode-docker-registries/src/clients/RegistryV2/RegistryV2DataProvider.ts @@ -8,7 +8,7 @@ import { CommonRegistryDataProvider } from '../Common/CommonRegistryDataProvider import { CommonRegistry, CommonRegistryItem, CommonRegistryRoot, CommonRepository, CommonTag } from '../Common/models'; import { AuthenticationProvider } from '../../contracts/AuthenticationProvider'; import { LoginInformation } from '../../contracts/BasicCredentials'; -import { registryV2Request } from './registryV2Request'; +import { RegistryV2Response, registryV2Request } from './registryV2Request'; import { getNextLinkFromHeaders } from '../../utils/httpRequest'; export type V2RegistryItem = CommonRegistryItem; @@ -83,7 +83,7 @@ export abstract class RegistryV2DataProvider extends CommonRegistryDataProvider // Asynchronously begin getting the created date details for each tag results.forEach(tag => { - this.getTagCreatedDate(repository, tag.label).then((createdAt) => { + this.getTagCreatedDate(tag).then((createdAt) => { tag.createdAt = createdAt; this.onDidChangeTreeDataEmitter.fire(tag); }, () => { /* Best effort */ }); @@ -114,18 +114,7 @@ export abstract class RegistryV2DataProvider extends CommonRegistryDataProvider } public async getImageDigest(item: CommonTag): Promise { - const registry = item.parent.parent as unknown as V2Registry; - const requestUrl = registry.baseUrl.with({ path: `v2/${item.parent.label}/manifests/${item.label}` }); - - const response = await registryV2Request({ - authenticationProvider: this.getAuthenticationProvider(registry), - method: 'GET', - requestUri: requestUrl, - scopes: [`repository:${item.parent.label}:pull`], - headers: { - 'accept': 'application/vnd.docker.distribution.manifest.v2+json' - } - }); + const response = await this.getManifestV2(item); const digest = response.headers['docker-content-digest']; if (!digest) { @@ -135,25 +124,60 @@ export abstract class RegistryV2DataProvider extends CommonRegistryDataProvider return digest; } - protected async getTagCreatedDate(repository: V2Repository, tag: string): Promise { - const requestUrl = repository.baseUrl.with({ path: `v2/${repository.label}/manifests/${tag}` }); + public async getManifestV1(item: V2Tag): Promise { + const repository = item.parent as V2Repository; + const requestUrl = repository.baseUrl.with({ path: `v2/${repository.label}/manifests/${item.label}` }); - const tagDetailResponse = await registryV2Request({ + const response = await registryV2Request({ authenticationProvider: this.getAuthenticationProvider(repository), method: 'GET', requestUri: requestUrl, scopes: [`repository:${repository.label}:pull`] }); - const history = JSON.parse(tagDetailResponse.body?.history?.[0]?.v1Compatibility || '{}'); - return history?.created ? new Date(history.created) : undefined; + if (!response.body) { + throw new Error(vscode.l10n.t('Could not find manifest for tag {0}', item.label)); + } + + // Parse the embedded JSON strings in the history + try { + response.body.history?.forEach(history => { + history.v1Compatibility = JSON.parse(history.v1Compatibility as unknown as string || '{}'); + }); + } catch { + // Best effort + } + + return response.body; + } + + protected async getManifestV2(item: V2Tag): Promise> { + const repository = item.parent as V2Repository; + const requestUrl = repository.baseUrl.with({ path: `v2/${item.parent.label}/manifests/${item.label}` }); + + return await registryV2Request({ + authenticationProvider: this.getAuthenticationProvider(repository), + method: 'GET', + requestUri: requestUrl, + scopes: [`repository:${item.parent.label}:pull`], + headers: { + 'accept': 'application/vnd.docker.distribution.manifest.v2+json' + } + }); + } + + protected async getTagCreatedDate(item: V2Tag): Promise { + const manifestv1 = await this.getManifestV1(item); + + const history = manifestv1.history?.[0]; + return history?.v1Compatibility?.created ? new Date(history.v1Compatibility.created) : undefined; } protected abstract getAuthenticationProvider(item: V2RegistryItem): AuthenticationProvider; } interface ManifestHistory { - v1Compatibility: string; // stringified ManifestHistoryV1Compatibility + v1Compatibility: ManifestHistoryV1Compatibility; // In the response this is an embedded JSON string but we will double-parse it to make it human-readable } interface ManifestHistoryV1Compatibility {