Skip to content

Commit

Permalink
Support private registries (#381)
Browse files Browse the repository at this point in the history
* Support private registries

* Password flag

* headers:

* Fix test

* increase timeout

* test fixes

* test fixes

* comment

* mock keytar

* test fixes

* test fixes

* test fixes

* PR fixes

* Fix

* Fix test

* Increase build tests timeout
  • Loading branch information
StephenWeatherford authored Aug 24, 2018
1 parent 9c3f873 commit 2d9e62b
Show file tree
Hide file tree
Showing 28 changed files with 1,021 additions and 290 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ out
node_modules
*.vsix
package-lock.json
.nyc_output/**
coverage/**

# Artifacts from running vscode extension tests
.vscode-test
Expand Down
2 changes: 1 addition & 1 deletion commands/system-prune.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
*--------------------------------------------------------------------------------------------*/

import vscode = require('vscode');
import { getCoreNodeModule } from '../explorer/utils/utils';
import { ext } from '../extensionVariables';
import { reporter } from '../telemetry/telemetry';
import { getCoreNodeModule } from '../utils/getCoreNodeModule';
import { docker } from './utils/docker-endpoint';

const teleCmdId: string = 'vscode-docker.system.prune';
Expand Down
102 changes: 92 additions & 10 deletions commands/utils/TerminalProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ export class DefaultTerminalProvider {
export class TestTerminalProvider {
private _currentTerminal: TestTerminal;

public createTerminal(name: string): Terminal {
public createTerminal(name: string): TestTerminal {
let terminal = new DefaultTerminalProvider().createTerminal(name);
let testTerminal = new TestTerminal(terminal);
this._currentTerminal = testTerminal;
Expand All @@ -47,11 +47,13 @@ export class TestTerminalProvider {
}

class TestTerminal implements vscode.Terminal {
private static _lastSuffix: number = 1;

private _outputFilePath: string;
private _errFilePath: string;
private _semaphorePath: string;
private _suffix: number;
private static _lastSuffix: number = 1;
private _disposed: boolean;

constructor(private _terminal: vscode.Terminal) {
let root = vscode.workspace.rootPath || os.tmpdir();
Expand All @@ -63,28 +65,96 @@ class TestTerminal implements vscode.Terminal {
}

/**
* Causes the terminal to exit after completing the current command, and returns the
* Causes the terminal to exit after completing the current commands, and returns the
* redirected standard and error output.
*/
public async exit(): Promise<{ errorText: string, outputText: string }> {
this.ensureNotDisposed();
let results = await this.waitForCompletion();
this.hide();
this.dispose();
return results;
}

/**
* Causes the terminal to wait for completion of the current commands, and returns the
* redirected standard and error output since the last call.
*/
public async waitForCompletion(): Promise<{ errorText: string, outputText: string }> {
return this.waitForCompletionCore();
}

private async waitForCompletionCore(options: { ignoreErrors?: boolean } = {}): Promise<{ errorText: string, outputText: string }> {
this.ensureNotDisposed();
console.log('Waiting for terminal command completion...');

// Output text to a semaphore file. This will execute when the terminal is no longer busy.
this.sendTextRaw(`echo Done > ${this._semaphorePath}`);

// Wait for the semaphore file
await this.waitForFileCreation(this._semaphorePath);

assert(await fse.pathExists(this._outputFilePath), 'The output file from the command was not created.');
let output = bufferToString(await fse.readFile(this._outputFilePath));
assert(await fse.pathExists(this._outputFilePath), 'The output file from the command was not created. Sometimes this can mean the command to execute was not found.');
let outputText = bufferToString(await fse.readFile(this._outputFilePath));

assert(await fse.pathExists(this._errFilePath), 'The error file from the command was not created.');
let err = bufferToString(await fse.readFile(this._errFilePath));
let errorText = bufferToString(await fse.readFile(this._errFilePath));

console.log("OUTPUT:");
console.log(outputText ? outputText : '(NONE)');
console.log("END OF OUTPUT");

if (errorText) {
if (options.ignoreErrors) {
// console.log("ERROR OUTPUT (IGNORED):");
// console.log(errorText.replace(/\r/, "\rIGNORED: "));
// console.log("END OF ERROR OUTPUT (IGNORED)");
} else {
console.log("ERRORS:");
console.log(errorText.replace(/\r/, "\rERROR: "));
console.log("END OF ERRORS");
}
}

// Remove files in preparation for next commands, if any
await fse.remove(this._semaphorePath);
await fse.remove(this._outputFilePath);
await fse.remove(this._errFilePath);

return { outputText: outputText, errorText: errorText };
}

return { outputText: output, errorText: err };
/**
* Executes one or more commands and waits for them to complete. Returns stdout output and
* throws if there is output to stdout.
*/
public async execute(commands: string | string[], options: { ignoreErrors?: boolean } = {}): Promise<string> {
if (typeof commands === 'string') {
commands = [commands];
}

this.show();
for (let command of commands) {
this.sendText(command);
}

let results = await this.waitForCompletionCore(options);

if (!options.ignoreErrors) {
assert.equal(results.errorText, '', `Encountered errors executing in terminal`);
}

return results.outputText;
}

public get name(): string { return this._terminal.name; }
public get name(): string {
this.ensureNotDisposed(); return this._terminal.name;
}

public get processId(): Thenable<number> { return this._terminal.processId; }
public get processId(): Thenable<number> {
this.ensureNotDisposed();
return this._terminal.processId;
}

private async waitForFileCreation(filePath: string): Promise<void> {
return new Promise<void>((resolve, _reject) => {
Expand All @@ -98,10 +168,15 @@ class TestTerminal implements vscode.Terminal {
});
}

/**
* Sends text to the terminal, does not wait for completion
*/
public sendText(text: string, addNewLine?: boolean): void {
this.ensureNotDisposed();
console.log(`Executing in terminal: ${text}`);
if (addNewLine !== false) {
// Redirect the output and error output to files (not a perfect solution, but it works)
text += ` >${this._outputFilePath} 2>${this._errFilePath}`;
text += ` >>${this._outputFilePath} 2>>${this._errFilePath}`;
}
this.sendTextRaw(text, addNewLine);
}
Expand All @@ -111,16 +186,23 @@ class TestTerminal implements vscode.Terminal {
}

public show(preserveFocus?: boolean): void {
this.ensureNotDisposed();
this._terminal.show(preserveFocus);
}

public hide(): void {
this.ensureNotDisposed();
this._terminal.hide();
}

public dispose(): void {
this._disposed = true;
this._terminal.dispose();
}

private ensureNotDisposed(): void {
assert(!this._disposed, 'Terminal has already been disposed.');
}
}

function bufferToString(buffer: Buffer): string {
Expand Down
3 changes: 3 additions & 0 deletions constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
export const MAX_CONCURRENT_REQUESTS = 8;
export const MAX_CONCURRENT_SUBSCRIPTON_REQUESTS = 5;

// Consider downloading multiple pages (images, tags, etc)
export const PAGE_SIZE = 100;

export namespace keytarConstants {
export const serviceId: string = 'vscode-docker';

Expand Down
42 changes: 25 additions & 17 deletions dockerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,14 +35,16 @@ import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper';
import * as util from "./explorer/deploy/util";
import { WebAppCreator } from './explorer/deploy/webAppCreator';
import { DockerExplorerProvider } from './explorer/dockerExplorer';
import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes';
import { DockerHubImageNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes';
import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes';
import { connectCustomRegistry, disconnectCustomRegistry } from './explorer/models/customRegistries';
import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes';
import { browseAzurePortal } from './explorer/utils/azureUtils';
import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils';
import { ext } from "./extensionVariables";
import { initializeTelemetryReporter, reporter } from './telemetry/telemetry';
import { AzureAccount } from './typings/azure-account.api';
import { AzureUtilityManager } from './utils/azureUtilityManager';
import { Keytar } from './utils/keytar';

export const FROM_DIRECTIVE_PATTERN = /^\s*FROM\s*([\w-\/:]*)(\s*AS\s*[a-z][a-z0-9-_\\.]*)?$/i;
export const COMPOSE_FILE_GLOB_PATTERN = '**/[dD]ocker-[cC]ompose*.{yaml,yml}';
Expand All @@ -64,25 +66,29 @@ const DOCUMENT_SELECTOR: DocumentSelector = [
{ language: 'dockerfile', scheme: 'file' }
];

// tslint:disable-next-line:max-func-body-length
export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
const installedExtensions: any[] = vscode.extensions.all;
const outputChannel = util.getOutputChannel();
let azureAccount: AzureAccount | undefined;

// Set up extension variables
function initializeExtensionVariables(ctx: vscode.ExtensionContext): void {
registerUIExtensionVariables(ext);
if (!ext.ui) {
// This allows for standard interactions with the end user (as opposed to test input)
ext.ui = new AzureUserInput(ctx.globalState);
}
ext.context = ctx;
ext.outputChannel = outputChannel;
ext.outputChannel = util.getOutputChannel();
if (!ext.terminalProvider) {
ext.terminalProvider = new DefaultTerminalProvider();
}
initializeTelemetryReporter(createTelemetryReporter(ctx));
ext.reporter = reporter;
if (!ext.keytar) {
ext.keytar = Keytar.tryCreate();
}
}

export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
const installedExtensions: any[] = vscode.extensions.all;
let azureAccount: AzureAccount | undefined;

initializeExtensionVariables(ctx);

// tslint:disable-next-line:prefer-for-of // Grandfathered in
for (let i = 0; i < installedExtensions.length; i++) {
Expand Down Expand Up @@ -131,11 +137,11 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
registerCommand('vscode-docker.compose.down', composeDown);
registerCommand('vscode-docker.compose.restart', composeRestart);
registerCommand('vscode-docker.system.prune', systemPrune);
registerCommand('vscode-docker.createWebApp', async (node?: AzureImageNode | DockerHubImageNode) => {
if (node) {
registerCommand('vscode-docker.createWebApp', async (context?: AzureImageTagNode | DockerHubImageTagNode) => {
if (context) {
if (azureAccount) {
const azureAccountWrapper = new AzureAccountWrapper(ctx, azureAccount);
const wizard = new WebAppCreator(outputChannel, azureAccountWrapper, node);
const wizard = new WebAppCreator(ext.outputChannel, azureAccountWrapper, context);
const result = await wizard.run();
if (result.status === 'Faulted') {
throw result.error;
Expand All @@ -152,12 +158,14 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
}
});
registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout);
registerCommand('vscode-docker.browseDockerHub', (node?: DockerHubImageNode | DockerHubRepositoryNode | DockerHubOrgNode) => {
browseDockerHub(node);
registerCommand('vscode-docker.browseDockerHub', (context?: DockerHubImageTagNode | DockerHubRepositoryNode | DockerHubOrgNode) => {
browseDockerHub(context);
});
registerCommand('vscode-docker.browseAzurePortal', (node?: AzureRegistryNode | AzureRepositoryNode | AzureImageNode) => {
browseAzurePortal(node);
registerCommand('vscode-docker.browseAzurePortal', (context?: AzureRegistryNode | AzureRepositoryNode | AzureImageTagNode) => {
browseAzurePortal(context);
});
registerCommand('vscode-docker.connectCustomRegistry', connectCustomRegistry);
registerCommand('vscode-docker.disconnectCustomRegistry', disconnectCustomRegistry);

ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider()));

Expand Down
16 changes: 10 additions & 6 deletions explorer/deploy/webAppCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,17 @@ import WebSiteManagementClient = require('azure-arm-website');
import * as WebSiteModels from 'azure-arm-website/lib/models';
import * as vscode from 'vscode';
import { reporter } from '../../telemetry/telemetry';
import { AzureImageNode } from '../models/azureRegistryNodes';
import { DockerHubImageNode } from '../models/dockerHubNodes';
import { AzureImageTagNode } from '../models/azureRegistryNodes';
import { CustomImageTagNode } from '../models/customRegistryNodes';
import { DockerHubImageTagNode } from '../models/dockerHubNodes';
import { AzureAccountWrapper } from './azureAccountWrapper';
import * as util from './util';
import { QuickPickItemWithData, SubscriptionStepBase, UserCancelledError, WizardBase, WizardResult, WizardStep } from './wizard';

const teleCmdId: string = 'vscode-docker.deploy.azureAppService';

export class WebAppCreator extends WizardBase {
constructor(output: vscode.OutputChannel, readonly azureAccount: AzureAccountWrapper, context: AzureImageNode | DockerHubImageNode, subscription?: SubscriptionModels.Subscription) {
constructor(output: vscode.OutputChannel, readonly azureAccount: AzureAccountWrapper, context: AzureImageTagNode | DockerHubImageTagNode, subscription?: SubscriptionModels.Subscription) {
super(output);
this.steps.push(new SubscriptionStep(this, azureAccount, subscription));
this.steps.push(new ResourceGroupStep(this, azureAccount));
Expand Down Expand Up @@ -421,16 +422,19 @@ class WebsiteStep extends WebAppCreatorStepBase {
private _imageSubscription: Subscription;
private _registry: Registry;

constructor(wizard: WizardBase, azureAccount: AzureAccountWrapper, context: AzureImageNode | DockerHubImageNode) {
constructor(wizard: WizardBase, azureAccount: AzureAccountWrapper, context: AzureImageTagNode | DockerHubImageTagNode | CustomImageTagNode) {
super(wizard, 'Create Web App', azureAccount);

this._serverUrl = context.serverUrl;
if (context instanceof DockerHubImageNode) {
if (context instanceof DockerHubImageTagNode) {
this._serverPassword = context.password;
this._serverUserName = context.userName;
} else if (context instanceof AzureImageNode) {
} else if (context instanceof AzureImageTagNode) {
this._imageSubscription = context.subscription;
this._registry = context.registry;
} else if (context instanceof CustomImageTagNode) {
this._serverPassword = context.registry.credentials.password;
this._serverUserName = context.registry.credentials.userName;
} else {
throw Error(`Invalid context, cannot deploy to Azure App services from ${context}`);
}
Expand Down
Loading

0 comments on commit 2d9e62b

Please sign in to comment.