Skip to content

Commit

Permalink
Revamped image push experience (#1783)
Browse files Browse the repository at this point in the history
  • Loading branch information
bwateratmsft authored Mar 30, 2020
1 parent a0bd864 commit 64750d8
Show file tree
Hide file tree
Showing 15 changed files with 125 additions and 160 deletions.
21 changes: 5 additions & 16 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,6 @@
"onCommand:vscode-docker.registries.pullRepository",
"onCommand:vscode-docker.registries.reconnectRegistry",
"onCommand:vscode-docker.registries.refresh",
"onCommand:vscode-docker.registries.setAsDefault",
"onCommand:vscode-docker.volumes.configureExplorer",
"onCommand:vscode-docker.volumes.inspect",
"onCommand:vscode-docker.volumes.prune",
Expand Down Expand Up @@ -370,7 +369,7 @@
},
{
"command": "vscode-docker.registries.azure.selectSubscriptions",
"when": "view == dockerRegistries && viewItem == azureextensionui.azureSubscription",
"when": "view == dockerRegistries && viewItem == azure;DockerV2;RegistryProvider;",
"group": "inline"
},
{
Expand All @@ -393,11 +392,6 @@
"when": "view == dockerRegistries && viewItem == azureextensionui.azureSubscription",
"group": "regs_1_general@1"
},
{
"command": "vscode-docker.registries.setAsDefault",
"when": "view == dockerRegistries && viewItem =~ /Registry;/",
"group": "regs_reg_1_general@1"
},
{
"command": "vscode-docker.registries.azure.deleteRegistry",
"when": "view == dockerRegistries && viewItem == azure;DockerV2;Registry;",
Expand Down Expand Up @@ -1325,10 +1319,10 @@
"configuration": {
"title": "Docker",
"properties": {
"docker.defaultRegistryPath": {
"type": "string",
"default": "",
"description": "%vscode-docker.config.docker.defaultRegistryPath%"
"docker.promptForRegistryWhenPushingImages": {
"type": "boolean",
"default": true,
"description": "%vscode-docker.config.docker.promptForRegistryWhenPushingImages%"
},
"docker.explorerRefreshInterval": {
"type": "number",
Expand Down Expand Up @@ -2326,11 +2320,6 @@
"dark": "resources/dark/refresh.svg"
}
},
{
"command": "vscode-docker.registries.setAsDefault",
"title": "%vscode-docker.commands.registries.setAsDefault%",
"category": "%vscode-docker.commands.category.dockerRegistries%"
},
{
"command": "vscode-docker.volumes.configureExplorer",
"title": "%vscode-docker.commands.volumes.configureExplorer%",
Expand Down
3 changes: 1 addition & 2 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@
"vscode-docker.tasks.docker-run.python.args": "Arguments passed to the Python app.",
"vscode-docker.tasks.docker-run.python.wait": "Whether to wait for debugger to attach.",
"vscode-docker.tasks.docker-run.python.debugPort": "The port that the debugger will listen on.",
"vscode-docker.config.docker.promptForRegistryWhenPushingImages": "Whether to prompt for the registry to push to if the image is not explicitly tagged.",
"vscode-docker.config.template.build.template": "The command template.",
"vscode-docker.config.template.build.label": "The label displayed to the user.",
"vscode-docker.config.template.build.match": "The regular expression for choosing the right template. Checked against container name, container's image name, etc.",
Expand Down Expand Up @@ -122,7 +123,6 @@
"vscode-docker.config.template.composeDown.label": "The label displayed to the user.",
"vscode-docker.config.template.composeDown.match": "The regular expression for choosing the right template. Checked against docker-compose YAML files, folder name, etc.",
"vscode-docker.config.template.composeDown.description": "Command templates for `docker-compose down` commands.",
"vscode-docker.config.docker.defaultRegistryPath": "Default registry and path when tagging an image",
"vscode-docker.config.docker.explorerRefreshInterval": "Docker view refresh interval (milliseconds)",
"vscode-docker.config.docker.containers.groupBy": "The property to use to group containers in Docker view: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, Tag, or None",
"vscode-docker.config.docker.containers.description": "Any secondary properties to display for a container (an array). Possible elements include: ContainerId, ContainerName, CreatedTime, FullTag, ImageId, Networks, Ports, Registry, Repository, RepositoryName, RepositoryNameAndTag, State, Status, and Tag",
Expand Down Expand Up @@ -223,7 +223,6 @@
"vscode-docker.commands.registries.pullImage": "Pull Image",
"vscode-docker.commands.registries.pullRepository": "Pull Repository",
"vscode-docker.commands.registries.refresh": "Refresh",
"vscode-docker.commands.registries.setAsDefault": "Set as Default",
"vscode-docker.commands.volumes.configureExplorer": "Configure Explorer...",
"vscode-docker.commands.volumes.inspect": "Inspect",
"vscode-docker.commands.volumes.prune": "Prune...",
Expand Down
2 changes: 1 addition & 1 deletion src/commands/images/buildImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ export async function buildImage(context: IActionContext, dockerFileUri: vscode.
await delay(500);

addImageTaggingTelemetry(context, suggestedImageName, '.before');
const imageName: string = await getTagFromUserInput(suggestedImageName, !prevImageName);
const imageName: string = await getTagFromUserInput(suggestedImageName);
addImageTaggingTelemetry(context, imageName, '.after');

await ext.context.globalState.update(dockerFileKey, imageName);
Expand Down
80 changes: 54 additions & 26 deletions src/commands/images/pushImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,77 @@
*--------------------------------------------------------------------------------------------*/

import vscode = require('vscode');
import { IActionContext } from 'vscode-azureextensionui';
import { configurationKeys } from '../../constants';
import { IActionContext, UserCancelledError } from 'vscode-azureextensionui';
import { ext } from '../../extensionVariables';
import { localize } from '../../localize';
import { ImageTreeItem } from '../../tree/images/ImageTreeItem';
import { askToSaveRegistryPath } from '../registries/registrySettings';
import { registryExpectedContextValues } from '../../tree/registries/registryContextValues';
import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase';
import { addImageTaggingTelemetry, tagImage } from './tagImage';

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

const defaultRegistryPath = vscode.workspace.getConfiguration('docker').get(configurationKeys.defaultRegistryPath);
let connectedRegistry: RegistryTreeItemBase | undefined;

let fullTag: string = node.fullTag;
if (fullTag.includes('/')) {
if (!defaultRegistryPath) {
await askToSaveRegistryPath(fullTag);
}
} else {
let askToPushPrefix: boolean = true;
if (askToPushPrefix && defaultRegistryPath) {
context.telemetry.properties.pushWithoutRepositoryAnswer = 'Cancel';

let tagFirst: vscode.MessageItem = { title: localize('vscode-docker.commands.images.push.tagFirst', 'Tag first') };
let pushAnyway: vscode.MessageItem = { title: localize('vscode-docker.commands.images.push.pushAnyway', 'Push anyway') }
let options: vscode.MessageItem[] = [tagFirst, pushAnyway];
let response: vscode.MessageItem = await ext.ui.showWarningMessage(localize('vscode-docker.commands.images.push.pushDockerHub', 'This will attempt to push to the official public Docker Hub library (docker.io/library), which you may not have permissions for. To push to your own repository, you must tag the image like <docker-id-or-registry-server>/<imagename>'), ...options);
context.telemetry.properties.pushWithoutRepositoryAnswer = response.title;

if (response === tagFirst) {
fullTag = await tagImage(context, node);
if (!node.fullTag.includes('/')) {
// The registry to push to is indeterminate--could be Docker Hub, or could need tagging.
const prompt: boolean = vscode.workspace.getConfiguration('docker').get('promptForRegistryWhenPushingImages', true);

// If the prompt setting is true, we'll ask; if not we'll assume Docker Hub.
if (prompt) {
try {
connectedRegistry = await ext.registriesTree.showTreeItemPicker<RegistryTreeItemBase>(registryExpectedContextValues.all.registry, context);
} catch (error) {
if (error instanceof UserCancelledError) {
// Rethrow UserCancelledError, otherwise, move on without a selected registry
throw error;
}
}
} else {
// Try to find a connected Docker Hub registry (primarily for login credentials)
connectedRegistry = await tryGetDockerHubRegistry(context);
}
} else {
// The registry to push to is determinate. If there's a connected registry in the tree view, we'll try to find it, to perform login ahead of time.
// Registry path is everything up to the last slash.
const baseImagePath = node.fullTag.substring(0, node.fullTag.lastIndexOf('/'));

const progressOptions: vscode.ProgressOptions = {
location: vscode.ProgressLocation.Notification,
title: localize('vscode-docker.commands.images.push.fetchingCreds', 'Fetching login credentials...'),
};

connectedRegistry = await vscode.window.withProgress(progressOptions, async () => await tryGetConnectedRegistryForPath(context, baseImagePath));
}

addImageTaggingTelemetry(context, fullTag, '');
// Give the user a chance to modify the tag however they want
const finalTag = await tagImage(context, node, connectedRegistry);

const terminal = ext.terminalProvider.createTerminal(fullTag);
terminal.sendText(`docker push ${fullTag}`);
if (connectedRegistry && finalTag.startsWith(connectedRegistry.baseImagePath)) {
// If a registry was found/chosen and is still the same as the final tag's registry, try logging in
await vscode.commands.executeCommand('vscode-docker.registries.logInToDockerCli', connectedRegistry);
}

addImageTaggingTelemetry(context, finalTag, '');

// Finally push the image
const terminal = ext.terminalProvider.createTerminal(finalTag);
terminal.sendText(`docker push ${finalTag}`);
terminal.show();
}

async function tryGetConnectedRegistryForPath(context: IActionContext, baseImagePath: string): Promise<RegistryTreeItemBase | undefined> {
const allRegistries = await ext.registriesRoot.getAllConnectedRegistries(context);
return allRegistries.find(r => r.baseImagePath === baseImagePath);
}

async function tryGetDockerHubRegistry(context: IActionContext): Promise<RegistryTreeItemBase | undefined> {
const allRegistries = await ext.registriesRoot.getAllConnectedRegistries(context);
return allRegistries.find(r => r.contextValue.match(registryExpectedContextValues.dockerHub.registry));
}
27 changes: 11 additions & 16 deletions src/commands/images/tagImage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
import { Image } from 'dockerode';
import * as vscode from 'vscode';
import { IActionContext, TelemetryProperties } from 'vscode-azureextensionui';
import { configurationKeys } from '../../constants';
import { ext } from '../../extensionVariables';
import { localize } from '../../localize';
import { ImageTreeItem } from '../../tree/images/ImageTreeItem';
import { RegistryTreeItemBase } from '../../tree/registries/RegistryTreeItemBase';
import { callDockerodeWithErrorHandling } from '../../utils/callDockerodeWithErrorHandling';
import { extractRegExGroups } from '../../utils/extractRegExGroups';

export async function tagImage(context: IActionContext, node: ImageTreeItem | undefined): Promise<string> {
export async function tagImage(context: IActionContext, node?: ImageTreeItem, registry?: RegistryTreeItemBase): Promise<string> {
if (!node) {
node = await ext.imagesTree.showTreeItemPicker<ImageTreeItem>(ImageTreeItem.contextValue, {
...context,
Expand All @@ -22,7 +22,7 @@ export async function tagImage(context: IActionContext, node: ImageTreeItem | un
}

addImageTaggingTelemetry(context, node.fullTag, '.before');
let newTaggedName: string = await getTagFromUserInput(node.fullTag, true);
let newTaggedName: string = await getTagFromUserInput(node.fullTag, registry?.baseImagePath);
addImageTaggingTelemetry(context, newTaggedName, '.after');

let repo: string = newTaggedName;
Expand All @@ -39,27 +39,22 @@ export async function tagImage(context: IActionContext, node: ImageTreeItem | un
return newTaggedName;
}

export async function getTagFromUserInput(fullTag: string, addDefaultRegistry: boolean): Promise<string> {
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
const defaultRegistryPath = configOptions.get(configurationKeys.defaultRegistryPath, '');

export async function getTagFromUserInput(fullTag: string, baseImagePath?: string): Promise<string> {
let opt: vscode.InputBoxOptions = {
ignoreFocusOut: true,
prompt: localize('vscode-docker.commands.images.tag.tagAs', 'Tag image as...'),
};
if (addDefaultRegistry) {
let registryLength: number = fullTag.indexOf('/');
if (defaultRegistryPath.length > 0 && registryLength < 0) {
fullTag = defaultRegistryPath + '/' + fullTag;
registryLength = defaultRegistryPath.length;
}
opt.valueSelection = registryLength < 0 ? undefined : [0, registryLength + 1]; // include the '/'

if (fullTag.includes('/')) {
opt.valueSelection = [0, fullTag.lastIndexOf('/')];
} else if (baseImagePath) {
fullTag = `${baseImagePath}/${fullTag}`;
opt.valueSelection = [0, fullTag.lastIndexOf('/')];
}

opt.value = fullTag;

const nameWithTag: string = await ext.ui.showInputBox(opt);
return nameWithTag;
return await ext.ui.showInputBox(opt);
}

const KnownRegistries: { type: string, regex: RegExp }[] = [
Expand Down
2 changes: 0 additions & 2 deletions src/commands/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ import { logInToDockerCli } from "./registries/logInToDockerCli";
import { logOutOfDockerCli } from "./registries/logOutOfDockerCli";
import { pullImage, pullRepository } from "./registries/pullImages";
import { reconnectRegistry } from "./registries/reconnectRegistry";
import { setRegistryAsDefault } from "./registries/registrySettings";
import { configureVolumesExplorer } from "./volumes/configureVolumesExplorer";
import { inspectVolume } from "./volumes/inspectVolume";
import { pruneVolumes } from "./volumes/pruneVolumes";
Expand Down Expand Up @@ -112,7 +111,6 @@ export function registerCommands(): void {
registerWorkspaceCommand('vscode-docker.registries.pullImage', pullImage);
registerWorkspaceCommand('vscode-docker.registries.pullRepository', pullRepository);
registerCommand('vscode-docker.registries.reconnectRegistry', reconnectRegistry);
registerCommand('vscode-docker.registries.setAsDefault', setRegistryAsDefault);

registerCommand('vscode-docker.registries.dockerHub.openInBrowser', openDockerHubInBrowser);

Expand Down
2 changes: 1 addition & 1 deletion src/commands/registries/azure/tasks/scheduleRunRequest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ async function quickPickImageName(context: IActionContext, rootFolder: vscode.Wo
await delay(500);

addImageTaggingTelemetry(context, suggestedImageName, '.before');
const imageName: string = await getTagFromUserInput(suggestedImageName, false);
const imageName: string = await getTagFromUserInput(suggestedImageName);
addImageTaggingTelemetry(context, imageName, '.after');

await ext.context.globalState.update(dockerFileKey, imageName);
Expand Down
Loading

0 comments on commit 64750d8

Please sign in to comment.