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

Added Back createAzureRegistry command #4029

Merged
merged 9 commits into from
Aug 10, 2023
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -481,7 +481,7 @@
},
{
"command": "vscode-docker.registries.azure.createRegistry",
"when": "view == dockerRegistries && viewItem == azureextensionui.azureSubscription",
"when": "view == dockerRegistries && viewItem =~ /azuresubscription/i",
"group": "regs_1_general@1"
},
{
Expand Down
52 changes: 44 additions & 8 deletions src/commands/registries/azure/createAzureRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,54 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IActionContext } from '@microsoft/vscode-azext-utils';
// import type { SubscriptionTreeItem } from '../../../tree/registries/azure/SubscriptionTreeItem'; // These are only dev-time imports so don't need to be lazy
import { AzureWizard, IActionContext, contextValueExperience, createSubscriptionContext, nonNullProp } from '@microsoft/vscode-azext-utils';
import { l10n, window } from 'vscode';
import { ext } from '../../../extensionVariables';
import { AzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider';
import { AzureRegistryCreateStep } from '../../../tree/registries/Azure/createWizard/AzureRegistryCreateStep';
import { AzureRegistryNameStep } from '../../../tree/registries/Azure/createWizard/AzureRegistryNameStep';
import { AzureRegistrySkuStep } from '../../../tree/registries/Azure/createWizard/AzureRegistrySkuStep';
import { IAzureRegistryWizardContext } from '../../../tree/registries/Azure/createWizard/IAzureRegistryWizardContext';
import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider';
// import { getAzSubTreeItem } from '../../../utils/lazyPackages';
import { getAzExtAzureUtils } from '../../../utils/lazyPackages';

export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem<unknown>): Promise<void> {
// const azSubTreeItem = await getAzSubTreeItem();
export async function createAzureRegistry(context: IActionContext, node?: UnifiedRegistryItem<AzureSubscriptionRegistryItem>): Promise<void> {

if (!node) {
// node = await ext.registriesTree.showTreeItemPicker<SubscriptionTreeItem>(azSubTreeItem.SubscriptionTreeItem.contextValue, context);
node = await contextValueExperience(context, ext.registriesTree, { include: 'azuresubscription' });
}

// await node.createChild(context);
// TODO: review this later
const subscriptionContext = createSubscriptionContext(node.wrappedItem.subscription);
const wizardContext: IAzureRegistryWizardContext = {
...context,
...subscriptionContext,
azureSubscription: node.wrappedItem.subscription,
};
const azExtAzureUtils = await getAzExtAzureUtils();

const promptSteps = [
new AzureRegistryNameStep(),
new AzureRegistrySkuStep(),
new azExtAzureUtils.ResourceGroupListStep(),
];
azExtAzureUtils.LocationListStep.addStep(wizardContext, promptSteps);

const wizard = new AzureWizard(
wizardContext,
{
promptSteps,
executeSteps: [
new AzureRegistryCreateStep()
],
title: l10n.t('Create new Azure Container Registry')
}
);

await wizard.prompt();
const newRegistryName: string = nonNullProp(wizardContext, 'newRegistryName');
await wizard.execute();

/* eslint-disable-next-line @typescript-eslint/no-floating-promises */
window.showInformationMessage(`Successfully created registry "${newRegistryName}".`);
void ext.registriesTree.refresh();
}
2 changes: 1 addition & 1 deletion src/commands/registries/azure/deleteAzureRegistry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export async function deleteAzureRegistry(context: IActionContext, node?: Unifie
await azureRegistryDataProvider.deleteRegistry(node.wrappedItem);
});

ext.registriesTree.refresh();
void ext.registriesTree.refresh();
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we don't need to wait here right?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah


const message = l10n.t('Successfully deleted registry "{0}".', registryName);
// don't wait
Expand Down
2 changes: 1 addition & 1 deletion src/commands/registries/azure/deleteAzureRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function deleteAzureRepository(context: IActionContext, node?: Unif
await azureDataProvider.deleteRepository(node.wrappedItem);
});

ext.registriesTree.refresh();
void ext.registriesTree.refresh();

