add support for Registries
This commit is contained in:
Chris Dias 2017-10-10 10:12:19 -07:00 коммит произвёл GitHub
Родитель 5d02087ec7
Коммит 4a0934caf5
37 изменённых файлов: 2307 добавлений и 295 удалений

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

@ -1,6 +1,10 @@
## 0.0.19 - 24 Sept 2017
## 0.0.19 - 10 Oct 2017
* Add an automatic refresh option for the explorer (`"docker.explorerRefreshInterval": 1000`)
* Add support fro Multi-Root Workspaces
* Add support for DockerHub and Azure Container Registries
* `docker-compose` now runs detached and always invokes a build (e.g. `docker-compose -f docker-compose.yml -d --build`)
* `docker system prune` command no longer prompts for confirmation
## 0.0.18 - 18 Sept 2017

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

@ -1,9 +1,9 @@
import { DockerNode } from "../explorer/dockerExplorer";
import { ImageNode } from "../explorer/models/imageNode";
import DockerInspectDocumentContentProvider from "../documentContentProviders/dockerInspect";
import { quickPickImage } from "./utils/quick-pick-image";
import { reporter } from "../telemetry/telemetry";
export default async function inspectImage(context?: DockerNode) {
export default async function inspectImage(context?: ImageNode) {
let imageToInspect: Docker.ImageDesc;

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

@ -1,7 +1,7 @@
import * as vscode from 'vscode';
import { ContainerItem, quickPickContainer } from './utils/quick-pick-container';
import { DockerEngineType, docker } from './utils/docker-endpoint';
import { DockerNode } from '../explorer/dockerExplorer';
import { ContainerNode } from '../explorer/models/containerNode';
import { reporter } from '../telemetry/telemetry';
const teleCmdId: string = 'vscode-docker.container.open-shell';
@ -10,7 +10,7 @@ const engineTypeShellCommands = {
[DockerEngineType.Windows]: "powershell"
}
export async function openShellContainer(context?: DockerNode) {
export async function openShellContainer(context?: ContainerNode) {
let containerToAttach: Docker.ContainerDesc;
if (context && context.containerDesc) {

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

@ -1,10 +1,10 @@
import vscode = require('vscode');
import { ImageItem, quickPickImage } from './utils/quick-pick-image';
import { reporter } from '../telemetry/telemetry';
import { DockerNode } from '../explorer/dockerExplorer';
import { ImageNode } from '../explorer/models/imageNode';
const teleCmdId: string = 'vscode-docker.image.push';
export async function pushImage(context?: DockerNode) {
export async function pushImage(context?: ImageNode) {
let imageToPush: Docker.ImageDesc;
let imageName: string = "";

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

@ -1,13 +1,13 @@
import vscode = require('vscode');
import { docker } from './utils/docker-endpoint';
import { reporter } from '../telemetry/telemetry';
import { DockerNode } from '../explorer/dockerExplorer';
import { ContainerNode } from '../explorer/models/containerNode';
import { dockerExplorerProvider } from '../dockerExtension';
import { ContainerItem, quickPickContainer } from './utils/quick-pick-container';
const teleCmdId: string = 'vscode-docker.container.remove';
export async function removeContainer(context?: DockerNode) {
export async function removeContainer(context?: ContainerNode) {
let containersToRemove: Docker.ContainerDesc[];

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

@ -2,12 +2,12 @@ import { docker } from './utils/docker-endpoint';
import { ImageItem, quickPickImage } from './utils/quick-pick-image';
import vscode = require('vscode');
import { reporter } from '../telemetry/telemetry';
import { DockerNode } from '../explorer/dockerExplorer';
import { ImageNode } from "../explorer/models/imageNode";
import { dockerExplorerProvider } from '../dockerExtension';
const teleCmdId: string = 'vscode-docker.image.remove';
export async function removeImage(context?: DockerNode) {
export async function removeImage(context?: ImageNode) {
let imagesToRemove: Docker.ImageDesc[];
@ -34,11 +34,9 @@ export async function removeImage(context?: DockerNode) {
imageCounter++;
if (err) {
vscode.window.showErrorMessage(err.message);
dockerExplorerProvider.refreshImages();
reject();
}
if (imageCounter === numImages) {
dockerExplorerProvider.refreshImages();
resolve();
}
});

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

@ -1,10 +1,10 @@
import vscode = require('vscode');
import { ContainerItem, quickPickContainer } from './utils/quick-pick-container';
import { DockerNode } from '../explorer/dockerExplorer';
import { ContainerNode } from '../explorer/models/containerNode';
import { reporter } from '../telemetry/telemetry';
const teleCmdId: string = 'vscode-docker.container.show-logs';
export async function showLogsContainer(context?: DockerNode) {
export async function showLogsContainer(context?: ContainerNode) {
let containerToLog: Docker.ContainerDesc;

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

@ -4,11 +4,11 @@ import { DockerEngineType, docker } from './utils/docker-endpoint';
import * as cp from 'child_process';
import os = require('os');
import { reporter } from '../telemetry/telemetry';
import { DockerNode } from '../explorer/dockerExplorer';
import { ImageNode } from '../explorer/models/imageNode';
const teleCmdId: string = 'vscode-docker.container.start';
export async function startContainer(context?:DockerNode, interactive?: boolean) {
export async function startContainer(context?: ImageNode, interactive?: boolean) {
let imageName: string;
let imageToStart: Docker.ImageDesc;
@ -44,7 +44,7 @@ export async function startContainer(context?:DockerNode, interactive?: boolean)
}
}
export async function startContainerInteractive(context: DockerNode) {
export async function startContainerInteractive(context: ImageNode) {
await startContainer(context, true);
}

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

@ -1,14 +1,14 @@
import { docker } from './utils/docker-endpoint';
import { ContainerItem, quickPickContainer } from './utils/quick-pick-container';
import { reporter } from '../telemetry/telemetry';
import { DockerNode } from '../explorer/dockerExplorer';
import { ContainerNode } from '../explorer/models/containerNode';
import { dockerExplorerProvider } from '../dockerExtension';
import vscode = require('vscode');
const teleCmdId: string = 'vscode-docker.container.stop';
export async function stopContainer(context?: DockerNode) {
export async function stopContainer(context?: ContainerNode) {
let containersToStop: Docker.ContainerDesc[];

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

@ -2,11 +2,11 @@ import vscode = require('vscode');
import { ImageItem, quickPickImage } from './utils/quick-pick-image';
import { docker } from './utils/docker-endpoint';
import { reporter } from '../telemetry/telemetry';
import { DockerNode } from '../explorer/dockerExplorer';
import { ImageNode } from "../explorer/models/imageNode";
const teleCmdId: string = 'vscode-docker.image.tag';
export async function tagImage(context?: DockerNode) {
export async function tagImage(context?: ImageNode) {
let imageName: string;
let imageToTag: Docker.ImageDesc;

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

@ -26,6 +26,12 @@ import DockerInspectDocumentContentProvider, { SCHEME as DOCKER_INSPECT_SCHEME }
import { DockerExplorerProvider } from './explorer/dockerExplorer';
import { removeContainer } from './commands/remove-container';
import { LanguageClient, LanguageClientOptions, SettingMonitor, ServerOptions, TransportKind } from 'vscode-languageclient';
import { WebAppCreator } from './explorer/deploy/webAppCreator';
import { AzureImageNode } from './explorer/models/azureRegistryNodes';
import { DockerHubImageNode } from './explorer/models/dockerHubNodes';
import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper';
import * as util from "./explorer/deploy/util";
import { dockerHubLogout } from './explorer/models/dockerHubUtils';
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}';
@ -47,6 +53,8 @@ export function activate(ctx: vscode.ExtensionContext): void {
ctx.subscriptions.push(new Reporter(ctx));
const outputChannel = util.getOutputChannel();
const azureAccount = new AzureAccountWrapper(ctx);
dockerExplorerProvider = new DockerExplorerProvider();
vscode.window.registerTreeDataProvider('dockerExplorer', dockerExplorerProvider);
@ -80,6 +88,13 @@ export function activate(ctx: vscode.ExtensionContext): void {
ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.down', composeDown));
ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.system.prune', systemPrune));
ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => {
const wizard = new WebAppCreator(outputChannel, azureAccount, context);
const result = await wizard.run();
}));
ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout));
activateLanguageClient(ctx);
}

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

@ -0,0 +1,105 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ExtensionContext, Extension, extensions, Disposable } from 'vscode';
import { ServiceClientCredentials } from 'ms-rest';
import { AzureEnvironment } from 'ms-rest-azure';
import { SubscriptionClient, SubscriptionModels } from 'azure-arm-resource';
import { AzureAccount, AzureSession, AzureLoginStatus } from '../../typings/azure-account.api';
import * as util from './util';
export class NotSignedInError extends Error { }
export class CredentialError extends Error { }
export class AzureAccountWrapper {
readonly accountApi: AzureAccount;
constructor(readonly extensionConext: ExtensionContext) {
this.accountApi = extensions.getExtension<AzureAccount>('ms-vscode.azure-account')!.exports;
}
getAzureSessions(): AzureSession[] {
const status = this.signInStatus;
if (status !== 'LoggedIn') {
throw new NotSignedInError(status)
}
return this.accountApi.sessions;
}
getCredentialByTenantId(tenantId: string): ServiceClientCredentials {
const session = this.getAzureSessions().find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase());
if (session) {
return session.credentials;
}
throw new CredentialError(`Failed to get credential, tenant ${tenantId} not found.`);
}
get signInStatus(): AzureLoginStatus {
return this.accountApi.status;
}
getFilteredSubscriptions(): SubscriptionModels.Subscription[] {
return this.accountApi.filters.map<SubscriptionModels.Subscription>(filter => {
return {
id: filter.subscription.id,
subscriptionId: filter.subscription.subscriptionId,
tenantId: filter.session.tenantId,
displayName: filter.subscription.displayName,
state: filter.subscription.state,
subscriptionPolicies: filter.subscription.subscriptionPolicies,
authorizationSource: filter.subscription.authorizationSource
};
});
}
async getAllSubscriptions(): Promise<SubscriptionModels.Subscription[]> {
const tasks = new Array<Promise<SubscriptionModels.Subscription[]>>();
this.getAzureSessions().forEach((s, i, array) => {
const client = new SubscriptionClient(s.credentials);
const tenantId = s.tenantId;
tasks.push(util.listAll(client.subscriptions, client.subscriptions.list()).then(result => {
return result.map<SubscriptionModels.Subscription>((value) => {
// The list() API doesn't include tenantId information in the subscription object,
// however many places that uses subscription objects will be needing it, so we just create
// a copy of the subscription object with the tenantId value.
return {
id: value.id,
subscriptionId: value.subscriptionId,
tenantId: tenantId,
displayName: value.displayName,
state: value.state,
subscriptionPolicies: value.subscriptionPolicies,
authorizationSource: value.authorizationSource
};
});
}));
});
const results = await Promise.all(tasks);
const subscriptions = new Array<SubscriptionModels.Subscription>();
results.forEach((result) => result.forEach((subscription) => subscriptions.push(subscription)));
return subscriptions;
}
async getLocationsBySubscription(subscription: SubscriptionModels.Subscription): Promise<SubscriptionModels.Location[]> {
const credential = this.getCredentialByTenantId(subscription.tenantId);
const client = new SubscriptionClient(credential);
const locations = <SubscriptionModels.Location[]>(await client.subscriptions.listLocations(subscription.subscriptionId));
return locations;
}
registerSessionsChangedListener(listener: (e: void) => any, thisArg: any): Disposable {
return this.accountApi.onSessionsChanged(listener, thisArg, this.extensionConext.subscriptions);
}
registerFiltersChangedListener(listener: (e: void) => any, thisArg: any): Disposable {
return this.accountApi.onFiltersChanged(listener, thisArg, this.extensionConext.subscriptions);
}
}

63
explorer/deploy/util.ts Normal file
Просмотреть файл

@ -0,0 +1,63 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { ServiceClientCredentials } from 'ms-rest';
import { SubscriptionModels } from 'azure-arm-resource';
import { AzureAccountWrapper } from './azureAccountWrapper';
import WebSiteManagementClient = require('azure-arm-website');
import * as vscode from 'vscode';
import * as WebSiteModels from '../../node_modules/azure-arm-website/lib/models';
export interface PartialList<T> extends Array<T> {
nextLink?: string;
}
export async function listAll<T>(client: { listNext(nextPageLink: string): Promise<PartialList<T>>; }, first: Promise<PartialList<T>>): Promise<T[]> {
const all: T[] = [];
for (let list = await first; list.length || list.nextLink; list = list.nextLink ? await client.listNext(list.nextLink) : []) {
all.push(...list);
}
return all;
}
export function waitForWebSiteState(webSiteManagementClient: WebSiteManagementClient, site: WebSiteModels.Site, state: string, intervalMs = 5000, timeoutMs = 60000): Promise<void> {
return new Promise((resolve, reject) => {
const func = async (count: number) => {
const currentSite = await webSiteManagementClient.webApps.get(site.resourceGroup, site.name);
if (currentSite.state.toLowerCase() === state.toLowerCase()) {
resolve();
} else {
count += intervalMs;
if (count < timeoutMs) {
setTimeout(func, intervalMs, count);
} else {
reject(new Error(`Timeout waiting for Web Site "${site.name}" state "${state}".`));
}
}
};
setTimeout(func, intervalMs, intervalMs);
});
}
export function getSignInCommandString(): string {
return 'azure-account.login';
}
export function getWebAppPublishCredential(azureAccount: AzureAccountWrapper, subscription: SubscriptionModels.Subscription, site: WebSiteModels.Site): Promise<WebSiteModels.User> {
const credentials = azureAccount.getCredentialByTenantId(subscription.tenantId);
const websiteClient = new WebSiteManagementClient(credentials, subscription.subscriptionId);
return websiteClient.webApps.listPublishingCredentials(site.resourceGroup, site.name);
}
// Output channel for the extension
const outputChannel = vscode.window.createOutputChannel("Azure App Service");
export function getOutputChannel(): vscode.OutputChannel {
return outputChannel;
}

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

@ -0,0 +1,614 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { AzureAccountWrapper } from './azureAccountWrapper';
import { WizardBase, WizardResult, WizardStep, SubscriptionStepBase, QuickPickItemWithData, UserCancelledError } from './wizard';
import { SubscriptionModels, ResourceManagementClient, ResourceModels } from 'azure-arm-resource';
import WebSiteManagementClient = require('azure-arm-website');
import * as WebSiteModels from '../../node_modules/azure-arm-website/lib/models';
import * as util from './util';
import { AzureImageNode } from '../models/azureRegistryNodes';
import { DockerHubImageNode } from '../models/dockerHubNodes';
import * as path from 'path';
import * as fs from 'fs';
export class WebAppCreator extends WizardBase {
constructor(output: vscode.OutputChannel, readonly azureAccount: AzureAccountWrapper, context: AzureImageNode | DockerHubImageNode, subscription?: SubscriptionModels.Subscription) {
super(output);
this.steps.push(new SubscriptionStep(this, azureAccount, subscription));
this.steps.push(new ResourceGroupStep(this, azureAccount));
this.steps.push(new AppServicePlanStep(this, azureAccount));
this.steps.push(new WebsiteStep(this, azureAccount, context));
this.steps.push(new ShellScriptStep(this, azureAccount));
}
async run(promptOnly = false): Promise<WizardResult> {
// If not signed in, execute the sign in command and wait for it...
if (this.azureAccount.signInStatus !== 'LoggedIn') {
await vscode.commands.executeCommand(util.getSignInCommandString());
}
// Now check again, if still not signed in, cancel.
if (this.azureAccount.signInStatus !== 'LoggedIn') {
return {
status: 'Cancelled',
step: this.steps[0],
error: null
};
}
return super.run(promptOnly);
}
get createdWebSite(): WebSiteModels.Site {
const websiteStep = this.steps.find(step => step instanceof WebsiteStep);
return (<WebsiteStep>websiteStep).website;
}
protected beforeExecute(step: WizardStep, stepIndex: number) {
if (stepIndex == 0) {
this.writeline('Start creating new Web App...');
}
}
protected onExecuteError(step: WizardStep, stepIndex: number, error: Error) {
if (error instanceof UserCancelledError) {
return;
}
this.writeline(`Failed to create new Web App - ${error.message}`);
this.writeline('');
}
}
class WebAppCreatorStepBase extends WizardStep {
protected constructor(wizard: WizardBase, stepTitle: string, readonly azureAccount: AzureAccountWrapper) {
super(wizard, stepTitle);
}
protected getSelectedSubscription(): SubscriptionModels.Subscription {
const subscriptionStep = <SubscriptionStep>this.wizard.findStep(step => step instanceof SubscriptionStep, 'The Wizard must have a SubscriptionStep.');
if (!subscriptionStep.subscription) {
throw new Error('A subscription must be selected first.');
}
return subscriptionStep.subscription;
}
protected getSelectedResourceGroup(): ResourceModels.ResourceGroup {
const resourceGroupStep = <ResourceGroupStep>this.wizard.findStep(step => step instanceof ResourceGroupStep, 'The Wizard must have a ResourceGroupStep.');
if (!resourceGroupStep.resourceGroup) {
throw new Error('A resource group must be selected first.');
}
return resourceGroupStep.resourceGroup;
}
protected getSelectedAppServicePlan(): WebSiteModels.AppServicePlan {
const appServicePlanStep = <AppServicePlanStep>this.wizard.findStep(step => step instanceof AppServicePlanStep, 'The Wizard must have a AppServicePlanStep.');
if (!appServicePlanStep.servicePlan) {
throw new Error('An App Service Plan must be selected first.');
}
return appServicePlanStep.servicePlan;
}
protected getWebSite(): WebSiteModels.Site {
const websiteStep = <WebsiteStep>this.wizard.findStep(step => step instanceof WebsiteStep, 'The Wizard must have a WebsiteStep.');
if (!websiteStep.website) {
throw new Error('A website must be created first.');
}
return websiteStep.website;
}
protected getImageInfo(): { serverUrl: string, serverUser: string, serverPassword: string} {
const websiteStep = <WebsiteStep>this.wizard.findStep(step => step instanceof WebsiteStep, 'The Wizard must have a WebsiteStep.');
if (!websiteStep.website) {
throw new Error('A website must be created first.');
}
return websiteStep.imageInfo;
}
}
class SubscriptionStep extends SubscriptionStepBase {
constructor(wizard: WizardBase, azureAccount: AzureAccountWrapper, subscrption?: SubscriptionModels.Subscription) {
super(wizard, 'Select subscription', azureAccount);
this._subscription = subscrption;
}
async prompt(): Promise<void> {
if (!!this.subscription) {
return;
}
const quickPickItems = await this.getSubscriptionsAsQuickPickItems();
const quickPickOptions = { placeHolder: `Select the subscription where the new Web App will be created in. (${this.stepProgressText})` };
const result = await this.showQuickPick(quickPickItems, quickPickOptions);
this._subscription = result.data;
}
async execute(): Promise<void> {
this.wizard.writeline(`The new Web App will be created in subscription "${this.subscription.displayName}" (${this.subscription.subscriptionId}).`);
}
}
class ResourceGroupStep extends WebAppCreatorStepBase {
private _createNew: boolean;
private _rg: ResourceModels.ResourceGroup;
constructor(wizard: WizardBase, azureAccount: AzureAccountWrapper) {
super(wizard, 'Select or create resource group', azureAccount);
}
async prompt(): Promise<void> {
const createNewItem: QuickPickItemWithData<ResourceModels.ResourceGroup> = {
label: '$(plus) Create New Resource Group',
description: '',
data: null
};
const quickPickItems = [createNewItem];
const quickPickOptions = { placeHolder: `Select the resource group where the new Web App will be created in. (${this.stepProgressText})` };
const subscription = this.getSelectedSubscription();
const resourceClient = new ResourceManagementClient(this.azureAccount.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId);
var resourceGroups: ResourceModels.ResourceGroup[];
var locations: SubscriptionModels.Location[];
const resourceGroupsTask = util.listAll(resourceClient.resourceGroups, resourceClient.resourceGroups.list());
const locationsTask = this.azureAccount.getLocationsBySubscription(this.getSelectedSubscription());
await Promise.all([resourceGroupsTask, locationsTask]).then(results => {
resourceGroups = results[0];
locations = results[1];
resourceGroups.forEach(rg => {
quickPickItems.push({
label: rg.name,
description: `(${locations.find(l => l.name.toLowerCase() === rg.location.toLowerCase()).displayName})`,
detail: '',
data: rg
});
});
});
const result = await this.showQuickPick(quickPickItems, quickPickOptions);
if (result !== createNewItem) {
const rg = result.data;
this._createNew = false;
this._rg = rg;
return;
}
const newRgName = await this.showInputBox({
prompt: 'Enter the name of the new resource group.',
validateInput: (value: string) => {
value = value.trim();
if (resourceGroups.findIndex(rg => rg.name.localeCompare(value) === 0) >= 0) {
return `Resource group name "${value}" already exists.`;
}
if (!value.match(/^[a-z0-9.\-_()]{0,89}[a-z0-9\-_()]$/ig)) {
return 'Resource group name should be 1-90 characters long and can only include alphanumeric characters, periods, ' +
'underscores, hyphens and parenthesis and cannot end in a period.';
}
return null;
}
});
const locationPickItems = locations.map<QuickPickItemWithData<SubscriptionModels.Location>>(location => {
return {
label: location.displayName,
description: `(${location.name})`,
detail: '',
data: location
};
});
const locationPickOptions = { placeHolder: 'Select the location of the new resource group.' };
const pickedLocation = await this.showQuickPick(locationPickItems, locationPickOptions);
this._createNew = true;
this._rg = {
name: newRgName,
location: pickedLocation.data.name
}
}
async execute(): Promise<void> {
if (!this._createNew) {
this.wizard.writeline(`Existing resource group "${this._rg.name} (${this._rg.location})" will be used.`);
return;
}
this.wizard.writeline(`Creating new resource group "${this._rg.name} (${this._rg.location})"...`);
const subscription = this.getSelectedSubscription();
const resourceClient = new ResourceManagementClient(this.azureAccount.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId);
this._rg = await resourceClient.resourceGroups.createOrUpdate(this._rg.name, this._rg);
this.wizard.writeline(`Resource group created.`);
}
get resourceGroup(): ResourceModels.ResourceGroup {
return this._rg;
}
get createNew(): boolean {
return this._createNew;
}
}
class AppServicePlanStep extends WebAppCreatorStepBase {
private _createNew: boolean;
private _plan: WebSiteModels.AppServicePlan;
constructor(wizard: WizardBase, azureAccount: AzureAccountWrapper) {
super(wizard, 'Select or create App Service Plan', azureAccount);
}
async prompt(): Promise<void> {
const createNewItem: QuickPickItemWithData<WebSiteModels.AppServicePlan> = {
label: '$(plus) Create New App Service Plan',
description: '',
data: null
};
const quickPickItems = [createNewItem];
const quickPickOptions = { placeHolder: `Select the App Service Plan for the new Web App. (${this.stepProgressText})` };
const subscription = this.getSelectedSubscription();
const client = new WebSiteManagementClient(this.azureAccount.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId);
// You can create a web app and associate it with a plan from another resource group.
// That's why we use list instead of listByResourceGroup below; and show resource group name in the quick pick list.
const plans = await util.listAll(client.appServicePlans, client.appServicePlans.list());
plans.forEach(plan => {
// Currently we only support Linux web apps.
if (plan.kind.toLowerCase() === 'linux') {
quickPickItems.push({
label: plan.appServicePlanName,
description: `${plan.sku.name} (${plan.geoRegion})`,
detail: plan.resourceGroup,
data: plan
});
}
});
const pickedItem = await this.showQuickPick(quickPickItems, quickPickOptions);
if (pickedItem !== createNewItem) {
this._createNew = false;
this._plan = pickedItem.data;
return;
}
// Prompt for new plan information.
const rg = this.getSelectedResourceGroup();
const newPlanName = await this.showInputBox({
prompt: 'Enter the name of the new App Service Plan.',
validateInput: (value: string) => {
value = value.trim();
if (plans.findIndex(plan => plan.resourceGroup.toLowerCase() === rg.name && value.localeCompare(plan.name) === 0) >= 0) {
return `App Service Plan name "${value}" already exists in resource group "${rg.name}".`;
}
if (!value.match(/^[a-z0-9\-]{0,39}$/ig)) {
return 'App Service Plan name should be 1-40 characters long and can only include alphanumeric characters and hyphens.';
}
return null;
}
});
// Prompt for Pricing tier
const pricingTiers: QuickPickItemWithData<WebSiteModels.SkuDescription>[] = [];
const availableSkus = this.getPlanSkus();
availableSkus.forEach(sku => {
pricingTiers.push({
label: sku.name,
description: sku.tier,
detail: '',
data: sku
});
});
const pickedSkuItem = await this.showQuickPick(pricingTiers, { placeHolder: 'Choose your pricing tier.' });
const newPlanSku = pickedSkuItem.data;
this._createNew = true;
this._plan = {
appServicePlanName: newPlanName,
kind: 'linux', // Currently we only support Linux web apps.
sku: newPlanSku,
location: rg.location,
reserved: true // The secret property - must be set to true to make it a Linux plan. Confirmed by the team who owns this API.
};
}
async execute(): Promise<void> {
if (!this._createNew) {
this.wizard.writeline(`Existing App Service Plan "${this._plan.appServicePlanName} (${this._plan.sku.name})" will be used.`);
return;
}
this.wizard.writeline(`Creating new App Service Plan "${this._plan.appServicePlanName} (${this._plan.sku.name})"...`);
const subscription = this.getSelectedSubscription();
const rg = this.getSelectedResourceGroup();
const websiteClient = new WebSiteManagementClient(this.azureAccount.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId);
this._plan = await websiteClient.appServicePlans.createOrUpdate(rg.name, this._plan.appServicePlanName, this._plan);
this.wizard.writeline(`App Service Plan created.`);
}
get servicePlan(): WebSiteModels.AppServicePlan {
return this._plan;
}
get createNew(): boolean {
return this._createNew;
}
private getPlanSkus(): WebSiteModels.SkuDescription[] {
return [
{
name: 'S1',
tier: 'Standard',
size: 'S1',
family: 'S',
capacity: 1
},
{
name: 'S2',
tier: 'Standard',
size: 'S2',
family: 'S',
capacity: 1
},
{
name: 'S3',
tier: 'Standard',
size: 'S3',
family: 'S',
capacity: 1
},
{
name: 'B1',
tier: 'Basic',
size: 'B1',
family: 'B',
capacity: 1
},
{
name: 'B2',
tier: 'Basic',
size: 'B2',
family: 'B',
capacity: 1
},
{
name: 'B3',
tier: 'Basic',
size: 'B3',
family: 'B',
capacity: 1
}
];
}
}
class WebsiteStep extends WebAppCreatorStepBase {
private _website: WebSiteModels.Site;
private _serverUrl: string;
private _serverUserName: string;
private _serverPassword: string;
private _imageName: string;
constructor(wizard: WizardBase, azureAccount: AzureAccountWrapper, context: AzureImageNode | DockerHubImageNode) {
super(wizard, 'Create Web App', azureAccount);
this._serverUrl = context.serverUrl;
this._serverPassword = context.password;
this._serverUserName = context.userName;
this._imageName = context.label;
}
async prompt(): Promise<void> {
const subscription = this.getSelectedSubscription();
const client = new WebSiteManagementClient(this.azureAccount.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId);
let siteName: string;
let siteNameOkay = false;
while (!siteNameOkay) {
siteName = await this.showInputBox({
prompt: `Enter a globally unique name for the new Web App. (${this.stepProgressText})`,
validateInput: (value: string) => {
value = value ? value.trim() : '';
if (!value.match(/^[a-z0-9\-]{1,60}$/ig)) {
return 'App name should be 1-60 characters long and can only include alphanumeric characters and hyphens.';
}
return null;
}
});
// Check if the name has already been taken...
const nameAvailability = await client.checkNameAvailability(siteName, 'site');
siteNameOkay = nameAvailability.nameAvailable;
if (!siteNameOkay) {
await vscode.window.showWarningMessage(nameAvailability.message);
}
}
let linuxFXVersion: string;
if (this._serverUrl.length > 0) {
// azure container registry
linuxFXVersion = 'DOCKER|' + this._serverUrl + '/' + this._imageName;
} else {
// dockerhub
linuxFXVersion = 'DOCKER|' + this._serverUserName + '/' + this._imageName;
}
const rg = this.getSelectedResourceGroup();
const plan = this.getSelectedAppServicePlan();
this._website = {
name: siteName.trim(),
kind: 'app,linux',
location: rg.location,
serverFarmId: plan.id,
siteConfig: {
linuxFxVersion: linuxFXVersion
}
}
}
async execute(): Promise<void> {
this.wizard.writeline(`Creating new Web App "${this._website.name}"...`);
const subscription = this.getSelectedSubscription();
const rg = this.getSelectedResourceGroup();
const websiteClient = new WebSiteManagementClient(this.azureAccount.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId);
// If the plan is also newly created, its resource ID won't be available at this step's prompt stage, but should be available now.
if (!this._website.serverFarmId) {
this._website.serverFarmId = this.getSelectedAppServicePlan().id;
}
this._website = await websiteClient.webApps.createOrUpdate(rg.name, this._website.name, this._website);
this.wizard.writeline('Updating Application Settings...');
let appSettings: WebSiteModels.StringDictionary;
if (this._serverUrl.length > 0) {
// azure container registry
appSettings = {
"id": this._website.id, "name": "appsettings", "location": this._website.location, "type": "Microsoft.Web/sites/config", "properties": {
"DOCKER_REGISTRY_SERVER_URL": 'https://' + this._serverUrl, "DOCKER_REGISTRY_SERVER_USERNAME": this._serverUserName, "DOCKER_REGISTRY_SERVER_PASSWORD": this._serverPassword
}
};
} else {
// dockerhub - dont set docker_registry_server_url
appSettings = {
"id": this._website.id, "name": "appsettings", "location": this._website.location, "type": "Microsoft.Web/sites/config", "properties": {
"DOCKER_REGISTRY_SERVER_USERNAME": this._serverUserName, "DOCKER_REGISTRY_SERVER_PASSWORD": this._serverPassword
}
};
}
await websiteClient.webApps.updateApplicationSettings(rg.name, this._website.name, appSettings);
this._website.siteConfig = await websiteClient.webApps.getConfiguration(rg.name, this._website.name);
this.wizard.writeline(`Restarting Site...`);
await websiteClient.webApps.stop(rg.name, this._website.name);
await websiteClient.webApps.start(rg.name, this._website.name);
this.wizard.writeline(`Web App "${this._website.name}" ready: https://${this._website.defaultHostName}`);
this.wizard.writeline('');
}
get website(): WebSiteModels.Site {
return this._website;
}
get imageInfo(): { serverUrl: string, serverUser: string, serverPassword: string} {
return {
serverUrl: this._serverUrl,
serverUser: this._serverUserName,
serverPassword: this._serverPassword
}
}
}
class ShellScriptStep extends WebAppCreatorStepBase {
constructor(wizard: WizardBase, azureAccount: AzureAccountWrapper) {
super(wizard, 'Create Web App', azureAccount);
}
async execute(): Promise<void> {
const subscription = this.getSelectedSubscription();
const rg = this.getSelectedResourceGroup();
const plan = this.getSelectedAppServicePlan();
const site = this.getWebSite();
const imageInfo = this.getImageInfo();
const script = scriptTemplate.replace('%SUBSCRIPTION_NAME%', subscription.displayName)
.replace('%RG_NAME%', rg.name)
.replace('%LOCATION%', rg.location)
.replace('%PLAN_NAME%', plan.name)
.replace('%PLAN_SKU%', plan.sku.name)
.replace('%SITE_NAME%', site.name)
.replace('%IMAGENAME%', site.siteConfig.linuxFxVersion)
.replace('%SERVERPASSWORD%','********')
.replace('%SERVERURL%', imageInfo.serverUrl)
.replace('%SERVERUSER%', imageInfo.serverUser);
let uri: vscode.Uri;
if (vscode.workspace.rootPath) {
let count = 0;
const maxCount = 1024;
while (count < maxCount) {
uri = vscode.Uri.file(path.join(vscode.workspace.rootPath, `deploy-${site.name}${count === 0 ? '' : count.toString()}.sh`));
if (!vscode.workspace.textDocuments.find(doc => doc.uri.fsPath === uri.fsPath) && !fs.existsSync(uri.fsPath)) {
uri = uri.with({ scheme: 'untitled' });
break;
} else {
uri = null;
}
count++;
}
}
if (uri) {
const doc = await vscode.workspace.openTextDocument(uri);
const editor = await vscode.window.showTextDocument(doc);
await editor.edit(editorBuilder => editorBuilder.insert(new vscode.Position(0, 0), script));
} else {
const doc = await vscode.workspace.openTextDocument({ content: script, language: 'shellscript' });
await vscode.window.showTextDocument(doc);
}
}
}
interface LinuxRuntimeStack {
name: string;
displayName: string;
}
const scriptTemplate = 'SUBSCRIPTION="%SUBSCRIPTION_NAME%"\n\
RESOURCEGROUP="%RG_NAME%"\n\
LOCATION="%LOCATION%"\n\
PLANNAME="%PLAN_NAME%"\n\
PLANSKU="%PLAN_SKU%"\n\
SITENAME="%SITE_NAME%"\n\
IMAGENAME="%IMAGENAME%"\n\
SERVERPASSWORD="%SERVERPASSWORD%"\n\
SERVERURL="%SERVERURL%"\n\
SERVERUSER="%SERVERUSER%"\n\
\n\
# login supports device login, username/password, and service principals\n\
# see https://docs.microsoft.com/en-us/cli/azure/?view=azure-cli-latest#az_login\n\
az login\n\
# list all of the available subscriptions\n\
az account list -o table\n\
# set the default subscription for subsequent operations\n\
az account set --subscription $SUBSCRIPTION\n\
# create a resource group for your application\n\
az group create --name $RESOURCEGROUP --location $LOCATION\n\
# create an appservice plan (a machine) where your site will run\n\
az appservice plan create --name $PLANNAME --location $LOCATION --is-linux --sku $PLANSKU --resource-group $RESOURCEGROUP\n\
# create the web application on the plan\n\
az webapp create --name $SITENAME --plan $PLANNAME --deployment-container-image-name $IMAGENAME --resource-group $RESOURCEGROUP\n\
\n\
# configure the container information\n\
az webapp config container set --docker-custom-image-name $IMAGENAME --docker-registry-server-url $SERVERURL --docker-registry-server-user $SERVERUSER --docker-registry-server-password $SERVERPASSWORD --name $SITENAME\n\
\n\
# restart and browse to the site\n\
az webapp restart --name $SITENAME --resource-group $RESOURCEGROUP\n\
az webapp browse --name $SITENAME --resource-group $RESOURCEGROUP\n\
';

210
explorer/deploy/wizard.ts Normal file
Просмотреть файл

@ -0,0 +1,210 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import * as vscode from 'vscode';
import { AzureAccountWrapper } from './azureAccountWrapper';
import { SubscriptionModels } from 'azure-arm-resource';
export type WizardStatus = 'PromptCompleted' | 'Completed' | 'Faulted' | 'Cancelled';
export class WizardBase {
private readonly _steps: WizardStep[] = [];
private _result: WizardResult;
protected constructor(protected readonly output: vscode.OutputChannel) { }
async run(promptOnly = false): Promise<WizardResult> {
// Go through the prompts...
for (var i = 0; i < this.steps.length; i++) {
const step = this.steps[i];
try {
await this.steps[i].prompt();
} catch (err) {
if (err instanceof UserCancelledError) {
return {
status: 'Cancelled',
step: step,
error: err
};
}
return {
status: 'Faulted',
step: step,
error: err
};
}
}
if (promptOnly) {
return {
status: 'PromptCompleted',
step: this.steps[this.steps.length - 1],
error: null
};
}
return this.execute();
}
async execute(): Promise<WizardResult> {
// Execute each step...
this.output.show(true);
for (var i = 0; i < this.steps.length; i++) {
const step = this.steps[i];
try {
this.beforeExecute(step, i);
await this.steps[i].execute();
} catch (err) {
this.onExecuteError(step, i, err);
if (err instanceof UserCancelledError) {
this._result = {
status: 'Cancelled',
step: step,
error: err
};
} else {
this._result = {
status: 'Faulted',
step: step,
error: err
};
}
return this._result;
}
}
this._result = {
status: 'Completed',
step: this.steps[this.steps.length - 1],
error: null
};
return this._result;
}
get steps(): WizardStep[] {
return this._steps;
}
findStep(predicate: (step: WizardStep) => boolean, errorMessage: string): WizardStep {
const step = this.steps.find(predicate);
if (!step) {
throw new Error(errorMessage);
}
return step;
}
write(text: string) {
this.output.append(text);
}
writeline(text: string) {
this.output.appendLine(text);
}
protected beforeExecute(step: WizardStep, stepIndex: number) { }
protected onExecuteError(step: WizardStep, stepIndex: number, error: Error) { }
}
export interface WizardResult {
status: WizardStatus;
step: WizardStep;
error: Error;
}
export class WizardStep {
protected constructor(readonly wizard: WizardBase, readonly stepTitle: string) { }
async prompt(): Promise<void> { }
async execute(): Promise<void> { }
get stepIndex(): number {
return this.wizard.steps.findIndex(step => step === this);
}
get stepProgressText(): string {
return `Step ${this.stepIndex + 1}/${this.wizard.steps.length}`;
}
async showQuickPick<T>(items: QuickPickItemWithData<T>[], options: vscode.QuickPickOptions, token?: vscode.CancellationToken): Promise<QuickPickItemWithData<T>> {
const result = await vscode.window.showQuickPick(items, options, token);
if (!result) {
throw new UserCancelledError();
}
return result;
}
async showInputBox(options?: vscode.InputBoxOptions, token?: vscode.CancellationToken): Promise<string> {
const result = await vscode.window.showInputBox(options, token);
if (!result) {
throw new UserCancelledError();
}
return result;
}
}
export class SubscriptionStepBase extends WizardStep {
constructor(wizard: WizardBase, title: string, readonly azureAccount: AzureAccountWrapper, protected _subscription?: SubscriptionModels.Subscription) {
super(wizard, title);
}
protected async getSubscriptionsAsQuickPickItems(): Promise<QuickPickItemWithData<SubscriptionModels.Subscription>[]> {
const quickPickItems: QuickPickItemWithData<SubscriptionModels.Subscription>[] = [];
await Promise.all([this.azureAccount.getFilteredSubscriptions(), this.azureAccount.getAllSubscriptions()]).then(results => {
const inFilterSubscriptions = results[0];
const otherSubscriptions = results[1];
inFilterSubscriptions.forEach(s => {
const index = otherSubscriptions.findIndex(other => other.subscriptionId === s.subscriptionId);
if (index >= 0) { // Remove duplicated items from "all subscriptions".
otherSubscriptions.splice(index, 1);
}
const item = {
label: `📌 ${s.displayName}`,
description: '',
detail: s.subscriptionId,
data: s
};
quickPickItems.push(item);
});
otherSubscriptions.forEach(s => {
const item = {
label: s.displayName,
description: '',
detail: s.subscriptionId,
data: s
};
quickPickItems.push(item);
});
});
return quickPickItems;
}
get subscription(): SubscriptionModels.Subscription {
return this._subscription;
}
}
export interface QuickPickItemWithData<T> extends vscode.QuickPickItem {
data: T;
}
export class UserCancelledError extends Error { }

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

@ -1,22 +1,19 @@
import * as vscode from 'vscode';
import * as fs from 'fs';
import * as path from 'path';
import { docker } from '../commands/utils/docker-endpoint';
import { NodeBase } from './models/nodeBase';
import { RootNode } from './models/rootNode';
export class DockerExplorerProvider implements vscode.TreeDataProvider<DockerNode> {
export class DockerExplorerProvider implements vscode.TreeDataProvider<NodeBase> {
private _onDidChangeTreeData: vscode.EventEmitter<DockerNode | undefined> = new vscode.EventEmitter<DockerNode | undefined>();
readonly onDidChangeTreeData: vscode.Event<DockerNode | undefined> = this._onDidChangeTreeData.event;
private _imagesNode: DockerNode;
private _containersNode: DockerNode;
private _imageCache: Docker.ImageDesc[];
private _containerCache: Docker.ContainerDesc[];
private _imageDebounceTimer: NodeJS.Timer;
private _containerDebounceTimer: NodeJS.Timer;
private _onDidChangeTreeData: vscode.EventEmitter<NodeBase> = new vscode.EventEmitter<NodeBase>();
readonly onDidChangeTreeData: vscode.Event<NodeBase> = this._onDidChangeTreeData.event;
private _imagesNode: RootNode;
private _containersNode: RootNode;
private _registriesNode: RootNode
refresh(): void {
this._onDidChangeTreeData.fire(this._imagesNode);
this._onDidChangeTreeData.fire(this._containersNode);
this._onDidChangeTreeData.fire(this._registriesNode);
}
refreshImages(): void {
@ -27,245 +24,37 @@ export class DockerExplorerProvider implements vscode.TreeDataProvider<DockerNod
this._onDidChangeTreeData.fire(this._imagesNode);
}
autoRefreshImages(): void {
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 1000);
// https://github.com/Microsoft/vscode/issues/30535
// if (this._imagesNode.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) {
// clearInterval(this._imageDebounceTimer);
// return;
// }
clearInterval(this._imageDebounceTimer);
if (refreshInterval > 0) {
this._imageDebounceTimer = setInterval(async () => {
const opts = {
"filters": {
"dangling": ["false"]
}
};
let needToRefresh: boolean = false;
let found: boolean = false;
const images: Docker.ImageDesc[] = await docker.getImageDescriptors(opts);
if (this._imageCache.length !== images.length) {
needToRefresh = true;
} else {
for (let i: number = 0; i < this._imageCache.length; i++) {
let before: string = JSON.stringify(this._imageCache[i]);
for (let j: number = 0; j < images.length; j++) {
let after: string = JSON.stringify(images[j]);
if (before === after) {
found = true;
break;
}
}
if (!found) {
needToRefresh = true;
break
}
}
}
if (needToRefresh) {
this._onDidChangeTreeData.fire(this._imagesNode);
this._imageCache = images;
}
}, refreshInterval);
}
refreshRegistries(): void {
this._onDidChangeTreeData.fire(this._registriesNode);
}
getTreeItem(element: NodeBase): vscode.TreeItem {
return element.getTreeItem();
}
containersAutoRefresh(): void {
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
const refreshInterval = configOptions.get('explorerRefreshInterval', 1000);
// https://github.com/Microsoft/vscode/issues/30535
// if (this._containersNode.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) {
// clearInterval(this._containerDebounceTimer);
// return;
// }
clearInterval(this._containerDebounceTimer);
if (refreshInterval > 0) {
this._containerDebounceTimer = setInterval(async () => {
const opts = {
"filters": {
"status": ["created", "restarting", "running", "paused", "exited", "dead"]
}
};
let needToRefresh: boolean = false;
let found: boolean = false;
const containers: Docker.ContainerDesc[] = await docker.getContainerDescriptors(opts);
if (this._containerCache.length !== containers.length) {
needToRefresh = true;
} else {
for (let i = 0; i < this._containerCache.length; i++) {
let img: Docker.ContainerDesc = this._containerCache[i];
for (let j = 0; j < containers.length; j++) {
// can't do a full object compare because "Status" keeps changing for running containers
if (img.Id === containers[j].Id &&
img.Image === containers[j].Image &&
img.State === containers[j].State) {
found = true;
break;
}
}
if (!found) {
needToRefresh = true;
break
}
}
}
if (needToRefresh) {
this._onDidChangeTreeData.fire(this._containersNode);
this._containerCache = containers;
}
}, refreshInterval);
}
}
getTreeItem(element: DockerNode): vscode.TreeItem {
return element;
}
async getChildren(element?: DockerNode): Promise<DockerNode[]> {
return this.getDockerNodes(element);
}
private async getDockerNodes(element?: DockerNode): Promise<DockerNode[]> {
let iconPath: any = {};
let contextValue: string = "";
let node: DockerNode;
const nodes: DockerNode[] = [];
async getChildren(element?: NodeBase): Promise<NodeBase[]> {
if (!element) {
this._imagesNode = new DockerNode('Images', vscode.TreeItemCollapsibleState.Collapsed, 'rootImages', null);
this._containersNode = new DockerNode('Containers', vscode.TreeItemCollapsibleState.Collapsed, 'rootContainers', null);
nodes.push(this._imagesNode);
nodes.push(this._containersNode);
} else {
if (element.contextValue === 'rootImages') {
let opts = {
"filters": {
"dangling": ["false"]
}
};
try {
const images: Docker.ImageDesc[] = await docker.getImageDescriptors(opts);
this._imageCache = images;
this.autoRefreshImages();
if (!images || images.length == 0) {
return [];
} else {
iconPath = {
light: path.join(__filename, '..', '..', '..', 'images', 'light', 'application.svg'),
dark: path.join(__filename, '..', '..', '..', 'images', 'dark', 'application.svg')
};
for (let i = 0; i < images.length; i++) {
contextValue = "dockerImage";
if (!images[i].RepoTags) {
let node = new DockerNode("<none>:<none>", vscode.TreeItemCollapsibleState.None, contextValue, iconPath);
node.imageDesc = images[i];
nodes.push(node);
} else {
for (let j = 0; j < images[i].RepoTags.length; j++) {
let node = new DockerNode(images[i].RepoTags[j], vscode.TreeItemCollapsibleState.None, contextValue, iconPath);
node.imageDesc = images[i];
nodes.push(node);
}
}
}
}
} catch (error) {
vscode.window.showErrorMessage('Unable to connect to Docker, is the Docker daemon running?');
console.log(error);
}
}
if (element.contextValue === 'rootContainers') {
let opts = {
"filters": {
"status": ["created", "restarting", "running", "paused", "exited", "dead"]
}
};
try {
const containers: Docker.ContainerDesc[] = await docker.getContainerDescriptors(opts);
this._containerCache = containers;
this.containersAutoRefresh();
if (!containers || containers.length == 0) {
return [];
} else {
for (let i = 0; i < containers.length; i++) {
if (['exited', 'dead'].includes(containers[i].State)) {
contextValue = "dockerContainerStopped";
iconPath = {
light: path.join(__filename, '..', '..', '..', 'images', 'light', 'stoppedContainer.svg'),
dark: path.join(__filename, '..', '..', '..', 'images', 'dark', 'stoppedContainer.svg')
};
} else {
contextValue = "dockerContainerRunning";
iconPath = {
light: path.join(__filename, '..', '..', '..', 'images', 'light', 'runningContainer.svg'),
dark: path.join(__filename, '..', '..', '..', 'images', 'dark', 'runningContainer.svg')
};
}
const containerName = containers[i].Names[0].substring(1);
let node = new DockerNode(`${containers[i].Image} (${containerName}) [${containers[i].Status}]`, vscode.TreeItemCollapsibleState.None, contextValue, iconPath);
node.containerDesc = containers[i];
nodes.push(node);
}
}
} catch (error) {
vscode.window.showErrorMessage('Unable to connect to Docker, is the Docker daemon running?');
console.log(error);
}
}
return this.getRootNodes();
}
return nodes;
return element.getChildren(element);
}
private async getRootNodes(): Promise<RootNode[]> {
const rootNodes: RootNode[] = [];
let node: RootNode;
node = new RootNode('Images', 'imagesRootNode', this._onDidChangeTreeData);
this._imagesNode = node;
rootNodes.push(node);
node = new RootNode('Containers', 'containersRootNode', this._onDidChangeTreeData);
this._containersNode = node;
rootNodes.push(node);
node = new RootNode('Registries', 'registriesRootNode', this._onDidChangeTreeData);
this._registriesNode = node;
rootNodes.push(node);
return rootNodes;
}
}
export class DockerNode extends vscode.TreeItem {
constructor(public readonly label: string,
public readonly collapsibleState: vscode.TreeItemCollapsibleState,
public readonly contextValue: string,
public readonly iconPath: any
) {
super(label, collapsibleState);
}
public containerDesc: Docker.ContainerDesc;
public imageDesc: Docker.ImageDesc;
}

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

@ -0,0 +1,264 @@
import * as vscode from 'vscode';
import * as path from 'path';
import request = require('request-promise');
import { NodeBase } from './nodeBase';
import { SubscriptionClient, ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource';
import { AzureAccount, AzureSession } from '../../typings/azure-account.api';
import { RegistryType } from './registryType';
const azureAccount: AzureAccount = vscode.extensions.getExtension<AzureAccount>('ms-vscode.azure-account')!.exports;
export class AzureRegistryNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string,
public readonly iconPath: any = {}
) {
super(label);
}
public type: RegistryType;
public subscription: SubscriptionModels.Subscription;
public userName: string;
public password: string;
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: this.contextValue,
iconPath: this.iconPath
}
}
async getChildren(element: AzureRegistryNode): Promise<AzureRepositoryNode[]> {
const repoNodes: AzureRepositoryNode[] = [];
let node: AzureRepositoryNode;
const tenantId: string = element.subscription.tenantId;
const session: AzureSession = azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase());
const { accessToken, refreshToken } = await acquireToken(session);
if (accessToken && refreshToken) {
let refreshTokenARC;
let accessTokenARC;
await request.post('https://' + element.label + '/oauth2/exchange', {
form: {
grant_type: 'access_token_refresh_token',
service: element.label,
tenant: tenantId,
refresh_token: refreshToken,
access_token: accessToken
}
}, (err, httpResponse, body) => {
if (body.length > 0) {
refreshTokenARC = JSON.parse(body).refresh_token;
} else {
return [];
}
});
await request.post('https://' + element.label + '/oauth2/token', {
form: {
grant_type: 'refresh_token',
service: element.label,
scope: 'registry:catalog:*',
refresh_token: refreshTokenARC
}
}, (err, httpResponse, body) => {
if (body.length > 0) {
accessTokenARC = JSON.parse(body).access_token;
} else {
return [];
}
});
await request.get('https://' + element.label + '/v2/_catalog', {
auth: {
bearer: accessTokenARC
}
}, (err, httpResponse, body) => {
if (body.length > 0) {
const repositories = JSON.parse(body).repositories;
for (let i = 0; i < repositories.length; i++) {
node = new AzureRepositoryNode(repositories[i], "azureRepository");
node.repository = element.label;
node.subscription = element.subscription;
node.accessTokenARC = accessTokenARC;
node.refreshTokenARC = refreshTokenARC;
node.userName = element.userName;
node.password = element.password;
repoNodes.push(node);
}
}
});
}
return repoNodes;
}
}
export class AzureRepositoryNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string,
public readonly iconPath = {
light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'Repository_16x.svg'),
dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'Repository_16x.svg')
}
) {
super(label);
}
public repository: string;
public subscription: any;
public accessTokenARC: string;
public refreshTokenARC: string;
public userName: string;
public password: string;
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: this.contextValue,
iconPath: this.iconPath
}
}
async getChildren(element: AzureRepositoryNode): Promise<AzureImageNode[]> {
const imageNodes: AzureImageNode[] = [];
let node: AzureImageNode;
const { accessToken, refreshToken } = await acquireToken(element.subscription.session);
if (accessToken && refreshToken) {
const tenantId = element.subscription.tenantId;
let refreshTokenARC;
let accessTokenARC;
await request.post('https://' + element.repository + '/oauth2/exchange', {
form: {
grant_type: 'access_token_refresh_token',
service: element.repository,
tenant: tenantId,
refresh_token: refreshToken,
access_token: accessToken
}
}, (err, httpResponse, body) => {
if (body.length > 0) {
refreshTokenARC = JSON.parse(body).refresh_token;
} else {
return [];
}
});
await request.post('https://' + element.repository + '/oauth2/token', {
form: {
grant_type: 'refresh_token',
service: element.repository,
scope: 'repository:' + element.label + ':pull',
refresh_token: refreshTokenARC
}
}, (err, httpResponse, body) => {
if (body.length > 0) {
accessTokenARC = JSON.parse(body).access_token;
} else {
return [];
}
});
await request.get('https://' + element.repository + '/v2/' + element.label + '/tags/list', {
auth: {
bearer: accessTokenARC
}
}, (err, httpResponse, body) => {
if (err) { return []; }
if (body.length > 0) {
const tags = JSON.parse(body).tags;
for (let i = 0; i < tags.length; i++) {
node = new AzureImageNode(element.label + ':' + tags[i], 'azureImageTag');
node.serverUrl = element.repository;
node.userName = element.userName;
node.password = element.password;
imageNodes.push(node);
}
}
});
}
return imageNodes;
}
}
export class AzureImageNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string
) {
super(label);
}
public serverUrl: string;
public userName: string;
public password: string;
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: this.contextValue
}
}
}
export class AzureNotSignedInNode extends NodeBase {
constructor() {
super('Sign in to Azure...');
}
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
command: {
title: this.label,
command: 'azure-account.login'
},
collapsibleState: vscode.TreeItemCollapsibleState.None
}
}
}
export class AzureLoadingNode extends NodeBase {
constructor() {
super('Loading...');
}
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.None
}
}
}
async function acquireToken(session: AzureSession) {
return new Promise<{ accessToken: string; refreshToken: string; }>((resolve, reject) => {
const credentials: any = session.credentials;
const environment: any = session.environment;
credentials.context.acquireToken(environment.activeDirectoryResourceId, credentials.username, credentials.clientId, function (err: any, result: any) {
if (err) {
reject(err);
} else {
resolve({
accessToken: result.accessToken,
refreshToken: result.refreshToken
});
}
});
});
}

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

