Skip to content

Commit

Permalink
Add docker.context setting and support DOCKER_CONTEXT (microsoft#2355)
Browse files Browse the repository at this point in the history
  • Loading branch information
bwateratmsft authored and Dmarch28 committed Mar 4, 2021
1 parent 84666a9 commit cb6c661
Show file tree
Hide file tree
Showing 6 changed files with 50 additions and 16 deletions.
13 changes: 12 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@
{
"command": "vscode-docker.registries.deployImageToAci",
"when": "vscode-docker:newCliPresent"
},
{
"command": "vscode-docker.contexts.use",
"when": "!vscode-docker:contextLocked"
}
],
"editor/context": [
Expand Down Expand Up @@ -561,7 +565,7 @@
},
{
"command": "vscode-docker.contexts.use",
"when": "view == vscode-docker.views.dockerContexts && viewItem =~ /Context/i",
"when": "view == vscode-docker.views.dockerContexts && viewItem =~ /Context/i && !vscode-docker:contextLocked",
"group": "contexts_1_general@2"
},
{
Expand Down Expand Up @@ -2036,6 +2040,12 @@
"description": "%vscode-docker.config.docker.host%",
"scope": "machine-overridable"
},
"docker.context": {
"type": "string",
"default": "",
"description": "%vscode-docker.config.docker.context%",
"scope": "machine-overridable"
},
"docker.certPath": {
"type": "string",
"default": "",
Expand Down Expand Up @@ -2768,6 +2778,7 @@
"@azure/arm-containerregistry": "^8.0.0",
"@azure/storage-blob": "^12.2.1",
"@docker/sdk": "^0.1.15",
"@grpc/grpc-js": "^1.1.7",
"dockerfile-language-server-nodejs": "^0.1.1",
"dockerode": "^3.2.1",
"fs-extra": "^9.0.1",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@
"vscode-docker.config.docker.truncateMaxLength": "Maximum length of a registry paths displayed in Docker view, including elipsis. The truncateLongRegistryPaths setting must be set to true for truncateMaxLength setting to be effective.",
"vscode-docker.config.docker.dockerodeOptions": "If specified, this object will be passed to the Dockerode constructor. Takes precedence over DOCKER_HOST, the Docker Host setting, and any existing Docker contexts.",
"vscode-docker.config.docker.host": "Equivalent to setting the DOCKER_HOST environment variable.",
"vscode-docker.config.docker.context": "Equivalent to setting the DOCKER_CONTEXT environment variable.",
"vscode-docker.config.docker.certPath": "Equivalent to setting the DOCKER_CERT_PATH environment variable.",
"vscode-docker.config.docker.tlsVerify": "Equivalent to setting the DOCKER_TLS_VERIFY environment variable.",
"vscode-docker.config.docker.machineName": "Equivalent to setting the DOCKER_MACHINE_NAME environment variable.",
Expand Down
37 changes: 26 additions & 11 deletions src/docker/ContextManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const defaultContext: Partial<DockerContext> = {
};

// These contexts are used by external consumers (e.g. the "Remote - Containers" extension), and should NOT be changed
type VSCodeContext = 'vscode-docker:aciContext' | 'vscode-docker:newSdkContext' | 'vscode-docker:newCliPresent';
type VSCodeContext = 'vscode-docker:aciContext' | 'vscode-docker:newSdkContext' | 'vscode-docker:newCliPresent' | 'vscode-docker:contextLocked';

export interface ContextManager {
readonly onContextChanged: Event<DockerContext>;
Expand Down Expand Up @@ -122,7 +122,7 @@ export class DockerContextManager implements ContextManager, Disposable {
// But that probably won't be true in the future, so define both as separate concepts now
await this.setVsCodeContext('vscode-docker:aciContext', true);
await this.setVsCodeContext('vscode-docker:newSdkContext', true);
ext.dockerClient = new DockerServeClient();
ext.dockerClient = new DockerServeClient(currentContext);
} else {
await this.setVsCodeContext('vscode-docker:aciContext', false);
await this.setVsCodeContext('vscode-docker:newSdkContext', false);
Expand Down Expand Up @@ -180,34 +180,43 @@ export class DockerContextManager implements ContextManager, Disposable {

ext.treeInitError = undefined;

let dockerHost: string | undefined;
let dockerHostEnv: string | undefined;
let dockerContextEnv: string | undefined;
const config = workspace.getConfiguration('docker');
if ((dockerHost = config.get('host'))) { // Assignment + check is intentional
if ((dockerHostEnv = config.get('host'))) { // Assignment + check is intentional
actionContext.telemetry.properties.hostSource = 'docker.host';
} else if ((dockerHost = process.env.DOCKER_HOST)) { // Assignment + check is intentional
} else if ((dockerHostEnv = process.env.DOCKER_HOST)) { // Assignment + check is intentional
actionContext.telemetry.properties.hostSource = 'env';
} else if ((dockerContextEnv = config.get('context'))) { // Assignment + check is intentional
actionContext.telemetry.properties.hostSource = 'docker.context';
} else if ((dockerContextEnv = process.env.DOCKER_CONTEXT)) { // Assignment + check is intentional
actionContext.telemetry.properties.hostSource = 'envContext';
} else if (!(await fse.pathExists(dockerContextsFolder)) || (await fse.readdir(dockerContextsFolder)).length === 0) {
// If there's nothing inside ~/.docker/contexts/meta, then there's only the default, unmodifiable DOCKER_HOST-based context
// It is unnecessary to call `docker context inspect`
actionContext.telemetry.properties.hostSource = 'defaultContextOnly';
dockerHost = isWindows() ? WindowsLocalPipe : UnixLocalPipe;
dockerHostEnv = isWindows() ? WindowsLocalPipe : UnixLocalPipe;
} else {
dockerHost = undefined;
dockerHostEnv = undefined;
}

if (dockerHost !== undefined) {
actionContext.telemetry.properties.hostProtocol = new URL(dockerHost).protocol;
if (dockerHostEnv !== undefined) {
actionContext.telemetry.properties.hostProtocol = new URL(dockerHostEnv).protocol;
await this.setVsCodeContext('vscode-docker:contextLocked', true);

return [{
...defaultContext,
Current: true,
DockerEndpoint: dockerHost,
DockerEndpoint: dockerHostEnv,
} as DockerContext];
}

// No value for DOCKER_HOST, and multiple contexts exist, so check them
const result: DockerContext[] = [];
const { stdout } = await execAsync('docker context ls --format="{{json .}}"', ContextCmdExecOptions);

// Setting the DOCKER_CONTEXT environment variable to whatever is passed along means the CLI will always
// return that specified context as Current = true. This way we don't need extra logic below in parsing.
const { stdout } = await execAsync('docker context ls --format="{{json .}}"', { ...ContextCmdExecOptions, env: { ...process.env, DOCKER_CONTEXT: dockerContextEnv } });
const lines = stdout.split(/\r?\n/im);

for (const line of lines) {
Expand Down Expand Up @@ -242,6 +251,12 @@ export class DockerContextManager implements ContextManager, Disposable {
actionContext.telemetry.properties.hostProtocol = 'unknown';
}

if (dockerContextEnv) {
await this.setVsCodeContext('vscode-docker:contextLocked', true);
} else {
await this.setVsCodeContext('vscode-docker:contextLocked', false);
}

return result;
});

Expand Down
13 changes: 9 additions & 4 deletions src/docker/DockerServeClient/DockerServeClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,14 @@

import { Containers as ContainersClient } from '@docker/sdk';
import { DeleteRequest, InspectRequest, InspectResponse, ListRequest, ListResponse, StartRequest, StopRequest } from '@docker/sdk/containers';
import { Client as GrpcClient, Metadata } from '@grpc/grpc-js';
import { CancellationToken } from 'vscode';
import { IActionContext } from 'vscode-azureextensionui';
import { localize } from '../../localize';
import { DockerInfo, DockerOSType, PruneResult } from '../Common';
import { DockerContainer, DockerContainerInspection } from '../Containers';
import { ContextChangeCancelClient } from '../ContextChangeCancelClient';
import { DockerContext } from '../Contexts';
import { DockerApiClient } from '../DockerApiClient';
import { DockerImage, DockerImageInspection } from '../Images';
import { DockerNetwork, DockerNetworkInspection, DriverType } from '../Networks';
Expand All @@ -23,10 +25,13 @@ const dockerServeCallTimeout = 20 * 1000;

export class DockerServeClient extends ContextChangeCancelClient implements DockerApiClient {
private readonly containersClient: ContainersClient;
private readonly callMetadata: Metadata;

public constructor() {
public constructor(currentContext: DockerContext) {
super();
this.containersClient = new ContainersClient();
this.callMetadata = new Metadata();
this.callMetadata.add('context_key', currentContext.Name);
}

public dispose(): void {
Expand Down Expand Up @@ -168,14 +173,14 @@ export class DockerServeClient extends ContextChangeCancelClient implements Dock

private async promisify<TRequest, TResponse>(
context: IActionContext,
thisArg: unknown,
clientCallback: (req: TRequest, callback: (err: unknown, response: TResponse) => void) => unknown,
client: GrpcClient,
clientCallback: (req: TRequest, md: Metadata, callback: (err: unknown, response: TResponse) => void) => unknown,
request: TRequest,
token?: CancellationToken): Promise<TResponse> {

const callPromise: Promise<TResponse> = new Promise((resolve, reject) => {
try {
clientCallback.call(thisArg, request, (err, response) => {
clientCallback.call(client, request, this.callMetadata, (err, response) => {
if (err) {
reject(err);
}
Expand Down
1 change: 1 addition & 0 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,7 @@ namespace Configuration {
// These settings will result in a need to change context that doesn't actually change the docker context
// So, force a manual refresh so the settings get picked up
if (e.affectsConfiguration('docker.host') ||
e.affectsConfiguration('docker.context') ||
e.affectsConfiguration('docker.certPath') ||
e.affectsConfiguration('docker.tlsVerify') ||
e.affectsConfiguration('docker.machineName') ||
Expand Down
1 change: 1 addition & 0 deletions src/utils/addDockerSettingsToEnv.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { localize } from '../localize';

export function addDockerSettingsToEnv(env: NodeJS.ProcessEnv, oldEnv: NodeJS.ProcessEnv): void {
addDockerSettingToEnv("host", 'DOCKER_HOST', env, oldEnv);
addDockerSettingToEnv("context", 'DOCKER_CONTEXT', env, oldEnv);
addDockerSettingToEnv("certPath", 'DOCKER_CERT_PATH', env, oldEnv);
addDockerSettingToEnv("tlsVerify", 'DOCKER_TLS_VERIFY', env, oldEnv);
addDockerSettingToEnv("machineName", 'DOCKER_MACHINE_NAME', env, oldEnv);
Expand Down

0 comments on commit cb6c661

Please sign in to comment.