diff --git a/package-lock.json b/package-lock.json index 8eb41ee63c..1b91afb8fe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@microsoft/vscode-azext-azureutils": "^2.0.0", "@microsoft/vscode-azext-utils": "^2.1.1", "@microsoft/vscode-container-client": "^0.1.0", - "@microsoft/vscode-docker-registries": "^0.1.2", + "@microsoft/vscode-docker-registries": "^0.1.3", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.11.0", "fs-extra": "^11.1.1", @@ -812,9 +812,9 @@ } }, "node_modules/@microsoft/vscode-docker-registries": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/@microsoft/vscode-docker-registries/-/vscode-docker-registries-0.1.2.tgz", - "integrity": "sha512-NWy0VYHZq7g+RbJWUrcre+1/DzObhMBUmPfZo/1zZMS4vHVqpQzVlSnnbLilK2Xl0+XnwKDSjp7zY7XbOMCTvg==", + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@microsoft/vscode-docker-registries/-/vscode-docker-registries-0.1.3.tgz", + "integrity": "sha512-3ORzbV3Ea764Nh3QuyGsqUTPTCk6T45KXBc+j7MUirJ39lUZAAeiXwAC85UHT40xPURhgQdqHdbhDz3kZ1Vw7g==", "dependencies": { "dayjs": "^1.11.7", "node-fetch": "^2.6.11" diff --git a/package.json b/package.json index b0d39f82d5..6efa19c64b 100644 --- a/package.json +++ b/package.json @@ -3007,7 +3007,7 @@ "@microsoft/vscode-azext-azureutils": "^2.0.0", "@microsoft/vscode-azext-utils": "^2.1.1", "@microsoft/vscode-container-client": "^0.1.0", - "@microsoft/vscode-docker-registries": "^0.1.2", + "@microsoft/vscode-docker-registries": "^0.1.3", "dayjs": "^1.11.7", "dockerfile-language-server-nodejs": "^0.11.0", "fs-extra": "^11.1.1", diff --git a/src/DockerExtensionApi.ts b/src/DockerExtensionApi.ts index 082f590a74..464e6d48d1 100644 --- a/src/DockerExtensionApi.ts +++ b/src/DockerExtensionApi.ts @@ -21,7 +21,9 @@ export class DockerExtensionApi implements MementoExplorerExport, DockerExtensio } public registerRegistryDataProvider(id: string, registryDataProvider: RegistryDataProvider): vscode.Disposable { - return ext.registriesTree.registerProvider(registryDataProvider); + const disposable = ext.registriesTree.registerProvider(registryDataProvider); + void ext.registriesTree.refresh(); + return disposable; } public get memento(): ExtensionMementos | undefined { diff --git a/src/tree/registerTrees.ts b/src/tree/registerTrees.ts index d29b58b7b3..3a6283a04b 100644 --- a/src/tree/registerTrees.ts +++ b/src/tree/registerTrees.ts @@ -17,6 +17,7 @@ import { ImagesTreeItem } from "./images/ImagesTreeItem"; import { NetworksTreeItem } from "./networks/NetworksTreeItem"; import { AzureRegistryDataProvider } from "./registries/Azure/AzureRegistryDataProvider"; import { UnifiedRegistryTreeDataProvider } from "./registries/UnifiedRegistryTreeDataProvider"; +import { migrateRegistriesData } from "./registries/migrateRegistriesData"; import { VolumesTreeItem } from "./volumes/VolumesTreeItem"; export function registerTrees(): void { @@ -60,6 +61,7 @@ export function registerTrees(): void { ext.azureRegistryDataProvider = azureRegistryDataProvider; ext.dockerHubRegistryDataProvider = dockerHubRegistryDataProvider; ext.githubRegistryDataProvider = githubRegistryDataProvider; + void migrateRegistriesData(ext.context); ext.volumesRoot = new VolumesTreeItem(undefined); const volumesLoadMore = 'vscode-docker.volumes.loadMore'; diff --git a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts index cbc87a93a8..2897d3a314 100644 --- a/src/tree/registries/UnifiedRegistryTreeDataProvider.ts +++ b/src/tree/registries/UnifiedRegistryTreeDataProvider.ts @@ -19,7 +19,7 @@ export interface UnifiedRegistryItem { parent: UnifiedRegistryItem | undefined; } -const ConnectedRegistryProvidersKey = 'ConnectedRegistryProviders'; +export const ConnectedRegistryProvidersKey = 'ConnectedRegistryProviders'; export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider> { private readonly onDidChangeTreeDataEmitter = new vscode.EventEmitter | UnifiedRegistryItem[] | undefined>(); @@ -154,14 +154,19 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider< if (!connectedProviderIds.includes(provider.id)) { await provider?.onConnect?.(); - const connectedProviderIdsSet: Set = new Set(connectedProviderIds); - connectedProviderIdsSet.add(provider.id); - await this.storageMemento.update(ConnectedRegistryProvidersKey, Array.from(connectedProviderIdsSet)); + await this.storeRegistryProvider(provider.id); } void this.refresh(); } + public async storeRegistryProvider(providerId: string): Promise { + const connectedProviderIds = this.storageMemento.get(ConnectedRegistryProvidersKey, []); + const connectedProviderIdsSet: Set = new Set(connectedProviderIds); + connectedProviderIdsSet.add(providerId); + await this.storageMemento.update(ConnectedRegistryProvidersKey, Array.from(connectedProviderIdsSet)); + } + public async disconnectRegistryProvider(item: UnifiedRegistryItem): Promise { await item.provider?.onDisconnect?.(); diff --git a/src/tree/registries/migrateRegistriesData.ts b/src/tree/registries/migrateRegistriesData.ts new file mode 100644 index 0000000000..076a5e824c --- /dev/null +++ b/src/tree/registries/migrateRegistriesData.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See LICENSE.md in the project root for license information. + *--------------------------------------------------------------------------------------------*/ +import * as vscode from 'vscode'; + +const IsRegistriesDataMigratedKey = 'isRegistryMigrated'; +const OldRegistriesProvidersKey = 'docker.registryProviders'; + +// constants for generic v2 storage, see link below for more info: +// https://github.com/microsoft/vscode-docker-extensibility/blob/main/packages/vscode-docker-registries/src/clients/GenericRegistryV2/GenericRegistryV2DataProvider.ts +const GenericV2StorageKey = 'GenericV2ContainerRegistry'; +const TrackedRegistriesKey = `${GenericV2StorageKey}.TrackedRegistries`; + +/** + * This function migrates the registries data from the old extension to the new one. It should be deleted after the migration is complete + * after a few months or so. + */ +export async function migrateRegistriesData(ctx: vscode.ExtensionContext): Promise { + // check to see if we've already migrated + if (ctx.globalState.get(IsRegistriesDataMigratedKey)) { + return; + } + + // get the old registry providers + const oldRegistries = ctx.globalState.get(OldRegistriesProvidersKey, []); + for (const oldRegistry of oldRegistries) { + if (!oldRegistry.id) { + continue; + } + + // provider id that we use to store providers that are connected to the tree + let registryProviderId = undefined; + // credential storage key that we use to store username and password + let credentialStorageKey = undefined; + + switch (oldRegistry.id) { + case "genericDockerV2": + registryProviderId = ext.genericRegistryV2DataProvider.id; + // we need to parse it as a uri so that the format is consistent with the new storage + credentialStorageKey = vscode.Uri.parse(oldRegistry.url).toString(); + break; + case "azure": + registryProviderId = ext.azureRegistryDataProvider.id; + break; + case "dockerHub": + registryProviderId = ext.dockerHubRegistryDataProvider.id; + credentialStorageKey = 'DockerHub'; + break; + } + + // if we don't have a registry provider id, then we can't migrate + if (!registryProviderId) { + continue; + } + + // add new registry provider to the list of providers + await ext.registriesTree.storeRegistryProvider(registryProviderId); + + // if it's azure then our job is done + if (registryProviderId === ext.azureRegistryDataProvider.id) { + continue; + } + + // if it's generic v2, then we need to store the url in the list of tracked registries + if (registryProviderId === ext.genericRegistryV2DataProvider.id && credentialStorageKey) { + const trackedRegistryStrings = ctx.globalState.get(TrackedRegistriesKey, []); + trackedRegistryStrings.push(credentialStorageKey); + await ctx.globalState.update(TrackedRegistriesKey, trackedRegistryStrings); + } + + // store username in new secret storage + if (oldRegistry.username) { + await ctx.globalState.update(`BasicAuthProvider.${credentialStorageKey}.username`, oldRegistry.username); + } + + // set the password for the old registry in the new secret storage + const password = await getRegistryPassword(oldRegistry); + if (password) { + await ctx.secrets.store(`BasicAuthProvider.${credentialStorageKey}.secret`, password); + } + } + + // don't wait & mark the migration as done + void ctx.globalState.update(IsRegistriesDataMigratedKey, true); + void ext.registriesTree.refresh(); +} + +// -------------------------------------------------------------------------------------------- +// +// Old code that we need to get the secrets from the old extension. This should be deleted after +// the migration is complete. +// +// ------------------------------------------------------------------------------------------- +import * as crypto from 'crypto'; +import { ext } from '../../extensionVariables'; + +export enum RegistryApi { + /** + * https://docs.docker.com/registry/spec/api/ + */ + DockerV2 = 'DockerV2', + + /** + * https://docs.gitlab.com/ee/api/README.html + * https://docs.gitlab.com/ee/api/container_registry.html + */ + GitLabV4 = 'GitLabV4', + + /** + * No public docs found + */ + DockerHubV2 = 'DockerHubV2' +} + +export interface ICachedRegistryProvider { + id: string; + api: RegistryApi; + url?: string; + username?: string; +} + +export async function getRegistryPassword(cached: ICachedRegistryProvider): Promise { + return ext.context.secrets.get(getRegistryPasswordKey(cached)); +} + +function getRegistryPasswordKey(cached: ICachedRegistryProvider): string { + return getPseudononymousStringHash(cached.id + cached.api + (cached.url || '') + (cached.username || '')); +} + +function getPseudononymousStringHash(s: string): string { + return crypto.createHash('sha256').update(s).digest('hex'); +}