@ -0,0 +1,24 @@
import * as vscode from 'vscode';
import { NodeBase } from './nodeBase';
export class ContainerNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string,
public readonly iconPath: any = {}
) {
super(label)
}
public containerDesc: Docker.ContainerDesc;
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: this.contextValue,
iconPath: this.iconPath
}
}
}

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

@ -0,0 +1,118 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as dockerHub from './dockerHubUtils';
import { NodeBase } from './nodeBase';
export class DockerHubOrgNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string,
public readonly iconPath: any = {}
) {
super(label);
}
public repository: string;
public userName: string;
public password: string;
public token: string;
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: this.contextValue,
iconPath: this.iconPath
}
}
async getChildren(element: DockerHubOrgNode): Promise<DockerHubRepositoryNode[]> {
const repoNodes: DockerHubRepositoryNode[] = [];
let node: DockerHubRepositoryNode;
const user: dockerHub.User = await dockerHub.getUser();
const myRepos: dockerHub.Repository[] = await dockerHub.getRepositories(user.username);
for (let i = 0; i < myRepos.length; i++) {
const myRepo: dockerHub.RepositoryInfo = await dockerHub.getRepositoryInfo(myRepos[i]);
let iconPath = {
light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'Repository_16x.svg'),
dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'Repository_16x.svg')
};
node = new DockerHubRepositoryNode(myRepo.name, 'dockerHubRepository', iconPath);
node.repository = myRepo;
node.userName = element.userName;
node.password = element.password;
repoNodes.push(node);
}
return repoNodes;
}
}
export class DockerHubRepositoryNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string,
public readonly iconPath: any = {}
) {
super(label);
}
public repository: any;
public userName: string;
public password: string;
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: this.contextValue,
iconPath: this.iconPath
}
}
async getChildren(element: DockerHubRepositoryNode): Promise<DockerHubImageNode[]> {
const imageNodes: DockerHubImageNode[] = [];
let node: DockerHubImageNode;
const myTags: dockerHub.Tag[] = await dockerHub.getRepositoryTags({namespace: element.repository.namespace, name: element.repository.name});
for (let i = 0; i < myTags.length; i++) {
node = new DockerHubImageNode(`${element.repository.name}:${myTags[i].name}`, 'dockerHubImageTag');
node.password = element.password;
node.userName = element.userName;
imageNodes.push(node);
}
return imageNodes;
}
}
export class DockerHubImageNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string
) {
super(label);
}
// this needs to be empty string for DockerHub
public serverUrl: string = '';
public userName: string;
public password: string;
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: this.contextValue
}
}
}

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

