Run most commands without a shell (#3747)

This commit is contained in:
Brandon Waterloo [MSFT] 2022-12-06 13:01:46 -05:00 коммит произвёл GitHub
Родитель 10de482586
Коммит f6e55987c1
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
46 изменённых файлов: 364 добавлений и 384 удалений

Просмотреть файл

@ -33,7 +33,7 @@ export async function attachShellContainer(context: IActionContext, node?: Conta
// If so use it, otherwise use sh
try {
// If this succeeds, bash is present (exit code 0)
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
// Since we're not interested in the output, just the exit code, we can pretend this is a `VoidCommandResponse`
client.execContainer({ container: node.containerId, interactive: true, command: ['sh', '-c', 'which bash'] }) as Promise<VoidCommandResponse>
);

Просмотреть файл

@ -17,7 +17,7 @@ export async function inspectContainer(context: IActionContext, node?: Container
});
}
const inspectInfo = await ext.runWithDefaultShell(client =>
const inspectInfo = await ext.runWithDefaults(client =>
client.inspectContainers({ containers: [node.containerId] })
);
await openReadOnlyJson(node, JSON.parse(inspectInfo[0].raw));

Просмотреть файл

@ -17,7 +17,7 @@ export async function pruneContainers(context: IActionContext): Promise<void> {
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.containers.pruning', 'Pruning containers...') },
async () => {
const result = await ext.runWithDefaultShell(client =>
const result = await ext.runWithDefaults(client =>
client.pruneContainers({})
);

Просмотреть файл

@ -20,7 +20,7 @@ export async function restartContainer(context: IActionContext, node?: Container
);
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.containers.restart.restarting', 'Restarting Container(s)...') }, async () => {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.restartContainers({ container: nodes.map(n => n.containerId) })
);
});

Просмотреть файл

@ -20,7 +20,7 @@ export async function startContainer(context: IActionContext, node?: ContainerTr
);
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.containers.start.starting', 'Starting Container(s)...') }, async () => {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.startContainers({ container: nodes.map(n => n.containerId) })
);
});

Просмотреть файл

@ -20,7 +20,7 @@ export async function stopContainer(context: IActionContext, node?: ContainerTre
);
await vscode.window.withProgress({ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.containers.stop.stopping', 'Stopping Container(s)...') }, async () => {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.stopContainers({ container: nodes.map(n => n.containerId) })
);
});

Просмотреть файл

@ -17,7 +17,7 @@ export async function inspectImage(context: IActionContext, node?: ImageTreeItem
});
}
const inspectResult = await ext.runWithDefaultShell(client =>
const inspectResult = await ext.runWithDefaults(client =>
client.inspectImages({ imageRefs: [node.imageId] })
);
await openReadOnlyJson(node, JSON.parse(inspectResult[0].raw));

Просмотреть файл

@ -17,7 +17,7 @@ export async function pruneImages(context: IActionContext): Promise<void> {
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.images.pruning', 'Pruning images...') },
async () => {
const result = await ext.runWithDefaultShell(client =>
const result = await ext.runWithDefaults(client =>
client.pruneImages({})
);

Просмотреть файл

@ -27,7 +27,7 @@ async function runImageCore(context: IActionContext, node: ImageTreeItem | undef
});
}
const inspectResult = await ext.runWithDefaultShell(client =>
const inspectResult = await ext.runWithDefaults(client =>
client.inspectImages({ imageRefs: [node.imageId] })
);

Просмотреть файл

@ -23,7 +23,7 @@ export async function tagImage(context: IActionContext, node?: ImageTreeItem, re
const newTaggedName: string = await getTagFromUserInput(context, node.fullTag, registry?.baseImagePath);
addImageTaggingTelemetry(context, newTaggedName, '.after');
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.tagImage({ fromImageRef: node.imageId, toImageRef: newTaggedName })
);

Просмотреть файл

@ -36,7 +36,7 @@ export async function createNetwork(context: IActionContext): Promise<void> {
}
);
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.createNetwork({ name: name, driver: driverSelection.label })
);
}

Просмотреть файл

