Skip to content

Commit

Permalink
Refactor push image to be a wizard, show ACR create pick (#4146)
Browse files Browse the repository at this point in the history
  • Loading branch information
bwateratmsft authored Nov 7, 2023
1 parent 0c33ed0 commit 5372c56
Show file tree
Hide file tree
Showing 13 changed files with 292 additions and 133 deletions.
99 changes: 0 additions & 99 deletions src/commands/images/pushImage.ts

This file was deleted.

36 changes: 36 additions & 0 deletions src/commands/images/pushImage/CreatePickAcrPromptStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep, IAzureQuickPickItem } from '@microsoft/vscode-azext-utils';
import { CommonRegistry } from '@microsoft/vscode-docker-registries';
import * as vscode from 'vscode';
import { ext } from '../../../extensionVariables';
import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider';
import { createAzureRegistry } from '../../registries/azure/createAzureRegistry';
import { PushImageWizardContext } from './PushImageWizardContext';

export class CreatePickAcrPromptStep extends AzureWizardPromptStep<PushImageWizardContext> {
public async prompt(wizardContext: PushImageWizardContext): Promise<void> {
const acrs = await ext.registriesRoot.getChildren(wizardContext.azureSubscriptionNode) as UnifiedRegistryItem<CommonRegistry>[];
const picks: IAzureQuickPickItem<string | UnifiedRegistryItem<CommonRegistry>>[] = acrs.map(acr => <IAzureQuickPickItem<UnifiedRegistryItem<CommonRegistry>>>{ label: acr.wrappedItem.label, data: acr });
picks.push({ label: vscode.l10n.t('$(plus) Create new Azure Container Registry...'), data: 'create' });

const response = await wizardContext.ui.showQuickPick(picks, { placeHolder: vscode.l10n.t('Select an Azure Container Registry to push to') });

if (response.data === 'create') {
const createdAcrName = await createAzureRegistry(wizardContext, wizardContext.azureSubscriptionNode);

const acrNodes = await ext.registriesRoot.getChildren(wizardContext.azureSubscriptionNode) as UnifiedRegistryItem<CommonRegistry>[];
const selectedAcrNode = acrNodes.find(acrNode => acrNode.wrappedItem.label === createdAcrName);
wizardContext.connectedRegistry = selectedAcrNode;
} else {
wizardContext.connectedRegistry = response.data as UnifiedRegistryItem<CommonRegistry>;
}
}

public shouldPrompt(wizardContext: PushImageWizardContext): boolean {
return !!wizardContext.azureSubscriptionNode;
}
}
18 changes: 18 additions & 0 deletions src/commands/images/pushImage/FinalTagPromptStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep } from '@microsoft/vscode-azext-utils';
import { tagImage } from '../tagImage';
import { PushImageWizardContext } from './PushImageWizardContext';