@ -0,0 +1,224 @@
import * as vscode from 'vscode';
import * as keytarType from 'keytar';
import request = require('request-promise');
let _token: Token;
export interface Token {
token: string
};
export interface User {
company: string
date_joined: string
full_name: string
gravatar_email: string
gravatar_url: string
id: string
is_admin: boolean
is_staff: boolean
location: string
profile_url: string
type: string
username: string
};
export interface Repository {
namespace: string
name: string
};
export interface RepositoryInfo {
user: string
name: string
namespace: string
repository_type: string
status: number
description: string
is_private: boolean
is_automated: boolean
can_edit: boolean
star_count: number
pull_count: number
last_updated: string
build_on_cloud: any
has_starred: boolean
full_description: string
affiliation: string
permissions: {
read: boolean
write: boolean
admin: boolean
}
}
export interface Tag {
creator: number
full_size: number
id: number
image_id: any
images: Image[]
last_updated: string
last_updater: number
name: string
repository: number
v2: boolean
}
export interface Image {
architecture: string
features: any
os: string
os_features: any
os_version: any
size: number
variant: any
}
export function dockerHubLogout(): void {
const keytar: typeof keytarType = require(`${vscode.env.appRoot}/node_modules/keytar`);
if (keytar) {
keytar.deletePassword('vscode-docker', 'dockerhub.token');
keytar.deletePassword('vscode-docker', 'dockerhub.password');
keytar.deletePassword('vscode-docker', 'dockerhub.username');
}
_token = null;
}
export async function dockerHubLogin(): Promise<{ username: string, password: string, token: string }> {
const username: string = await vscode.window.showInputBox({ prompt: 'Username' });
if (username) {
const password: string = await vscode.window.showInputBox({ prompt: 'Password', password: true });
if (password) {
_token = await login(username, password);
if (_token) {
return { username: username, password: password, token: <string>_token.token };
}
}
}
return;
}
export function setDockerHubToken(token: string) {
_token = { token: token };
}
async function login(username: string, password: string): Promise<Token> {
let t: Token;
let options = {
method: 'POST',
uri: 'https://hub.docker.com/v2/users/login',
body: {
username: username,
password: password
},
json: true
}
try {
t = await request(options);
} catch (error) {
console.log(error);
vscode.window.showErrorMessage(error.error.detail);
}
return t;
}
export async function getUser(): Promise<User> {
let u: User;
let options = {
method: 'GET',
uri: 'https://hub.docker.com/v2/user/',
headers: {
Authorization: 'JWT ' + _token.token
},
json: true
}
try {
u = await request(options);
} catch (error) {
console.log(error);
vscode.window.showErrorMessage('Docker: Unable to retrieve User information');
}
return u;
}
export async function getRepositories(username: string): Promise<Repository[]> {
let repos: Repository[];
let options = {
method: 'GET',
uri: `https://hub.docker.com/v2/users/${username}/repositories/`,
headers: {
Authorization: 'JWT ' + _token.token
},
json: true
}
try {
repos = await request(options);
} catch (error) {
console.log(error);
vscode.window.showErrorMessage('Docker: Unable to retrieve Repositories');
}
return repos;
}
export async function getRepositoryInfo(repository: Repository): Promise<any> {
let res: any;
let options = {
method: 'GET',
uri: `https://hub.docker.com/v2/repositories/${repository.namespace}/${repository.name}/`,
headers: {
Authorization: 'JWT ' + _token.token
},
json: true
}
try {
res = await request(options);
} catch (error) {
console.log(error);
vscode.window.showErrorMessage('Docker: Unable to get Repository Details');
}
return res;
}
export async function getRepositoryTags(repository: Repository): Promise<Tag[]> {
let tagsPage: any;
let options = {
method: 'GET',
uri: `https://hub.docker.com/v2/repositories/${repository.namespace}/${repository.name}/tags?page_size=100&page=1`,
headers: {
Authorization: 'JWT ' + _token.token
},
json: true
}
try {
tagsPage = await request(options);
} catch (error) {
console.log(error);
vscode.window.showErrorMessage('Docker: Unable to retrieve Repository Tags');
}
return <Tag[]>tagsPage.results;
}

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