const deleteSucceeded = l10n.t('Successfully deleted repository "{0}".', node.wrappedItem.label);
// don't wait
Expand Down
6 changes: 3 additions & 3 deletions src/commands/registries/azure/tasks/scheduleRunRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import * as vscode from 'vscode';
import { ext } from '../../../../extensionVariables';
import { AzureRegistryItem } from "../../../../tree/registries/Azure/AzureRegistryDataProvider";
import { UnifiedRegistryItem } from "../../../../tree/registries/UnifiedRegistryTreeDataProvider";
import { createAzureClient } from "../../../../tree/registries/registryTreeUtils";
import { createAzureContainerRegistryClient } from "../../../../utils/azureUtils";
import { getStorageBlob } from '../../../../utils/lazyPackages';
import { delay } from '../../../../utils/promiseUtils';
import { Item, quickPickDockerFileItem, quickPickYamlFileItem } from '../../../../utils/quickPickFile';
Expand Down Expand Up @@ -67,7 +67,7 @@ export async function scheduleRunRequest(context: IActionContext, requestType: '
rootUri = vscode.Uri.file(path.dirname(fileItem.absoluteFilePath));
}

const azureRegistryClient = await createAzureClient(registry.subscription);
const azureRegistryClient = await createAzureContainerRegistryClient(registry.subscription);
const uploadedSourceLocation: string = await uploadSourceCode(azureRegistryClient, registry.subscription.subscriptionId, resourceGroup, rootUri, tarFilePath);
ext.outputChannel.info(vscode.l10n.t('Uploaded source code from {0}', tarFilePath));