export class FinalTagPromptStep extends AzureWizardPromptStep<PushImageWizardContext> {
public async prompt(wizardContext: PushImageWizardContext): Promise<void> {
wizardContext.finalTag = await tagImage(wizardContext, wizardContext.node, wizardContext.connectedRegistry);
}

public shouldPrompt(wizardContext: PushImageWizardContext): boolean {
return !wizardContext.finalTag;
}
}
67 changes: 67 additions & 0 deletions src/commands/images/pushImage/GetRegistryTargetPromptStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardPromptStep, IActionContext, NoResourceFoundError } from '@microsoft/vscode-azext-utils';
import { parseDockerLikeImageName } from '@microsoft/vscode-container-client';
import { CommonRegistry } from '@microsoft/vscode-docker-registries';
import * as vscode from 'vscode';
import { ext } from '../../../extensionVariables';
import { NormalizedImageNameInfo } from '../../../tree/images/NormalizedImageNameInfo';
import { AzureSubscriptionRegistryItem, isAzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider';
import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider';
import { registryExperience } from '../../../utils/registryExperience';
import { PushImageWizardContext } from './PushImageWizardContext';

export class GetRegistryTargetPromptStep extends AzureWizardPromptStep<PushImageWizardContext> {
public async configureBeforePrompt(wizardContext: PushImageWizardContext): Promise<void> {
// If the image is qualified (has a slash), we'll look for a matching registry in the tree view
if (this.registryIsDeterminate(wizardContext.initialTag)) {
wizardContext.connectedRegistry = await this.tryGetConnectedRegistryForPath(wizardContext, wizardContext.initialTag);
}
}

public async prompt(wizardContext: PushImageWizardContext): Promise<void> {
try {
const pickedNode = await registryExperience<CommonRegistry | AzureSubscriptionRegistryItem>(wizardContext, { contextValueFilter: { include: [/commonregistry/i, /azuresubscription/i] } });
if (isAzureSubscriptionRegistryItem(pickedNode.wrappedItem)) {
// An Azure subscription node was chosen. The CreatePickAcrPromptStep will be activated, instead of the generic RecursiveQuickPickStep that would normally be used.
wizardContext.azureSubscriptionNode = pickedNode as UnifiedRegistryItem<AzureSubscriptionRegistryItem>;
} else {
wizardContext.connectedRegistry = pickedNode as UnifiedRegistryItem<CommonRegistry>;
}
} catch (error) {
if (error instanceof NoResourceFoundError) {
// Do nothing, move on without a selected registry
} else {
// Rethrow
throw error;
}
}
}

public shouldPrompt(wizardContext: PushImageWizardContext): boolean {
return !wizardContext.connectedRegistry && !this.registryIsDeterminate(wizardContext.initialTag) && this.shouldPromptForRegistry;
}

private async tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise<UnifiedRegistryItem<CommonRegistry> | undefined> {
const baseImageNameInfo = parseDockerLikeImageName(baseImagePath);
const normalizedImageNameInfo = new NormalizedImageNameInfo(baseImageNameInfo);

const allRegistries = await vscode.window.withProgress({
location: vscode.ProgressLocation.Notification,
title: vscode.l10n.t('Determining registry to push to...'),
}, () => ext.registriesTree.getConnectedRegistries(normalizedImageNameInfo.normalizedRegistry));

return allRegistries.find((registry) => registry.wrappedItem.baseUrl.authority === normalizedImageNameInfo.normalizedRegistry);
}

private get shouldPromptForRegistry(): boolean {
return vscode.workspace.getConfiguration('docker').get('promptForRegistryWhenPushingImages', true);
}

private registryIsDeterminate(imageName: string): boolean {
return imageName.includes('/');
}
}
33 changes: 33 additions & 0 deletions src/commands/images/pushImage/ImagePushStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils';
import { ext } from '../../../extensionVariables';
import { TaskCommandRunnerFactory } from '../../../runtimes/runners/TaskCommandRunnerFactory';
import { addImageTaggingTelemetry } from '../tagImage';
import { PushImageWizardContext } from './PushImageWizardContext';

export class ImagePushStep extends AzureWizardExecuteStep<PushImageWizardContext> {
public priority: number = 300;

public async execute(wizardContext: PushImageWizardContext): Promise<void> {
addImageTaggingTelemetry(wizardContext, wizardContext.finalTag, '');

const client = await ext.runtimeManager.getClient();
const taskCRF = new TaskCommandRunnerFactory(
{
taskName: wizardContext.finalTag
}
);

await taskCRF.getCommandRunner()(
client.pushImage({ imageRef: wizardContext.finalTag })
);
}

public shouldExecute(wizardContext: PushImageWizardContext): boolean {
return true;
}
}
20 changes: 20 additions & 0 deletions src/commands/images/pushImage/PushImageWizardContext.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { IActionContext } from '@microsoft/vscode-azext-utils';
import { CommonRegistry } from '@microsoft/vscode-docker-registries';
import { ImageTreeItem } from '../../../tree/images/ImageTreeItem';
import { AzureSubscriptionRegistryItem } from '../../../tree/registries/Azure/AzureRegistryDataProvider';
import { UnifiedRegistryItem } from '../../../tree/registries/UnifiedRegistryTreeDataProvider';

export interface PushImageWizardContext extends IActionContext {
connectedRegistry?: UnifiedRegistryItem<CommonRegistry>;
finalTag?: string;

initialTag: string;
node: ImageTreeItem;

azureSubscriptionNode?: UnifiedRegistryItem<AzureSubscriptionRegistryItem>;
}
29 changes: 29 additions & 0 deletions src/commands/images/pushImage/RegistryLoginStep.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizardExecuteStep } from '@microsoft/vscode-azext-utils';
import { parseDockerLikeImageName } from '@microsoft/vscode-container-client';
import * as vscode from 'vscode';
import { NormalizedImageNameInfo } from '../../../tree/images/NormalizedImageNameInfo';
import { PushImageWizardContext } from './PushImageWizardContext';