@ -0,0 +1,30 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { NodeBase } from './nodeBase';
export class ImageNode extends NodeBase {
constructor(
public readonly label: string,
public readonly contextValue: string,
public readonly eventEmitter: vscode.EventEmitter<NodeBase>
) {
super(label)
}
public imageDesc: Docker.ImageDesc
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.None,
contextValue: "localImageNode",
iconPath: {
light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'application.svg'),
dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'application.svg')
}
}
}
// no children
}

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

@ -0,0 +1,21 @@
import * as vscode from 'vscode';
export class NodeBase {
readonly label: string;
protected constructor(label: string) {
this.label = label;
}
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.None
};
}
async getChildren(element): Promise<NodeBase[]> {
return [];
}
}

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

@ -0,0 +1,182 @@
import * as vscode from 'vscode';
import * as path from 'path';
import * as dockerHub from './dockerHubUtils'
import * as keytarType from 'keytar';
import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models';
import * as ContainerOps from '../../node_modules/azure-arm-containerregistry/lib/operations';
import ContainerRegistryManagementClient = require('azure-arm-containerregistry');
import { AzureAccount, AzureSession } from '../../typings/azure-account.api';
import { AzureRegistryNode, AzureLoadingNode, AzureNotSignedInNode } from './azureRegistryNodes';
import { DockerHubOrgNode } from './dockerHubNodes';
import { NodeBase } from './nodeBase';
import { RegistryType } from './registryType';
import { ServiceClientCredentials } from 'ms-rest';
import { SubscriptionClient, ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource';
const ContainerRegistryManagement = require('azure-arm-containerregistry');
const azureAccount: AzureAccount = vscode.extensions.getExtension<AzureAccount>('ms-vscode.azure-account')!.exports;
export class RegistryRootNode extends NodeBase {
private _keytar: typeof keytarType;
constructor(
public readonly label: string,
public readonly contextValue: string,
public readonly eventEmitter: vscode.EventEmitter<NodeBase>
) {
super(label);
try {
this._keytar = require(`${vscode.env.appRoot}/node_modules/keytar`);
} catch (e) {
// unable to find keytar
}
if (this.eventEmitter && this.contextValue === 'azureRegistryRootNode') {
azureAccount.onFiltersChanged((e) => {
this.eventEmitter.fire(this);
});
azureAccount.onStatusChanged((e) => {
this.eventEmitter.fire(this);
});
azureAccount.onSessionsChanged((e) => {
this.eventEmitter.fire(this);
});
}
}
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: this.contextValue,
}
}
async getChildren(element: RegistryRootNode): Promise<NodeBase[]> {
if (element.contextValue === 'azureRegistryRootNode') {
return this.getAzureRegistries();
} else if (element.contextValue === 'dockerHubRootNode') {
return this.getDockerHubOrgs();
} else {
return [];
}
}
private async getDockerHubOrgs(): Promise<DockerHubOrgNode[]> {
const orgNodes: DockerHubOrgNode[] = [];
let id: { username: string, password: string, token: string } = { username: null, password: null, token: null };
if (this._keytar) {
id.token = await this._keytar.getPassword('vscode-docker', 'dockerhub.token');
id.username = await this._keytar.getPassword('vscode-docker', 'dockerhub.username');
id.password = await this._keytar.getPassword('vscode-docker', 'dockerhub.password');
}
if (!id.token) {
id = await dockerHub.dockerHubLogin();
if (id && id.token) {
if (this._keytar) {
this._keytar.setPassword('vscode-docker', 'dockerhub.token', id.token);
this._keytar.setPassword('vscode-docker', 'dockerhub.password', id.password);
this._keytar.setPassword('vscode-docker', 'dockerhub.username', id.username);
}
} else {
return orgNodes;
}
} else {
dockerHub.setDockerHubToken(id.token);
}
const user: dockerHub.User = await dockerHub.getUser();
const myRepos: dockerHub.Repository[] = await dockerHub.getRepositories(user.username);
const namespaces = [...new Set(myRepos.map(item => item.namespace))];
namespaces.forEach((namespace) => {
let iconPath = {
light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'Registry_16x.svg'),
dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'Registry_16x.svg')
};
let node = new DockerHubOrgNode(`${namespace}`, 'dockerHubNamespace', iconPath);
node.userName = id.username;
node.password = id.password;
node.token = id.token;
orgNodes.push(node);
});
return orgNodes;
}
private async getAzureRegistries(): Promise<AzureRegistryNode[] | AzureLoadingNode[] | AzureNotSignedInNode[]> {
const loggedIntoAzure: boolean = await azureAccount.waitForLogin()
const azureRegistryNodes: AzureRegistryNode[] = [];
if (azureAccount.status === 'Initializing' || azureAccount.status === 'LoggingIn') {
return [new AzureLoadingNode()];
}
if (azureAccount.status === 'LoggedOut') {
return [new AzureNotSignedInNode()];
}
if (loggedIntoAzure) {
const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptions();
for (let i = 0; i < subs.length; i++) {
const client = new ContainerRegistryManagement(this.getCredentialByTenantId(subs[i].tenantId), subs[i].subscriptionId);
const registries: ContainerModels.RegistryListResult = await client.registries.list();
for (let j = 0; j < registries.length; j++) {
if (registries[j].adminUserEnabled && registries[j].sku.tier.includes('Managed')) {
const resourceGroup: string = registries[j].id.slice(registries[j].id.search('resourceGroups/') + 'resourceGroups/'.length, registries[j].id.search('/providers/'));
const creds: ContainerModels.RegistryListCredentialsResult = await client.registries.listCredentials(resourceGroup, registries[j].name);
let iconPath = {
light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'Registry_16x.svg'),
dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'Registry_16x.svg')
};
let node = new AzureRegistryNode(registries[j].loginServer, 'registry', iconPath);
node.type = RegistryType.Azure;
node.password = creds.passwords[0].value;
node.userName = creds.username;
node.subscription = subs[i];
azureRegistryNodes.push(node);
}
}
}
}
return azureRegistryNodes;
}
private getCredentialByTenantId(tenantId: string): ServiceClientCredentials {
const session = azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase());
if (session) {
return session.credentials;
}
throw new Error(`Failed to get credentials, tenant ${tenantId} not found.`);
}
private getFilteredSubscriptions(): SubscriptionModels.Subscription[] {
return azureAccount.filters.map<SubscriptionModels.Subscription>(filter => {
return {
id: filter.subscription.id,
session: filter.session,
subscriptionId: filter.subscription.subscriptionId,
tenantId: filter.session.tenantId,
displayName: filter.subscription.displayName,
state: filter.subscription.state,
subscriptionPolicies: filter.subscription.subscriptionPolicies,
authorizationSource: filter.subscription.authorizationSource
};
});
}
}

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

