Skip to content

Commit

Permalink
Added Back createAzureRegistry command (#4029)
Browse files Browse the repository at this point in the history
* adjusted scheduleRunReuqest to work with new registries tree

* delete azure repository command implementation

* actually added back delete azure repository

* added back delete Azure Registry command

* changed registry tree util file name to be more general

* added openInAzurePortalCommand

* added back create azure registry command

* move createAzureClient to azureUtils
  • Loading branch information
alexyaang authored Aug 10, 2023
1 parent 008e9bf commit e090fb4
Show file tree
Hide file tree
Showing 12 changed files with 211 additions and 43 deletions.
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();

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 */

0 comments on commit e090fb4

Please sign in to comment.