Skip to content

Commit

Permalink
Registries Data Migration (#4106)
Browse files Browse the repository at this point in the history
* basic implementation of registries data migration

* added more comments

* fix typo

* refresh tree after registering

* to TODO
  • Loading branch information
alexyaang authored Oct 3, 2023
1 parent 007bc79 commit 26b89f5
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 10 deletions.
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');
}

0 comments on commit 26b89f5

Please sign in to comment.