@ -0,0 +1,6 @@
export enum RegistryType {
DockerHub,
Azure,
Unknown
}

250
explorer/models/rootNode.ts Normal file
Просмотреть файл

@ -0,0 +1,250 @@
import * as vscode from 'vscode';
import * as path from 'path';
import { ContainerNode } from './containerNode';
import { docker } from '../../commands/utils/docker-endpoint';
import { ImageNode } from './imageNode';
import { NodeBase } from './nodeBase';
import { RegistryRootNode } from './registryRootNode';
export class RootNode extends NodeBase {
private _imageCache: Docker.ImageDesc[];
private _imageDebounceTimer: NodeJS.Timer;
private _imagesNode: RootNode;
private _containerCache: Docker.ContainerDesc[];
private _containerDebounceTimer: NodeJS.Timer;
private _containersNode: RootNode;
constructor(
public readonly label: string,
public readonly contextValue: string,
public eventEmitter: vscode.EventEmitter<NodeBase>
) {
super(label);
if (this.contextValue === 'imagesRootNode') {
this._imagesNode = this;
} else if (this.contextValue === 'containersRootNode') {
this._containersNode = this;
}
}
autoRefreshImages(): void {
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
const refreshInterval: number = configOptions.get<number>('explorerRefreshInterval', 1000);
// https://github.com/Microsoft/vscode/issues/30535
// if (this._imagesNode.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) {
// clearInterval(this._imageDebounceTimer);
// return;
// }
clearInterval(this._imageDebounceTimer);
if (refreshInterval > 0) {
this._imageDebounceTimer = setInterval(async () => {
const opts = {
"filters": {
"dangling": ["false"]
}
};
let needToRefresh: boolean = false;
let found: boolean = false;
const images: Docker.ImageDesc[] = await docker.getImageDescriptors(opts);
if (!this._imageCache) {
this._imageCache = images;
}
if (this._imageCache.length !== images.length) {
needToRefresh = true;
} else {
for (let i: number = 0; i < this._imageCache.length; i++) {
let before: string = JSON.stringify(this._imageCache[i]);
for (let j: number = 0; j < images.length; j++) {
let after: string = JSON.stringify(images[j]);
if (before === after) {
found = true;
break;
}
}
if (!found) {
needToRefresh = true;
break
}
}
}
if (needToRefresh) {
this.eventEmitter.fire(this._imagesNode);
this._imageCache = images;
}
}, refreshInterval);
}
}
getTreeItem(): vscode.TreeItem {
return {
label: this.label,
collapsibleState: vscode.TreeItemCollapsibleState.Collapsed,
contextValue: this.contextValue
}
}
async getChildren(element): Promise<NodeBase[]> {
if (element.contextValue === 'imagesRootNode') {
this.autoRefreshImages();
return this.getImages();
}
if (element.contextValue === 'containersRootNode') {
this.autoRefreshContainers();
return this.getContainers();
}
if (element.contextValue === 'registriesRootNode') {
return this.getRegistries()
}
}
private async getImages(): Promise<ImageNode[]> {
const imageNodes: ImageNode[] = [];
const images: Docker.ImageDesc[] = await docker.getImageDescriptors();
if (!images || images.length === 0) {
return [];
}
for (let i = 0; i < images.length; i++) {
if (!images[i].RepoTags) {
let node = new ImageNode("<none>:<none>", "localImageNode", this.eventEmitter);
node.imageDesc = images[i];
imageNodes.push(node);
} else {
for (let j = 0; j < images[i].RepoTags.length; j++) {
let node = new ImageNode(images[i].RepoTags[j], "localImageNode", this.eventEmitter);
node.imageDesc = images[i];
imageNodes.push(node);
}
}
}
return imageNodes;
}
autoRefreshContainers(): void {
const configOptions: vscode.WorkspaceConfiguration = vscode.workspace.getConfiguration('docker');
const refreshInterval = configOptions.get('explorerRefreshInterval', 1000);
// https://github.com/Microsoft/vscode/issues/30535
// if (this._containersNode.collapsibleState === vscode.TreeItemCollapsibleState.Collapsed) {
// clearInterval(this._containerDebounceTimer);
// return;
// }
clearInterval(this._containerDebounceTimer);
if (refreshInterval > 0) {
this._containerDebounceTimer = setInterval(async () => {
const opts = {
"filters": {
"status": ["created", "restarting", "running", "paused", "exited", "dead"]
}
};
let needToRefresh: boolean = false;
let found: boolean = false;
const containers: Docker.ContainerDesc[] = await docker.getContainerDescriptors(opts);
if (!this._containerCache) {
this._containerCache = containers;
}
if (this._containerCache.length !== containers.length) {
needToRefresh = true;
} else {
for (let i = 0; i < this._containerCache.length; i++) {
let ctr: Docker.ContainerDesc = this._containerCache[i];
for (let j = 0; j < containers.length; j++) {
// can't do a full object compare because "Status" keeps changing for running containers
if (ctr.Id === containers[j].Id &&
ctr.Image === containers[j].Image &&
ctr.State === containers[j].State) {
found = true;
break;
}
}
if (!found) {
needToRefresh = true;
break
}
}
}
if (needToRefresh) {
this.eventEmitter.fire(this._containersNode);
this._containerCache = containers;
}
}, refreshInterval);
}
}
private async getContainers(): Promise<ContainerNode[]> {
const containerNodes: ContainerNode[] = [];
let contextValue: string;
let iconPath: any = {};
const opts = {
"filters": {
"status": ["created", "restarting", "running", "paused", "exited", "dead"]
}
};
const containers: Docker.ContainerDesc[] = await docker.getContainerDescriptors(opts);
if (!containers || containers.length == 0) {
return [];
} else {
for (let i = 0; i < containers.length; i++) {
if (['exited', 'dead'].includes(containers[i].State)) {
contextValue = "stoppedLocalContainerNode";
iconPath = {
light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'stoppedContainer.svg'),
dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'stoppedContainer.svg')
};
} else {
contextValue = "runningLocalContainerNode";
iconPath = {
light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'runningContainer.svg'),
dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'runningContainer.svg')
};
}
let containerNode: ContainerNode = new ContainerNode(`${containers[i].Image} (${containers[i].Names[0].substring(1)}) [${containers[i].Status}]`, contextValue, iconPath);
containerNode.containerDesc = containers[i];
containerNodes.push(containerNode);
}
}
return containerNodes;
}
private async getRegistries(): Promise<RegistryRootNode[]> {
const registryRootNodes: RegistryRootNode[] = [];
registryRootNodes.push(new RegistryRootNode('DockerHub', "dockerHubRootNode", null));
registryRootNodes.push(new RegistryRootNode('Azure', "azureRegistryRootNode", this.eventEmitter));
return registryRootNodes;
}
}

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z" id="outline"/><path class="icon-vs-bg" d="M8 1a7 7 0 0 0-6.158 10.33l4.451-4.451L8.414 9l3-3H10V5h3v3h-1V6.829l-3.586 3.586-2.121-2.122-3.894 3.893A6.984 6.984 0 0 0 8 15 7 7 0 1 0 8 1z" id="iconBg"/><path class="icon-vs-fg" d="M12 8V6.829l-3.586 3.586-2.121-2.122-3.894 3.893a7.062 7.062 0 0 1-.558-.856l4.451-4.451L8.414 9l3-3H10V5h3v3h-1z" id="iconFg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 728 B

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{opacity:0}.st0,.st1{fill:#f6f6f6}.st2{fill:#424242}</style><g id="outline"><path class="st0" d="M0 0h16v16H0z"/><path class="st1" d="M10 5.57l2.465 2.5L16 4.535 12.465 1 8.93 4.535l.5.465H6V1H1v13h13V9h-4z"/></g><g id="icon_x5F_bg"><path class="st2" d="M2 6h3v3H2zM2 10h3v3H2zM2 2h3v3H2z"/><path transform="rotate(-45.001 12.465 4.535)" class="st2" d="M10.965 3.035h3v3h-3z"/><path class="st2" d="M10 10h3v3h-3zM6 6h3v3H6zM6 10h3v3H6z"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 519 B

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M15 14H1V1h14v13z" id="outline"/><path class="icon-vs-bg" d="M14 5v8H2V5h4v2h4V5h4zm0-3H2v2h12V2z" id="iconBg"/><path class="icon-vs-fg" d="M14 4v1h-4v2H6V5H2V4h12z" id="iconFg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 486 B

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

@ -0,0 +1 @@
<svg viewBox="1 1 45 45" focusable="false" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svg="http://www.w3.org/2000/svg" class="" role="presentation" id="FxSymbol0-025" width="100%" height="100%"><g><title></title><path class="msportalfx-svg-c15" d="M15 34H9c-4-1-7.085-4.151-7.085-8.33 0-3.459 2.232-6.617 5.263-7.819a10.937 10.937 0 0 1-.055-1.092c0-5.94 4.817-10.753 10.757-10.753a10.74 10.74 0 0 1 8.376 4.019 8.672 8.672 0 0 1 3.924-.927c4.704 0 8.778 4.173 8.944 8.839l-16.063-6.625L15 16.167V34z"></path><path d="M45 38.435l-22.132 4.218.102-15.559L45 31.179v7.256zM22.945 13.348L45 21.649v8.311l-22.017-4.691" fill="#959595"></path><path d="M22.983 25.268l-5.999 2.789v-11.08l5.96-3.63M44 29l-20-4.563V14.5l20 7.375V29zm-11-3.563l2 .548v-6.478l-2-.7v6.63zm-2-7.268l-2-.722v6.926l2 .564v-6.768zm6 8.363l1.972.52.034-6.175-2.006-.67v6.325zm-12-3.187l2 .515v-7.07l-2-.658v7.213zm18-1.101l-2-.7v6.034l2 .548v-5.882zM24 28.75v12.833l20-3.667v-6.083L24 28.75zm1 11.333v-9.875l2 .25v9.292l-2 .333zm6-1.01l-2 .365v-8.73L31 31v8.073zm2-.288v-7.577l2 .25v6.958l-2 .369zm6-1.052l-2 .34v-6.365L39 32v5.733zm4.068-.647L41 37.444v-5.236l1.931.241.137 4.637z" fill="#b3b4b5"></path><path d="M19.009 25.077l-1 .585v-7.88l1-.538v7.833zM21 16.098l-.997.688L20 24.44l1-.547v-7.795z" fill="#959595"></path><path d="M16.984 39.1l-.038-9.094 6.021-2.912L23 42.691" fill="#b3b4b5"></path><path d="M18.887 39.066l-.91-.463v-7.679l.91-.41v8.552zm2.147-9.518l-.993.435-.038 9.69 1.032.535v-10.66z" fill="#959595"></path><path class="msportalfx-svg-c04" d="M42.981 22.892l-17.977-6.031.029-.779 17.948 6.175v.635zm-.05 9.556l-17.899-2.246v.777l17.916 2.119-.017-.65zM21 17.129v-.982l-2.904 1.611-.039.825L21 17.129zm-3.019 14.369l3.049-1.104.006-.847-3.061 1.374.006.577z"></path><path class="msportalfx-svg-c01" d="M45 21.695v16.74L23 42.65l16.669-22.973L45 21.695z" opacity="0.2"></path></g></svg>

После

Ширина:  |  Высота:  |  Размер: 1.8 KiB

Двоичные данные
images/explorer.gif Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 70 KiB

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#2d2d30}.icon-vs-out{fill:#2d2d30}.icon-vs-bg{fill:#c5c5c5}.icon-vs-fg{fill:#2b282e}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M8 16c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z" id="outline"/><path class="icon-vs-bg" d="M8 1a7 7 0 0 0-6.158 10.33l4.451-4.451L8.414 9l3-3H10V5h3v3h-1V6.829l-3.586 3.586-2.121-2.122-3.894 3.893A6.984 6.984 0 0 0 8 15 7 7 0 1 0 8 1z" id="iconBg"/><path class="icon-vs-fg" d="M12 8V6.829l-3.586 3.586-2.121-2.122-3.894 3.893a7.062 7.062 0 0 1-.558-.856l4.451-4.451L8.414 9l3-3H10V5h3v3h-1z" id="iconFg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 728 B

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.st0{opacity:0}.st0,.st1{fill:#f6f6f6}.st2{fill:#424242}</style><g id="outline"><path class="st0" d="M0 0h16v16H0z"/><path class="st1" d="M10 5.57l2.465 2.5L16 4.535 12.465 1 8.93 4.535l.5.465H6V1H1v13h13V9h-4z"/></g><g id="icon_x5F_bg"><path class="st2" d="M2 6h3v3H2zM2 10h3v3H2zM2 2h3v3H2z"/><path transform="rotate(-45.001 12.465 4.535)" class="st2" d="M10.965 3.035h3v3h-3z"/><path class="st2" d="M10 10h3v3h-3zM6 6h3v3H6zM6 10h3v3H6z"/></g></svg>

После

Ширина:  |  Высота:  |  Размер: 519 B

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

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><style>.icon-canvas-transparent{opacity:0;fill:#f6f6f6}.icon-vs-out{fill:#f6f6f6}.icon-vs-bg{fill:#424242}.icon-vs-fg{fill:#f0eff1}</style><path class="icon-canvas-transparent" d="M16 16H0V0h16v16z" id="canvas"/><path class="icon-vs-out" d="M15 14H1V1h14v13z" id="outline"/><path class="icon-vs-bg" d="M14 5v8H2V5h4v2h4V5h4zm0-3H2v2h12V2z" id="iconBg"/><path class="icon-vs-fg" d="M14 4v1h-4v2H6V5H2V4h12z" id="iconFg"/></svg>

После

Ширина:  |  Высота:  |  Размер: 486 B

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

@ -0,0 +1 @@
<svg viewBox="1 1 45 45" focusable="false" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:svg="http://www.w3.org/2000/svg" class="" role="presentation" id="FxSymbol0-025" width="100%" height="100%"><g><title></title><path class="msportalfx-svg-c15" d="M15 34H9c-4-1-7.085-4.151-7.085-8.33 0-3.459 2.232-6.617 5.263-7.819a10.937 10.937 0 0 1-.055-1.092c0-5.94 4.817-10.753 10.757-10.753a10.74 10.74 0 0 1 8.376 4.019 8.672 8.672 0 0 1 3.924-.927c4.704 0 8.778 4.173 8.944 8.839l-16.063-6.625L15 16.167V34z"></path><path d="M45 38.435l-22.132 4.218.102-15.559L45 31.179v7.256zM22.945 13.348L45 21.649v8.311l-22.017-4.691" fill="#959595"></path><path d="M22.983 25.268l-5.999 2.789v-11.08l5.96-3.63M44 29l-20-4.563V14.5l20 7.375V29zm-11-3.563l2 .548v-6.478l-2-.7v6.63zm-2-7.268l-2-.722v6.926l2 .564v-6.768zm6 8.363l1.972.52.034-6.175-2.006-.67v6.325zm-12-3.187l2 .515v-7.07l-2-.658v7.213zm18-1.101l-2-.7v6.034l2 .548v-5.882zM24 28.75v12.833l20-3.667v-6.083L24 28.75zm1 11.333v-9.875l2 .25v9.292l-2 .333zm6-1.01l-2 .365v-8.73L31 31v8.073zm2-.288v-7.577l2 .25v6.958l-2 .369zm6-1.052l-2 .34v-6.365L39 32v5.733zm4.068-.647L41 37.444v-5.236l1.931.241.137 4.637z" fill="#b3b4b5"></path><path d="M19.009 25.077l-1 .585v-7.88l1-.538v7.833zM21 16.098l-.997.688L20 24.44l1-.547v-7.795z" fill="#959595"></path><path d="M16.984 39.1l-.038-9.094 6.021-2.912L23 42.691" fill="#b3b4b5"></path><path d="M18.887 39.066l-.91-.463v-7.679l.91-.41v8.552zm2.147-9.518l-.993.435-.038 9.69 1.032.535v-10.66z" fill="#959595"></path><path class="msportalfx-svg-c04" d="M42.981 22.892l-17.977-6.031.029-.779 17.948 6.175v.635zm-.05 9.556l-17.899-2.246v.777l17.916 2.119-.017-.65zM21 17.129v-.982l-2.904 1.611-.039.825L21 17.129zm-3.019 14.369l3.049-1.104.006-.847-3.061 1.374.006.577z"></path><path class="msportalfx-svg-c01" d="M45 21.695v16.74L23 42.65l16.669-22.973L45 21.695z" opacity="0.2"></path></g></svg>

После

Ширина:  |  Высота:  |  Размер: 1.8 KiB

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

@ -42,8 +42,10 @@
"onCommand:vscode-docker.compose.up",
"onCommand:vscode-docker.compose.down",
"onCommand:vscode-docker.configure",
"onCommand:vscode-docker.createWebApp",
"onCommand:vscode-docker.debug.configureLaunchJson",
"onCommand:vscode-docker.system.prune",
"onCommand:vscode-docker.dockerHubLogout",
"onView:dockerExplorer"
],
"main": "./out/dockerExtension",
@ -78,87 +80,99 @@
"view/item/context": [
{
"command": "vscode-docker.container.start",
"when": "view == dockerExplorer && viewItem == dockerImage"
"when": "view == dockerExplorer && viewItem == localImageNode"
},
{
"command": "vscode-docker.container.start",
"when": "view == dockerExplorer && viewItem == rootImages"
"when": "view == dockerExplorer && viewItem == imagesRootNode"
},
{
"command": "vscode-docker.container.start.interactive",
"when": "view == dockerExplorer && viewItem == dockerImage"
"when": "view == dockerExplorer && viewItem == localImageNode"
},
{
"command": "vscode-docker.container.start.interactive",
"when": "view == dockerExplorer && viewItem == rootImages"
"when": "view == dockerExplorer && viewItem == imagesRootNode"
},
{
"command": "vscode-docker.image.push",
"when": "view == dockerExplorer && viewItem == dockerImage"
"when": "view == dockerExplorer && viewItem == localImageNode"
},
{
"command": "vscode-docker.image.push",
"when": "view == dockerExplorer && viewItem == rootImages"
"when": "view == dockerExplorer && viewItem == imagesRootNode"
},
{
"command": "vscode-docker.image.remove",
"when": "view == dockerExplorer && viewItem == dockerImage"
"when": "view == dockerExplorer && viewItem == localImageNode"
},
{
"command": "vscode-docker.image.remove",
"when": "view == dockerExplorer && viewItem == rootImages"
"when": "view == dockerExplorer && viewItem == imagesRootNode"
},
{
"command": "vscode-docker.image.inspect",
"when": "view == dockerExplorer && viewItem == dockerImage"
"when": "view == dockerExplorer && viewItem == localImageNode"
},
{
"command": "vscode-docker.image.inspect",
"when": "view == dockerExplorer && viewItem == rootImages"
"when": "view == dockerExplorer && viewItem == imagesRootNode"
},
{
"command": "vscode-docker.image.tag",
"when": "view == dockerExplorer && viewItem == dockerImage"
"when": "view == dockerExplorer && viewItem == localImageNode"
},
{
"command": "vscode-docker.image.tag",
"when": "view == dockerExplorer && viewItem == rootImages"
"when": "view == dockerExplorer && viewItem == imagesRootNode"
},
{
"command": "vscode-docker.container.stop",
"when": "view == dockerExplorer && viewItem == dockerContainerRunning"
"when": "view == dockerExplorer && viewItem == runningLocalContainerNode"
},
{
"command": "vscode-docker.container.stop",
"when": "view == dockerExplorer && viewItem == dockerContainersLabel"
"when": "view == dockerExplorer && viewItem == containersRootNode"
},
{
"command": "vscode-docker.container.show-logs",
"when": "view == dockerExplorer && viewItem == dockerContainerRunning"
"when": "view == dockerExplorer && viewItem == runningLocalContainerNode"
},
{
"command": "vscode-docker.container.show-logs",
"when": "view == dockerExplorer && viewItem == dockerContainersLabel"
"when": "view == dockerExplorer && viewItem == containersRootNode"
},
{
"command": "vscode-docker.container.open-shell",
"when": "view == dockerExplorer && viewItem == dockerContainerRunning"
"when": "view == dockerExplorer && viewItem == runningLocalContainerNode"
},
{
"command": "vscode-docker.container.open-shell",
"when": "view == dockerExplorer && viewItem == dockerContainersLabel"
"when": "view == dockerExplorer && viewItem == containersRootNode"
},
{
"command": "vscode-docker.container.remove",
"when": "view == dockerExplorer && viewItem == dockerContainerStopped"
"when": "view == dockerExplorer && viewItem == stoppedLocalContainerNode"
},
{
"command": "vscode-docker.container.remove",
"when": "view == dockerExplorer && viewItem == dockerContainerRunning"
"when": "view == dockerExplorer && viewItem == runningLocalContainerNode"
},
{
"command": "vscode-docker.container.remove",
"when": "view == dockerExplorer && viewItem == rootContainers"
"when": "view == dockerExplorer && viewItem == containersRootNode"
},
{
"command": "vscode-docker.createWebApp",
"when": "view == dockerExplorer && viewItem == azureImageTag"
},
{
"command": "vscode-docker.createWebApp",
"when": "view == dockerExplorer && viewItem == dockerHubImageTag"
},
{
"command": "vscode-docker.dockerHubLogout",
"when": "view == dockerExplorer && viewItem == dockerHubRootNode"
}
]
},
@ -376,6 +390,16 @@
"light": "images/light/refresh.svg",
"dark": "images/dark/refresh.svg"
}
},
{
"command": "vscode-docker.createWebApp",
"title": "Deploy Image to Azure App Service",
"category": "Docker"
},
{
"command": "vscode-docker.dockerHubLogout",
"title": "DockerHub Logout",
"category": "Docker"
}
],
"views": {
@ -398,17 +422,23 @@
},
"extensionDependencies": [
"vscode.docker",
"vscode.yaml"
"vscode.yaml",
"ms-vscode.azure-account"
],
"devDependencies": {
"vscode": "^1.0.0",
"typescript": "^2.1.5",
"@types/node": "^6.0.40"
"@types/node": "^6.0.40",
"@types/keytar": "^4.0.1"
},
"dependencies": {
"dockerfile-language-server-nodejs": "^0.0.7",
"dockerode": "^2.5.1",
"vscode-extension-telemetry": "^0.0.6",
"vscode-languageclient": "^3.1.0"
"vscode-languageclient": "^3.1.0",
"azure-arm-containerregistry": "^1.0.0-preview",
"azure-arm-resource": "^2.0.0-preview",
"azure-arm-website": "^1.0.0-preview",
"request-promise": "^4.2.2"
}
}
}

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

