2018-08-17 03:17:28 +03:00
|
|
|
/*---------------------------------------------------------------------------------------------
|
|
|
|
* Copyright (c) Microsoft Corporation. All rights reserved.
|
|
|
|
* Licensed under the MIT License. See LICENSE.md in the project root for license information.
|
|
|
|
*--------------------------------------------------------------------------------------------*/
|
|
|
|
|
2016-10-20 07:25:54 +03:00
|
|
|
import vscode = require('vscode');
|
2018-09-01 04:57:32 +03:00
|
|
|
import { IActionContext } from 'vscode-azureextensionui';
|
2018-09-01 02:19:53 +03:00
|
|
|
import { configurationKeys } from '../constants';
|
2018-09-04 21:29:37 +03:00
|
|
|
import { ImageNode } from '../explorer/models/imageNode';
|
|
|
|
import { RootNode } from '../explorer/models/rootNode';
|
2018-09-05 09:23:07 +03:00
|
|
|
import { delay } from '../explorer/utils/utils';
|
2018-09-01 02:19:53 +03:00
|
|
|
import { ext } from '../extensionVariables';
|
2018-09-18 21:32:34 +03:00
|
|
|
import { extractRegExGroups } from '../helpers/extractRegExGroups';
|
2018-07-18 02:42:00 +03:00
|
|
|
import { docker } from './utils/docker-endpoint';
|
|
|
|
import { ImageItem, quickPickImage } from './utils/quick-pick-image';
|
2017-09-15 04:25:19 +03:00
|
|
|
|
2018-09-04 21:29:37 +03:00
|
|
|
export async function tagImage(actionContext: IActionContext, context: ImageNode | RootNode | IHasImageDescriptorAndLabel | undefined): Promise<string> {
|
|
|
|
// If a RootNode or no node is passed in, we ask the user to pick an image
|
2018-09-05 02:06:04 +03:00
|
|
|
let [imageToTag, currentName] = await getOrAskForImageAndTag(actionContext, context instanceof RootNode ? undefined : context);
|
2016-10-20 07:25:54 +03:00
|
|
|
|
2018-09-01 02:19:53 +03:00
|
|
|
if (imageToTag) {
|
2018-09-01 04:57:32 +03:00
|
|
|
addImageTaggingTelemetry(actionContext, currentName, '.before');
|
|
|
|
let newTaggedName: string = await getTagFromUserInput(currentName, true);
|
|
|
|
addImageTaggingTelemetry(actionContext, newTaggedName, '.after');
|
2018-09-01 02:19:53 +03:00
|
|
|
|
2018-09-01 04:57:32 +03:00
|
|
|
let repo: string = newTaggedName;
|
2018-09-01 02:19:53 +03:00
|
|
|
let tag: string = 'latest';
|
|
|
|
|
2018-09-01 04:57:32 +03:00
|
|
|
if (newTaggedName.lastIndexOf(':') > 0) {
|
|
|
|
repo = newTaggedName.slice(0, newTaggedName.lastIndexOf(':'));
|
|
|
|
tag = newTaggedName.slice(newTaggedName.lastIndexOf(':') + 1);
|
2017-08-11 09:17:33 +03:00
|
|
|
}
|
2016-10-20 07:25:54 +03:00
|
|
|
|
2018-09-01 02:19:53 +03:00
|
|
|
const image: Docker.Image = docker.getImage(imageToTag.Id);
|
|
|
|
|
2018-09-08 00:46:30 +03:00
|
|
|
// tslint:disable-next-line:no-function-expression no-any // Grandfathered in
|
|
|
|
image.tag({ repo: repo, tag: tag }, function (err: { message?: string }, _data: any): void {
|
2018-09-01 02:19:53 +03:00
|
|
|
if (err) {
|
|
|
|
// TODO: use parseError, proper error handling
|
|
|
|
vscode.window.showErrorMessage('Docker Tag error: ' + err.message);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
2018-09-01 04:57:32 +03:00
|
|
|
return newTaggedName;
|
2017-08-11 09:17:33 +03:00
|
|
|
}
|
2018-09-01 02:19:53 +03:00
|
|
|
}
|
2016-10-25 02:40:34 +03:00
|
|
|
|
2018-09-05 02:06:04 +03:00
|
|
|
export async function getTagFromUserInput(imageName: string, addDefaultRegistry: boolean): Promise<string> {
|
2018-09-01 02:19:53 +03:00
|
|
|
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
|
|
|
|
const defaultRegistryPath = configOptions.get(configurationKeys.defaultRegistryPath, '');
|
2016-10-25 02:40:34 +03:00
|
|
|
|
2018-09-01 02:19:53 +03:00
|
|
|
let opt: vscode.InputBoxOptions = {
|
|
|
|
ignoreFocusOut: true,
|
|
|
|
prompt: 'Tag image as...',
|
|
|
|
};
|
2018-09-05 02:06:04 +03:00
|
|
|
if (addDefaultRegistry) {
|
2018-09-01 04:36:39 +03:00
|
|
|
let registryLength: number = imageName.indexOf('/');
|
|
|
|
if (defaultRegistryPath.length > 0 && registryLength < 0) {
|
|
|
|
imageName = defaultRegistryPath + '/' + imageName;
|
|
|
|
registryLength = defaultRegistryPath.length;
|
|
|
|
}
|
2018-09-05 02:06:04 +03:00
|
|
|
opt.valueSelection = registryLength < 0 ? undefined : [0, registryLength + 1]; //include the '/'
|
2018-09-01 04:36:39 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
opt.value = imageName;
|
2017-08-11 09:17:33 +03:00
|
|
|
|
2018-09-01 02:19:53 +03:00
|
|
|
const nameWithTag: string = await ext.ui.showInputBox(opt);
|
|
|
|
return nameWithTag;
|
|
|
|
}
|
2016-10-20 07:25:54 +03:00
|
|
|
|
2018-09-01 02:19:53 +03:00
|
|
|
export interface IHasImageDescriptorAndLabel {
|
|
|
|
imageDesc: Docker.ImageDesc,
|
|
|
|
label: string
|
|
|
|
}
|
2017-08-11 09:17:33 +03:00
|
|
|
|
2018-09-05 02:06:04 +03:00
|
|
|
export async function getOrAskForImageAndTag(actionContext: IActionContext, context: IHasImageDescriptorAndLabel | undefined): Promise<[Docker.ImageDesc, string]> {
|
2018-09-01 02:19:53 +03:00
|
|
|
let name: string;
|
|
|
|
let description: Docker.ImageDesc;
|
2017-08-11 09:17:33 +03:00
|
|
|
|
2018-09-01 02:19:53 +03:00
|
|
|
if (context && context.imageDesc) {
|
|
|
|
description = context.imageDesc;
|
|
|
|
name = context.label;
|
|
|
|
} else {
|
2018-09-05 02:06:04 +03:00
|
|
|
const selectedItem: ImageItem = await quickPickImage(actionContext, false);
|
2018-09-01 02:19:53 +03:00
|
|
|
if (selectedItem) {
|
|
|
|
description = selectedItem.imageDesc
|
|
|
|
name = selectedItem.label;
|
2017-08-11 09:17:33 +03:00
|
|
|
}
|
2018-09-01 02:19:53 +03:00
|
|
|
|
2018-09-05 09:23:07 +03:00
|
|
|
// Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox
|
|
|
|
await delay(500);
|
2018-07-14 00:16:56 +03:00
|
|
|
}
|
2018-09-01 02:19:53 +03:00
|
|
|
|
|
|
|
return [description, name];
|
2018-07-07 02:49:46 +03:00
|
|
|
}
|
2018-09-01 04:57:32 +03:00
|
|
|
|
|
|
|
const KnownRegistries: { type: string, regex: RegExp }[] = [
|
2018-09-05 03:16:37 +03:00
|
|
|
// Like username/path
|
|
|
|
{ type: 'dockerhub-namespace', regex: /^[^.:]+\/[^.:]+\/$/ },
|
|
|
|
|
2018-09-01 04:57:32 +03:00
|
|
|
{ type: 'dockerhub-dockerio', regex: /^docker.io.*\// },
|
|
|
|
{ type: 'gitlab', regex: /gitlab.*\// },
|
|
|
|
{ type: 'ACR', regex: /azurecr\.io.*\// },
|
|
|
|
{ type: 'GCR', regex: /gcr\.io.*\// },
|
|
|
|
{ type: 'ECR', regex: /\.ecr\..*\// },
|
|
|
|
{ type: 'localhost', regex: /localhost:.*\// },
|
|
|
|
|
|
|
|
// Has a port, probably a private registry
|
|
|
|
{ type: 'privateWithPort', regex: /:[0-9]+\// },
|
|
|
|
|
|
|
|
// Match anything remaining
|
|
|
|
{ type: 'other', regex: /\// }, // has a slash
|
|
|
|
{ type: 'none', regex: /./ } // no slash
|
|
|
|
];
|
|
|
|
|
|
|
|
export function addImageTaggingTelemetry(actionContext: IActionContext, fullImageName: string, propertyPostfix: '.before' | '.after' | ''): void {
|
|
|
|
try {
|
|
|
|
let defaultRegistryPath: string = vscode.workspace.getConfiguration('docker').get('defaultRegistryPath', '');
|
|
|
|
let properties: {
|
|
|
|
numSlashes?: string;
|
|
|
|
hasTag?: string;
|
|
|
|
isDefaultRegistryPathSet?: string; // docker.defaultRegistryPath has a value
|
|
|
|
isDefaultRegistryPathInName?: string; // image name starts with defaultRegistryPath
|
|
|
|
safeTag?: string;
|
|
|
|
registryType?: string;
|
|
|
|
} = {};
|
|
|
|
|
|
|
|
let [repository, tag] = extractRegExGroups(fullImageName, /^(.*):(.*)$/, [fullImageName, '']);
|
|
|
|
|
|
|
|
if (!!tag.match(/^[0-9.-]*(|alpha|beta|latest|edge|v|version)?[0-9.-]*$/)) {
|
|
|
|
properties.safeTag = tag
|
|
|
|
}
|
|
|
|
properties.hasTag = String(!!tag);
|
|
|
|
properties.numSlashes = String(numberMatches(repository.match(/\//g)));
|
|
|
|
properties.isDefaultRegistryPathInName = String(repository.startsWith(`${defaultRegistryPath}/`));
|
|
|
|
properties.isDefaultRegistryPathSet = String(!!defaultRegistryPath);
|
|
|
|
|
|
|
|
let knownRegistry = KnownRegistries.find(kr => !!repository.match(kr.regex));
|
|
|
|
properties.registryType = knownRegistry.type;
|
|
|
|
|
|
|
|
for (let propertyName of Object.getOwnPropertyNames(properties)) {
|
2018-09-07 21:27:11 +03:00
|
|
|
actionContext.properties[propertyName + propertyPostfix] = <string>properties[propertyName];
|
2018-09-01 04:57:32 +03:00
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
console.error(error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function numberMatches(matches: RegExpMatchArray | null): number {
|
|
|
|
return matches ? matches.length : 0;
|
|
|
|
}
|