Expand Down Expand Up @@ -160,7 +160,7 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis
const blobCheckInterval = 1000;
const maxBlobChecks = 30;
async function streamLogs(context: IActionContext, node: UnifiedRegistryItem<AzureRegistryItem>, run: AcrRun): Promise<void> {
const azureRegistryClient = await createAzureClient(node.wrappedItem.subscription);
const azureRegistryClient = await createAzureContainerRegistryClient(node.wrappedItem.subscription);
const resourceGroup = getResourceGroupFromId(node.wrappedItem.id);
const result = await azureRegistryClient.runs.getLogSasUrl(resourceGroup, node.wrappedItem.label, run.runId);

Expand Down
31 changes: 8 additions & 23 deletions src/tree/registries/Azure/AzureRegistryDataProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,7 @@ import { AzureSubscription, VSCodeAzureSubscriptionProvider } from '@microsoft/v
import { RegistryV2DataProvider, V2Registry, V2RegistryItem, V2Repository, registryV2Request } from '@microsoft/vscode-docker-registries';
import { CommonRegistryItem, isRegistryRoot } from '@microsoft/vscode-docker-registries/lib/clients/Common/models';
import * as vscode from 'vscode';
import { getResourceGroupFromId } from '../../../utils/azureUtils';
import { createAzureClient } from '../registryTreeUtils';
import { createAzureContainerRegistryClient, getResourceGroupFromId } from '../../../utils/azureUtils';
import { ACROAuthProvider } from './ACROAuthProvider';

export interface AzureRegistryItem extends V2RegistryItem {
Expand Down Expand Up @@ -114,27 +113,13 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements
}

public override async getRepositories(registry: AzureRegistry): Promise<AzureRepository[]> {
const catalogResponse = await registryV2Request<{ repositories: string[] }>({
authenticationProvider: this.getAuthenticationProvider(registry),
method: 'GET',
registryUri: registry.baseUrl,
path: ['v2', '_catalog'],
scopes: ['registry:catalog:*'],
});

const results: AzureRepository[] = [];

for (const repository of catalogResponse.body?.repositories || []) {
results.push({
parent: registry,
baseUrl: registry.baseUrl,
label: repository,
type: 'commonrepository',
additionalContextValues: ['azureContainerRepository']
});
}
const repositories = await super.getRepositories(registry);
const repositoriesWithAdditionalContext = repositories.map(repository => ({
...repository,
additionalContextValues: ['azureContainerRepository']
}));

return results;
return repositoriesWithAdditionalContext;
}

public override getTreeItem(element: CommonRegistryItem): Promise<vscode.TreeItem> {
Expand Down Expand Up @@ -167,7 +152,7 @@ export class AzureRegistryDataProvider extends RegistryV2DataProvider implements
}

public async deleteRegistry(item: AzureRegistry): Promise<void> {
const client = await createAzureClient(item.subscription);
const client = await createAzureContainerRegistryClient(item.subscription);
const resourceGroup = getResourceGroupFromId(item.id);
await client.registries.beginDeleteAndWait(resourceGroup, item.label);
}
Expand Down
58 changes: 58 additions & 0 deletions src/tree/registries/Azure/createWizard/AzureRegistryCreateStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { AzExtLocation } from '@microsoft/vscode-azext-azureutils';
import { AzureWizardExecuteStep, nonNullProp, parseError } from '@microsoft/vscode-azext-utils';
import { Progress, l10n } from 'vscode';
import { ext } from '../../../../extensionVariables';
import { createAzureContainerRegistryClient } from '../../../../utils/azureUtils';
import { getAzExtAzureUtils } from '../../../../utils/lazyPackages';
import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext';

export class AzureRegistryCreateStep extends AzureWizardExecuteStep<IAzureRegistryWizardContext> {
public priority: number = 130;

public async execute(context: IAzureRegistryWizardContext, progress: Progress<{ message?: string; increment?: number }>): Promise<void> {
const newRegistryName = nonNullProp(context, 'newRegistryName');

const client = await createAzureContainerRegistryClient(context.azureSubscription);

const azExtAzureUtils = await getAzExtAzureUtils();
const creating: string = l10n.t('Creating registry "{0}"...', newRegistryName);
ext.outputChannel.info(creating);
progress.report({ message: creating });

const location: AzExtLocation = await azExtAzureUtils.LocationListStep.getLocation(context);
const locationName: string = nonNullProp(location, 'name');
const resourceGroup = nonNullProp(context, 'resourceGroup');
try {
context.registry = await client.registries.beginCreateAndWait(
nonNullProp(resourceGroup, 'name'),
newRegistryName,
{
sku: {
name: nonNullProp(context, 'newRegistrySku')
},
location: locationName
}
);
}
catch (err) {
const parsedError = parseError(err);
if (parsedError.errorType === 'MissingSubscriptionRegistration') {
context.errorHandling.suppressReportIssue = true;
}

throw err;
}

const created = l10n.t('Successfully created registry "{0}".', newRegistryName);
ext.outputChannel.info(created);
}

public shouldExecute(context: IAzureRegistryWizardContext): boolean {
return !context.registry;
}
}
50 changes: 50 additions & 0 deletions src/tree/registries/Azure/createWizard/AzureRegistryNameStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { ContainerRegistryManagementClient } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy
import { AzureNameStep } from '@microsoft/vscode-azext-utils';
import { l10n } from 'vscode';
import { getArmContainerRegistry, getAzExtAzureUtils } from '../../../../utils/lazyPackages';
import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext';

export class AzureRegistryNameStep extends AzureNameStep<IAzureRegistryWizardContext> {
protected async isRelatedNameAvailable(context: IAzureRegistryWizardContext, name: string): Promise<boolean> {
const azExtAzureUtils = await getAzExtAzureUtils();
return await azExtAzureUtils.ResourceGroupListStep.isNameAvailable(context, name);
}

public async prompt(context: IAzureRegistryWizardContext): Promise<void> {
const azExtAzureUtils = await getAzExtAzureUtils();
const armContainerRegistry = await getArmContainerRegistry();
const client = azExtAzureUtils.createAzureClient(context, armContainerRegistry.ContainerRegistryManagementClient);
context.newRegistryName = (await context.ui.showInputBox({
placeHolder: l10n.t('Registry name'),
prompt: l10n.t('Provide a registry name'),
/* eslint-disable-next-line @typescript-eslint/promise-function-async */
validateInput: (name: string) => validateRegistryName(name, client)
})).trim();

context.relatedNameTask = this.generateRelatedName(context, context.newRegistryName, azExtAzureUtils.resourceGroupNamingRules);
}

public shouldPrompt(context: IAzureRegistryWizardContext): boolean {
return !context.newRegistryName;
}
}

async function validateRegistryName(name: string, client: ContainerRegistryManagementClient): Promise<string | undefined> {
name = name ? name.trim() : '';

const min = 5;
const max = 50;
if (name.length < min || name.length > max) {
return l10n.t('The name must be between {0} and {1} characters.', min, max);
} else if (name.match(/[^a-z0-9]/i)) {
return l10n.t('The name can only contain alphanumeric characters.');
} else {
const nameStatus = await client.registries.checkNameAvailability({ name, type: 'Microsoft.ContainerRegistry/registries' });
return nameStatus.message;
}
}
23 changes: 23 additions & 0 deletions src/tree/registries/Azure/createWizard/AzureRegistrySkuStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { SkuName as AcrSkuName } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy
import { AzureWizardPromptStep, IAzureQuickPickItem } from '@microsoft/vscode-azext-utils';
import { l10n } from 'vscode';
import { IAzureRegistryWizardContext } from './IAzureRegistryWizardContext';

export class AzureRegistrySkuStep extends AzureWizardPromptStep<IAzureRegistryWizardContext> {
public async prompt(context: IAzureRegistryWizardContext): Promise<void> {
const skus: AcrSkuName[] = ["Basic", "Standard", "Premium"];
const picks: IAzureQuickPickItem<AcrSkuName>[] = skus.map(s => { return { label: s, data: s }; });

const placeHolder: string = l10n.t('Select a SKU');
context.newRegistrySku = (await context.ui.showQuickPick(picks, { placeHolder })).data;
}

public shouldPrompt(context: IAzureRegistryWizardContext): boolean {
return !context.newRegistrySku;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { Registry as AcrRegistry, SkuName as AcrSkuName } from '@azure/arm-containerregistry'; // These are only dev-time imports so don't need to be lazy
import { AzureSubscription } from '@microsoft/vscode-azext-azureauth';
import { IResourceGroupWizardContext } from '@microsoft/vscode-azext-azureutils';

export interface IAzureRegistryWizardContext extends IResourceGroupWizardContext {
newRegistryName?: string;
newRegistrySku?: AcrSkuName;
registry?: AcrRegistry;
readonly azureSubscription: AzureSubscription;
}
6 changes: 0 additions & 6 deletions src/tree/registries/registryTreeUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ContainerRegistryManagementClient } from "@azure/arm-containerregistry";
import { AzureSubscription } from "@microsoft/vscode-azext-azureauth";
import { CommonRegistry, CommonRepository, CommonTag, isRegistry, isRepository, isTag } from "@microsoft/vscode-docker-registries";
import { UnifiedRegistryItem } from "./UnifiedRegistryTreeDataProvider";

Expand Down Expand Up @@ -36,7 +34,3 @@ export function getFullImageNameFromRegistryItem(node: UnifiedRegistryItem<Commo
return `${registry.label}/${imageName}`;
}
}

export async function createAzureClient(subscriptionItem: AzureSubscription): Promise<ContainerRegistryManagementClient> {
return new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.credential, subscriptionItem.subscriptionId);
}
7 changes: 7 additions & 0 deletions src/utils/azureUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import type { ContainerRegistryManagementClient } from '@azure/arm-containerregistry';
import { AzureSubscription } from '@microsoft/vscode-azext-azureauth';
import { ISubscriptionContext } from '@microsoft/vscode-azext-utils';
import { Request } from 'node-fetch';
import { URLSearchParams } from 'url';
Expand Down Expand Up @@ -69,4 +71,9 @@ export async function acquireAcrRefreshToken(registryHost: string, subContext: I

return (await response.json()).refresh_token;
}

export async function createAzureContainerRegistryClient(subscriptionItem: AzureSubscription): Promise<ContainerRegistryManagementClient> {
return new (await import('@azure/arm-containerregistry')).ContainerRegistryManagementClient(subscriptionItem.credential, subscriptionItem.subscriptionId);
}

/* eslint-enable @typescript-eslint/naming-convention */