@ -78,3 +78,21 @@ LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE A
NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
5. request-promise version 4.2.2 (https://github.com/request/request-promise)
ISC License
Copyright (c) 2017, Nicolai Kamenzky, Ty Abonil, and contributors
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.)

39
typings/azure-account.api.d.ts поставляемый Normal file
Просмотреть файл

@ -0,0 +1,39 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
import { Event } from 'vscode';
import { ServiceClientCredentials } from 'ms-rest';
import { AzureEnvironment } from 'ms-rest-azure';
import { SubscriptionModels } from 'azure-arm-resource';
export type AzureLoginStatus = 'Initializing' | 'LoggingIn' | 'LoggedIn' | 'LoggedOut';
export interface AzureAccount {
readonly status: AzureLoginStatus;
readonly onStatusChanged: Event<AzureLoginStatus>;
readonly waitForLogin: () => Promise<boolean>;
readonly sessions: AzureSession[];
readonly onSessionsChanged: Event<void>;
readonly filters: AzureResourceFilter[];
readonly onFiltersChanged: Event<void>;
}
export interface AzureSession {
readonly environment: AzureEnvironment;
readonly userId: string;
readonly tenantId: string;
readonly credentials: ServiceClientCredentials;
}
export interface AzureResourceFilter {
readonly session: AzureSession;
readonly subscription: SubscriptionModels.Subscription;
}
export interface Credentials {
readSecret(service: string, account: string): Thenable<string | undefined>;
writeSecret(service: string, account: string, secret: string): Thenable<void>;
deleteSecret(service: string, account: string): Thenable<boolean>;
}