Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Registries Data Migration #4106

Merged
merged 5 commits into from
Oct 3, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
4 changes: 3 additions & 1 deletion src/DockerExtensionApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ export class DockerExtensionApi implements MementoExplorerExport, DockerExtensio
}

public registerRegistryDataProvider<T extends RegistryItem>(id: string, registryDataProvider: RegistryDataProvider<T>): vscode.Disposable {
return ext.registriesTree.registerProvider(registryDataProvider);
const disposable = ext.registriesTree.registerProvider(registryDataProvider);
void ext.registriesTree.refresh();
return disposable;
}

public get memento(): ExtensionMementos | undefined {
Expand Down
2 changes: 2 additions & 0 deletions src/tree/registerTrees.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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';
Expand Down
13 changes: 9 additions & 4 deletions src/tree/registries/UnifiedRegistryTreeDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export interface UnifiedRegistryItem<T> {
parent: UnifiedRegistryItem<T> | undefined;
}

const ConnectedRegistryProvidersKey = 'ConnectedRegistryProviders';
export const ConnectedRegistryProvidersKey = 'ConnectedRegistryProviders';

export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider<UnifiedRegistryItem<unknown>> {
private readonly onDidChangeTreeDataEmitter = new vscode.EventEmitter<UnifiedRegistryItem<unknown> | UnifiedRegistryItem<unknown>[] | undefined>();
Expand Down Expand Up @@ -154,14 +154,19 @@ export class UnifiedRegistryTreeDataProvider implements vscode.TreeDataProvider<

if (!connectedProviderIds.includes(provider.id)) {
await provider?.onConnect?.();
const connectedProviderIdsSet: Set<string> = 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<void> {
const connectedProviderIds = this.storageMemento.get<string[]>(ConnectedRegistryProvidersKey, []);
const connectedProviderIdsSet: Set<string> = new Set(connectedProviderIds);
connectedProviderIdsSet.add(providerId);
await this.storageMemento.update(ConnectedRegistryProvidersKey, Array.from(connectedProviderIdsSet));
}

public async disconnectRegistryProvider(item: UnifiedRegistryItem<unknown>): Promise<void> {
await item.provider?.onDisconnect?.();

Expand Down
133 changes: 133 additions & 0 deletions src/tree/registries/migrateRegistriesData.ts
Original file line number Diff line number Diff line change
@@ -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<void> {
// check to see if we've already migrated
if (ctx.globalState.get(IsRegistriesDataMigratedKey)) {
return;
}

// get the old registry providers
const oldRegistries = ctx.globalState.get<ICachedRegistryProvider[]>(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<string[]>(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<string | undefined> {
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');
}