export class RegistryLoginStep extends AzureWizardExecuteStep<PushImageWizardContext> {
public priority: number = 200;

public async execute(wizardContext: PushImageWizardContext): Promise<void> {
await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', wizardContext.connectedRegistry);
}

public shouldExecute(wizardContext: PushImageWizardContext): boolean {
// If a registry was found/chosen and is still the same as the final tag's registry, try logging in
if (!wizardContext.connectedRegistry) {
return false;
}

const baseAuthority = wizardContext.connectedRegistry.wrappedItem.baseUrl.authority;
const desiredRegistry = new NormalizedImageNameInfo(parseDockerLikeImageName(wizardContext.finalTag)).normalizedRegistry;
return desiredRegistry === baseAuthority;
}
}
45 changes: 45 additions & 0 deletions src/commands/images/pushImage/pushImage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { AzureWizard, IActionContext } from '@microsoft/vscode-azext-utils';
import * as vscode from 'vscode';
import { ext } from '../../../extensionVariables';
import { ImageTreeItem } from '../../../tree/images/ImageTreeItem';
import { CreatePickAcrPromptStep } from './CreatePickAcrPromptStep';
import { FinalTagPromptStep } from './FinalTagPromptStep';
import { GetRegistryTargetPromptStep } from './GetRegistryTargetPromptStep';
import { ImagePushStep } from './ImagePushStep';
import { PushImageWizardContext } from './PushImageWizardContext';
import { RegistryLoginStep } from './RegistryLoginStep';

export async function pushImage(context: IActionContext, node: ImageTreeItem | undefined): Promise<void> {
if (!node) {
await ext.imagesTree.refresh(context);
node = await ext.imagesTree.showTreeItemPicker<ImageTreeItem>(ImageTreeItem.contextValue, {
...context,
noItemFoundErrorMessage: vscode.l10n.t('No images are available to push'),
});
}

const wizardContext = context as PushImageWizardContext;
wizardContext.initialTag = node.fullTag;
wizardContext.node = node;

const wizard = new AzureWizard(wizardContext, {
promptSteps: [
new GetRegistryTargetPromptStep(),
new CreatePickAcrPromptStep(),
new FinalTagPromptStep(),
],
executeSteps: [
new RegistryLoginStep(),
new ImagePushStep(),
],
showLoadingPrompt: true,
});

await wizard.prompt();
await wizard.execute();
}
2 changes: 1 addition & 1 deletion src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import { copyFullTag } from "./images/copyFullTag";
import { inspectImage } from "./images/inspectImage";
import { pruneImages } from "./images/pruneImages";
import { pullImage } from "./images/pullImage";
import { pushImage } from "./images/pushImage";
import { pushImage } from "./images/pushImage/pushImage";
import { removeImage } from "./images/removeImage";
import { removeImageGroup } from "./images/removeImageGroup";
import { runAzureCliImage } from "./images/runAzureCliImage";
Expand Down
Loading

0 comments on commit 5372c56

Please sign in to comment.