@ -17,7 +17,7 @@ export async function inspectNetwork(context: IActionContext, node?: NetworkTree
});
}
const inspectResult = await ext.runWithDefaultShell(client =>
const inspectResult = await ext.runWithDefaults(client =>
client.inspectNetworks({ networks: [node.networkId] })
);
await openReadOnlyJson(node, JSON.parse(inspectResult[0].raw));

Просмотреть файл

@ -16,7 +16,7 @@ export async function pruneNetworks(context: IActionContext): Promise<void> {
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.networks.pruning', 'Pruning networks...') },
async () => {
const result = await ext.runWithDefaultShell(client =>
const result = await ext.runWithDefaults(client =>
client.pruneNetworks({})
);

Просмотреть файл

@ -17,16 +17,16 @@ export async function pruneSystem(context: IActionContext): Promise<void> {
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.pruneSystem.pruning', 'Pruning system...') },
async () => {
const containersResult = await ext.runWithDefaultShell(client =>
const containersResult = await ext.runWithDefaults(client =>
client.pruneContainers({})
);
const imagesResult = await ext.runWithDefaultShell(client =>
const imagesResult = await ext.runWithDefaults(client =>
client.pruneImages({})
);
const networksResult = await ext.runWithDefaultShell(client =>
const networksResult = await ext.runWithDefaults(client =>
client.pruneNetworks({})
);
const volumesResult = await ext.runWithDefaultShell(client =>
const volumesResult = await ext.runWithDefaults(client =>
client.pruneVolumes({})
);

Просмотреть файл

@ -39,7 +39,7 @@ export async function logInToDockerCli(context: IActionContext, node?: RegistryT
await vscode.window.withProgress(progressOptions, async () => {
try {
await ext.runWithDefaultShell(
await ext.runWithDefaults(
client => client.login({
username: username,
passwordStdIn: true,

Просмотреть файл

@ -14,7 +14,7 @@ export async function inspectVolume(context: IActionContext, node?: VolumeTreeIt
node = await ext.volumesTree.showTreeItemPicker<VolumeTreeItem>(VolumeTreeItem.contextValue, { ...context, noItemFoundErrorMessage: localize('vscode-docker.commands.volumes.inspect.noVolumes', 'No volumes are available to inspect') });
}
const inspectResult = await ext.runWithDefaultShell(client =>
const inspectResult = await ext.runWithDefaults(client =>
client.inspectVolumes({ volumes: [node.volumeName] })
);
await openReadOnlyJson(node, JSON.parse(inspectResult[0].raw));

Просмотреть файл

@ -17,7 +17,7 @@ export async function pruneVolumes(context: IActionContext): Promise<void> {
await vscode.window.withProgress(
{ location: vscode.ProgressLocation.Notification, title: localize('vscode-docker.commands.volumes.pruning', 'Pruning volumes...') },
async () => {
const result = await ext.runWithDefaultShell(client =>
const result = await ext.runWithDefaults(client =>
client.pruneVolumes({})
);

Просмотреть файл

@ -141,7 +141,7 @@ export class DockerDebugConfigurationProvider implements DebugConfigurationProvi
configuration?.dockerOptions?.containerName && // containerName must be specified
!(configuration?.subProcessId)) { // Must not have subProcessId, i.e. not a subprocess debug session (which is how Python does hot reload sessions)
try {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.removeContainers({ containers: [configuration.dockerOptions.containerName], force: true })
);
} catch {
@ -153,7 +153,7 @@ export class DockerDebugConfigurationProvider implements DebugConfigurationProvi
private async outputPortsAtDebuggingIfNeeded(context: IActionContext, configuration: ResolvedDebugConfiguration): Promise<void> {
if (configuration?.dockerOptions?.containerName) {
try {
const inspectInfo = (await ext.runWithDefaultShell(client =>
const inspectInfo = (await ext.runWithDefaults(client =>
client.inspectContainers({ containers: [configuration.dockerOptions.containerName] })
))?.[0];
const portMappings: string[] = [];

Просмотреть файл

@ -134,7 +134,7 @@ class ServerReadyDetector implements DockerServerReadyDetector {
}
private async getHostPortForContainerPort(containerName: string, containerPort: number): Promise<number> {
const containerInspectInfo = (await ext.runWithDefaultShell(client =>
const containerInspectInfo = (await ext.runWithDefaults(client =>
client.inspectContainers({ containers: [containerName] })
))?.[0];
const hostPort = containerInspectInfo?.ports.find(p => p.containerPort === containerPort)?.hostPort;
@ -211,7 +211,7 @@ class DockerLogsTracker extends vscode.Disposable {
private async listen(): Promise<void> {
try {
const generator = ext.streamWithDefaultShell(
const generator = ext.streamWithDefaults(
client => client.logsForContainer({ container: this.containerName, follow: true }),
{
cancellationToken: this.cts.token,

Просмотреть файл

@ -268,7 +268,7 @@ export class NetCoreDebugHelper implements DebugHelper {
private async copyDebuggerToContainer(context: IActionContext, containerName: string, containerDebuggerDirectory: string, containerOS: ContainerOS): Promise<void> {
if (containerOS === 'windows') {
const inspectInfo = (await ext.runWithDefaultShell(client =>
const inspectInfo = (await ext.runWithDefaults(client =>
client.inspectContainers({ containers: [containerName] })
))?.[0];
const containerInfo = inspectInfo ? JSON.parse(inspectInfo.raw) : undefined;
@ -295,7 +295,7 @@ export class NetCoreDebugHelper implements DebugHelper {
location: ProgressLocation.Notification,
title: localize('vscode-docker.debug.netcore.copyDebugger', 'Copying the .NET Core debugger to the container ({0} --> {1})...', vsDbgInstallBasePath, containerDebuggerDirectory),
}, async () => {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.writeFile({
container: containerName,
inputFile: vsDbgInstallBasePath,
@ -323,7 +323,7 @@ export class NetCoreDebugHelper implements DebugHelper {
}
try {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
// Since we're not interested in the output, just the exit code, we can pretend this is a `VoidCommandResponse`
client.execContainer({
container: containerName,

Просмотреть файл

@ -144,7 +144,7 @@ export class PythonDebugHelper implements DebugHelper {
}
// For Docker Desktop on WSL or Linux, we also use 'localhost'
const dockerInfo = await ext.runWithDefaultShell(client =>
const dockerInfo = await ext.runWithDefaults(client =>
client.info({})
);
@ -154,7 +154,7 @@ export class PythonDebugHelper implements DebugHelper {
// For other Docker setups on WSL or Linux, 'host.docker.internal' doesn't work, so we ask debugpy to listen
// on the bridge network's ip address (predefined network).
const networkInspection = (await ext.runWithDefaultShell(client =>
const networkInspection = (await ext.runWithDefaults(client =>
client.inspectNetworks({ networks: ['bridge'] })
))?.[0];

Просмотреть файл

@ -14,7 +14,7 @@ import { NetworksTreeItem } from './tree/networks/NetworksTreeItem';
import { RegistriesTreeItem } from './tree/registries/RegistriesTreeItem';
import { VolumesTreeItem } from './tree/volumes/VolumesTreeItem';
import { OrchestratorRuntimeManager } from './runtimes/OrchestratorRuntimeManager';
import { runOrchestratorWithDefaultShellInternal, runWithDefaultShellInternal, streamOrchestratorWithDefaultShellInternal, streamWithDefaultShellInternal } from './runtimes/runners/runWithDefaultShell';
import { runWithDefaults as runWithDefaultsImpl, streamWithDefaults as streamWithDefaultsImpl } from './runtimes/runners/runWithDefaults';
/**
* Namespace for common variables used throughout the extension. They must be initialized in the activate() method of extension.ts
@ -59,8 +59,6 @@ export namespace ext {
// Container runtime related items
export let runtimeManager: ContainerRuntimeManager;
export let orchestratorManager: OrchestratorRuntimeManager;
export const runWithDefaultShell = runWithDefaultShellInternal;
export const streamWithDefaultShell = streamWithDefaultShellInternal;
export const runOrchestratorWithDefaultShell = runOrchestratorWithDefaultShellInternal;
export const streamOrchestratorWithDefaultShell = streamOrchestratorWithDefaultShellInternal;
export const runWithDefaults = runWithDefaultsImpl;
export const streamWithDefaults = streamWithDefaultsImpl;
}

Просмотреть файл

@ -39,7 +39,7 @@ export class ContextManager implements IContextManager, vscode.Disposable {
}
public async getContexts(): Promise<ListContextItem[]> {
const allContexts = await ext.runWithDefaultShell(client =>
const allContexts = await ext.runWithDefaults(client =>
client.listContexts({})
) || [];
const currentContext: ListContextItem | undefined = this.tryGetCurrentContext(allContexts);
@ -58,20 +58,20 @@ export class ContextManager implements IContextManager, vscode.Disposable {
}
public async useContext(name: string): Promise<void> {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.useContext({ context: name })
);
await this.getCurrentContext(); // Reestablish the current context, to cause the change emitter to fire indirectly if the context has actually changed
}
public async removeContext(name: string): Promise<void> {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.removeContexts({ contexts: [name] })
);
}
public async inspectContext(name: string): Promise<InspectContextsItem | undefined> {
const result = await ext.runWithDefaultShell(client =>
const result = await ext.runWithDefaults(client =>
client.inspectContexts({ contexts: [name] })
);
return result?.[0];

Просмотреть файл

@ -1,79 +1,79 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ListNetworkItem } from '../../contracts/ContainerClient';
import { dayjs } from '../../utils/dayjs';
import { parseDockerLikeLabels } from './parseDockerLikeLabels';
export type DockerListNetworkRecord = {
ID: string;
Name: string;
Driver: string;
Labels: string;
Scope: string;
IPv6: string;
CreatedAt: string;
Internal: string;
};
export function isDockerListNetworkRecord(maybeNetwork: unknown): maybeNetwork is DockerListNetworkRecord {
const network = maybeNetwork as DockerListNetworkRecord;
if (!network || typeof network !== 'object') {
return false;
}
if (typeof network.ID !== 'string') {
return false;
}
if (typeof network.Name !== 'string') {
return false;
}
if (typeof network.Driver !== 'string') {
return false;
}
if (typeof network.Labels !== 'string') {
return false;
}
if (typeof network.Scope !== 'string') {
return false;
}
if (typeof network.IPv6 !== 'string') {
return false;
}
if (typeof network.CreatedAt !== 'string') {
return false;
}
if (typeof network.Internal !== 'string') {
return false;
}
return true;
}
export function normalizeDockerListNetworkRecord(network: DockerListNetworkRecord): ListNetworkItem {
// Parse the labels assigned to the networks and normalize to key value pairs
const labels = parseDockerLikeLabels(network.Labels);
const createdAt = dayjs.utc(network.CreatedAt).toDate();
return {
id: network.ID,
name: network.Name,
driver: network.Driver,
labels,
scope: network.Scope,
ipv6: network.IPv6.toLowerCase() === 'true',
internal: network.Internal.toLowerCase() === 'true',
createdAt,
};
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ListNetworkItem } from '../../contracts/ContainerClient';
import { dayjs } from '../../utils/dayjs';
import { parseDockerLikeLabels } from './parseDockerLikeLabels';
export type DockerListNetworkRecord = {
ID: string;
Name: string;
Driver: string;
Labels: string;
Scope: string;
IPv6: string;
CreatedAt: string;
Internal: string;
};
export function isDockerListNetworkRecord(maybeNetwork: unknown): maybeNetwork is DockerListNetworkRecord {
const network = maybeNetwork as DockerListNetworkRecord;
if (!network || typeof network !== 'object') {
return false;
}
if (typeof network.ID !== 'string') {
return false;
}
if (typeof network.Name !== 'string') {
return false;
}
if (typeof network.Driver !== 'string') {
return false;
}
if (typeof network.Labels !== 'string') {
return false;
}
if (typeof network.Scope !== 'string') {
return false;
}
if (typeof network.IPv6 !== 'string') {
return false;
}
if (typeof network.CreatedAt !== 'string') {
return false;
}
if (typeof network.Internal !== 'string') {
return false;
}
return true;
}
export function normalizeDockerListNetworkRecord(network: DockerListNetworkRecord): ListNetworkItem {
// Parse the labels assigned to the networks and normalize to key value pairs
const labels = parseDockerLikeLabels(network.Labels);
const createdAt = dayjs.utc(network.CreatedAt).toDate();
return {
id: network.ID,
name: network.Name,
driver: network.Driver,
labels,
scope: network.Scope,
ipv6: network.IPv6.toLowerCase() === 'true',
internal: network.Internal.toLowerCase() === 'true',
createdAt,
};
}

Просмотреть файл

@ -1,26 +1,26 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Parse the standard Docker environment variable format into a key value record object
* @param environmentVariables A Docker like list of key=value environment variables
* @returns An object of key value pairs for the environment variables
*/
export function parseDockerLikeEnvironmentVariables(environmentVariables: Array<string>): Record<string, string> {
return environmentVariables.reduce<Record<string, string>>((evs, ev) => {
const index = ev.indexOf('=');
if (index > -1) {
const name = ev.slice(0, index);
const value = ev.slice(index + 1);
return {
...evs,
[name]: value,
};
}
return evs;
}, {});
}
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
/**
* Parse the standard Docker environment variable format into a key value record object
* @param environmentVariables A Docker like list of key=value environment variables
* @returns An object of key value pairs for the environment variables
*/
export function parseDockerLikeEnvironmentVariables(environmentVariables: Array<string>): Record<string, string> {
return environmentVariables.reduce<Record<string, string>>((evs, ev) => {
const index = ev.indexOf('=');
if (index > -1) {
const name = ev.slice(0, index);
const value = ev.slice(index + 1);
return {
...evs,
[name]: value,
};
}
return evs;
}, {});
}

Просмотреть файл

@ -3,6 +3,12 @@
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { withNamedArg } from '../../utils/commandLineBuilder';
import { composeArgs, withArg, withVerbatimArg } from '../../utils/commandLineBuilder';
export const withDockerJsonFormatArg = withNamedArg('--format', '{{json .}}');
// Because Docker's wrapper script would split up `{{json .}}` into two arguments, we need to
// pre-quote it to prevent that, for cases where we're executing without a shell.
// Making it a verbatim argument also prevents it from being requoted later.
export const withDockerJsonFormatArg = composeArgs(
withArg('--format'),
withVerbatimArg('"{{json .}}"'),
);

Просмотреть файл

@ -18,8 +18,8 @@ import {
import { CancellationTokenLike } from '../typings/CancellationTokenLike';
import { AccumulatorStream } from '../utils/AccumulatorStream';
import { CancellationError } from '../utils/CancellationError';
import { CommandLineArgs } from '../utils/commandLineBuilder';
import {
Shell,
spawnStreamAsync,
StreamSpawnOptions,
} from '../utils/spawnStreamAsync';
@ -94,12 +94,8 @@ export class ShellStreamCommandRunnerFactory<TOptions extends ShellStreamCommand
await processPromise;
}
protected getCommandAndArgs(commandResponse: CommandResponseBase): { command: string, args: string[] } {
return {
command: commandResponse.command,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
args: Shell.getShellOrDefault(this.options.shellProvider).quote(commandResponse.args),
};
protected getCommandAndArgs(commandResponse: CommandResponseBase): { command: string, args: CommandLineArgs } {
return commandResponse;
}
}

Просмотреть файл

@ -7,7 +7,7 @@ import {
CommandResponseBase,
ICommandRunnerFactory,
} from '../contracts/CommandRunner';
import { Shell } from '../utils/spawnStreamAsync';
import { CommandLineArgs, composeArgs, withArg, withNamedArg } from '../utils/commandLineBuilder';
import {
ShellStreamCommandRunnerFactory,
ShellStreamCommandRunnerOptions,
@ -22,20 +22,14 @@ export type WslShellCommandRunnerOptions = ShellStreamCommandRunnerOptions & {
* Special case of {@link ShellStreamCommandRunnerFactory} for executing commands in a wsl distro
*/
export class WslShellCommandRunnerFactory extends ShellStreamCommandRunnerFactory<WslShellCommandRunnerOptions> implements ICommandRunnerFactory {
protected override getCommandAndArgs(
commandResponse: CommandResponseBase,
): {
command: string;
args: string[];
} {
protected getCommandAndArgs(commandResponse: CommandResponseBase): { command: string, args: CommandLineArgs } {
const command = this.options.wslPath ?? 'wsl.exe';
const args = [
...(this.options.distro ? ['-d', this.options.distro] : []),
'--',
commandResponse.command,
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
...Shell.getShellOrDefault(this.options.shellProvider).quote(commandResponse.args),
];
const args = composeArgs(
withNamedArg('-d', this.options.distro),
withArg('--'),
withArg(commandResponse.command),
withArg(...commandResponse.args),
)();
return { command, args };
}

Просмотреть файл

@ -1,54 +1,54 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dayjsinner from 'dayjs';
import * as customParseFormat from 'dayjs/plugin/customParseFormat';
import * as utc from 'dayjs/plugin/utc';
dayjsinner.extend(customParseFormat);
dayjsinner.extend(utc);
const defaultFormats = ['YYYY-MM-DD HH:mm:ss ZZ'];
/**
* Wrap the dayjs methods to apply a default Docker friendly format to all parsing
*/
export const dayjs = new Proxy(dayjsinner, {
apply(target, thisArg, argArray: Parameters<typeof dayjsinner>) {
const formats = [...defaultFormats]; // formats should always include default Docker date format
if (argArray.length > 1) {
if (typeof argArray[1] === 'string' || Array.isArray(argArray[1])) {
argArray[1] = formats.concat(argArray[1]);
}
} else if (argArray.length === 1) {
argArray.push(formats);
}
return target.apply(thisArg, argArray);
},
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') {
if (prop === 'utc') {
return function (this: typeof dayjsinner, ...args: Array<unknown>) {
const formats = [...defaultFormats]; // formats should always include default Docker date format
if (args.length > 1) {
if (typeof args[1] === 'string' || Array.isArray(args[1])) {
args[1] = formats.concat(args[1]);
}
} else if (args.length === 1) {
args.push(formats);
}
return value.apply(this === receiver ? target : this, args);
};
}
return value.bind(this === receiver ? target : this);
} else {
return value;
}
},
});
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as dayjsinner from 'dayjs';
import * as customParseFormat from 'dayjs/plugin/customParseFormat';
import * as utc from 'dayjs/plugin/utc';
dayjsinner.extend(customParseFormat);
dayjsinner.extend(utc);
const defaultFormats = ['YYYY-MM-DD HH:mm:ss ZZ'];
/**
* Wrap the dayjs methods to apply a default Docker friendly format to all parsing
*/
export const dayjs = new Proxy(dayjsinner, {
apply(target, thisArg, argArray: Parameters<typeof dayjsinner>) {
const formats = [...defaultFormats]; // formats should always include default Docker date format
if (argArray.length > 1) {
if (typeof argArray[1] === 'string' || Array.isArray(argArray[1])) {
argArray[1] = formats.concat(argArray[1]);
}
} else if (argArray.length === 1) {
argArray.push(formats);
}
return target.apply(thisArg, argArray);
},
get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === 'function') {
if (prop === 'utc') {
return function (this: typeof dayjsinner, ...args: Array<unknown>) {
const formats = [...defaultFormats]; // formats should always include default Docker date format
if (args.length > 1) {
if (typeof args[1] === 'string' || Array.isArray(args[1])) {
args[1] = formats.concat(args[1]);
}
} else if (args.length === 1) {
args.push(formats);
}
return value.apply(this === receiver ? target : this, args);
};
}
return value.bind(this === receiver ? target : this);
} else {
return value;
}
},
});

Просмотреть файл

@ -141,7 +141,7 @@ export type StreamSpawnOptions = SpawnOptions & {
export async function spawnStreamAsync(
command: string,
args: Array<string>,
args: CommandLineArgs,
options: StreamSpawnOptions,
): Promise<void> {
const cancellationToken = options.cancellationToken || CancellationTokenLike.None;
@ -149,6 +149,9 @@ export async function spawnStreamAsync(
// *nix
const shell = options.shellProvider?.getShellOrDefault(options.shell) ?? options.shell;
// If there is a shell provider, apply its quoting, otherwise just flatten arguments into strings
const normalizedArgs: string[] = options.shellProvider?.quote(args) ?? args.map(arg => typeof arg === 'string' ? arg : arg.value);
if (cancellationToken.isCancellationRequested) {
throw new CancellationError('Command cancelled', cancellationToken);
}
@ -159,7 +162,7 @@ export async function spawnStreamAsync(
const childProcess = spawn(
command,
args,
normalizedArgs,
{
...options,
shell,

Просмотреть файл

@ -54,7 +54,7 @@ export class ContainerFilesProvider extends vscode.Disposable implements vscode.
const dockerUri = DockerUri.parse(uri);
const containerOS = dockerUri.options?.containerOS || await getDockerOSType();
const items: ListFilesItem[] = await ext.runWithDefaultShell(client =>
const items: ListFilesItem[] = await ext.runWithDefaults(client =>
client.listFiles({
container: dockerUri.containerId,
path: containerOS === 'windows' ? dockerUri.windowsPath : dockerUri.path,
@ -81,7 +81,7 @@ export class ContainerFilesProvider extends vscode.Disposable implements vscode.
const accumulator = new AccumulatorStream();
const targetStream = containerOS === 'windows' ? accumulator : tarUnpackStream(accumulator);
const generator = ext.streamWithDefaultShell(
const generator = ext.streamWithDefaults(
client => client.readFile({
container: dockerUri.containerId,
path: containerOS === 'windows' ? dockerUri.windowsPath : dockerUri.path,
@ -108,7 +108,7 @@ export class ContainerFilesProvider extends vscode.Disposable implements vscode.
path.win32.dirname(dockerUri.windowsPath) :
path.posix.dirname(dockerUri.path);
await ext.runWithDefaultShell(
await ext.runWithDefaults(
client => client.writeFile({
container: dockerUri.containerId,
path: destDirectory,

Просмотреть файл

@ -1,139 +0,0 @@
/*!--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { AccumulatorStream, ClientIdentity, GeneratorCommandResponse, IContainerOrchestratorClient, IContainersClient, isChildProcessError, Like, normalizeCommandResponseLike, PromiseCommandResponse, Shell, ShellStreamCommandRunnerFactory, ShellStreamCommandRunnerOptions, VoidCommandResponse } from '../docker';
import { ext } from '../../extensionVariables';
import { RuntimeManager } from '../RuntimeManager';
import { withDockerEnvSettings } from '../../utils/withDockerEnvSettings';
type ClientCallback<TClient, T> = (client: TClient) => Like<PromiseCommandResponse<T>>;
type VoidClientCallback<TClient> = (client: TClient) => Like<VoidCommandResponse>;
type StreamingClientCallback<TClient, T> = (client: TClient) => Like<GeneratorCommandResponse<T>>;
// 'env', 'shellProvider', 'stdErrPipe', and 'strict' are set by this function and thus should not be included as arguments to the additional options
type DefaultEnvShellStreamCommandRunnerOptions = Omit<ShellStreamCommandRunnerOptions, 'env' | 'shellProvider' | 'stdErrPipe' | 'strict'>;
export async function runWithDefaultShellInternal<T>(callback: ClientCallback<IContainersClient, T>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): Promise<T>;
export async function runWithDefaultShellInternal(callback: VoidClientCallback<IContainersClient>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): Promise<void>;
export async function runWithDefaultShellInternal<T>(callback: ClientCallback<IContainersClient, T> | VoidClientCallback<IContainersClient>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): Promise<T | void> {
return await runWithDefaultShell(
callback,
ext.runtimeManager,
additionalOptions
);
}
export function streamWithDefaultShellInternal<T>(callback: StreamingClientCallback<IContainersClient, T>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): AsyncGenerator<T> {
return streamWithDefaultShell(
callback,
ext.runtimeManager,
additionalOptions
);
}
export async function runOrchestratorWithDefaultShellInternal<T>(callback: ClientCallback<IContainerOrchestratorClient, T>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): Promise<T>;
export async function runOrchestratorWithDefaultShellInternal(callback: VoidClientCallback<IContainerOrchestratorClient>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): Promise<void>;
export async function runOrchestratorWithDefaultShellInternal<T>(callback: ClientCallback<IContainerOrchestratorClient, T> | VoidClientCallback<IContainerOrchestratorClient>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): Promise<T | void> {
return await runWithDefaultShell(
callback,
ext.orchestratorManager,
additionalOptions
);
}
export function streamOrchestratorWithDefaultShellInternal<T>(callback: StreamingClientCallback<IContainerOrchestratorClient, T>, additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions): AsyncGenerator<T> {
return streamWithDefaultShell(
callback,
ext.orchestratorManager,
additionalOptions
);
}
async function runWithDefaultShell<TClient extends ClientIdentity, T>(
callback: ClientCallback<TClient, T> | VoidClientCallback<TClient>,
runtimeManager: RuntimeManager<TClient>,
additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions
): Promise<T | void> {
// Get a `DefaultEnvShellStreamCommandRunnerFactory`
const factory = new DefaultEnvShellStreamCommandRunnerFactory(additionalOptions);
// Get the active client
const client: TClient = await runtimeManager.getClient();
try {
// Flatten the callback
const response = await normalizeCommandResponseLike(callback(client));
if (response.parse) {
return await factory.getCommandRunner()(response as PromiseCommandResponse<T>);
} else {
await factory.getCommandRunner()(response as VoidCommandResponse);
}
} catch (err) {
if (isChildProcessError(err)) {
// If this is a child process error, alter the message to be the stderr output, if it isn't falsy
const stdErr = await factory.errAccumulator.getString();
err.message = stdErr || err.message;
}
throw err;
} finally {
factory.dispose();
}
}
async function* streamWithDefaultShell<TClient extends ClientIdentity, T>(
callback: StreamingClientCallback<TClient, T>,
runtimeManager: RuntimeManager<TClient>,
additionalOptions?: DefaultEnvShellStreamCommandRunnerOptions
): AsyncGenerator<T> {
// Get a `DefaultEnvShellStreamCommandRunnerFactory`
const factory = new DefaultEnvShellStreamCommandRunnerFactory(additionalOptions);
// Get the active client
const client: TClient = await runtimeManager.getClient();
try {
const runner = factory.getStreamingCommandRunner();
const generator = runner(callback(client));
for await (const element of generator) {
yield element;
}
} catch (err) {
if (isChildProcessError(err)) {
// If this is a child process error, alter the message to be the stderr output, if it isn't falsy
const stdErr = await factory.errAccumulator.getString();
err.message = stdErr || err.message;
}
throw err;
} finally {
factory.dispose();
}
}
class DefaultEnvShellStreamCommandRunnerFactory<TOptions extends DefaultEnvShellStreamCommandRunnerOptions> extends ShellStreamCommandRunnerFactory<ShellStreamCommandRunnerOptions> implements vscode.Disposable {
public readonly errAccumulator: AccumulatorStream;
public constructor(options: TOptions) {
const errAccumulator = new AccumulatorStream();
super({
...options,
strict: true,
stdErrPipe: errAccumulator,
shellProvider: Shell.getShellOrDefault(),
env: withDockerEnvSettings(process.env),
});
this.errAccumulator = errAccumulator;
}
public dispose(): void {
this.errAccumulator.destroy();
}
}

Просмотреть файл

@ -0,0 +1,122 @@
/*!--------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See LICENSE in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { AccumulatorStream, ClientIdentity, GeneratorCommandResponse, IContainersClient, isChildProcessError, Like, normalizeCommandResponseLike, PromiseCommandResponse, ShellStreamCommandRunnerFactory, ShellStreamCommandRunnerOptions, VoidCommandResponse } from '../docker';
import { ext } from '../../extensionVariables';
import { RuntimeManager } from '../RuntimeManager';
import { withDockerEnvSettings } from '../../utils/withDockerEnvSettings';
type ClientCallback<TClient, T> = (client: TClient) => Like<PromiseCommandResponse<T>>;
type VoidClientCallback<TClient> = (client: TClient) => Like<VoidCommandResponse>;
type StreamingClientCallback<TClient, T> = (client: TClient) => Like<GeneratorCommandResponse<T>>;
// 'env', 'shell', 'shellProvider', 'stdErrPipe', and 'strict' are set by this function and thus should not be included as arguments to the additional options
type DefaultEnvStreamCommandRunnerOptions = Omit<ShellStreamCommandRunnerOptions, 'env' | 'shell' | 'shellProvider' | 'stdErrPipe' | 'strict'>;
export async function runWithDefaults<T>(callback: ClientCallback<IContainersClient, T>, additionalOptions?: DefaultEnvStreamCommandRunnerOptions): Promise<T>;
export async function runWithDefaults(callback: VoidClientCallback<IContainersClient>, additionalOptions?: DefaultEnvStreamCommandRunnerOptions): Promise<void>;
export async function runWithDefaults<T>(callback: ClientCallback<IContainersClient, T> | VoidClientCallback<IContainersClient>, additionalOptions?: DefaultEnvStreamCommandRunnerOptions): Promise<T | void> {
return await runWithDefaultsInternal(
callback,
ext.runtimeManager,
additionalOptions
);
}
export function streamWithDefaults<T>(callback: StreamingClientCallback<IContainersClient, T>, additionalOptions?: DefaultEnvStreamCommandRunnerOptions): AsyncGenerator<T> {
return streamWithDefaultsInternal(
callback,
ext.runtimeManager,
additionalOptions
);
}
async function runWithDefaultsInternal<TClient extends ClientIdentity, T>(
callback: ClientCallback<TClient, T> | VoidClientCallback<TClient>,
runtimeManager: RuntimeManager<TClient>,
additionalOptions?: DefaultEnvStreamCommandRunnerOptions
): Promise<T | void> {
// Get a `DefaultEnvStreamCommandRunnerFactory`
const factory = new DefaultEnvStreamCommandRunnerFactory(additionalOptions);
// Get the active client
const client: TClient = await runtimeManager.getClient();
try {
// Flatten the callback
const response = await normalizeCommandResponseLike(callback(client));
if (response.parse) {
return await factory.getCommandRunner()(response as PromiseCommandResponse<T>);
} else {
await factory.getCommandRunner()(response as VoidCommandResponse);
}
} catch (err) {
if (isChildProcessError(err)) {
// If this is a child process error, alter the message to be the stderr output, if it isn't falsy
const stdErr = await factory.errAccumulator.getString();
err.message = stdErr || err.message;
}
throw err;
} finally {
factory.dispose();
}
}
async function* streamWithDefaultsInternal<TClient extends ClientIdentity, T>(
callback: StreamingClientCallback<TClient, T>,
runtimeManager: RuntimeManager<TClient>,
additionalOptions?: DefaultEnvStreamCommandRunnerOptions
): AsyncGenerator<T> {
// Get a `DefaultEnvStreamCommandRunnerFactory`
const factory = new DefaultEnvStreamCommandRunnerFactory(additionalOptions);
// Get the active client
const client: TClient = await runtimeManager.getClient();
try {
const runner = factory.getStreamingCommandRunner();
const generator = runner(callback(client));
for await (const element of generator) {
yield element;
}
} catch (err) {
if (isChildProcessError(err)) {
// If this is a child process error, alter the message to be the stderr output, if it isn't falsy
const stdErr = await factory.errAccumulator.getString();
err.message = stdErr || err.message;
}
throw err;
} finally {
factory.dispose();
}
}
class DefaultEnvStreamCommandRunnerFactory<TOptions extends DefaultEnvStreamCommandRunnerOptions> extends ShellStreamCommandRunnerFactory<ShellStreamCommandRunnerOptions> implements vscode.Disposable {
public readonly errAccumulator: AccumulatorStream;
public constructor(options: TOptions) {
const errAccumulator = new AccumulatorStream();
super({
...options,
env: withDockerEnvSettings(process.env),
shell: false,
shellProvider: undefined,
stdErrPipe: errAccumulator,
strict: true,
});
this.errAccumulator = errAccumulator;
}
public dispose(): void {
this.errAccumulator.destroy();
}
}

Просмотреть файл

@ -242,7 +242,7 @@ export class NetCoreTaskHelper implements TaskHelper {
// Try to get a container username from the image (best effort only)
let userName: string | undefined;
try {
const imageInspection = (await ext.runWithDefaultShell(client =>
const imageInspection = (await ext.runWithDefaults(client =>
client.inspectImages({ imageRefs: [runOptions.image] })
))?.[0];
userName = imageInspection?.user;

Просмотреть файл

@ -115,7 +115,7 @@ export class RefreshManager extends vscode.Disposable {
const eventsUntilTimestamp = eventsSinceTimestamp + eventListenerLifetimeSeconds;
try {
const eventGenerator = ext.streamWithDefaultShell(client =>
const eventGenerator = ext.streamWithDefaults(client =>
client.getEventStream({
types: eventTypesToWatch,
events: eventActionsToWatch,

Просмотреть файл

@ -101,7 +101,7 @@ export class ContainerTreeItem extends ToolTipParentTreeItem implements MultiSel
}
public async deleteTreeItemImpl(context: IActionContext): Promise<void> {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.removeContainers({ containers: [this.containerId], force: true })
);
}
@ -147,7 +147,7 @@ export class ContainerTreeItem extends ToolTipParentTreeItem implements MultiSel
public async resolveTooltipInternal(actionContext: IActionContext): Promise<vscode.MarkdownString> {
actionContext.telemetry.properties.tooltipType = 'container';
const containerInspection = (await ext.runWithDefaultShell(client =>
const containerInspection = (await ext.runWithDefaults(client =>
client.inspectContainers({ containers: [this.containerId] })
))?.[0];

Просмотреть файл

@ -56,7 +56,7 @@ export class ContainersTreeItem extends LocalRootTreeItemBase<DockerContainerInf
}
public async getItems(context: IActionContext): Promise<DockerContainerInfo[]> {
const rawResults = await ext.runWithDefaultShell(client =>
const rawResults = await ext.runWithDefaults(client =>
client.listContainers({ all: true })
);

Просмотреть файл

@ -76,7 +76,7 @@ export class ImageTreeItem extends ToolTipTreeItem {
ref = this._item.id;
}
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.removeImages({ imageRefs: [ref] })
);
}
@ -85,10 +85,10 @@ export class ImageTreeItem extends ToolTipTreeItem {
actionContext.telemetry.properties.tooltipType = 'image';
// Allows some parallelization of the two commands
const imagePromise = ext.runWithDefaultShell(client =>
const imagePromise = ext.runWithDefaults(client =>
client.inspectImages({ imageRefs: [this.imageId] })
);
const containersPromise = ext.runWithDefaultShell(client =>
const containersPromise = ext.runWithDefaults(client =>
client.listContainers({ imageAncestors: [this.imageId] })
);

Просмотреть файл

@ -67,7 +67,7 @@ export class ImagesTreeItem extends LocalRootTreeItemBase<DatedDockerImage, Imag
dangling: includeDangling ? undefined : false,
};
const result = await ext.runWithDefaultShell(client =>
const result = await ext.runWithDefaults(client =>
client.listImages(options)
);
this.outdatedImageChecker.markOutdatedImages(result);

Просмотреть файл

@ -89,7 +89,7 @@ export class OutdatedImageChecker {
const latestImageDigest = await this.getLatestImageDigest(registry, imageNameInfo.image, imageNameInfo.tag);
// 2. Compare it with the current image's value
const imageInspectInfo = (await ext.runWithDefaultShell(client =>
const imageInspectInfo = (await ext.runWithDefaults(client =>
client.inspectImages({ imageRefs: [image.id] })
))?.[0];

Просмотреть файл

@ -56,7 +56,7 @@ export class NetworkTreeItem extends ToolTipTreeItem {
}
public async deleteTreeItemImpl(context: IActionContext): Promise<void> {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.removeNetworks({ networks: [this.networkId] })
);
}
@ -65,10 +65,10 @@ export class NetworkTreeItem extends ToolTipTreeItem {
actionContext.telemetry.properties.tooltipType = 'network';
// Allows some parallelization of the two commands
const networkPromise = ext.runWithDefaultShell(client =>
const networkPromise = ext.runWithDefaults(client =>
client.inspectNetworks({ networks: [this.networkName] })
);
const containersPromise = ext.runWithDefaultShell(client =>
const containersPromise = ext.runWithDefaults(client =>
client.listContainers({ networks: [this.networkName] })
);

Просмотреть файл

@ -47,7 +47,7 @@ export class NetworksTreeItem extends LocalRootTreeItemBase<ListNetworkItem, Net
const config = workspace.getConfiguration(configPrefix);
const showBuiltInNetworks: boolean = config.get<boolean>('networks.showBuiltInNetworks');
let networks = await ext.runWithDefaultShell(client =>
let networks = await ext.runWithDefaults(client =>
client.listNetworks({})
) || [];

Просмотреть файл

@ -53,7 +53,7 @@ export class VolumeTreeItem extends ToolTipTreeItem implements VolumeTreeItemUse
}
public async deleteTreeItemImpl(context: IActionContext): Promise<void> {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.removeVolumes({ volumes: [this.volumeName] })
);
}
@ -62,10 +62,10 @@ export class VolumeTreeItem extends ToolTipTreeItem implements VolumeTreeItemUse
actionContext.telemetry.properties.tooltipType = 'volume';
// Allows some parallelization of the two commands
const volumePromise = ext.runWithDefaultShell(client =>
const volumePromise = ext.runWithDefaults(client =>
client.inspectVolumes({ volumes: [this.volumeName] })
);
const containersPromise = ext.runWithDefaultShell(client =>
const containersPromise = ext.runWithDefaults(client =>
client.listContainers({ volumes: [this.volumeName] })
);

Просмотреть файл

@ -42,7 +42,7 @@ export class VolumesTreeItem extends LocalRootTreeItemBase<ListVolumeItem, Volum
}
public async getItems(context: IActionContext): Promise<ListVolumeItem[]> {
return ext.runWithDefaultShell(client =>
return ext.runWithDefaults(client =>
client.listVolumes({})
);
}

Просмотреть файл

@ -28,7 +28,7 @@ class RuntimeInstallStatusProvider {
public async isRuntimeInstalledRealTimeCheck(): Promise<boolean> {
try {
await ext.runWithDefaultShell(client =>
await ext.runWithDefaults(client =>
client.checkInstall({})
);
return true; // As long as the -v command did't throw exception, assume it is installed.

Просмотреть файл

@ -15,7 +15,7 @@ export async function getDockerOSType(): Promise<ContainerOS> {
// so short-circuit the Docker call entirely.
return 'linux';
} else {
const info = await ext.runWithDefaultShell(client =>
const info = await ext.runWithDefaults(client =>
client.info({})
);
return info?.osType || 'linux';