Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support private registries #381

Merged
merged 18 commits into from
Aug 24, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we expect tests to run multiple things from the same terminal? I'm wondering if this should be in the dispose method instead

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, I'm currently running multiple commands sometimes in tests. It's also possible we might add a setting to allow the user to do that eventually. I think this is safer and more flexible.

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