From a7be0b3db1f2de7897abb70e446ec371ec7951f4 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Wed, 25 Jul 2018 10:22:54 -0700 Subject: [PATCH 01/77] Added Azure Credentials Manager Singleton (#18) * Added Azure Credentials Manager Singleton * Added getResourceManagementClient --- dockerExtension.ts | 5 ++ utils/azureCredentialsManager.ts | 140 +++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 utils/azureCredentialsManager.ts diff --git a/dockerExtension.ts b/dockerExtension.ts index 55bb705a87..e812280618 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -40,6 +40,7 @@ import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtil import { ext } from "./extensionVariables"; import { Reporter } from './telemetry/telemetry'; import { AzureAccount } from './typings/azure-account.api'; +import { AzureCredentialsManager } from './utils/azureCredentialsManager'; 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}'; @@ -142,6 +143,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider())); + if (azureAccount) { + AzureCredentialsManager.getInstance().setAccount(azureAccount); + } + activateLanguageClient(ctx); } diff --git a/utils/azureCredentialsManager.ts b/utils/azureCredentialsManager.ts new file mode 100644 index 0000000000..6ab1c8eae9 --- /dev/null +++ b/utils/azureCredentialsManager.ts @@ -0,0 +1,140 @@ +import { SubscriptionClient, ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; +import { AzureAccount } from '../typings/azure-account.api'; +import { ServiceClientCredentials } from 'ms-rest'; +import { AsyncPool } from '../utils/asyncpool'; +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import * as ContainerModels from '../node_modules/azure-arm-containerregistry/lib/models'; +import { ResourceGroup, ResourceGroupListResult } from "azure-arm-resource/lib/resource/models"; +import { MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from './constants'; + +/* Singleton for facilitating communication with Azure account services by providing extended shared + functionality and extension wide access to azureAccount. Tool for internal use. + Authors: Esteban Rey L, Jackson Stokes +*/ + +export class AzureCredentialsManager { + + //SETUP + private static _instance: AzureCredentialsManager = new AzureCredentialsManager(); + private azureAccount: AzureAccount; + + private constructor() { + AzureCredentialsManager._instance = this; + } + + public static getInstance(): AzureCredentialsManager { + if (!AzureCredentialsManager._instance) { // lazy initialization + AzureCredentialsManager._instance = new AzureCredentialsManager(); + } + return AzureCredentialsManager._instance; + } + + //This function has to be called explicitly before using the singleton. + public setAccount(azureAccount) { + this.azureAccount = azureAccount; + } + + //GETTERS + public getAccount() { + if (this.azureAccount) return this.azureAccount; + throw ('Azure account is not present, you may have forgotten to call setAccount'); + } + + public getFilteredSubscriptionList(): SubscriptionModels.Subscription[] { + return this.getAccount().filters.map(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 + }; + }); + } + + public getContainerRegistryManagementClient(subscription: SubscriptionModels.Subscription): ContainerRegistryManagementClient { + return new ContainerRegistryManagementClient(this.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + } + + public getResourceManagementClient(subscription: SubscriptionModels.Subscription): ResourceManagementClient { + return new ResourceManagementClient(this.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + } + + public async getRegistries(subscription?: SubscriptionModels.Subscription, resourceGroup?: string, sortFunction?): Promise { + let registries: ContainerModels.Registry[] = []; + + if (subscription && resourceGroup) { + //Get all registries under one resourcegroup + const client = this.getContainerRegistryManagementClient(subscription); + registries = await client.registries.listByResourceGroup(resourceGroup); + + } else if (subscription) { + //Get all registries under one subscription + const client = this.getContainerRegistryManagementClient(subscription); + registries = await client.registries.list(); + + } else { + //Get all registries for all subscriptions + const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptionList(); + const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); + + for (let i = 0; i < subs.length; i++) { + subPool.addTask(async () => { + const client = this.getContainerRegistryManagementClient(subs[i]); + let subscriptionRegistries: ContainerModels.Registry[] = await client.registries.list(); + registries = registries.concat(subscriptionRegistries); + }); + } + await subPool.runAll(); + } + + if (sortFunction && registries.length > 1) { + registries.sort(sortFunction); + } + + return registries; + } + + public async getResourceGroups(subscription?: SubscriptionModels.Subscription): Promise { + if (subscription) { + const resourceClient = this.getResourceManagementClient(subscription); + return await resourceClient.resourceGroups.list(); + } + const subs = this.getFilteredSubscriptionList(); + const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); + let resourceGroups: ResourceGroup[] = []; + //Acquire each subscription's data simultaneously + for (let i = 0; i < subs.length; i++) { + subPool.addTask(async () => { + const resourceClient = this.getResourceManagementClient(subs[i]); + const internalGroups = await resourceClient.resourceGroups.list(); + resourceGroups = resourceGroups.concat(internalGroups); + }); + } + await subPool.runAll(); + return resourceGroups; + } + + public getCredentialByTenantId(tenantId: string): ServiceClientCredentials { + + const session = this.getAccount().sessions.find((azureSession) => azureSession.tenantId.toLowerCase() === tenantId.toLowerCase()); + + if (session) { + return session.credentials; + } + + throw new Error(`Failed to get credentials, tenant ${tenantId} not found.`); + } + + //CHECKS + //Provides a unified check for login that should be called once before using the rest of the singletons capabilities + public async isLoggedIn(): Promise { + if (!this.azureAccount) { + return false; + } + return await this.azureAccount.waitForLogin(); + } +} From b2e5740718ae33d00a198f01efd267f5217bbec8 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Wed, 25 Jul 2018 13:32:09 -0700 Subject: [PATCH 02/77] Sorted Existing Create Registry ready for code review --- commands/azureCommands/create-registry.ts | 133 ++++++++++++++++++++++ dockerExtension.ts | 3 +- package.json | 10 ++ 3 files changed, 145 insertions(+), 1 deletion(-) create mode 100644 commands/azureCommands/create-registry.ts diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts new file mode 100644 index 0000000000..d9fddfe0cb --- /dev/null +++ b/commands/azureCommands/create-registry.ts @@ -0,0 +1,133 @@ + +import * as vscode from "vscode"; +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; +import { RegistryNameStatus } from "azure-arm-containerregistry/lib/models"; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; +import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; + +const teleCmdId: string = 'vscode-docker.createRegistry'; + +export async function createRegistry() { + let subscription: SubscriptionModels.Subscription; + let resourceGroup: ResourceGroup; + try { + subscription = await acquireSubscription(); + resourceGroup = await acquireResourceGroup(subscription); + } catch (error) { + return; + } + const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); + + let registryName: string; + try { + registryName = await acquireRegistryName(client); + } catch (error) { + return; + } + + const sku: string = await vscode.window.showInputBox({ + ignoreFocusOut: false, + placeHolder: 'Basic', + value: 'Basic', + prompt: 'SKU? ' + }); + + client.registries.beginCreate(resourceGroup.name, registryName, { 'sku': { 'name': sku }, 'location': resourceGroup.location }).then(function (response) { + vscode.window.showInformationMessage(response.name + ' has been created succesfully!'); + }, function (error) { + vscode.window.showErrorMessage(error.message); + }) + +} + +// INPUT HELPERS +async function acquireSubscription(): Promise { + let subscription: SubscriptionModels.Subscription; + const subs = AzureCredentialsManager.getInstance().getFilteredSubscriptionList(); + + let subsNames: string[] = []; + for (let i = 0; i < subs.length; i++) { + subsNames.push(subs[i].displayName); + } + let subscriptionName: string; + do { + subscriptionName = await vscode.window.showQuickPick(subsNames, { 'canPickMany': false, 'placeHolder': 'Choose a subscription to be used' }); + + if (subscriptionName === undefined) throw 'User exit'; + } while (!subscriptionName); + + + return subs.find(sub => { return sub.displayName === subscriptionName }); +} + +async function acquireResourceGroup(subscription: SubscriptionModels.Subscription): Promise { + //Acquire each subscription's data simultaneously + let resourceGroup; + let resourceGroupName; + const resourceGroupClient = new ResourceManagementClient(AzureCredentialsManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + let resourceGroups = await AzureCredentialsManager.getInstance().getResourceGroups(subscription); + + let resourceGroupNames: string[] = []; + resourceGroupNames.push('+ Create new resource group'); + for (let i = 0; i < resourceGroups.length; i++) { + resourceGroupNames.push(resourceGroups[i].name); + } + + do { + resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, { 'canPickMany': false, 'placeHolder': 'Choose a Resource Group to be used' }); + if (resourceGroupName === undefined) throw 'user Exit'; + if (resourceGroupName === '+ Create new resource group') { + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: false, + prompt: 'Resource group name? ' + }; + resourceGroupName = await vscode.window.showInputBox(opt); + let resourceGroupStatus: boolean = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); + while (resourceGroupStatus) { + opt = { + ignoreFocusOut: false, + prompt: "That resource group name is already in existence. Try again: " + } + resourceGroupName = await vscode.window.showInputBox(opt); + if (resourceGroupName === undefined) throw 'user Exit'; + resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); + } + + let newResourceGroup: ResourceGroup = { + name: resourceGroupName, + location: 'West US', + }; + await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); + } + + resourceGroups = await resourceGroupClient.resourceGroups.list(); + resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); + if (!resourceGroupName) vscode.window.showErrorMessage('You must select a valid resource group'); + } while (!resourceGroupName); + + return resourceGroup; +} + +async function acquireRegistryName(client: ContainerRegistryManagementClient) { + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: false, + prompt: 'Registry name? ' + }; + let registryName: string = await vscode.window.showInputBox(opt); + + let registryStatus: RegistryNameStatus = await client.registries.checkNameAvailability({ 'name': registryName }); + while (!registryStatus.nameAvailable) { + opt = { + ignoreFocusOut: false, + prompt: "That registry name is unavailable. Try again: " + } + registryName = await vscode.window.showInputBox(opt); + + if (registryName === undefined) throw 'user Exit'; + + registryStatus = await client.registries.checkNameAvailability({ 'name': registryName }); + } + return registryName; +} + diff --git a/dockerExtension.ts b/dockerExtension.ts index e812280618..2cac6f0be5 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -25,6 +25,7 @@ import { DockerDebugConfigProvider } from './configureWorkspace/configDebugProvi import { configure } from './configureWorkspace/configure'; import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider'; import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider'; +import { createRegistry } from './commands/azureCommands/create-registry'; import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo'; import { DockerComposeParser } from './dockerCompose/dockerComposeParser'; import { DockerfileCompletionItemProvider } from './dockerfile/dockerfileCompletionItemProvider'; @@ -116,7 +117,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.down', composeDown)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.restart', composeRestart)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.system.prune', systemPrune)); - + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createRegistry', createRegistry)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { if (context) { if (azureAccount) { diff --git a/package.json b/package.json index f082c19c28..4b7423da6e 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ "onCommand:vscode-docker.compose.restart", "onCommand:vscode-docker.configure", "onCommand:vscode-docker.createWebApp", + "onCommand:vscode-docker.createRegistry", "onCommand:vscode-docker.system.prune", "onCommand:vscode-docker.dockerHubLogout", "onCommand:vscode-docker.browseDockerHub", @@ -247,6 +248,10 @@ "command": "vscode-docker.createWebApp", "when": "view == dockerExplorer && viewItem == dockerHubImageTag" }, + { + "command": "vscode-docker.createRegistry", + "when": "view == dockerExplorer && viewItem == azureRegistryRootNode" + }, { "command": "vscode-docker.dockerHubLogout", "when": "view == dockerExplorer && viewItem == dockerHubRootNode" @@ -567,6 +572,11 @@ "description": "Restarts a composition of containers", "category": "Docker" }, + { + "command": "vscode-docker.createRegistry", + "title": "Create Registry", + "category": "Docker" + }, { "command": "vscode-docker.image.push", "title": "Push", From ef1ccfa9ab8b3aecf101376af1784f3c87aa265e Mon Sep 17 00:00:00 2001 From: rsamai Date: Wed, 25 Jul 2018 15:03:54 -0700 Subject: [PATCH 03/77] Added acquiring telemetry data for create registry --- commands/azureCommands/create-registry.ts | 29 ++++++++++++++++++++++- typings/vscode-extension-telemetry.d.ts | 2 +- 2 files changed, 29 insertions(+), 2 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index d9fddfe0cb..ae070627cf 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -5,9 +5,13 @@ import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource import { RegistryNameStatus } from "azure-arm-containerregistry/lib/models"; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; - +import { reporter } from '../../telemetry/telemetry'; +import TelemetryReporter from "../../node_modules/vscode-extension-telemetry"; +const teleAzureId: string = 'vscode-docker.create.registry.azureContainerRegistry'; const teleCmdId: string = 'vscode-docker.createRegistry'; + + export async function createRegistry() { let subscription: SubscriptionModels.Subscription; let resourceGroup: ResourceGroup; @@ -39,6 +43,29 @@ export async function createRegistry() { vscode.window.showErrorMessage(error.message); }) + //Acquiring telemetry data here + if (reporter) { + /* __GDPR__ + "command" : { + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + + if (registryName.toLowerCase().indexOf('azurecr.io')) { + /* __GDPR__ + "command" : { + "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } + */ + reporter.sendTelemetryEvent('command', { + command: teleAzureId + }); + } + } + } // INPUT HELPERS diff --git a/typings/vscode-extension-telemetry.d.ts b/typings/vscode-extension-telemetry.d.ts index 216d4d0134..76722886f8 100644 --- a/typings/vscode-extension-telemetry.d.ts +++ b/typings/vscode-extension-telemetry.d.ts @@ -4,4 +4,4 @@ declare module 'vscode-extension-telemetry' { sendTelemetryEvent(eventName: string, properties?: { [key: string]: string }, measures?: { [key: string]: number }): void; dispose(); } -} \ No newline at end of file +} From 1fca0dad20f127b4aadcf7a98d0e016695db445d Mon Sep 17 00:00:00 2001 From: rsamai Date: Wed, 25 Jul 2018 19:02:28 -0700 Subject: [PATCH 04/77] broke up createnewresourcegroup method and fixed client use Added try catch loop and awaited for resource group list again to check for duplicates with ResourceManagementClient --- commands/azureCommands/create-registry.ts | 86 +++++++++++++---------- 1 file changed, 50 insertions(+), 36 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index ae070627cf..725cdb4b16 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -6,7 +6,6 @@ import { RegistryNameStatus } from "azure-arm-containerregistry/lib/models"; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; import { reporter } from '../../telemetry/telemetry'; -import TelemetryReporter from "../../node_modules/vscode-extension-telemetry"; const teleAzureId: string = 'vscode-docker.create.registry.azureContainerRegistry'; const teleCmdId: string = 'vscode-docker.createRegistry'; @@ -68,6 +67,28 @@ export async function createRegistry() { } +async function acquireRegistryName(client: ContainerRegistryManagementClient) { + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: false, + prompt: 'Registry name? ' + }; + let registryName: string = await vscode.window.showInputBox(opt); + + let registryStatus: RegistryNameStatus = await client.registries.checkNameAvailability({ 'name': registryName }); + while (!registryStatus.nameAvailable) { + opt = { + ignoreFocusOut: false, + prompt: "That registry name is unavailable. Try again: " + } + registryName = await vscode.window.showInputBox(opt); + + if (registryName === undefined) throw 'user Exit'; + + registryStatus = await client.registries.checkNameAvailability({ 'name': registryName }); + } + return registryName; +} + // INPUT HELPERS async function acquireSubscription(): Promise { let subscription: SubscriptionModels.Subscription; @@ -105,56 +126,49 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, { 'canPickMany': false, 'placeHolder': 'Choose a Resource Group to be used' }); if (resourceGroupName === undefined) throw 'user Exit'; if (resourceGroupName === '+ Create new resource group') { - let opt: vscode.InputBoxOptions = { - ignoreFocusOut: false, - prompt: 'Resource group name? ' - }; - resourceGroupName = await vscode.window.showInputBox(opt); - let resourceGroupStatus: boolean = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); - while (resourceGroupStatus) { - opt = { - ignoreFocusOut: false, - prompt: "That resource group name is already in existence. Try again: " - } - resourceGroupName = await vscode.window.showInputBox(opt); - if (resourceGroupName === undefined) throw 'user Exit'; - resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); - } - - let newResourceGroup: ResourceGroup = { - name: resourceGroupName, - location: 'West US', - }; - await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); + resourceGroupName = await createNewResourceGroup(resourceGroupClient); } - - resourceGroups = await resourceGroupClient.resourceGroups.list(); + resourceGroups = await AzureCredentialsManager.getInstance().getResourceGroups(subscription); resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); + if (!resourceGroupName) vscode.window.showErrorMessage('You must select a valid resource group'); } while (!resourceGroupName); return resourceGroup; } -async function acquireRegistryName(client: ContainerRegistryManagementClient) { +async function createNewResourceGroup(resourceGroupClient: ResourceManagementClient): Promise { + let opt: vscode.InputBoxOptions = { ignoreFocusOut: false, - prompt: 'Registry name? ' + prompt: 'Resource group name? ' }; - let registryName: string = await vscode.window.showInputBox(opt); - - let registryStatus: RegistryNameStatus = await client.registries.checkNameAvailability({ 'name': registryName }); - while (!registryStatus.nameAvailable) { + let resourceGroupName: string = await vscode.window.showInputBox(opt); + let resourceGroupStatus: boolean = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); + while (resourceGroupStatus) { opt = { ignoreFocusOut: false, - prompt: "That registry name is unavailable. Try again: " + prompt: "That resource group name is already in existence. Try again: " } - registryName = await vscode.window.showInputBox(opt); - - if (registryName === undefined) throw 'user Exit'; + resourceGroupName = await vscode.window.showInputBox(opt); + if (resourceGroupName === undefined) throw 'user Exit'; + resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); + } - registryStatus = await client.registries.checkNameAvailability({ 'name': registryName }); + let newResourceGroup: ResourceGroup = { + name: resourceGroupName, + location: 'West US', + }; + //Potential error when two clients try to create same resource group name at once + try { + await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); + } catch (error) { + vscode.window.showErrorMessage(error.message); } - return registryName; + + return resourceGroupName; } + + + From 60f68d3a5185d3cd8f96522141a4f518cf0562ef Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 26 Jul 2018 09:06:31 -0700 Subject: [PATCH 05/77] Jackson esteban/unified client nit Fix (#24) * Added Azure Credentials Manager Singleton * Small Style Fixes * Further Style fixes, added getResourceManagementClient * Lazy Initialization Patches --- utils/azureCredentialsManager.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/utils/azureCredentialsManager.ts b/utils/azureCredentialsManager.ts index 6ab1c8eae9..9c6062adf1 100644 --- a/utils/azureCredentialsManager.ts +++ b/utils/azureCredentialsManager.ts @@ -15,12 +15,10 @@ import { MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from './constants'; export class AzureCredentialsManager { //SETUP - private static _instance: AzureCredentialsManager = new AzureCredentialsManager(); + private static _instance: AzureCredentialsManager; private azureAccount: AzureAccount; - private constructor() { - AzureCredentialsManager._instance = this; - } + private constructor() { } public static getInstance(): AzureCredentialsManager { if (!AzureCredentialsManager._instance) { // lazy initialization From 26f00e266a62b4286690cf767aafa90f06dd3094 Mon Sep 17 00:00:00 2001 From: rsamai Date: Thu, 26 Jul 2018 12:01:20 -0700 Subject: [PATCH 06/77] Enabled location selection --- commands/azureCommands/create-registry.ts | 35 +++++++++++++++++++---- utils/azureCredentialsManager.ts | 7 +++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 725cdb4b16..30c6283fb6 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -14,9 +14,13 @@ const teleCmdId: string = 'vscode-docker.createRegistry'; export async function createRegistry() { let subscription: SubscriptionModels.Subscription; let resourceGroup: ResourceGroup; + let location: string; + try { subscription = await acquireSubscription(); - resourceGroup = await acquireResourceGroup(subscription); + location = await acquireLocation(subscription); + resourceGroup = await acquireResourceGroup(location, subscription); + } catch (error) { return; } @@ -36,7 +40,7 @@ export async function createRegistry() { prompt: 'SKU? ' }); - client.registries.beginCreate(resourceGroup.name, registryName, { 'sku': { 'name': sku }, 'location': resourceGroup.location }).then(function (response) { + client.registries.beginCreate(resourceGroup.name, registryName, { 'sku': { 'name': sku }, 'location': location }).then(function (response) { vscode.window.showInformationMessage(response.name + ' has been created succesfully!'); }, function (error) { vscode.window.showErrorMessage(error.message); @@ -109,7 +113,26 @@ async function acquireSubscription(): Promise { return subs.find(sub => { return sub.displayName === subscriptionName }); } -async function acquireResourceGroup(subscription: SubscriptionModels.Subscription): Promise { +async function acquireLocation(subscription): Promise { + let locations: SubscriptionModels.Location[] = await AzureCredentialsManager.getInstance().getLocationsBySubscription(subscription); + let locationNames: string[] = []; + + for (let i = 0; i < locations.length; i++) { + locationNames.push(locations[i].displayName); + } + let location: string; + do { + location = await vscode.window.showQuickPick(locationNames, { 'canPickMany': false, 'placeHolder': 'Choose a location' }); + if (location === undefined) throw 'User exit'; + } while (!location); + + let num = locationNames.indexOf(location); + return locations[num].name; +} + + + +async function acquireResourceGroup(loc: string, subscription: SubscriptionModels.Subscription): Promise { //Acquire each subscription's data simultaneously let resourceGroup; let resourceGroupName; @@ -126,7 +149,7 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, { 'canPickMany': false, 'placeHolder': 'Choose a Resource Group to be used' }); if (resourceGroupName === undefined) throw 'user Exit'; if (resourceGroupName === '+ Create new resource group') { - resourceGroupName = await createNewResourceGroup(resourceGroupClient); + resourceGroupName = await createNewResourceGroup(loc, resourceGroupClient); } resourceGroups = await AzureCredentialsManager.getInstance().getResourceGroups(subscription); resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); @@ -137,7 +160,7 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio return resourceGroup; } -async function createNewResourceGroup(resourceGroupClient: ResourceManagementClient): Promise { +async function createNewResourceGroup(loc: string, resourceGroupClient: ResourceManagementClient): Promise { let opt: vscode.InputBoxOptions = { ignoreFocusOut: false, @@ -157,7 +180,7 @@ async function createNewResourceGroup(resourceGroupClient: ResourceManagementCli let newResourceGroup: ResourceGroup = { name: resourceGroupName, - location: 'West US', + location: loc, }; //Potential error when two clients try to create same resource group name at once try { diff --git a/utils/azureCredentialsManager.ts b/utils/azureCredentialsManager.ts index 6ab1c8eae9..8899033d91 100644 --- a/utils/azureCredentialsManager.ts +++ b/utils/azureCredentialsManager.ts @@ -129,6 +129,13 @@ export class AzureCredentialsManager { throw new Error(`Failed to get credentials, tenant ${tenantId} not found.`); } + public async getLocationsBySubscription(subscription: SubscriptionModels.Subscription): Promise { + const credential = this.getCredentialByTenantId(subscription.tenantId); + const client = new SubscriptionClient(credential); + const locations = (await client.subscriptions.listLocations(subscription.subscriptionId)); + return locations; + } + //CHECKS //Provides a unified check for login that should be called once before using the rest of the singletons capabilities public async isLoggedIn(): Promise { From fe0fe2f7645b06204dabc56ddc81ff8bc0606bd4 Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 30 Jul 2018 11:45:32 -0700 Subject: [PATCH 07/77] Location request fixes -Changed order of questions asked to user for better UX (location of new res group & location of new registry) -Placeholder of location is display name view --- commands/azureCommands/create-registry.ts | 91 ++++++++++++----------- utils/azureCredentialsManager.ts | 28 +++---- 2 files changed, 63 insertions(+), 56 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 30c6283fb6..af121b9647 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -1,25 +1,22 @@ -import * as vscode from "vscode"; import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; import { RegistryNameStatus } from "azure-arm-containerregistry/lib/models"; +import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; -import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import * as vscode from "vscode"; import { reporter } from '../../telemetry/telemetry'; +import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; const teleAzureId: string = 'vscode-docker.create.registry.azureContainerRegistry'; const teleCmdId: string = 'vscode-docker.createRegistry'; - - -export async function createRegistry() { +export async function createRegistry(): Promise { let subscription: SubscriptionModels.Subscription; let resourceGroup: ResourceGroup; let location: string; try { subscription = await acquireSubscription(); - location = await acquireLocation(subscription); - resourceGroup = await acquireResourceGroup(location, subscription); + resourceGroup = await acquireResourceGroup(subscription); } catch (error) { return; @@ -40,9 +37,11 @@ export async function createRegistry() { prompt: 'SKU? ' }); - client.registries.beginCreate(resourceGroup.name, registryName, { 'sku': { 'name': sku }, 'location': location }).then(function (response) { + location = await acquireLocation(resourceGroup, subscription); + + client.registries.beginCreate(resourceGroup.name, registryName, { 'sku': { 'name': sku }, 'location': location }).then((response): void => { vscode.window.showInformationMessage(response.name + ' has been created succesfully!'); - }, function (error) { + }, (error): void => { vscode.window.showErrorMessage(error.message); }) @@ -71,7 +70,7 @@ export async function createRegistry() { } -async function acquireRegistryName(client: ContainerRegistryManagementClient) { +async function acquireRegistryName(client: ContainerRegistryManagementClient): Promise { let opt: vscode.InputBoxOptions = { ignoreFocusOut: false, prompt: 'Registry name? ' @@ -86,7 +85,7 @@ async function acquireRegistryName(client: ContainerRegistryManagementClient) { } registryName = await vscode.window.showInputBox(opt); - if (registryName === undefined) throw 'user Exit'; + if (registryName === undefined) { throw new Error('user Exit'); } registryStatus = await client.registries.checkNameAvailability({ 'name': registryName }); } @@ -99,40 +98,52 @@ async function acquireSubscription(): Promise { const subs = AzureCredentialsManager.getInstance().getFilteredSubscriptionList(); let subsNames: string[] = []; - for (let i = 0; i < subs.length; i++) { - subsNames.push(subs[i].displayName); + for (let sub of subs) { + subsNames.push(sub.displayName); } let subscriptionName: string; - do { - subscriptionName = await vscode.window.showQuickPick(subsNames, { 'canPickMany': false, 'placeHolder': 'Choose a subscription to be used' }); - - if (subscriptionName === undefined) throw 'User exit'; - } while (!subscriptionName); - + subscriptionName = await vscode.window.showQuickPick(subsNames, { 'canPickMany': false, 'placeHolder': 'Choose a subscription to be used' }); + if (subscriptionName === undefined) { throw new Error('User exit'); } return subs.find(sub => { return sub.displayName === subscriptionName }); } -async function acquireLocation(subscription): Promise { +async function acquireLocation(resourceGroup: ResourceGroup, subscription: SubscriptionModels.Subscription): Promise { let locations: SubscriptionModels.Location[] = await AzureCredentialsManager.getInstance().getLocationsBySubscription(subscription); let locationNames: string[] = []; + let placeHolder: string; + + for (let loc of locations) { + locationNames.push(loc.displayName); + } + + locationNames.sort((loc1: string, loc2: string): number => { + return loc1.localeCompare(loc2); + }); + + if (resourceGroup === undefined) { + placeHolder = "Choose location for your new resource group"; + } else { + placeHolder = resourceGroup.location; + + //makes placeholder the Display Name version of the location's name + locations.forEach((locObj: SubscriptionModels.Location): string => { + if (locObj.name === resourceGroup.location) { + placeHolder = locObj.displayName; + return; + } + }); - for (let i = 0; i < locations.length; i++) { - locationNames.push(locations[i].displayName); } let location: string; do { - location = await vscode.window.showQuickPick(locationNames, { 'canPickMany': false, 'placeHolder': 'Choose a location' }); - if (location === undefined) throw 'User exit'; + location = await vscode.window.showQuickPick(locationNames, { 'canPickMany': false, 'placeHolder': placeHolder }); + if (location === undefined) { throw new Error('User exit'); } } while (!location); - - let num = locationNames.indexOf(location); - return locations[num].name; + return location; } - - -async function acquireResourceGroup(loc: string, subscription: SubscriptionModels.Subscription): Promise { +async function acquireResourceGroup(subscription: SubscriptionModels.Subscription): Promise { //Acquire each subscription's data simultaneously let resourceGroup; let resourceGroupName; @@ -141,20 +152,21 @@ async function acquireResourceGroup(loc: string, subscription: SubscriptionModel let resourceGroupNames: string[] = []; resourceGroupNames.push('+ Create new resource group'); - for (let i = 0; i < resourceGroups.length; i++) { - resourceGroupNames.push(resourceGroups[i].name); + for (let resGroupName of resourceGroups) { + resourceGroupNames.push(resGroupName.name); } do { resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, { 'canPickMany': false, 'placeHolder': 'Choose a Resource Group to be used' }); - if (resourceGroupName === undefined) throw 'user Exit'; + if (resourceGroupName === undefined) { throw new Error('user Exit'); } if (resourceGroupName === '+ Create new resource group') { + let loc = await acquireLocation(resourceGroup, subscription); resourceGroupName = await createNewResourceGroup(loc, resourceGroupClient); } resourceGroups = await AzureCredentialsManager.getInstance().getResourceGroups(subscription); resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); - if (!resourceGroupName) vscode.window.showErrorMessage('You must select a valid resource group'); + if (!resourceGroupName) { vscode.window.showErrorMessage('You must select a valid resource group'); } } while (!resourceGroupName); return resourceGroup; @@ -174,7 +186,7 @@ async function createNewResourceGroup(loc: string, resourceGroupClient: Resource prompt: "That resource group name is already in existence. Try again: " } resourceGroupName = await vscode.window.showInputBox(opt); - if (resourceGroupName === undefined) throw 'user Exit'; + if (resourceGroupName === undefined) { throw new Error('user Exit'); } resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); } @@ -186,12 +198,7 @@ async function createNewResourceGroup(loc: string, resourceGroupClient: Resource try { await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); } catch (error) { - vscode.window.showErrorMessage(error.message); + vscode.window.showErrorMessage("That resource group name is already in existence. Try again"); } - return resourceGroupName; } - - - - diff --git a/utils/azureCredentialsManager.ts b/utils/azureCredentialsManager.ts index 8899033d91..de09946bd4 100644 --- a/utils/azureCredentialsManager.ts +++ b/utils/azureCredentialsManager.ts @@ -1,10 +1,10 @@ -import { SubscriptionClient, ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; -import { AzureAccount } from '../typings/azure-account.api'; -import { ServiceClientCredentials } from 'ms-rest'; -import { AsyncPool } from '../utils/asyncpool'; import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import * as ContainerModels from '../node_modules/azure-arm-containerregistry/lib/models'; +import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup, ResourceGroupListResult } from "azure-arm-resource/lib/resource/models"; +import { ServiceClientCredentials } from 'ms-rest'; +import * as ContainerModels from '../node_modules/azure-arm-containerregistry/lib/models'; +import { AzureAccount } from '../typings/azure-account.api'; +import { AsyncPool } from '../utils/asyncpool'; import { MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from './constants'; /* Singleton for facilitating communication with Azure account services by providing extended shared @@ -30,14 +30,14 @@ export class AzureCredentialsManager { } //This function has to be called explicitly before using the singleton. - public setAccount(azureAccount) { + public setAccount(azureAccount: AzureAccount): void { this.azureAccount = azureAccount; } //GETTERS - public getAccount() { - if (this.azureAccount) return this.azureAccount; - throw ('Azure account is not present, you may have forgotten to call setAccount'); + public getAccount(): AzureAccount { + if (this.azureAccount) { return this.azureAccount; } + throw new Error(('Azure account is not present, you may have forgotten to call setAccount')); } public getFilteredSubscriptionList(): SubscriptionModels.Subscription[] { @@ -63,7 +63,7 @@ export class AzureCredentialsManager { return new ResourceManagementClient(this.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); } - public async getRegistries(subscription?: SubscriptionModels.Subscription, resourceGroup?: string, sortFunction?): Promise { + public async getRegistries(subscription?: SubscriptionModels.Subscription, resourceGroup?: string, sortFunction?: any): Promise { let registries: ContainerModels.Registry[] = []; if (subscription && resourceGroup) { @@ -81,9 +81,9 @@ export class AzureCredentialsManager { const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptionList(); const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); - for (let i = 0; i < subs.length; i++) { + for (let sub of subs) { subPool.addTask(async () => { - const client = this.getContainerRegistryManagementClient(subs[i]); + const client = this.getContainerRegistryManagementClient(sub); let subscriptionRegistries: ContainerModels.Registry[] = await client.registries.list(); registries = registries.concat(subscriptionRegistries); }); @@ -107,9 +107,9 @@ export class AzureCredentialsManager { const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); let resourceGroups: ResourceGroup[] = []; //Acquire each subscription's data simultaneously - for (let i = 0; i < subs.length; i++) { + for (let sub of subs) { subPool.addTask(async () => { - const resourceClient = this.getResourceManagementClient(subs[i]); + const resourceClient = this.getResourceManagementClient(sub); const internalGroups = await resourceClient.resourceGroups.list(); resourceGroups = resourceGroups.concat(internalGroups); }); From 5c8d3641a6eefbbd693c955a947be32f7f62467e Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 30 Jul 2018 15:33:32 -0700 Subject: [PATCH 08/77] Refactor while loop for new res group --- commands/azureCommands/create-registry.ts | 29 +++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index af121b9647..ca3ac4b433 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -86,7 +86,6 @@ async function acquireRegistryName(client: ContainerRegistryManagementClient): P registryName = await vscode.window.showInputBox(opt); if (registryName === undefined) { throw new Error('user Exit'); } - registryStatus = await client.registries.checkNameAvailability({ 'name': registryName }); } return registryName; @@ -94,9 +93,10 @@ async function acquireRegistryName(client: ContainerRegistryManagementClient): P // INPUT HELPERS async function acquireSubscription(): Promise { - let subscription: SubscriptionModels.Subscription; const subs = AzureCredentialsManager.getInstance().getFilteredSubscriptionList(); + if (subs.length === 0) { vscode.window.showErrorMessage('You do not have any subscriptions. Head over to Azure Portal to make one.'); } + let subsNames: string[] = []; for (let sub of subs) { subsNames.push(sub.displayName); @@ -133,7 +133,6 @@ async function acquireLocation(resourceGroup: ResourceGroup, subscription: Subsc return; } }); - } let location: string; do { @@ -173,27 +172,33 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio } async function createNewResourceGroup(loc: string, resourceGroupClient: ResourceManagementClient): Promise { + let promptMessage = 'Resource group name?'; let opt: vscode.InputBoxOptions = { ignoreFocusOut: false, - prompt: 'Resource group name? ' + prompt: promptMessage }; - let resourceGroupName: string = await vscode.window.showInputBox(opt); - let resourceGroupStatus: boolean = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); - while (resourceGroupStatus) { - opt = { - ignoreFocusOut: false, - prompt: "That resource group name is already in existence. Try again: " - } + + let resourceGroupName: string; + let resourceGroupStatus: boolean; + + while (opt.prompt) { resourceGroupName = await vscode.window.showInputBox(opt); - if (resourceGroupName === undefined) { throw new Error('user Exit'); } resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); + if (!resourceGroupStatus) { + opt.prompt = null; + console.log("true status, prompt message = null"); + } else { + opt.prompt = "That resource group name is already in existence. Try again: "; + console.log("false status, prompt message = try again"); + } } let newResourceGroup: ResourceGroup = { name: resourceGroupName, location: loc, }; + //Potential error when two clients try to create same resource group name at once try { await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); From a61c72873a2a91040bebdccc45258dfb7e295823 Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 30 Jul 2018 16:07:56 -0700 Subject: [PATCH 09/77] Added SKU selection --- commands/azureCommands/create-registry.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index ca3ac4b433..9fbe3a3a7b 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -30,13 +30,7 @@ export async function createRegistry(): Promise { return; } - const sku: string = await vscode.window.showInputBox({ - ignoreFocusOut: false, - placeHolder: 'Basic', - value: 'Basic', - prompt: 'SKU? ' - }); - + const sku: string = await acquireSKU(); location = await acquireLocation(resourceGroup, subscription); client.registries.beginCreate(resourceGroup.name, registryName, { 'sku': { 'name': sku }, 'location': location }).then((response): void => { @@ -69,6 +63,18 @@ export async function createRegistry(): Promise { } } +async function acquireSKU(): Promise { + let skus: string[]; + skus.push("Basic"); + skus.push("Standard"); + skus.push("Premium"); + + let sku: string; + sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU' }); + if (sku === undefined) { throw new Error('User exit'); } + + return sku; +} async function acquireRegistryName(client: ContainerRegistryManagementClient): Promise { let opt: vscode.InputBoxOptions = { From 9b513977f71d626ee7c2031ac8d2e3dedb5e4e5a Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 30 Jul 2018 16:21:48 -0700 Subject: [PATCH 10/77] Quick fix- initializing array syntax --- commands/azureCommands/create-registry.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 9fbe3a3a7b..d8a51593e3 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -64,11 +64,7 @@ export async function createRegistry(): Promise { } async function acquireSKU(): Promise { - let skus: string[]; - skus.push("Basic"); - skus.push("Standard"); - skus.push("Premium"); - + let skus: string[] = ["Basic", "Standard", "Premium"]; let sku: string; sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU' }); if (sku === undefined) { throw new Error('User exit'); } @@ -173,7 +169,6 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio if (!resourceGroupName) { vscode.window.showErrorMessage('You must select a valid resource group'); } } while (!resourceGroupName); - return resourceGroup; } From 048eb6713ef9ca543752a11de947a09262e47252 Mon Sep 17 00:00:00 2001 From: rsamai Date: Wed, 1 Aug 2018 14:25:11 -0700 Subject: [PATCH 11/77] Added specific error messages and comments --- commands/azureCommands/create-registry.ts | 23 +++++++++++++++-------- explorer/utils/azureUtils.ts | 12 ++++++++++++ 2 files changed, 27 insertions(+), 8 deletions(-) diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index d8a51593e3..82e095510f 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -8,7 +8,9 @@ import { reporter } from '../../telemetry/telemetry'; import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; const teleAzureId: string = 'vscode-docker.create.registry.azureContainerRegistry'; const teleCmdId: string = 'vscode-docker.createRegistry'; +import * as opn from 'opn'; +/* Creates a new registry based on user input/selection of features, such as location */ export async function createRegistry(): Promise { let subscription: SubscriptionModels.Subscription; let resourceGroup: ResourceGroup; @@ -63,6 +65,8 @@ export async function createRegistry(): Promise { } } + +// INPUT HELPERS async function acquireSKU(): Promise { let skus: string[] = ["Basic", "Standard", "Premium"]; let sku: string; @@ -83,7 +87,7 @@ async function acquireRegistryName(client: ContainerRegistryManagementClient): P while (!registryStatus.nameAvailable) { opt = { ignoreFocusOut: false, - prompt: "That registry name is unavailable. Try again: " + prompt: `The registry name '${registryName}' is unavailable. Try again: ` } registryName = await vscode.window.showInputBox(opt); @@ -93,11 +97,15 @@ async function acquireRegistryName(client: ContainerRegistryManagementClient): P return registryName; } -// INPUT HELPERS async function acquireSubscription(): Promise { const subs = AzureCredentialsManager.getInstance().getFilteredSubscriptionList(); - - if (subs.length === 0) { vscode.window.showErrorMessage('You do not have any subscriptions. Head over to Azure Portal to make one.'); } + if (subs.length === 0) { + vscode.window.showErrorMessage("You do not have any subscriptions. You can create one in your Azure Portal", "Open Portal").then(val => { + if (val === "Open Portal") { + opn('https://portal.azure.com/'); + } + }); + } let subsNames: string[] = []; for (let sub of subs) { @@ -172,6 +180,7 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio return resourceGroup; } +/*Creates a new resource group within the current subscription */ async function createNewResourceGroup(loc: string, resourceGroupClient: ResourceManagementClient): Promise { let promptMessage = 'Resource group name?'; @@ -188,10 +197,8 @@ async function createNewResourceGroup(loc: string, resourceGroupClient: Resource resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); if (!resourceGroupStatus) { opt.prompt = null; - console.log("true status, prompt message = null"); } else { - opt.prompt = "That resource group name is already in existence. Try again: "; - console.log("false status, prompt message = try again"); + opt.prompt = `The resource group '${resourceGroupName}' already exists. Try again: `; } } @@ -204,7 +211,7 @@ async function createNewResourceGroup(loc: string, resourceGroupClient: Resource try { await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); } catch (error) { - vscode.window.showErrorMessage("That resource group name is already in existence. Try again"); + vscode.window.showErrorMessage(`The resource group '${resourceGroupName}' already exists. Try again: `); } return resourceGroupName; } diff --git a/explorer/utils/azureUtils.ts b/explorer/utils/azureUtils.ts index c31c1a9aa1..8a55faa55a 100644 --- a/explorer/utils/azureUtils.ts +++ b/explorer/utils/azureUtils.ts @@ -15,3 +15,15 @@ export function browseAzurePortal(context?: AzureRegistryNode | AzureRepositoryN } } + +export function openAzurePortal(): void { + + /* + let url: string = `${session.environment.portalUrl}/${tenantId}/#resource${context.registry.id}`; + if (context.contextValue === 'azureImageNode' || context.contextValue === 'azureRepositoryNode') { + url = `${url}/repository`; + } + opn(url); + }*/ + +} From b80af87cc20b7ffcb072bfb90c54f3d9fc71b0f3 Mon Sep 17 00:00:00 2001 From: Julia Lieberman <30445374+julialieberman@users.noreply.github.com> Date: Fri, 3 Aug 2018 17:10:01 -0700 Subject: [PATCH 12/77] Julia/delete image (#29) * first fully functional version of delete through input bar AND right click * refactored code to make it prettier! * comments * comments, added subscription function * fixed to style guide * style fixes, refactoring * delete image after reviews put my functions from azureCredentialsManager into two new files: utils/azure/acrTools.ts, and commands/utils/quick-pick-azure.ts Edited code based on Esteban's and Bin's reviews * One last little change to delete image * moved repository, azureimage, and getsubscriptions to the correct places within deleteImage * changes from PR reviews on delete image * fixed authentication issue, got rid of azureAccount property for repository and image **on constructor for repository, azurecredentialsmanager was being recreated and thus couldn't find the azureAccount. For this reason, I got rid of the azureAccount property of the classes Repository and AzureImage. This bug may lead to future problems (Esteban and I couldn't see why it was happening) * minor fixes deleteImage * delete a parentheses --- commands/azureCommands/delete-azure-image.ts | 60 ++++ commands/utils/quick-pick-azure.ts | 58 ++++ dockerExtension.ts | 3 + package.json | 10 + utils/Azure/acrTools.ts | 275 +++++++++++++++++++ utils/Azure/models/image.ts | 32 +++ utils/Azure/models/repository.ts | 36 +++ utils/azureCredentialsManager.ts | 19 +- 8 files changed, 482 insertions(+), 11 deletions(-) create mode 100644 commands/azureCommands/delete-azure-image.ts create mode 100644 commands/utils/quick-pick-azure.ts create mode 100644 utils/Azure/acrTools.ts create mode 100644 utils/Azure/models/image.ts create mode 100644 utils/Azure/models/repository.ts diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts new file mode 100644 index 0000000000..30afe43810 --- /dev/null +++ b/commands/azureCommands/delete-azure-image.ts @@ -0,0 +1,60 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { SubscriptionModels } from 'azure-arm-resource'; +import * as vscode from "vscode"; +import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; +import { Repository } from "../../utils/Azure/models/repository"; +import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +const teleCmdId: string = 'vscode-docker.deleteAzureImage'; +import * as quickPicks from '../../commands/utils/quick-pick-azure'; +import * as acrTools from '../../utils/Azure/acrTools'; + +/** + * function to delete an Azure repository and its associated images + * @param context : if called through right click on AzureRepositoryNode, the node object will be passed in. See azureRegistryNodes.ts for more info + */ +export async function deleteAzureImage(context?: AzureImageNode): Promise { + if (!AzureCredentialsManager.getInstance().isLoggedIn()) { + vscode.window.showErrorMessage('You are not logged into Azure'); + return; + } + let registry: Registry; + let subscription: SubscriptionModels.Subscription; + let repoName: string; + let username: string; + let password: string; + let tag: string; + if (!context) { + registry = await quickPicks.quickPickACRRegistry(); + subscription = acrTools.getRegistrySubscription(registry); + let repository: Repository = await quickPicks.quickPickACRRepository(registry); + repoName = repository.name; + const image = await quickPicks.quickPickACRImage(repository); + tag = image.tag; + } + + //ensure user truly wants to delete image + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: true, + placeHolder: 'No', + value: 'No', + prompt: 'Are you sure you want to delete this image? Enter Yes to continue: ' + }; + let answer = await vscode.window.showInputBox(opt); + answer = answer.toLowerCase(); + if (answer !== 'yes') { return; } + + if (context) { + repoName = context.label; + subscription = context.subscription; + registry = context.registry; + let wholeName = repoName.split(':'); + repoName = wholeName[0]; + tag = wholeName[1]; + } + + let creds = await acrTools.loginCredentials(subscription, registry); + username = creds.username; + password = creds.password; + let path = `/v2/_acr/${repoName}/tags/${tag}`; + await acrTools.requestDataFromRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image +} diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts new file mode 100644 index 0000000000..e88496036e --- /dev/null +++ b/commands/utils/quick-pick-azure.ts @@ -0,0 +1,58 @@ +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { Registry } from 'azure-arm-containerregistry/lib/models'; +import * as vscode from "vscode"; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureImage } from "../../utils/Azure/models/image"; +import { Repository } from "../../utils/Azure/models/Repository"; +import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; + +/** + * function to allow user to pick a desired image for use + * @param repository the repository to look in + * @returns an AzureImage object (see azureUtils.ts) + */ +export async function quickPickACRImage(repository: Repository): Promise { + const repoImages: AzureImage[] = await acrTools.getAzureImages(repository); + let imageListNames: string[] = []; + for (let tempImage of repoImages) { + imageListNames.push(tempImage.tag); + } + let desiredImage = await vscode.window.showQuickPick(imageListNames, { 'canPickMany': false, 'placeHolder': 'Choose the image you want to delete' }); + if (!desiredImage) { return; } + const image = repoImages.find((myImage): boolean => { return desiredImage === myImage.tag }); + return image; +} + +/** + * function to allow user to pick a desired repository for use + * @param registry the registry to choose a repository from + * @returns a Repository object (see azureUtils.ts) + */ +export async function quickPickACRRepository(registry: Registry): Promise { + const myRepos: Repository[] = await acrTools.getAzureRepositories(registry); + let rep: string[] = []; + for (let repo of myRepos) { + rep.push(repo.name); + } + let desiredRepo = await vscode.window.showQuickPick(rep, { 'canPickMany': false, 'placeHolder': 'Choose the repository from which your desired image exists' }); + if (!desiredRepo) { return; } + const repository = myRepos.find((currentRepo): boolean => { return desiredRepo === currentRepo.name }); + return repository; +} + +/** + * function to let user choose a registry for use + * @returns a Registry object + */ +export async function quickPickACRRegistry(): Promise { + //first get desired registry + let registries = await AzureCredentialsManager.getInstance().getRegistries(); + let reg: string[] = []; + for (let registryName of registries) { + reg.push(registryName.name); + } + let desired = await vscode.window.showQuickPick(reg, { 'canPickMany': false, 'placeHolder': 'Choose the Registry from which your desired image exists' }); + if (!desired) { return; } + const registry = registries.find((currentReg): boolean => { return desired === currentReg.name }); + return registry; +} diff --git a/dockerExtension.ts b/dockerExtension.ts index 2cac6f0be5..c38ba577e5 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { AzureUserInput } from 'vscode-azureextensionui'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; import inspectImage from './commands/inspect-image'; @@ -117,7 +118,9 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.down', composeDown)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.restart', composeRestart)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.system.prune', systemPrune)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createRegistry', createRegistry)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { if (context) { if (azureAccount) { diff --git a/package.json b/package.json index afb138d79f..fbd5ea054b 100644 --- a/package.json +++ b/package.json @@ -52,6 +52,7 @@ "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", + "onCommand:vscode-docker.deleteAzureImage", "onView:dockerExplorer", "onDebugInitialConfigurations" ], @@ -256,6 +257,10 @@ "command": "vscode-docker.dockerHubLogout", "when": "view == dockerExplorer && viewItem == dockerHubRootNode" }, + { + "command": "vscode-docker.deleteAzureImage", + "when": "view == dockerExplorer && viewItem == azureImageNode" + }, { "command": "vscode-docker.browseDockerHub", "when": "view == dockerExplorer && viewItem == dockerHubImageTag" @@ -620,6 +625,11 @@ "command": "vscode-docker.browseAzurePortal", "title": "Browse in the Azure Portal", "category": "Docker" + }, + { + "command": "vscode-docker.deleteAzureImage", + "title": "Delete Azure Image", + "category": "Docker" } ], "views": { diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts new file mode 100644 index 0000000000..ca98da0f5a --- /dev/null +++ b/utils/Azure/acrTools.ts @@ -0,0 +1,275 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { SubscriptionModels } from 'azure-arm-resource'; +import request = require('request-promise'); +import * as vscode from "vscode"; +import { AzureImageNode, AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; +import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; +import { AzureImage } from "../Azure/models/image"; +import { Repository } from "../Azure/models/Repository"; +import { AzureCredentialsManager } from '../azureCredentialsManager'; +const teleCmdId: string = 'vscode-docker.deleteAzureImage'; + +/** + * Developers can use this to visualize and list repositories on a given Registry. This is not a command, just a developer tool. + * @param registry : the registry whose repositories you want to see + * @returns allRepos : an array of Repository objects that exist within the given registry + */ +export async function getAzureRepositories(registry: Registry): Promise { + const allRepos: Repository[] = []; + let repo: Repository; + let azureAccount: AzureAccount = AzureCredentialsManager.getInstance().getAccount(); + if (!azureAccount) { + return []; + } + const { accessToken, refreshToken } = await getRegistryTokens(registry); + if (accessToken && refreshToken) { + + await request.get('https://' + registry.loginServer + '/v2/_catalog', { + auth: { + bearer: accessToken + } + }, (err, httpResponse, body) => { + if (body.length > 0) { + const repositories = JSON.parse(body).repositories; + for (let tempRepo of repositories) { + repo = new Repository(registry, tempRepo, accessToken, refreshToken); + allRepos.push(repo); + } + } + }); + } + //Note these are ordered by default in alphabetical order + return allRepos; +} + +/** + * @param registry : the registry to get credentials for + * @returns : the updated refresh and access tokens which can be used to generate a header for an API call + */ +export async function getRegistryTokens(registry: Registry): Promise<{ refreshToken: any, accessToken: any }> { + const subscription = getRegistrySubscription(registry); + const tenantId: string = subscription.tenantId; + let azureAccount: AzureAccount = AzureCredentialsManager.getInstance().getAccount(); + + const session: AzureSession = azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase()); + const { accessToken } = await acquireARMToken(session); + + //regenerates in case they have expired + if (accessToken) { + let refreshTokenACR; + let accessTokenACR; + + await request.post('https://' + registry.loginServer + '/oauth2/exchange', { + form: { + grant_type: 'access_token', + service: registry.loginServer, + tenant: tenantId, + access_token: accessToken + } + }, (err, httpResponse, body) => { + if (body.length > 0) { + refreshTokenACR = JSON.parse(body).refresh_token; + } else { + return; + } + }); + + await request.post('https://' + registry.loginServer + '/oauth2/token', { + form: { + grant_type: 'refresh_token', + service: registry.loginServer, + scope: 'registry:catalog:*', + refresh_token: refreshTokenACR + } + }, (err, httpResponse, body) => { + if (body.length > 0) { + accessTokenACR = JSON.parse(body).access_token; + } else { + return; + } + }); + if (refreshTokenACR && accessTokenACR) { + return { 'refreshToken': refreshTokenACR, 'accessToken': accessTokenACR }; + } + } + vscode.window.showErrorMessage('Could not generate tokens'); +} + +export async function acquireARMToken(localSession: AzureSession): Promise<{ accessToken: string; }> { + return new Promise<{ accessToken: string; }>((resolve, reject) => { + const credentials: any = localSession.credentials; + const environment: any = localSession.environment; + // tslint:disable-next-line:no-function-expression // Grandfathered in + credentials.context.acquireToken(environment.activeDirectoryResourceId, credentials.username, credentials.clientId, function (err: any, result: { accessToken: string; }): void { + if (err) { + reject(err); + } else { + resolve({ + accessToken: result.accessToken + }); + } + }); + }); +} + +/** + * + * function used to create header for http request to acr + */ +export function getAuthorizationHeader(username: string, password: string): string { + let auth; + if (username === '00000000-0000-0000-0000-000000000000') { + auth = 'Bearer ' + password; + } else { + auth = ('Basic ' + (encode(username + ':' + password).trim())); + } + return auth; +} + +/** + * first encodes to base 64, and then to latin1. See online documentation to see typescript encoding capabilities + * see https://nodejs.org/api/buffer.html#buffer_buf_tostring_encoding_start_end for details {Buffers and Character Encodings} + * current character encodings include: ascii, utf8, utf16le, ucs2, base64, latin1, binary, hex. Version v6.4.0 + * @param str : the string to encode for api URL purposes + */ +export function encode(str: string): string { + let bufferB64 = new Buffer(str); + let bufferLat1 = new Buffer(bufferB64.toString('base64')); + return bufferLat1.toString('latin1'); +} + +/** + * Lots of https requests but they must be separate from getTokens because the forms are different + * @param element the repository where the desired images are + * @returns a list of AzureImage objects from the given repository (see azureUtils.ts) + */ +export async function getAzureImages(element: Repository): Promise { + let allImages: AzureImage[] = []; + let image: AzureImage; + let tags; + let azureAccount: AzureAccount = AzureCredentialsManager.getInstance().getAccount(); + let tenantId: string = element.subscription.tenantId; + let refreshTokenACR; + let accessTokenACR; + const session: AzureSession = azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase()); + const { accessToken } = await acquireARMToken(session); + if (accessToken) { + await request.post('https://' + element.registry.loginServer + '/oauth2/exchange', { + form: { + grant_type: 'access_token', + service: element.registry.loginServer, + tenant: tenantId, + access_token: accessToken + } + }, (err, httpResponse, body) => { + if (body.length > 0) { + refreshTokenACR = JSON.parse(body).refresh_token; + } else { + return []; + } + }); + + await request.post('https://' + element.registry.loginServer + '/oauth2/token', { + form: { + grant_type: 'refresh_token', + service: element.registry.loginServer, + scope: 'repository:' + element.name + ':pull', + refresh_token: refreshTokenACR + } + }, (err, httpResponse, body) => { + if (body.length > 0) { + accessTokenACR = JSON.parse(body).access_token; + } else { + return []; + } + }); + + await request.get('https://' + element.registry.loginServer + '/v2/' + element.name + '/tags/list', { + auth: { + bearer: accessTokenACR + } + }, (err, httpResponse, body) => { + if (err) { return []; } + + if (body.length > 0) { + tags = JSON.parse(body).tags; + } + }); + + for (let tag of tags) { + image = new AzureImage(element, tag); + allImages.push(image); + } + } + return allImages; +} + +//Implements new Service principal model for ACR container registries while maintaining old admin enabled use +/** + * this function implements a new Service principal model for ACR and gets the valid login credentials to make an API call + * @param subscription : the subscription the registry is on + * @param registry : the registry to get login credentials for + * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username + */ +export async function loginCredentials(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { + let node: AzureImageNode | AzureRepositoryNode; + if (context) { + node = context; + } + let username: string; + let password: string; + const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); + const resourceGroup: string = registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); + if (context) { + username = node.userName; + password = node.password; + } else if (registry.adminUserEnabled) { + let creds = await client.registries.listCredentials(resourceGroup, registry.name); + password = creds.passwords[0].value; + username = creds.username; + } else { + //grab the access token to be used as a password, and a generic username + let creds = await getRegistryTokens(registry); + password = creds.accessToken; + username = '00000000-0000-0000-0000-000000000000'; + } + return { password, username }; +} + +/** + * + * @param http_method : the http method, this function currently only uses delete + * @param login_server: the login server of the registry + * @param path : the URL path + * @param username : registry username, can be in generic form of 0's, used to generate authorization header + * @param password : registry password, can be in form of accessToken, used to generate authorization header + */ +export async function sendRequestToRegistry(http_method: string, login_server: string, path: string, username: string, password: string): Promise { + let url: string = `https://${login_server}${path}`; + let header = getAuthorizationHeader(username, password); + let opt = { + headers: { 'Authorization': header }, + http_method: http_method, + url: url + } + try { + await request.delete(opt); + } catch (error) { + throw error; + } + vscode.window.showInformationMessage('Successfully deleted image'); +} + +/** + * + * @param registry gets the subscription for a given registry + * @returns a subscription object + */ +export function getRegistrySubscription(registry: Registry): SubscriptionModels.Subscription { + let subscriptionId = registry.id.slice('/subscriptions/'.length, registry.id.search('/resourceGroups/')); + const subs = AzureCredentialsManager.getInstance().getFilteredSubscriptionList(); + let subscription = subs.find((sub): boolean => { + return sub.subscriptionId === subscriptionId; + }); + return subscription; +} diff --git a/utils/Azure/models/image.ts b/utils/Azure/models/image.ts new file mode 100644 index 0000000000..0f047e3453 --- /dev/null +++ b/utils/Azure/models/image.ts @@ -0,0 +1,32 @@ +import { Registry } from 'azure-arm-containerregistry/lib/models'; +import { SubscriptionModels } from 'azure-arm-resource'; +import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; +import { Repository } from '../models/repository'; + +/** + * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository + * accessToken can be used like a password, and the username can be '00000000-0000-0000-0000-000000000000' + */ +export class AzureImage { + public registry: Registry; + public repository: Repository; + public tag: string; + public subscription: SubscriptionModels.Subscription; + public resourceGroupName: string; + public accessToken?: string; + public refreshToken?: string; + public password?: string; + public username?: string; + + constructor(repository: Repository, tag: string) { + this.registry = repository.registry; + this.repository = repository; + this.tag = tag; + this.subscription = repository.subscription; + this.resourceGroupName = repository.resourceGroupName; + if (repository.accessToken) { this.accessToken = repository.accessToken; } + if (repository.refreshToken) { this.refreshToken = repository.refreshToken; } + if (repository.password) { this.password = repository.password; } + if (repository.username) { this.username = repository.username; } + } +} diff --git a/utils/Azure/models/repository.ts b/utils/Azure/models/repository.ts new file mode 100644 index 0000000000..5491269b39 --- /dev/null +++ b/utils/Azure/models/repository.ts @@ -0,0 +1,36 @@ +import { Registry } from 'azure-arm-containerregistry/lib/models'; +import { SubscriptionModels } from 'azure-arm-resource'; +import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; +import * as acrTools from '../../../utils/Azure/acrTools'; +import { AzureCredentialsManager } from '../../AzureCredentialsManager'; +/** + * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository + * accessToken can be used like a password, and the username can be '00000000-0000-0000-0000-000000000000' + */ +export class Repository { + public registry: Registry; + public name: string; + public subscription: SubscriptionModels.Subscription; + public resourceGroupName: string; + public accessToken?: string; + public refreshToken?: string; + public password?: string; + public username?: string; + + constructor(registry: Registry, repository: string, accessToken?: string, refreshToken?: string, password?: string, username?: string) { + this.registry = registry; + this.resourceGroupName = registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); + this.subscription = acrTools.getRegistrySubscription(registry); + this.name = repository; + if (accessToken) { this.accessToken = accessToken; } + if (refreshToken) { this.refreshToken = refreshToken; } + if (password) { this.password = password; } + if (username) { this.username = username; } + } + + public async setTokens(registry: Registry): Promise { + let tokens = await acrTools.getRegistryTokens(registry); + this.accessToken = tokens.accessToken; + this.refreshToken = tokens.refreshToken; + } +} diff --git a/utils/azureCredentialsManager.ts b/utils/azureCredentialsManager.ts index 3a491e6a2c..a7839e3f78 100644 --- a/utils/azureCredentialsManager.ts +++ b/utils/azureCredentialsManager.ts @@ -1,15 +1,15 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { Registry } from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup, ResourceGroupListResult } from "azure-arm-resource/lib/resource/models"; import { ServiceClientCredentials } from 'ms-rest'; import * as ContainerModels from '../node_modules/azure-arm-containerregistry/lib/models'; -import { AzureAccount } from '../typings/azure-account.api'; +import { AzureAccount, AzureSession } from '../typings/azure-account.api'; import { AsyncPool } from '../utils/asyncpool'; import { MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from './constants'; - /* Singleton for facilitating communication with Azure account services by providing extended shared functionality and extension wide access to azureAccount. Tool for internal use. - Authors: Esteban Rey L, Jackson Stokes + Authors: Esteban Rey L, Jackson Stokes, Julia Lieberman */ export class AzureCredentialsManager { @@ -82,17 +82,16 @@ export class AzureCredentialsManager { for (let sub of subs) { subPool.addTask(async () => { const client = this.getContainerRegistryManagementClient(sub); + let subscriptionRegistries: ContainerModels.Registry[] = await client.registries.list(); registries = registries.concat(subscriptionRegistries); }); } await subPool.runAll(); } - if (sortFunction && registries.length > 1) { registries.sort(sortFunction); } - return registries; } @@ -101,13 +100,14 @@ export class AzureCredentialsManager { const resourceClient = this.getResourceManagementClient(subscription); return await resourceClient.resourceGroups.list(); } - const subs = this.getFilteredSubscriptionList(); + const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptionList(); const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); let resourceGroups: ResourceGroup[] = []; //Acquire each subscription's data simultaneously - for (let sub of subs) { + + for (let tempSub of subs) { subPool.addTask(async () => { - const resourceClient = this.getResourceManagementClient(sub); + const resourceClient = this.getResourceManagementClient(tempSub); const internalGroups = await resourceClient.resourceGroups.list(); resourceGroups = resourceGroups.concat(internalGroups); }); @@ -117,13 +117,10 @@ export class AzureCredentialsManager { } public getCredentialByTenantId(tenantId: string): ServiceClientCredentials { - const session = this.getAccount().sessions.find((azureSession) => azureSession.tenantId.toLowerCase() === tenantId.toLowerCase()); - if (session) { return session.credentials; } - throw new Error(`Failed to get credentials, tenant ${tenantId} not found.`); } From f4f1924e3b89333c6fa40cdcd393ba0ee4ecad30 Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 6 Aug 2018 16:24:54 -0700 Subject: [PATCH 13/77] copied previous push to acr into new pull-from-azure.ts file --- commands/azureCommands/pull-from-azure.ts | 108 ++++++++++++++++++++++ explorer/models/rootNode.ts | 1 - 2 files changed, 108 insertions(+), 1 deletion(-) create mode 100644 commands/azureCommands/pull-from-azure.ts diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts new file mode 100644 index 0000000000..6484d76372 --- /dev/null +++ b/commands/azureCommands/pull-from-azure.ts @@ -0,0 +1,108 @@ +/* push-azure.ts + * + * Very basic integration for pushing to azure container registry instead of Docker hub + * Author : Esteban Rey + * Version 0.01 + * Updated 6/25/2018 + * + * Known Issues: + * + * Does not currently identify if resource groups/container registry exist. + * Is currently dependent on terminal installation of azure CLI + * Review best practices for await + * If user is not logged in no idea what to do + * login rocketpenguininterns.azurecr.io + Username: rocketPenguinInterns + Password: + Login Succeeded + */ + +import vscode = require('vscode'); +import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; +import { ImageNode } from '../../explorer/models/imageNode'; +import { reporter } from '../../telemetry/telemetry'; +import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; +//FOR TELEMETRY DATA +const teleCmdId: string = 'vscode-docker.image.pushToAzure'; +const teleAzureId: string = 'vscode-docker.image.push.azureContainerRegistry'; + +const { exec } = require('child_process'); + +export async function pushAzure(context?: ImageNode): Promise { + let imageToPush: Docker.ImageDesc; + let imageName: string = ""; + + if (context && context.imageDesc) { + imageToPush = context.imageDesc; + imageName = context.label; + } else { + const selectedItem: ImageItem = await quickPickImage(); + if (selectedItem) { + imageToPush = selectedItem.imageDesc; + imageName = selectedItem.label; + } + } + + if (imageToPush) { + const terminal = vscode.window.createTerminal(imageName); + + // 1. Registry Name + let options: vscode.InputBoxOptions = { + prompt: "Azure Container Registry Name?" + } + + let regName = await vscode.window.showInputBox(options); + terminal.sendText(`az acr login --name ${regName}`); + + // 2. Resource Group Name + options = { + prompt: "Resource Group Name?" + } + let resGroup = await vscode.window.showInputBox(options); + + // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? + // 4. Acquire full acrLogin (Needs Testing) + let cont = function (err, stdout, stderr) { + console.log(stdout); + let jsonStdout = JSON.parse(stdout); + let soughtsrvr: string = ""; + for (let i = 0; i < jsonStdout.length; i++) { + let srvrName: string = jsonStdout[i].acrLoginServer; + let searchIndex: number = srvrName.search(`${regName}`); + if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? + soughtsrvr = srvrName; + break; + } + } + + if (soughtsrvr === '') { + vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); + return; + } + + let tagPrompts = async function () { + let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); + let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); + // 5. Tag image + terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); + // 6. Push image + terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); + terminal.show(); + } + + tagPrompts(); + + } + + exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); + } +} + +function streamToString(stream: any): Promise { + const chunks = [] + return new Promise((resolve, reject) => { + stream.on('data', chunks.push) + stream.on('error', reject) + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) + }) +} diff --git a/explorer/models/rootNode.ts b/explorer/models/rootNode.ts index a14001f48f..2813555db3 100644 --- a/explorer/models/rootNode.ts +++ b/explorer/models/rootNode.ts @@ -109,7 +109,6 @@ export class RootNode extends NodeBase { } public async getChildren(element: NodeBase): Promise { - if (element.contextValue === 'imagesRootNode') { return this.getImages(); } From 05df67cea9225c655bbcfee7793c609f8f6bc3c3 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Tue, 7 Aug 2018 12:13:14 -0700 Subject: [PATCH 14/77] Estebanreyl/dev merge fixes (#43) * Merge fixes to acquire latest telemetry items * Updated to master AzureUtilityManager --- commands/azureCommands/create-registry.ts | 14 +- commands/azureCommands/delete-azure-image.ts | 13 +- commands/utils/quick-pick-azure.ts | 4 +- dockerExtension.ts | 11 +- utils/Azure/acrTools.ts | 12 +- utils/Azure/models/repository.ts | 1 - utils/azureCredentialsManager.ts | 142 ------------------- utils/azureUtilityManager.ts | 9 +- 8 files changed, 33 insertions(+), 173 deletions(-) delete mode 100644 utils/azureCredentialsManager.ts diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 82e095510f..9c0ac8507d 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -5,7 +5,7 @@ import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import * as vscode from "vscode"; import { reporter } from '../../telemetry/telemetry'; -import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; const teleAzureId: string = 'vscode-docker.create.registry.azureContainerRegistry'; const teleCmdId: string = 'vscode-docker.createRegistry'; import * as opn from 'opn'; @@ -23,7 +23,7 @@ export async function createRegistry(): Promise { } catch (error) { return; } - const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); let registryName: string; try { @@ -98,7 +98,7 @@ async function acquireRegistryName(client: ContainerRegistryManagementClient): P } async function acquireSubscription(): Promise { - const subs = AzureCredentialsManager.getInstance().getFilteredSubscriptionList(); + const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); if (subs.length === 0) { vscode.window.showErrorMessage("You do not have any subscriptions. You can create one in your Azure Portal", "Open Portal").then(val => { if (val === "Open Portal") { @@ -119,7 +119,7 @@ async function acquireSubscription(): Promise { } async function acquireLocation(resourceGroup: ResourceGroup, subscription: SubscriptionModels.Subscription): Promise { - let locations: SubscriptionModels.Location[] = await AzureCredentialsManager.getInstance().getLocationsBySubscription(subscription); + let locations: SubscriptionModels.Location[] = await AzureUtilityManager.getInstance().getLocationsBySubscription(subscription); let locationNames: string[] = []; let placeHolder: string; @@ -156,8 +156,8 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio //Acquire each subscription's data simultaneously let resourceGroup; let resourceGroupName; - const resourceGroupClient = new ResourceManagementClient(AzureCredentialsManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); - let resourceGroups = await AzureCredentialsManager.getInstance().getResourceGroups(subscription); + const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + let resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); let resourceGroupNames: string[] = []; resourceGroupNames.push('+ Create new resource group'); @@ -172,7 +172,7 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio let loc = await acquireLocation(resourceGroup, subscription); resourceGroupName = await createNewResourceGroup(loc, resourceGroupClient); } - resourceGroups = await AzureCredentialsManager.getInstance().getResourceGroups(subscription); + resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); if (!resourceGroupName) { vscode.window.showErrorMessage('You must select a valid resource group'); } diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index 30afe43810..eb37c5a0e6 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -1,19 +1,18 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; import * as vscode from "vscode"; +import * as quickPicks from '../../commands/utils/quick-pick-azure'; import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; +import * as acrTools from '../../utils/Azure/acrTools'; import { Repository } from "../../utils/Azure/models/repository"; -import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; const teleCmdId: string = 'vscode-docker.deleteAzureImage'; -import * as quickPicks from '../../commands/utils/quick-pick-azure'; -import * as acrTools from '../../utils/Azure/acrTools'; -/** - * function to delete an Azure repository and its associated images +/** Function to delete an Azure repository and its associated images * @param context : if called through right click on AzureRepositoryNode, the node object will be passed in. See azureRegistryNodes.ts for more info */ export async function deleteAzureImage(context?: AzureImageNode): Promise { - if (!AzureCredentialsManager.getInstance().isLoggedIn()) { + if (!AzureUtilityManager.getInstance().waitForLogin()) { vscode.window.showErrorMessage('You are not logged into Azure'); return; } @@ -56,5 +55,5 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise username = creds.username; password = creds.password; let path = `/v2/_acr/${repoName}/tags/${tag}`; - await acrTools.requestDataFromRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image + await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image } diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index e88496036e..ae7b49f092 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -4,7 +4,7 @@ import * as vscode from "vscode"; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureImage } from "../../utils/Azure/models/image"; import { Repository } from "../../utils/Azure/models/Repository"; -import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; /** * function to allow user to pick a desired image for use @@ -46,7 +46,7 @@ export async function quickPickACRRepository(registry: Registry): Promise { //first get desired registry - let registries = await AzureCredentialsManager.getInstance().getRegistries(); + let registries = await AzureUtilityManager.getInstance().getRegistries(); let reg: string[] = []; for (let registryName of registries) { reg.push(registryName.name); diff --git a/dockerExtension.ts b/dockerExtension.ts index 81ac9f70b2..3fc10d1880 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExtensionVariables } from 'vscode-azureextensionui'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; @@ -26,7 +27,6 @@ import { DockerDebugConfigProvider } from './configureWorkspace/configDebugProvi import { configure } from './configureWorkspace/configure'; import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider'; import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider'; -import { createRegistry } from './commands/azureCommands/create-registry'; import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo'; import { DockerComposeParser } from './dockerCompose/dockerComposeParser'; import { DockerfileCompletionItemProvider } from './dockerfile/dockerfileCompletionItemProvider'; @@ -43,7 +43,6 @@ import { ext } from "./extensionVariables"; import { initializeTelemetryReporter, reporter } from './telemetry/telemetry'; import { AzureAccount } from './typings/azure-account.api'; import { AzureUtilityManager } from './utils/azureUtilityManager'; -import { AzureCredentialsManager } from './utils/azureCredentialsManager'; 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}'; @@ -127,11 +126,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.system.prune', systemPrune); registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createRegistry', createRegistry)); - - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { - if (context) { if (azureAccount) { const azureAccountWrapper = new AzureAccountWrapper(ctx, azureAccount); @@ -145,6 +139,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { } } } + }); registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout); @@ -158,6 +153,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider())); if (azureAccount) { + registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage); + registerCommand('vscode-docker.createRegistry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); } diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index ca98da0f5a..796113d4bc 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -6,7 +6,7 @@ import { AzureImageNode, AzureRepositoryNode } from '../../explorer/models/Azure import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; import { AzureImage } from "../Azure/models/image"; import { Repository } from "../Azure/models/Repository"; -import { AzureCredentialsManager } from '../azureCredentialsManager'; +import { AzureUtilityManager } from '../azureUtilityManager'; const teleCmdId: string = 'vscode-docker.deleteAzureImage'; /** @@ -17,7 +17,7 @@ const teleCmdId: string = 'vscode-docker.deleteAzureImage'; export async function getAzureRepositories(registry: Registry): Promise { const allRepos: Repository[] = []; let repo: Repository; - let azureAccount: AzureAccount = AzureCredentialsManager.getInstance().getAccount(); + let azureAccount: AzureAccount = AzureUtilityManager.getInstance().getAccount(); if (!azureAccount) { return []; } @@ -49,7 +49,7 @@ export async function getAzureRepositories(registry: Registry): Promise { const subscription = getRegistrySubscription(registry); const tenantId: string = subscription.tenantId; - let azureAccount: AzureAccount = AzureCredentialsManager.getInstance().getAccount(); + let azureAccount: AzureAccount = AzureUtilityManager.getInstance().getAccount(); const session: AzureSession = azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase()); const { accessToken } = await acquireARMToken(session); @@ -147,7 +147,7 @@ export async function getAzureImages(element: Repository): Promise let allImages: AzureImage[] = []; let image: AzureImage; let tags; - let azureAccount: AzureAccount = AzureCredentialsManager.getInstance().getAccount(); + let azureAccount: AzureAccount = AzureUtilityManager.getInstance().getAccount(); let tenantId: string = element.subscription.tenantId; let refreshTokenACR; let accessTokenACR; @@ -218,7 +218,7 @@ export async function loginCredentials(subscription: SubscriptionModels.Subscrip } let username: string; let password: string; - const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); const resourceGroup: string = registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); if (context) { username = node.userName; @@ -267,7 +267,7 @@ export async function sendRequestToRegistry(http_method: string, login_server: s */ export function getRegistrySubscription(registry: Registry): SubscriptionModels.Subscription { let subscriptionId = registry.id.slice('/subscriptions/'.length, registry.id.search('/resourceGroups/')); - const subs = AzureCredentialsManager.getInstance().getFilteredSubscriptionList(); + const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); let subscription = subs.find((sub): boolean => { return sub.subscriptionId === subscriptionId; }); diff --git a/utils/Azure/models/repository.ts b/utils/Azure/models/repository.ts index 5491269b39..efb47e5559 100644 --- a/utils/Azure/models/repository.ts +++ b/utils/Azure/models/repository.ts @@ -2,7 +2,6 @@ import { Registry } from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; import * as acrTools from '../../../utils/Azure/acrTools'; -import { AzureCredentialsManager } from '../../AzureCredentialsManager'; /** * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository * accessToken can be used like a password, and the username can be '00000000-0000-0000-0000-000000000000' diff --git a/utils/azureCredentialsManager.ts b/utils/azureCredentialsManager.ts deleted file mode 100644 index a7839e3f78..0000000000 --- a/utils/azureCredentialsManager.ts +++ /dev/null @@ -1,142 +0,0 @@ -import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import { Registry } from 'azure-arm-containerregistry/lib/models'; -import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; -import { ResourceGroup, ResourceGroupListResult } from "azure-arm-resource/lib/resource/models"; -import { ServiceClientCredentials } from 'ms-rest'; -import * as ContainerModels from '../node_modules/azure-arm-containerregistry/lib/models'; -import { AzureAccount, AzureSession } from '../typings/azure-account.api'; -import { AsyncPool } from '../utils/asyncpool'; -import { MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from './constants'; -/* Singleton for facilitating communication with Azure account services by providing extended shared - functionality and extension wide access to azureAccount. Tool for internal use. - Authors: Esteban Rey L, Jackson Stokes, Julia Lieberman -*/ - -export class AzureCredentialsManager { - - //SETUP - private static _instance: AzureCredentialsManager; - private azureAccount: AzureAccount; - - private constructor() { } - - public static getInstance(): AzureCredentialsManager { - if (!AzureCredentialsManager._instance) { // lazy initialization - AzureCredentialsManager._instance = new AzureCredentialsManager(); - } - return AzureCredentialsManager._instance; - } - - //This function has to be called explicitly before using the singleton. - public setAccount(azureAccount: AzureAccount): void { - this.azureAccount = azureAccount; - } - - //GETTERS - public getAccount(): AzureAccount { - if (this.azureAccount) { return this.azureAccount; } - throw new Error(('Azure account is not present, you may have forgotten to call setAccount')); - } - - public getFilteredSubscriptionList(): SubscriptionModels.Subscription[] { - return this.getAccount().filters.map(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 - }; - }); - } - - public getContainerRegistryManagementClient(subscription: SubscriptionModels.Subscription): ContainerRegistryManagementClient { - return new ContainerRegistryManagementClient(this.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); - } - - public getResourceManagementClient(subscription: SubscriptionModels.Subscription): ResourceManagementClient { - return new ResourceManagementClient(this.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); - } - - public async getRegistries(subscription?: SubscriptionModels.Subscription, resourceGroup?: string, sortFunction?: any): Promise { - let registries: ContainerModels.Registry[] = []; - - if (subscription && resourceGroup) { - //Get all registries under one resourcegroup - const client = this.getContainerRegistryManagementClient(subscription); - registries = await client.registries.listByResourceGroup(resourceGroup); - - } else if (subscription) { - //Get all registries under one subscription - const client = this.getContainerRegistryManagementClient(subscription); - registries = await client.registries.list(); - - } else { - //Get all registries for all subscriptions - const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptionList(); - const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); - - for (let sub of subs) { - subPool.addTask(async () => { - const client = this.getContainerRegistryManagementClient(sub); - - let subscriptionRegistries: ContainerModels.Registry[] = await client.registries.list(); - registries = registries.concat(subscriptionRegistries); - }); - } - await subPool.runAll(); - } - if (sortFunction && registries.length > 1) { - registries.sort(sortFunction); - } - return registries; - } - - public async getResourceGroups(subscription?: SubscriptionModels.Subscription): Promise { - if (subscription) { - const resourceClient = this.getResourceManagementClient(subscription); - return await resourceClient.resourceGroups.list(); - } - const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptionList(); - const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); - let resourceGroups: ResourceGroup[] = []; - //Acquire each subscription's data simultaneously - - for (let tempSub of subs) { - subPool.addTask(async () => { - const resourceClient = this.getResourceManagementClient(tempSub); - const internalGroups = await resourceClient.resourceGroups.list(); - resourceGroups = resourceGroups.concat(internalGroups); - }); - } - await subPool.runAll(); - return resourceGroups; - } - - public getCredentialByTenantId(tenantId: string): ServiceClientCredentials { - const session = this.getAccount().sessions.find((azureSession) => azureSession.tenantId.toLowerCase() === tenantId.toLowerCase()); - if (session) { - return session.credentials; - } - throw new Error(`Failed to get credentials, tenant ${tenantId} not found.`); - } - - public async getLocationsBySubscription(subscription: SubscriptionModels.Subscription): Promise { - const credential = this.getCredentialByTenantId(subscription.tenantId); - const client = new SubscriptionClient(credential); - const locations = (await client.subscriptions.listLocations(subscription.subscriptionId)); - return locations; - } - - //CHECKS - //Provides a unified check for login that should be called once before using the rest of the singletons capabilities - public async isLoggedIn(): Promise { - if (!this.azureAccount) { - return false; - } - return await this.azureAccount.waitForLogin(); - } -} diff --git a/utils/azureUtilityManager.ts b/utils/azureUtilityManager.ts index 9c723b5574..c367bff11f 100644 --- a/utils/azureUtilityManager.ts +++ b/utils/azureUtilityManager.ts @@ -1,5 +1,5 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; +import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { ServiceClientCredentials } from 'ms-rest'; import * as ContainerModels from '../node_modules/azure-arm-containerregistry/lib/models'; @@ -126,6 +126,13 @@ export class AzureUtilityManager { throw new Error(`Failed to get credentials, tenant ${tenantId} not found.`); } + public async getLocationsBySubscription(subscription: SubscriptionModels.Subscription): Promise { + const credential = this.getCredentialByTenantId(subscription.tenantId); + const client = new SubscriptionClient(credential); + const locations = (await client.subscriptions.listLocations(subscription.subscriptionId)); + return locations; + } + //CHECKS //Provides a unified check for login that should be called once before using the rest of the singletons capabilities public async waitForLogin(): Promise { From 2332645099ddef2593b806586cf2d69a5587f979 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Tue, 7 Aug 2018 12:16:26 -0700 Subject: [PATCH 15/77] added acrbuild stuff --- commands/acr-build.ts | 98 +++++++++++++++++++++++++++++++++++++++++++ package.json | 23 +++++++++- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 commands/acr-build.ts diff --git a/commands/acr-build.ts b/commands/acr-build.ts new file mode 100644 index 0000000000..28222a69b8 --- /dev/null +++ b/commands/acr-build.ts @@ -0,0 +1,98 @@ +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as vscode from "vscode"; +import { ImageNode } from "../explorer/models/imageNode"; +import { AzureCredentialsManager } from "../utils/AzureCredentialsManager"; +let tar = require('tar'); +let fs = require('fs'); +let os = require('os'); +let url = require('url'); + +export async function queueBuild(context?: ImageNode): Promise { + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: true, + prompt: 'Resource Group? ' + }; + const resourceGroup: string = await vscode.window.showInputBox(opt); + opt = { + ignoreFocusOut: true, + prompt: 'Registry name? ' + }; + const registryName: string = await vscode.window.showInputBox(opt); + + console.log("Obtaining Subscription and Client"); + let subscription = AzureCredentialsManager.getInstance().getFilteredSubscriptionList()[0]; + let client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); + + console.log("Setting up temp file with 'sourceArchive.tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); + + console.log("Uploading Source Code"); + let sourceLocation: string = vscode.workspace.rootPath; + sourceLocation = await uploadSourceCode(client, registryName, resourceGroup, sourceLocation, tarFilePath); + + console.log("Setting up Build Request"); + let buildRequest: QuickBuildRequest = { + 'type': 'QuickBuild', + 'imageNames': [], + 'isPushEnabled': false, + 'sourceLocation': sourceLocation, + 'platform': { 'osType': 'Linux' }, + 'dockerFilePath': 'DockerFile' + }; + + console.log("Queueing Build"); + try { + await client.registries.queueBuild(resourceGroup, registryName, buildRequest); + } catch (error) { + console.log(error.message); + } + console.log(client.builds.list(resourceGroup, registryName)); +} + +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { + console.log(" Sending source code to temp file"); + try { + tar.c( + { + gzip: true + }, + [sourceLocation] + ).pipe(fs.createWriteStream(tarFilePath)); + } catch (error) { + console.log(error); + } + + console.log(" Getting Build Source Upload Url "); + let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + let upload_url = sourceUploadLocation.uploadUrl; + let relative_path = sourceUploadLocation.relativePath; + + console.log(" Getting blob info from upload URl "); + // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. + let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); + + console.log(" Creating Blob service "); + let blob: BlobService = createBlobServiceWithSas(host, sasToken); + + console.log(" Creating Block Blob "); + try { + blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + } catch (error) { + console.log(error); + } + console.log(" Success "); + return relative_path; +} + +function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { + let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); + let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); + let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); + let containerName: string = items[1]; + let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); + let sasToken: string = items[4].slice(items[4].search('[?]') + 1); + let host: string = accountName + '.blob.' + endpointSuffix; + return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; +} diff --git a/package.json b/package.json index db72e6fb0f..3f1ab63dd5 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "onCommand:vscode-docker.createRegistry", "onCommand:vscode-docker.system.prune", "onCommand:vscode-docker.dockerHubLogout", + "onCommand:vscode-docker.queuebuild", "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", @@ -75,6 +76,11 @@ "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "editorLangId == dockerfile", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename == docker-compose.yml", "command": "vscode-docker.compose.up", @@ -112,6 +118,11 @@ "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "resourceFilename =~ /[dD]ocker[fF]ile/", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", "command": "vscode-docker.compose.up", @@ -273,6 +284,10 @@ "command": "vscode-docker.browseDockerHub", "when": "view == dockerExplorer && viewItem == dockerHubNamespace" }, + { + "command": "vscode-docker.queueBuild", + "when": "view == dockerExplorer && viewItem == localImageNode" + }, { "command": "vscode-docker.browseAzurePortal", "when": "view == dockerExplorer && viewItem == azureRegistryNode" @@ -630,6 +645,11 @@ "command": "vscode-docker.deleteAzureImage", "title": "Delete Azure Image", "category": "Docker" + }, + { + "command": "vscode-docker.queueBuild", + "title": "Lightweight Build", + "category": "Docker" } ], "views": { @@ -699,6 +719,7 @@ "request-promise": "^4.2.2", "vscode-azureextensionui": "^0.16.6", "vscode-extension-telemetry": "0.0.18", - "vscode-languageclient": "^4.4.0" + "vscode-languageclient": "^4.4.0", + "tar": "^4.4.6" } } From 7d7a7d51774caeb38e585f92083fd3a5039dc6ce Mon Sep 17 00:00:00 2001 From: jvstokes Date: Tue, 7 Aug 2018 12:16:26 -0700 Subject: [PATCH 16/77] added acrbuild stuff --- commands/acr-build.ts | 98 +++++++++++++++++++++++++++++++++++++++++++ package.json | 23 +++++++++- 2 files changed, 120 insertions(+), 1 deletion(-) create mode 100644 commands/acr-build.ts diff --git a/commands/acr-build.ts b/commands/acr-build.ts new file mode 100644 index 0000000000..28222a69b8 --- /dev/null +++ b/commands/acr-build.ts @@ -0,0 +1,98 @@ +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as vscode from "vscode"; +import { ImageNode } from "../explorer/models/imageNode"; +import { AzureCredentialsManager } from "../utils/AzureCredentialsManager"; +let tar = require('tar'); +let fs = require('fs'); +let os = require('os'); +let url = require('url'); + +export async function queueBuild(context?: ImageNode): Promise { + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: true, + prompt: 'Resource Group? ' + }; + const resourceGroup: string = await vscode.window.showInputBox(opt); + opt = { + ignoreFocusOut: true, + prompt: 'Registry name? ' + }; + const registryName: string = await vscode.window.showInputBox(opt); + + console.log("Obtaining Subscription and Client"); + let subscription = AzureCredentialsManager.getInstance().getFilteredSubscriptionList()[0]; + let client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); + + console.log("Setting up temp file with 'sourceArchive.tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); + + console.log("Uploading Source Code"); + let sourceLocation: string = vscode.workspace.rootPath; + sourceLocation = await uploadSourceCode(client, registryName, resourceGroup, sourceLocation, tarFilePath); + + console.log("Setting up Build Request"); + let buildRequest: QuickBuildRequest = { + 'type': 'QuickBuild', + 'imageNames': [], + 'isPushEnabled': false, + 'sourceLocation': sourceLocation, + 'platform': { 'osType': 'Linux' }, + 'dockerFilePath': 'DockerFile' + }; + + console.log("Queueing Build"); + try { + await client.registries.queueBuild(resourceGroup, registryName, buildRequest); + } catch (error) { + console.log(error.message); + } + console.log(client.builds.list(resourceGroup, registryName)); +} + +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { + console.log(" Sending source code to temp file"); + try { + tar.c( + { + gzip: true + }, + [sourceLocation] + ).pipe(fs.createWriteStream(tarFilePath)); + } catch (error) { + console.log(error); + } + + console.log(" Getting Build Source Upload Url "); + let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + let upload_url = sourceUploadLocation.uploadUrl; + let relative_path = sourceUploadLocation.relativePath; + + console.log(" Getting blob info from upload URl "); + // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. + let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); + + console.log(" Creating Blob service "); + let blob: BlobService = createBlobServiceWithSas(host, sasToken); + + console.log(" Creating Block Blob "); + try { + blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + } catch (error) { + console.log(error); + } + console.log(" Success "); + return relative_path; +} + +function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { + let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); + let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); + let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); + let containerName: string = items[1]; + let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); + let sasToken: string = items[4].slice(items[4].search('[?]') + 1); + let host: string = accountName + '.blob.' + endpointSuffix; + return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; +} diff --git a/package.json b/package.json index db72e6fb0f..3f1ab63dd5 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "onCommand:vscode-docker.createRegistry", "onCommand:vscode-docker.system.prune", "onCommand:vscode-docker.dockerHubLogout", + "onCommand:vscode-docker.queuebuild", "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", @@ -75,6 +76,11 @@ "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "editorLangId == dockerfile", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename == docker-compose.yml", "command": "vscode-docker.compose.up", @@ -112,6 +118,11 @@ "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "resourceFilename =~ /[dD]ocker[fF]ile/", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", "command": "vscode-docker.compose.up", @@ -273,6 +284,10 @@ "command": "vscode-docker.browseDockerHub", "when": "view == dockerExplorer && viewItem == dockerHubNamespace" }, + { + "command": "vscode-docker.queueBuild", + "when": "view == dockerExplorer && viewItem == localImageNode" + }, { "command": "vscode-docker.browseAzurePortal", "when": "view == dockerExplorer && viewItem == azureRegistryNode" @@ -630,6 +645,11 @@ "command": "vscode-docker.deleteAzureImage", "title": "Delete Azure Image", "category": "Docker" + }, + { + "command": "vscode-docker.queueBuild", + "title": "Lightweight Build", + "category": "Docker" } ], "views": { @@ -699,6 +719,7 @@ "request-promise": "^4.2.2", "vscode-azureextensionui": "^0.16.6", "vscode-extension-telemetry": "0.0.18", - "vscode-languageclient": "^4.4.0" + "vscode-languageclient": "^4.4.0", + "tar": "^4.4.6" } } From 17fedecb7d95ae83f06be49e2e911a61d08dea39 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Tue, 7 Aug 2018 14:27:48 -0700 Subject: [PATCH 17/77] Update to match utility manager class --- commands/acr-build.ts | 6 +++--- dockerExtension.ts | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index 28222a69b8..e3ec181506 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -3,7 +3,7 @@ import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as vscode from "vscode"; import { ImageNode } from "../explorer/models/imageNode"; -import { AzureCredentialsManager } from "../utils/AzureCredentialsManager"; +import { AzureUtilityManager } from "../utils/azureUtilityManager"; let tar = require('tar'); let fs = require('fs'); let os = require('os'); @@ -22,8 +22,8 @@ export async function queueBuild(context?: ImageNode): Promise { const registryName: string = await vscode.window.showInputBox(opt); console.log("Obtaining Subscription and Client"); - let subscription = AzureCredentialsManager.getInstance().getFilteredSubscriptionList()[0]; - let client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); + let subscription = AzureUtilityManager.getInstance().getFilteredSubscriptionList()[0]; + let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); console.log("Setting up temp file with 'sourceArchive.tar.gz' "); let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); diff --git a/dockerExtension.ts b/dockerExtension.ts index 3fc10d1880..2687d83e0f 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExtensionVariables } from 'vscode-azureextensionui'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { queueBuild } from './commands/acr-build'; import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; import { buildImage } from './commands/build-image'; @@ -156,6 +157,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage); registerCommand('vscode-docker.createRegistry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); + registerCommand('vscode-docker.queueBuild', queueBuild) } activateLanguageClient(ctx); From 126c01ecf521e49c2fcbf6aaacb0628563da5929 Mon Sep 17 00:00:00 2001 From: rsamai Date: Tue, 7 Aug 2018 14:40:21 -0700 Subject: [PATCH 18/77] Rutusamai/list build tasks for each registry (#37) * tslint updates, transfered from old branch * updated icon * Addressed PR comments- mostly styling/code efficiency * Changed url to aka.ms link * changed Error window to Info message for no build tasks in your registry * Changed default sku and unified parsing resource group into a new method, getResourceGroup in acrTools.ts * Changed build task icon --- commands/azureCommands/create-registry.ts | 2 +- explorer/models/azureRegistryNodes.ts | 37 ++++++----- explorer/models/registryRootNode.ts | 5 +- explorer/models/rootNode.ts | 76 ++++++++++------------- explorer/models/taskNode.ts | 67 ++++++++++++++++++++ images/dark/buildTasks_dark.svg | 1 + images/light/buildTasks_light.svg | 1 + utils/Azure/acrTools.ts | 12 +++- utils/Azure/models/repository.ts | 2 +- 9 files changed, 142 insertions(+), 61 deletions(-) create mode 100644 explorer/models/taskNode.ts create mode 100644 images/dark/buildTasks_dark.svg create mode 100644 images/light/buildTasks_light.svg diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 9c0ac8507d..e615ea8ad4 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -68,7 +68,7 @@ export async function createRegistry(): Promise { // INPUT HELPERS async function acquireSKU(): Promise { - let skus: string[] = ["Basic", "Standard", "Premium"]; + let skus: string[] = ["Standard", "Basic", "Premium"]; let sku: string; sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU' }); if (sku === undefined) { throw new Error('User exit'); } diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 410f81adc0..31d4cd5714 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -9,6 +9,7 @@ import { AsyncPool } from '../../utils/asyncpool'; import { MAX_CONCURRENT_REQUESTS } from '../../utils/constants' import { NodeBase } from './nodeBase'; import { RegistryType } from './registryType'; +import { TaskRootNode } from './taskNode'; export class AzureRegistryNode extends NodeBase { private _azureAccount: AzureAccount; @@ -38,9 +39,17 @@ export class AzureRegistryNode extends NodeBase { } } - public async getChildren(element: AzureRegistryNode): Promise { - const repoNodes: AzureRepositoryNode[] = []; - let node: AzureRepositoryNode; + public async getChildren(element: AzureRegistryNode): Promise { + const registryChildNodes: NodeBase[] = []; + + let iconPath = { + light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'buildTasks_light.svg'), + dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'buildTasks_dark.svg') + }; + + //Pushing single TaskRootNode under the current registry. All the following nodes added to registryNodes are type AzureRepositoryNode + let taskNode = new TaskRootNode("Build Tasks", "taskRootNode", element.subscription, element.azureAccount, element.registry, iconPath); + registryChildNodes.push(taskNode); const tenantId: string = element.subscription.tenantId; if (!this._azureAccount) { @@ -50,6 +59,8 @@ export class AzureRegistryNode extends NodeBase { const session: AzureSession = this._azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase()); const { accessToken, refreshToken } = await acquireToken(session); + let node: AzureRepositoryNode; + if (accessToken && refreshToken) { let refreshTokenARC; let accessTokenARC; @@ -91,9 +102,8 @@ export class AzureRegistryNode extends NodeBase { }, (err, httpResponse, body) => { if (body.length > 0) { const repositories = JSON.parse(body).repositories; - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < repositories.length; i++) { - node = new AzureRepositoryNode(repositories[i], "azureRepositoryNode"); + for (let repo of repositories) { + node = new AzureRepositoryNode(repo, "azureRepositoryNode"); node.accessTokenARC = accessTokenARC; node.azureAccount = element.azureAccount; node.password = element.password; @@ -102,13 +112,13 @@ export class AzureRegistryNode extends NodeBase { node.repository = element.label; node.subscription = element.subscription; node.userName = element.userName; - repoNodes.push(node); + registryChildNodes.push(node); } } }); } //Note these are ordered by default in alphabetical order - return repoNodes; + return registryChildNodes; } } @@ -117,7 +127,7 @@ export class AzureRepositoryNode extends NodeBase { constructor( public readonly label: string, public readonly contextValue: string, - public readonly iconPath: { light: string | vscode.Uri; dark: string | vscode.Uri } = { + public readonly iconPath: AzureRegistryNode["iconPath"] = { light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'Repository_16x.svg'), dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'Repository_16x.svg') } @@ -199,10 +209,9 @@ export class AzureRepositoryNode extends NodeBase { }); const pool = new AsyncPool(MAX_CONCURRENT_REQUESTS); - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < tags.length; i++) { + for (let tag of tags) { pool.addTask(async () => { - let data = await request.get('https://' + element.repository + '/v2/' + element.label + `/manifests/${tags[i]}`, { + let data = await request.get('https://' + element.repository + '/v2/' + element.label + `/manifests/${tag}`, { auth: { bearer: accessTokenARC } @@ -210,7 +219,7 @@ export class AzureRepositoryNode extends NodeBase { //Acquires each image's manifest to acquire build time. let manifest = JSON.parse(data); - node = new AzureImageNode(`${element.label}:${tags[i]}`, 'azureImageNode'); + node = new AzureImageNode(`${element.label}:${tag}`, 'azureImageNode'); node.azureAccount = element.azureAccount; node.password = element.password; node.registry = element.registry; @@ -296,7 +305,7 @@ async function acquireToken(session: AzureSession): Promise<{ accessToken: strin const credentials: any = session.credentials; const environment: any = session.environment; // tslint:disable-next-line:no-function-expression // Grandfathered in - credentials.context.acquireToken(environment.activeDirectoryResourceId, credentials.username, credentials.clientId, function (err: any, result: { accessToken: string; refreshToken: string; }): void { + credentials.context.acquireToken(environment.activeDirectoryResourceId, credentials.username, credentials.clientId, function (err: any, result: any): any { if (err) { reject(err); } else { diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 4456dcfebd..649b993dcc 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -9,6 +9,7 @@ import * as ContainerModels from '../../node_modules/azure-arm-containerregistry import * as ContainerOps from '../../node_modules/azure-arm-containerregistry/lib/operations'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import { AsyncPool } from '../../utils/asyncpool'; +import * as acrTools from '../../utils/Azure/acrTools'; import { MAX_CONCURRENT_REQUESTS, MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from '../../utils/constants' import * as dockerHub from '../utils/dockerHubUtils' import { getCoreNodeModule } from '../utils/utils'; @@ -158,8 +159,10 @@ export class RegistryRootNode extends NodeBase { //Go through the registries and add them to the async pool // tslint:disable-next-line:prefer-for-of // Grandfathered in for (let j = 0; j < registries.length; j++) { + if (registries[j].adminUserEnabled && !registries[j].sku.tier.includes('Classic')) { - const resourceGroup: string = registries[j].id.slice(registries[j].id.search('resourceGroups/') + 'resourceGroups/'.length, registries[j].id.search('/providers/')); + const resourceGroup: string = acrTools.getResourceGroup(registries[j]); + regPool.addTask(async () => { let creds = await client.registries.listCredentials(resourceGroup, registries[j].name); let iconPath = { diff --git a/explorer/models/rootNode.ts b/explorer/models/rootNode.ts index a14001f48f..e9bb05ac31 100644 --- a/explorer/models/rootNode.ts +++ b/explorer/models/rootNode.ts @@ -53,7 +53,6 @@ export class RootNode extends NodeBase { // clearInterval(this._imageDebounceTimer); // return; // } - clearInterval(this._imageDebounceTimer); if (refreshInterval > 0) { @@ -70,13 +69,9 @@ export class RootNode extends NodeBase { if (this._imageCache.length !== images.length) { needToRefresh = true; } else { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i: number = 0; i < this._imageCache.length; i++) { - let before: string = JSON.stringify(this._imageCache[i]); - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let j: number = 0; j < images.length; j++) { - let after: string = JSON.stringify(images[j]); - if (before === after) { + for (let cachedImage of this._imageCache) { + for (let image of images) { + if (image === cachedImage) { found = true; break; } @@ -108,18 +103,21 @@ export class RootNode extends NodeBase { } - public async getChildren(element: NodeBase): Promise { - - if (element.contextValue === 'imagesRootNode') { - return this.getImages(); - } - if (element.contextValue === 'containersRootNode') { - return this.getContainers(); - } - if (element.contextValue === 'registriesRootNode') { - return this.getRegistries() + public async getChildren(element: RootNode): Promise { + switch (element.contextValue) { + case 'imagesRootNode': { + return this.getImages(); + } + case 'containersRootNode': { + return this.getContainers(); + } + case 'registriesRootNode': { + return this.getRegistries(); + } + default: { + break; + } } - } private async getImages(): Promise { @@ -132,19 +130,15 @@ export class RootNode extends NodeBase { return []; } - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < images.length; i++) { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - if (!images[i].RepoTags) { + for (let image of images) { + if (!image.RepoTags) { let node = new ImageNode(`:`, "localImageNode", this.eventEmitter); - node.imageDesc = images[i]; + node.imageDesc = image; imageNodes.push(node); } else { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let j = 0; j < images[i].RepoTags.length; j++) { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - let node = new ImageNode(`${images[i].RepoTags[j]}`, "localImageNode", this.eventEmitter); - node.imageDesc = images[i]; + for (let repoTag of image.RepoTags) { + let node = new ImageNode(`${repoTag}`, "localImageNode", this.eventEmitter); + node.imageDesc = image; imageNodes.push(node); } } @@ -168,7 +162,6 @@ export class RootNode extends NodeBase { // clearInterval(this._containerDebounceTimer); // return; // } - clearInterval(this._containerDebounceTimer); if (refreshInterval > 0) { @@ -186,15 +179,13 @@ export class RootNode extends NodeBase { if (this._containerCache.length !== containers.length) { needToRefresh = true; } else { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < this._containerCache.length; i++) { - let ctr: Docker.ContainerDesc = this._containerCache[i]; - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let j = 0; j < containers.length; j++) { + for (let cachedContainer of this._containerCache) { + let ctr: Docker.ContainerDesc = cachedContainer; + for (let cont of containers) { // 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) { + if (ctr.Id === cont.Id && + ctr.Image === cont.Image && + ctr.State === cont.State) { found = true; break; } @@ -230,9 +221,8 @@ export class RootNode extends NodeBase { return []; } - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < containers.length; i++) { - if (['exited', 'dead'].includes(containers[i].State)) { + for (let container of containers) { + if (['exited', 'dead'].includes(container.State)) { contextValue = "stoppedLocalContainerNode"; iconPath = { light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'stoppedContainer.svg'), @@ -246,8 +236,8 @@ export class RootNode extends NodeBase { }; } - let containerNode: ContainerNode = new ContainerNode(`${containers[i].Image} (${containers[i].Names[0].substring(1)}) (${containers[i].Status})`, contextValue, iconPath); - containerNode.containerDesc = containers[i]; + let containerNode: ContainerNode = new ContainerNode(`${container.Image} (${container.Names[0].substring(1)}) (${container.Status})`, contextValue, iconPath); + containerNode.containerDesc = container; containerNodes.push(containerNode); } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts new file mode 100644 index 0000000000..a3ddc9059d --- /dev/null +++ b/explorer/models/taskNode.ts @@ -0,0 +1,67 @@ +import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; +import * as opn from 'opn'; +import * as vscode from 'vscode'; +import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; +import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import { NodeBase } from './nodeBase'; + +/* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ +export class TaskRootNode extends NodeBase { + constructor( + public readonly label: string, + public readonly contextValue: string, + public subscription: SubscriptionModels.Subscription, + public readonly azureAccount: AzureAccount, + public registry: ContainerModels.Registry, + public readonly iconPath: any = {} + ) { + super(label); + } + + public name: string; + + public getTreeItem(): vscode.TreeItem { + return { + label: this.label, + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + contextValue: this.contextValue, + iconPath: this.iconPath + } + } + + /* Making a list view of BuildTaskNodes, or the Build Tasks of the current registry */ + public async getChildren(element: TaskRootNode): Promise { + const buildTaskNodes: BuildTaskNode[] = []; + let buildTasks: ContainerModels.BuildTask[] = []; + + const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(element.subscription); + const resourceGroup: string = acrTools.getResourceGroup(element.registry); + + buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); + if (buildTasks.length === 0) { + vscode.window.showInformationMessage(`You do not have any Build Tasks in the registry, '${element.registry.name}'. You can create one with ACR Build. `, "Learn More").then(val => { + if (val === "Learn More") { + opn('https://aka.ms/acr/buildtask'); + } + }) + } + + for (let buildTask of buildTasks) { + let node = new BuildTaskNode(buildTask.name, "buildTaskNode"); + buildTaskNodes.push(node); + } + return buildTaskNodes; + } +} + +export class BuildTaskNode extends NodeBase { + + constructor( + public readonly label: string, + public readonly contextValue: string, + ) { + super(label); + } +} diff --git a/images/dark/buildTasks_dark.svg b/images/dark/buildTasks_dark.svg new file mode 100644 index 0000000000..618310ec7c --- /dev/null +++ b/images/dark/buildTasks_dark.svg @@ -0,0 +1 @@ +Manufacture_16x \ No newline at end of file diff --git a/images/light/buildTasks_light.svg b/images/light/buildTasks_light.svg new file mode 100644 index 0000000000..27f50db2ce --- /dev/null +++ b/images/light/buildTasks_light.svg @@ -0,0 +1 @@ +Manufacture_16x \ No newline at end of file diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 796113d4bc..f31653e39a 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -7,7 +7,6 @@ import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; import { AzureImage } from "../Azure/models/image"; import { Repository } from "../Azure/models/Repository"; import { AzureUtilityManager } from '../azureUtilityManager'; -const teleCmdId: string = 'vscode-docker.deleteAzureImage'; /** * Developers can use this to visualize and list repositories on a given Registry. This is not a command, just a developer tool. @@ -42,6 +41,15 @@ export async function getAzureRepositories(registry: Registry): Promise Date: Tue, 7 Aug 2018 17:12:56 -0700 Subject: [PATCH 19/77] Added quick pick for selecting resource group and registry --- commands/acr-build.ts | 40 ++++++++++- commands/utils/quick-pick-azure.ts | 106 ++++++++++++++++++++++++++++- 2 files changed, 142 insertions(+), 4 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index e3ec181506..6db934aef2 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -1,15 +1,38 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; +<<<<<<< HEAD +import { Registry } from 'azure-arm-containerregistry/lib/models'; +import { ResourceManagementClient } from 'azure-arm-resource'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as vscode from "vscode"; import { ImageNode } from "../explorer/models/imageNode"; import { AzureUtilityManager } from "../utils/azureUtilityManager"; +import { acquireResourceGroup, quickPickACRRegistry } from './utils/quick-pick-azure'; +======= +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as vscode from "vscode"; +import { ImageNode } from "../explorer/models/imageNode"; +import { AzureCredentialsManager } from "../utils/AzureCredentialsManager"; +>>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 let tar = require('tar'); let fs = require('fs'); let os = require('os'); let url = require('url'); export async function queueBuild(context?: ImageNode): Promise { +<<<<<<< HEAD + + console.log("Obtaining Subscription and Client"); + let subscription = AzureUtilityManager.getInstance().getFilteredSubscriptionList()[0]; + let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + + let resourceGroup = await acquireResourceGroup(subscription, resourceGroupClient); + let resourceGroupName = resourceGroup.name; + + let registry: Registry = await quickPickACRRegistry(subscription, resourceGroupName); + let registryName = registry.name; +======= let opt: vscode.InputBoxOptions = { ignoreFocusOut: true, prompt: 'Resource Group? ' @@ -22,15 +45,20 @@ export async function queueBuild(context?: ImageNode): Promise { const registryName: string = await vscode.window.showInputBox(opt); console.log("Obtaining Subscription and Client"); - let subscription = AzureUtilityManager.getInstance().getFilteredSubscriptionList()[0]; - let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let subscription = AzureCredentialsManager.getInstance().getFilteredSubscriptionList()[0]; + let client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); +>>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 console.log("Setting up temp file with 'sourceArchive.tar.gz' "); let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); console.log("Uploading Source Code"); let sourceLocation: string = vscode.workspace.rootPath; +<<<<<<< HEAD + sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); +======= sourceLocation = await uploadSourceCode(client, registryName, resourceGroup, sourceLocation, tarFilePath); +>>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 console.log("Setting up Build Request"); let buildRequest: QuickBuildRequest = { @@ -44,11 +72,19 @@ export async function queueBuild(context?: ImageNode): Promise { console.log("Queueing Build"); try { +<<<<<<< HEAD + await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); + } catch (error) { + console.log(error.message); + } + console.log(client.builds.list(resourceGroupName, registryName)); +======= await client.registries.queueBuild(resourceGroup, registryName, buildRequest); } catch (error) { console.log(error.message); } console.log(client.builds.list(resourceGroup, registryName)); +>>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index ae7b49f092..c1c3bfe2ff 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -1,11 +1,15 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { Registry } from 'azure-arm-containerregistry/lib/models'; import * as vscode from "vscode"; +import { ResourceGroup } from '../../node_modules/azure-arm-resource/lib/resource/models'; +import { Subscription } from '../../node_modules/azure-arm-resource/lib/subscription/models'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureImage } from "../../utils/Azure/models/image"; import { Repository } from "../../utils/Azure/models/Repository"; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; + /** * function to allow user to pick a desired image for use * @param repository the repository to look in @@ -44,9 +48,9 @@ export async function quickPickACRRepository(registry: Registry): Promise { +export async function quickPickACRRegistry(subscription?: Subscription, resourceGroup?: string): Promise { //first get desired registry - let registries = await AzureUtilityManager.getInstance().getRegistries(); + let registries = await AzureUtilityManager.getInstance().getRegistries(subscription, resourceGroup); let reg: string[] = []; for (let registryName of registries) { reg.push(registryName.name); @@ -56,3 +60,101 @@ export async function quickPickACRRegistry(): Promise { const registry = registries.find((currentReg): boolean => { return desired === currentReg.name }); return registry; } + +export async function acquireResourceGroup(subscription: Subscription, resourceGroupClient: ResourceManagementClient): Promise { + //Acquire each subscription's data simultaneously + let resourceGroup; + let resourceGroupName; + //const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + let resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); + + let resourceGroupNames: string[] = []; + resourceGroupNames.push('+ Create new resource group'); + for (let resGroupName of resourceGroups) { + resourceGroupNames.push(resGroupName.name); + } + + do { + resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, { 'canPickMany': false, 'placeHolder': 'Choose a Resource Group to be used' }); + if (resourceGroupName === undefined) { throw new Error('user Exit'); } + if (resourceGroupName === '+ Create new resource group') { + let loc = await acquireLocation(resourceGroup, subscription); + resourceGroupName = await createNewResourceGroup(loc, resourceGroupClient); + } + resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); + resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); + + if (!resourceGroupName) { vscode.window.showErrorMessage('You must select a valid resource group'); } + } while (!resourceGroupName); + return resourceGroup; +} + +async function acquireLocation(resourceGroup: ResourceGroup, subscription: SubscriptionModels.Subscription): Promise { + let locations: SubscriptionModels.Location[] = await AzureUtilityManager.getInstance().getLocationsBySubscription(subscription); + let locationNames: string[] = []; + let placeHolder: string; + + for (let loc of locations) { + locationNames.push(loc.displayName); + } + + locationNames.sort((loc1: string, loc2: string): number => { + return loc1.localeCompare(loc2); + }); + + if (resourceGroup === undefined) { + placeHolder = "Choose location for your new resource group"; + } else { + placeHolder = resourceGroup.location; + + //makes placeholder the Display Name version of the location's name + locations.forEach((locObj: SubscriptionModels.Location): string => { + if (locObj.name === resourceGroup.location) { + placeHolder = locObj.displayName; + return; + } + }); + } + let location: string; + do { + location = await vscode.window.showQuickPick(locationNames, { 'canPickMany': false, 'placeHolder': placeHolder }); + if (location === undefined) { throw new Error('User exit'); } + } while (!location); + return location; +} + +/*Creates a new resource group within the current subscription */ +async function createNewResourceGroup(loc: string, resourceGroupClient: ResourceManagementClient): Promise { + let promptMessage = 'Resource group name?'; + + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: false, + prompt: promptMessage + }; + + let resourceGroupName: string; + let resourceGroupStatus: boolean; + + while (opt.prompt) { + resourceGroupName = await vscode.window.showInputBox(opt); + resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); + if (!resourceGroupStatus) { + opt.prompt = null; + } else { + opt.prompt = `The resource group '${resourceGroupName}' already exists. Try again: `; + } + } + + let newResourceGroup: ResourceGroup = { + name: resourceGroupName, + location: loc, + }; + + //Potential error when two clients try to create same resource group name at once + try { + await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); + } catch (error) { + vscode.window.showErrorMessage(`The resource group '${resourceGroupName}' already exists. Try again: `); + } + return resourceGroupName; +} From 73f6e8193fd4f81d88060d673681ba76ecd89d61 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Tue, 7 Aug 2018 17:13:38 -0700 Subject: [PATCH 20/77] clean --- commands/acr-build.ts | 36 ------------------------------------ 1 file changed, 36 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index 6db934aef2..75095e10eb 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -1,6 +1,5 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; -<<<<<<< HEAD import { Registry } from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient } from 'azure-arm-resource'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; @@ -8,19 +7,12 @@ import * as vscode from "vscode"; import { ImageNode } from "../explorer/models/imageNode"; import { AzureUtilityManager } from "../utils/azureUtilityManager"; import { acquireResourceGroup, quickPickACRRegistry } from './utils/quick-pick-azure'; -======= -import { BlobService, createBlobServiceWithSas } from "azure-storage"; -import * as vscode from "vscode"; -import { ImageNode } from "../explorer/models/imageNode"; -import { AzureCredentialsManager } from "../utils/AzureCredentialsManager"; ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 let tar = require('tar'); let fs = require('fs'); let os = require('os'); let url = require('url'); export async function queueBuild(context?: ImageNode): Promise { -<<<<<<< HEAD console.log("Obtaining Subscription and Client"); let subscription = AzureUtilityManager.getInstance().getFilteredSubscriptionList()[0]; @@ -32,33 +24,13 @@ export async function queueBuild(context?: ImageNode): Promise { let registry: Registry = await quickPickACRRegistry(subscription, resourceGroupName); let registryName = registry.name; -======= - let opt: vscode.InputBoxOptions = { - ignoreFocusOut: true, - prompt: 'Resource Group? ' - }; - const resourceGroup: string = await vscode.window.showInputBox(opt); - opt = { - ignoreFocusOut: true, - prompt: 'Registry name? ' - }; - const registryName: string = await vscode.window.showInputBox(opt); - - console.log("Obtaining Subscription and Client"); - let subscription = AzureCredentialsManager.getInstance().getFilteredSubscriptionList()[0]; - let client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 console.log("Setting up temp file with 'sourceArchive.tar.gz' "); let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); console.log("Uploading Source Code"); let sourceLocation: string = vscode.workspace.rootPath; -<<<<<<< HEAD sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); -======= - sourceLocation = await uploadSourceCode(client, registryName, resourceGroup, sourceLocation, tarFilePath); ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 console.log("Setting up Build Request"); let buildRequest: QuickBuildRequest = { @@ -72,19 +44,11 @@ export async function queueBuild(context?: ImageNode): Promise { console.log("Queueing Build"); try { -<<<<<<< HEAD await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); } catch (error) { console.log(error.message); } console.log(client.builds.list(resourceGroupName, registryName)); -======= - await client.registries.queueBuild(resourceGroup, registryName, buildRequest); - } catch (error) { - console.log(error.message); - } - console.log(client.builds.list(resourceGroup, registryName)); ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { From 53b215a70d55e9c92fb84a12c37e82633f93d37d Mon Sep 17 00:00:00 2001 From: jvstokes Date: Tue, 7 Aug 2018 17:58:25 -0700 Subject: [PATCH 21/77] Added subscription support --- commands/acr-build.ts | 41 +++--------------------------- commands/utils/quick-pick-azure.ts | 22 ++++++++++++++++ 2 files changed, 25 insertions(+), 38 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index 6db934aef2..2da2f8d808 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -1,29 +1,21 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; -<<<<<<< HEAD import { Registry } from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient } from 'azure-arm-resource'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as vscode from "vscode"; import { ImageNode } from "../explorer/models/imageNode"; import { AzureUtilityManager } from "../utils/azureUtilityManager"; -import { acquireResourceGroup, quickPickACRRegistry } from './utils/quick-pick-azure'; -======= -import { BlobService, createBlobServiceWithSas } from "azure-storage"; -import * as vscode from "vscode"; -import { ImageNode } from "../explorer/models/imageNode"; -import { AzureCredentialsManager } from "../utils/AzureCredentialsManager"; ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 +import { acquireResourceGroup, acquireSubscription, quickPickACRRegistry } from './utils/quick-pick-azure'; let tar = require('tar'); let fs = require('fs'); let os = require('os'); let url = require('url'); export async function queueBuild(context?: ImageNode): Promise { -<<<<<<< HEAD console.log("Obtaining Subscription and Client"); - let subscription = AzureUtilityManager.getInstance().getFilteredSubscriptionList()[0]; + let subscription = await acquireSubscription(); let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); @@ -32,33 +24,13 @@ export async function queueBuild(context?: ImageNode): Promise { let registry: Registry = await quickPickACRRegistry(subscription, resourceGroupName); let registryName = registry.name; -======= - let opt: vscode.InputBoxOptions = { - ignoreFocusOut: true, - prompt: 'Resource Group? ' - }; - const resourceGroup: string = await vscode.window.showInputBox(opt); - opt = { - ignoreFocusOut: true, - prompt: 'Registry name? ' - }; - const registryName: string = await vscode.window.showInputBox(opt); - - console.log("Obtaining Subscription and Client"); - let subscription = AzureCredentialsManager.getInstance().getFilteredSubscriptionList()[0]; - let client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 console.log("Setting up temp file with 'sourceArchive.tar.gz' "); let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); console.log("Uploading Source Code"); let sourceLocation: string = vscode.workspace.rootPath; -<<<<<<< HEAD sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); -======= - sourceLocation = await uploadSourceCode(client, registryName, resourceGroup, sourceLocation, tarFilePath); ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 console.log("Setting up Build Request"); let buildRequest: QuickBuildRequest = { @@ -72,19 +44,12 @@ export async function queueBuild(context?: ImageNode): Promise { console.log("Queueing Build"); try { -<<<<<<< HEAD await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); } catch (error) { + console.log('Build Failed'); console.log(error.message); } console.log(client.builds.list(resourceGroupName, registryName)); -======= - await client.registries.queueBuild(resourceGroup, registryName, buildRequest); - } catch (error) { - console.log(error.message); - } - console.log(client.builds.list(resourceGroup, registryName)); ->>>>>>> 2332645099ddef2593b806586cf2d69a5587f979 } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index c1c3bfe2ff..5421e5ecfc 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -1,5 +1,6 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { Registry } from 'azure-arm-containerregistry/lib/models'; +import * as opn from 'opn'; import * as vscode from "vscode"; import { ResourceGroup } from '../../node_modules/azure-arm-resource/lib/resource/models'; import { Subscription } from '../../node_modules/azure-arm-resource/lib/subscription/models'; @@ -158,3 +159,24 @@ async function createNewResourceGroup(loc: string, resourceGroupClient: Resource } return resourceGroupName; } + +export async function acquireSubscription(): Promise { + const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); + if (subs.length === 0) { + vscode.window.showErrorMessage("You do not have any subscriptions. You can create one in your Azure Portal", "Open Portal").then(val => { + if (val === "Open Portal") { + opn('https://portal.azure.com/'); + } + }); + } + + let subsNames: string[] = []; + for (let sub of subs) { + subsNames.push(sub.displayName); + } + let subscriptionName: string; + subscriptionName = await vscode.window.showQuickPick(subsNames, { 'canPickMany': false, 'placeHolder': 'Choose a subscription to be used' }); + if (subscriptionName === undefined) { throw new Error('User exit'); } + + return subs.find(sub => { return sub.displayName === subscriptionName }); +} From 108a5bb058fc78af200b4194f2844462edf4618f Mon Sep 17 00:00:00 2001 From: jvstokes Date: Wed, 8 Aug 2018 10:33:55 -0700 Subject: [PATCH 22/77] utility bug fix --- explorer/models/taskNode.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index a3ddc9059d..ccc3f65305 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode'; import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; -import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { NodeBase } from './nodeBase'; /* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ @@ -36,7 +36,7 @@ export class TaskRootNode extends NodeBase { const buildTaskNodes: BuildTaskNode[] = []; let buildTasks: ContainerModels.BuildTask[] = []; - const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(element.subscription); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); const resourceGroup: string = acrTools.getResourceGroup(element.registry); buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); From 0a3d6e1aee6f32e28f2f57e2a3cfa53c7d78a491 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 9 Aug 2018 14:40:50 -0700 Subject: [PATCH 23/77] Julia/delete repository final (#49) * deleteRepo moved over to branch off dev * Got rid of unnecessary code, fully functioning! * deleteRepo moved over to branch off dev * Got rid of unnecessary code, fully functioning! * spacing * final commit * Cleaned code * Added Telemetry --- commands/azureCommands/delete-azure-image.ts | 13 ++++- commands/azureCommands/delete-repository.ts | 58 ++++++++++++++++++++ dockerExtension.ts | 4 +- explorer/models/taskNode.ts | 4 +- package.json | 22 ++++++-- utils/Azure/acrTools.ts | 23 +++----- utils/constants.ts | 2 + 7 files changed, 100 insertions(+), 26 deletions(-) create mode 100644 commands/azureCommands/delete-repository.ts diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index eb37c5a0e6..f8ec7a220e 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -3,10 +3,12 @@ import { SubscriptionModels } from 'azure-arm-resource'; import * as vscode from "vscode"; import * as quickPicks from '../../commands/utils/quick-pick-azure'; import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; +import { reporter } from '../../telemetry/telemetry'; import * as acrTools from '../../utils/Azure/acrTools'; import { Repository } from "../../utils/Azure/models/repository"; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; -const teleCmdId: string = 'vscode-docker.deleteAzureImage'; + +const teleCmdId: string = 'vscode-docker.deleteACRImage'; /** Function to delete an Azure repository and its associated images * @param context : if called through right click on AzureRepositoryNode, the node object will be passed in. See azureRegistryNodes.ts for more info @@ -56,4 +58,13 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise password = creds.password; let path = `/v2/_acr/${repoName}/tags/${tag}`; await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image + reportTelemetry(); +} + +function reportTelemetry(): void { + if (reporter) { + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + } } diff --git a/commands/azureCommands/delete-repository.ts b/commands/azureCommands/delete-repository.ts new file mode 100644 index 0000000000..c6fdcb5af0 --- /dev/null +++ b/commands/azureCommands/delete-repository.ts @@ -0,0 +1,58 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { SubscriptionModels } from 'azure-arm-resource'; +import * as vscode from "vscode"; +import * as quickPicks from '../../commands/utils/quick-pick-azure'; +import { AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; +import { reporter } from '../../telemetry/telemetry'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { Repository } from "../../utils/Azure/models/repository"; + +const teleCmdId: string = 'vscode-docker.deleteACRRepository'; +/** + * function to delete an Azure repository and its associated images + * @param context : if called through right click on AzureRepositoryNode, the node object will be passed in. See azureRegistryNodes.ts for more info + */ +export async function deleteRepository(context?: AzureRepositoryNode): Promise { + + let registry: Registry; + let subscription: SubscriptionModels.Subscription; + let repoName: string; + + if (context) { + repoName = context.label; + subscription = context.subscription; + registry = context.registry; + } else { + registry = await quickPicks.quickPickACRRegistry(); + subscription = acrTools.getRegistrySubscription(registry); + const repository: Repository = await quickPicks.quickPickACRRepository(registry); + repoName = repository.name; + } + + // Ensure user truly wants to delete registry + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: true, + placeHolder: 'No', + value: 'No', + prompt: 'Are you sure you want to delete this repository and its associated images? Enter Yes to continue: ' + }; + + let answer = await vscode.window.showInputBox(opt); + answer = answer.toLowerCase(); + if (answer !== 'yes') { return; } + + let creds = await acrTools.loginCredentials(subscription, registry); + const username: string = creds.username; + const password: string = creds.password; + let path = `/v2/_acr/${repoName}/repository`; + await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); + reportTelemetry(); +} + +function reportTelemetry(): void { + if (reporter) { + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + } +} diff --git a/dockerExtension.ts b/dockerExtension.ts index 3fc10d1880..adbefbb7a0 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -9,6 +9,7 @@ import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExt import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient'; import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; +import { deleteRepository } from './commands/azureCommands/delete-repository'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; import inspectImage from './commands/inspect-image'; @@ -153,7 +154,8 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider())); if (azureAccount) { - registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage); + registerCommand('vscode-docker.deleteACRImage', deleteAzureImage); + registerCommand('vscode-docker.deleteACRRepository', deleteRepository); registerCommand('vscode-docker.createRegistry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index a3ddc9059d..486802b3cb 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode'; import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; -import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import { AzureUtilityManager } from '../../utils/AzureUtilityManager'; import { NodeBase } from './nodeBase'; /* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ @@ -36,7 +36,7 @@ export class TaskRootNode extends NodeBase { const buildTaskNodes: BuildTaskNode[] = []; let buildTasks: ContainerModels.BuildTask[] = []; - const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(element.subscription); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); const resourceGroup: string = acrTools.getResourceGroup(element.registry); buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); diff --git a/package.json b/package.json index db72e6fb0f..a88d6bc189 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,8 @@ "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", - "onCommand:vscode-docker.deleteAzureImage", + "onCommand:vscode-docker.deleteACRRepository", + "onCommand:vscode-docker.deleteACRImage", "onView:dockerExplorer", "onDebugInitialConfigurations" ], @@ -257,6 +258,10 @@ "command": "vscode-docker.dockerHubLogout", "when": "view == dockerExplorer && viewItem == dockerHubRootNode" }, + { + "command": "vscode-docker.deleteACRRepository", + "when": "view == dockerExplorer && viewItem == azureRepositoryNode" + }, { "command": "vscode-docker.deleteAzureImage", "when": "view == dockerExplorer && viewItem == azureImageNode" @@ -582,6 +587,16 @@ "title": "Create Registry", "category": "Docker" }, + { + "command": "vscode-docker.deleteACRRepository", + "title": "Delete Azure Repository", + "category": "Docker" + }, + { + "command": "vscode-docker.deleteAzureImage", + "title": "Delete Azure Image", + "category": "Docker" + }, { "command": "vscode-docker.image.push", "title": "Push", @@ -625,11 +640,6 @@ "command": "vscode-docker.browseAzurePortal", "title": "Browse in the Azure Portal", "category": "Docker" - }, - { - "command": "vscode-docker.deleteAzureImage", - "title": "Delete Azure Image", - "category": "Docker" } ], "views": { diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index f31653e39a..b5dcb9b2d1 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -7,6 +7,7 @@ import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; import { AzureImage } from "../Azure/models/image"; import { Repository } from "../Azure/models/Repository"; import { AzureUtilityManager } from '../azureUtilityManager'; +import { NULL_GUID } from "../constants"; /** * Developers can use this to visualize and list repositories on a given Registry. This is not a command, just a developer tool. @@ -42,7 +43,6 @@ export async function getAzureRepositories(registry: Registry): Promise return allImages; } -//Implements new Service principal model for ACR container registries while maintaining old admin enabled use -/** - * this function implements a new Service principal model for ACR and gets the valid login credentials to make an API call +/** Acquires login credentials for a registry in the form of refresh tokens and NULL_GUID * @param subscription : the subscription the registry is on * @param registry : the registry to get login credentials for * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username @@ -241,13 +236,12 @@ export async function loginCredentials(subscription: SubscriptionModels.Subscrip //grab the access token to be used as a password, and a generic username let creds = await getRegistryTokens(registry); password = creds.accessToken; - username = '00000000-0000-0000-0000-000000000000'; + username = NULL_GUID; } return { password, username }; } /** - * * @param http_method : the http method, this function currently only uses delete * @param login_server: the login server of the registry * @param path : the URL path @@ -262,16 +256,13 @@ export async function sendRequestToRegistry(http_method: string, login_server: s http_method: http_method, url: url } - try { + if (http_method === 'delete') { await request.delete(opt); - } catch (error) { - throw error; + vscode.window.showInformationMessage('Successfully deleted item'); } - vscode.window.showInformationMessage('Successfully deleted image'); } /** - * * @param registry gets the subscription for a given registry * @returns a subscription object */ diff --git a/utils/constants.ts b/utils/constants.ts index 5cbe68c1f0..745230d2d4 100644 --- a/utils/constants.ts +++ b/utils/constants.ts @@ -2,3 +2,5 @@ //AsyncPool Constants export const MAX_CONCURRENT_REQUESTS = 8; export const MAX_CONCURRENT_SUBSCRIPTON_REQUESTS = 5; +//Credentials Constants +export const NULL_GUID = '00000000-0000-0000-0000-000000000000'; From 19f8b72a067d673cf9ed3ae5b55000941aac78a6 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 9 Aug 2018 15:28:51 -0700 Subject: [PATCH 24/77] Julia/delete registry final (#47) Delete azure registry functionality added Delete azure registry moved to branch off dev Reorganized stye --- .../azureCommands/delete-azure-registry.ts | 49 +++++++ dockerExtension.ts | 7 +- explorer/models/registryRootNode.ts | 2 +- explorer/models/taskNode.ts | 4 +- package.json | 131 +++++++++--------- utils/Azure/acrTools.ts | 2 +- utils/Azure/models/repository.ts | 2 +- utils/azureUtilityManager.ts | 4 +- 8 files changed, 125 insertions(+), 76 deletions(-) create mode 100644 commands/azureCommands/delete-azure-registry.ts diff --git a/commands/azureCommands/delete-azure-registry.ts b/commands/azureCommands/delete-azure-registry.ts new file mode 100644 index 0000000000..6d03c9cc31 --- /dev/null +++ b/commands/azureCommands/delete-azure-registry.ts @@ -0,0 +1,49 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import * as vscode from "vscode"; +import { quickPickACRRegistry } from '../../commands/utils/quick-pick-azure'; +import { AzureRegistryNode } from '../../explorer/models/azureRegistryNodes'; +import { SubscriptionModels } from "../../node_modules/azure-arm-resource"; +import { reporter } from '../../telemetry/telemetry'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from '../../utils/AzureUtilityManager'; + +const teleCmdId: string = 'vscode-docker.deleteAzureRegistry'; + +/** Delete a registry and all it's associated nested items + * @param context : the AzureRegistryNode the user right clicked on to delete + */ +export async function deleteAzureRegistry(context?: AzureRegistryNode): Promise { + let registry: Registry; + if (context) { + registry = context.registry; + } else { + registry = await quickPickACRRegistry(); + } + + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: true, + placeHolder: 'No', + value: 'No', + prompt: 'Are you sure you want to delete this registry and its associated images? Enter yes to continue: ' + }; + let answer = await vscode.window.showInputBox(opt); + + answer = answer.toLowerCase(); + if (answer !== 'yes') { return; } + + let subscription: SubscriptionModels.Subscription = acrTools.getRegistrySubscription(registry); + let resourceGroup: string = acrTools.getResourceGroupName(registry); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + + await client.registries.beginDeleteMethod(resourceGroup, registry.name); + vscode.window.showInformationMessage('Successfully deleted registry ' + registry.name); + telemetryReport(); +} + +function telemetryReport(): void { + if (reporter) { + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + } +} diff --git a/dockerExtension.ts b/dockerExtension.ts index adbefbb7a0..5deb172764 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -9,6 +9,7 @@ import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExt import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient'; import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; +import { deleteAzureRegistry } from './commands/azureCommands/delete-azure-registry'; import { deleteRepository } from './commands/azureCommands/delete-repository'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; @@ -125,7 +126,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.compose.down', composeDown); registerCommand('vscode-docker.compose.restart', composeRestart); registerCommand('vscode-docker.system.prune', systemPrune); - registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { if (context) { if (azureAccount) { @@ -154,8 +154,9 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider())); if (azureAccount) { - registerCommand('vscode-docker.deleteACRImage', deleteAzureImage); - registerCommand('vscode-docker.deleteACRRepository', deleteRepository); + registerCommand('vscode-docker.delete-ACR-Registry', deleteAzureRegistry); + registerCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); + registerCommand('vscode-docker.delete-ACR-Repository', deleteRepository); registerCommand('vscode-docker.createRegistry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); } diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 649b993dcc..21a6a5a117 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -161,7 +161,7 @@ export class RegistryRootNode extends NodeBase { for (let j = 0; j < registries.length; j++) { if (registries[j].adminUserEnabled && !registries[j].sku.tier.includes('Classic')) { - const resourceGroup: string = acrTools.getResourceGroup(registries[j]); + const resourceGroup: string = acrTools.getResourceGroupName(registries[j]); regPool.addTask(async () => { let creds = await client.registries.listCredentials(resourceGroup, registries[j].name); diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 486802b3cb..eb6a74b21d 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode'; import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; -import { AzureUtilityManager } from '../../utils/AzureUtilityManager'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { NodeBase } from './nodeBase'; /* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ @@ -37,7 +37,7 @@ export class TaskRootNode extends NodeBase { let buildTasks: ContainerModels.BuildTask[] = []; const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); - const resourceGroup: string = acrTools.getResourceGroup(element.registry); + const resourceGroup: string = acrTools.getResourceGroupName(element.registry); buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); if (buildTasks.length === 0) { diff --git a/package.json b/package.json index a88d6bc189..9dc58eb032 100644 --- a/package.json +++ b/package.json @@ -52,16 +52,16 @@ "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", - "onCommand:vscode-docker.deleteACRRepository", - "onCommand:vscode-docker.deleteACRImage", + "onCommand:vscode-docker.delete-ACR-Registry", + "onCommand:vscode-docker.delete-ACR-Repository", + "onCommand:vscode-docker.delete-ACR-Image", "onView:dockerExplorer", "onDebugInitialConfigurations" ], "main": "./out/dockerExtension", "contributes": { "menus": { - "commandPalette": [ - { + "commandPalette": [{ "command": "vscode-docker.browseDockerHub", "when": "false" }, @@ -70,8 +70,7 @@ "when": "false" } ], - "editor/context": [ - { + "editor/context": [{ "when": "editorLangId == dockerfile", "command": "vscode-docker.image.build", "group": "docker" @@ -107,8 +106,7 @@ "group": "docker" } ], - "explorer/context": [ - { + "explorer/context": [{ "when": "resourceFilename =~ /[dD]ocker[fF]ile/", "command": "vscode-docker.image.build", "group": "docker" @@ -129,8 +127,7 @@ "group": "docker" } ], - "view/title": [ - { + "view/title": [{ "command": "vscode-docker.explorer.refresh", "when": "view == dockerExplorer", "group": "navigation" @@ -141,8 +138,7 @@ "group": "navigation" } ], - "view/item/context": [ - { + "view/item/context": [{ "command": "vscode-docker.container.start", "when": "view == dockerExplorer && viewItem == localImageNode" }, @@ -259,13 +255,17 @@ "when": "view == dockerExplorer && viewItem == dockerHubRootNode" }, { - "command": "vscode-docker.deleteACRRepository", + "command": "vscode-docker.delete-ACR-Repository", "when": "view == dockerExplorer && viewItem == azureRepositoryNode" }, { - "command": "vscode-docker.deleteAzureImage", + "command": "vscode-docker.delete-ACR-Image", "when": "view == dockerExplorer && viewItem == azureImageNode" }, + { + "command": "vscode-docker.delete-ACR-Registry", + "when": "view == dockerExplorer && viewItem == azureRegistryNode" + }, { "command": "vscode-docker.browseDockerHub", "when": "view == dockerExplorer && viewItem == dockerHubImageTag" @@ -292,40 +292,34 @@ } ] }, - "debuggers": [ - { - "type": "docker", - "label": "Docker", - "configurationSnippets": [ - { - "label": "Docker: Attach to Node", - "description": "Docker: Attach to Node", - "body": { - "type": "node", - "request": "attach", - "name": "Docker: Attach to Node", - "port": 9229, - "address": "localhost", - "localRoot": "^\"\\${workspaceFolder}\"", - "remoteRoot": "/usr/src/app", - "protocol": "inspector" - } - } - ] - } - ], - "languages": [ - { - "id": "dockerfile", - "aliases": [ - "Dockerfile" - ], - "filenamePatterns": [ - "*.dockerfile", - "Dockerfile" - ] - } - ], + "debuggers": [{ + "type": "docker", + "label": "Docker", + "configurationSnippets": [{ + "label": "Docker: Attach to Node", + "description": "Docker: Attach to Node", + "body": { + "type": "node", + "request": "attach", + "name": "Docker: Attach to Node", + "port": 9229, + "address": "localhost", + "localRoot": "^\"\\${workspaceFolder}\"", + "remoteRoot": "/usr/src/app", + "protocol": "inspector" + } + }] + }], + "languages": [{ + "id": "dockerfile", + "aliases": [ + "Dockerfile" + ], + "filenamePatterns": [ + "*.dockerfile", + "Dockerfile" + ] + }], "configuration": { "type": "object", "title": "Docker configuration options", @@ -485,8 +479,7 @@ } } }, - "commands": [ - { + "commands": [{ "command": "vscode-docker.configure", "title": "Add Docker files to Workspace", "description": "Add Dockerfile, docker-compose.yml files", @@ -588,12 +581,12 @@ "category": "Docker" }, { - "command": "vscode-docker.deleteACRRepository", + "command": "vscode-docker.delete-ACR-Repository", "title": "Delete Azure Repository", "category": "Docker" }, { - "command": "vscode-docker.deleteAzureImage", + "command": "vscode-docker.delete-ACR-Image", "title": "Delete Azure Image", "category": "Docker" }, @@ -640,25 +633,31 @@ "command": "vscode-docker.browseAzurePortal", "title": "Browse in the Azure Portal", "category": "Docker" + }, + { + "command": "vscode-docker.delete-ACR-Registry", + "title": "Delete Azure Registry", + "category": "Docker" + }, + { + "command": "vscode-docker.delete-ACR-Image", + "title": "Delete Azure Image", + "category": "Docker" } ], "views": { - "dockerView": [ - { - "id": "dockerExplorer", - "name": "Explorer", - "when": "config.docker.showExplorer == true" - } - ] + "dockerView": [{ + "id": "dockerExplorer", + "name": "Explorer", + "when": "config.docker.showExplorer == true" + }] }, "viewsContainers": { - "activitybar": [ - { - "icon": "images/docker.svg", - "id": "dockerView", - "title": "Docker" - } - ] + "activitybar": [{ + "icon": "images/docker.svg", + "id": "dockerView", + "title": "Docker" + }] } }, "engines": { diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index b5dcb9b2d1..79b4373139 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -46,7 +46,7 @@ export async function getAzureRepositories(registry: Registry): Promise 1) { registries.sort(sortFunction); } - - return registries; + //Return only non classic registries + return registries.filter((registry) => { return !registry.sku.tier.includes('Classic') }); } public async getResourceGroups(subscription?: SubscriptionModels.Subscription): Promise { From 2b12ed08770ff03e1b65a0fa9a5aa07f4fbde71b Mon Sep 17 00:00:00 2001 From: rsamai Date: Thu, 9 Aug 2018 15:33:10 -0700 Subject: [PATCH 25/77] Made loginCredentials method in acrTools more efficient, added Pull Image from Azure option, temporary fix for no registries --- commands/azureCommands/delete-azure-image.ts | 4 +- commands/azureCommands/pull-from-azure.ts | 106 ++---------------- commands/azureCommands/push-to-azure.ts | 108 +++++++++++++++++++ commands/utils/quick-pick-azure.ts | 2 +- dockerExtension.ts | 8 +- explorer/deploy/util.ts | 2 +- explorer/deploy/webAppCreator.ts | 4 +- explorer/models/azureRegistryNodes.ts | 2 +- explorer/models/registryRootNode.ts | 26 ++--- explorer/utils/azureUtils.ts | 2 +- package.json | 10 ++ test/global.test.ts | 2 +- utils/Azure/acrTools.ts | 30 ++---- utils/Azure/models/image.ts | 2 +- utils/Azure/models/repository.ts | 4 +- utils/azureCredentialsManager.ts | 4 +- 16 files changed, 164 insertions(+), 152 deletions(-) create mode 100644 commands/azureCommands/push-to-azure.ts diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index 30afe43810..58a2e0da23 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -5,8 +5,8 @@ import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; import { Repository } from "../../utils/Azure/models/repository"; import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; const teleCmdId: string = 'vscode-docker.deleteAzureImage'; -import * as quickPicks from '../../commands/utils/quick-pick-azure'; import * as acrTools from '../../utils/Azure/acrTools'; +import * as quickPicks from '../utils/quick-pick-azure'; /** * function to delete an Azure repository and its associated images @@ -56,5 +56,5 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise username = creds.username; password = creds.password; let path = `/v2/_acr/${repoName}/tags/${tag}`; - await acrTools.requestDataFromRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image + await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image } diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 6484d76372..a930410f96 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -1,108 +1,14 @@ -/* push-azure.ts - * - * Very basic integration for pushing to azure container registry instead of Docker hub - * Author : Esteban Rey - * Version 0.01 - * Updated 6/25/2018 - * - * Known Issues: - * - * Does not currently identify if resource groups/container registry exist. - * Is currently dependent on terminal installation of azure CLI - * Review best practices for await - * If user is not logged in no idea what to do - * login rocketpenguininterns.azurecr.io - Username: rocketPenguinInterns - Password: - Login Succeeded - */ - import vscode = require('vscode'); import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; import { ImageNode } from '../../explorer/models/imageNode'; import { reporter } from '../../telemetry/telemetry'; import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; //FOR TELEMETRY DATA -const teleCmdId: string = 'vscode-docker.image.pushToAzure'; -const teleAzureId: string = 'vscode-docker.image.push.azureContainerRegistry'; - -const { exec } = require('child_process'); - -export async function pushAzure(context?: ImageNode): Promise { - let imageToPush: Docker.ImageDesc; - let imageName: string = ""; - - if (context && context.imageDesc) { - imageToPush = context.imageDesc; - imageName = context.label; - } else { - const selectedItem: ImageItem = await quickPickImage(); - if (selectedItem) { - imageToPush = selectedItem.imageDesc; - imageName = selectedItem.label; - } - } - - if (imageToPush) { - const terminal = vscode.window.createTerminal(imageName); - - // 1. Registry Name - let options: vscode.InputBoxOptions = { - prompt: "Azure Container Registry Name?" - } - - let regName = await vscode.window.showInputBox(options); - terminal.sendText(`az acr login --name ${regName}`); - - // 2. Resource Group Name - options = { - prompt: "Resource Group Name?" - } - let resGroup = await vscode.window.showInputBox(options); - - // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? - // 4. Acquire full acrLogin (Needs Testing) - let cont = function (err, stdout, stderr) { - console.log(stdout); - let jsonStdout = JSON.parse(stdout); - let soughtsrvr: string = ""; - for (let i = 0; i < jsonStdout.length; i++) { - let srvrName: string = jsonStdout[i].acrLoginServer; - let searchIndex: number = srvrName.search(`${regName}`); - if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? - soughtsrvr = srvrName; - break; - } - } - - if (soughtsrvr === '') { - vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); - return; - } - - let tagPrompts = async function () { - let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); - let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); - // 5. Tag image - terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); - // 6. Push image - terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); - terminal.show(); - } - - tagPrompts(); - - } - - exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); - } -} +const teleCmdId: string = 'vscode-docker.image.pullFromAzure'; +//const { exec } = require('child_process'); -function streamToString(stream: any): Promise { - const chunks = [] - return new Promise((resolve, reject) => { - stream.on('data', chunks.push) - stream.on('error', reject) - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) - }) +export async function pullFromAzure(context?: ImageNode): Promise { + //1. call loginCredentials(),which gives us username and password + //2. docker login with username and password + //3. docker pull } diff --git a/commands/azureCommands/push-to-azure.ts b/commands/azureCommands/push-to-azure.ts new file mode 100644 index 0000000000..2a4ab84e60 --- /dev/null +++ b/commands/azureCommands/push-to-azure.ts @@ -0,0 +1,108 @@ +/* push-azure.ts + * + * Very basic integration for pushing to azure container registry instead of Docker hub + * Author : Esteban Rey + * Version 0.01 + * Updated 6/25/2018 + * + * Known Issues: + * + * Does not currently identify if resource groups/container registry exist. + * Is currently dependent on terminal installation of azure CLI + * Review best practices for await + * If user is not logged in no idea what to do + * login rocketpenguininterns.azurecr.io + Username: rocketPenguinInterns + Password: + Login Succeeded + */ + +import vscode = require('vscode'); +import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; +import { ImageNode } from '../../explorer/models/imageNode'; +import { reporter } from '../../telemetry/telemetry'; +import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; +//FOR TELEMETRY DATA +const teleCmdId: string = 'vscode-docker.image.pushToAzure'; +import * as exec from 'child_process'; + +export async function pushAzure(context?: ImageNode): Promise { + let imageToPush: Docker.ImageDesc; + let imageName: string = ""; + + if (context && context.imageDesc) { + imageToPush = context.imageDesc; + imageName = context.label; + } else { + const selectedItem: ImageItem = await quickPickImage(); + if (selectedItem) { + imageToPush = selectedItem.imageDesc; + imageName = selectedItem.label; + } + } + + if (imageToPush) { + const terminal = vscode.window.createTerminal(imageName); + + // 1. Registry Name + let options: vscode.InputBoxOptions = { + prompt: "Azure Container Registry Name?" + } + + let regName = await vscode.window.showInputBox(options); + terminal.sendText(`az acr login --name ${regName}`); + + // 2. Resource Group Name + options = { + prompt: "Resource Group Name?" + } + let resGroup = await vscode.window.showInputBox(options); + + // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? + // 4. Acquire full acrLogin (Needs Testing) + + let cont = (err: any, stdout: any, stderr: any): void => { + console.log(stdout); + let jsonStdout = JSON.parse(stdout); + let soughtsrvr: string = ""; + for (let currJsonStdout of jsonStdout) { + let srvrName: string = currJsonStdout.acrLoginServer; + let searchIndex: number = srvrName.search(`${regName}`); + if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? + soughtsrvr = srvrName; + break; + } + } + + if (soughtsrvr === '') { + vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); + return; + } + + let tagPrompts = async (): Promise => { + let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); + let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); + // 5. Tag image + terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); + // 6. Push image + terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); + terminal.show(); + } + + tagPrompts(); + + } + + //exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); + + } + + function streamToString(stream: any): Promise { + const chunks = [] + return new Promise((resolve, reject) => { + stream.on('data', chunks.push) + stream.on('error', reject) + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) + }) + } +} diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index e88496036e..897abb4b1b 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -3,7 +3,7 @@ import { Registry } from 'azure-arm-containerregistry/lib/models'; import * as vscode from "vscode"; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureImage } from "../../utils/Azure/models/image"; -import { Repository } from "../../utils/Azure/models/Repository"; +import { Repository } from "../../utils/Azure/models/repository"; import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; /** diff --git a/dockerExtension.ts b/dockerExtension.ts index c38ba577e5..889084b744 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -6,8 +6,10 @@ import * as opn from 'opn'; import * as path from 'path'; import * as vscode from 'vscode'; import { AzureUserInput } from 'vscode-azureextensionui'; -import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; +import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; +import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; import inspectImage from './commands/inspect-image'; @@ -26,7 +28,6 @@ import { DockerDebugConfigProvider } from './configureWorkspace/configDebugProvi import { configure } from './configureWorkspace/configure'; import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider'; import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider'; -import { createRegistry } from './commands/azureCommands/create-registry'; import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo'; import { DockerComposeParser } from './dockerCompose/dockerComposeParser'; import { DockerfileCompletionItemProvider } from './dockerfile/dockerfileCompletionItemProvider'; @@ -35,7 +36,7 @@ import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper'; import * as util from "./explorer/deploy/util"; import { WebAppCreator } from './explorer/deploy/webAppCreator'; import { DockerExplorerProvider } from './explorer/dockerExplorer'; -import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes'; +import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/AzureRegistryNodes'; import { DockerHubImageNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; import { browseAzurePortal } from './explorer/utils/azureUtils'; import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; @@ -119,6 +120,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.restart', composeRestart)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.system.prune', systemPrune)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.pullFromAzure', pullFromAzure)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createRegistry', createRegistry)); ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { diff --git a/explorer/deploy/util.ts b/explorer/deploy/util.ts index 492bf3bc6a..593bcb903d 100644 --- a/explorer/deploy/util.ts +++ b/explorer/deploy/util.ts @@ -5,9 +5,9 @@ import { SubscriptionModels } from 'azure-arm-resource'; import WebSiteManagementClient = require('azure-arm-website'); +import * as WebSiteModels from 'azure-arm-website/lib/models'; import { ServiceClientCredentials } from 'ms-rest'; import * as vscode from 'vscode'; -import * as WebSiteModels from '../../node_modules/azure-arm-website/lib/models'; import { AzureAccountWrapper } from './azureAccountWrapper'; export interface PartialList extends Array { diff --git a/explorer/deploy/webAppCreator.ts b/explorer/deploy/webAppCreator.ts index 79ddacd8ff..8bd7c21c28 100644 --- a/explorer/deploy/webAppCreator.ts +++ b/explorer/deploy/webAppCreator.ts @@ -5,12 +5,12 @@ import { ResourceManagementClient, ResourceModels, SubscriptionModels } from 'azure-arm-resource'; import WebSiteManagementClient = require('azure-arm-website'); +import * as WebSiteModels from 'azure-arm-website/lib/models'; import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; -import * as WebSiteModels from '../../node_modules/azure-arm-website/lib/models'; import { reporter } from '../../telemetry/telemetry'; -import { AzureImageNode } from '../models/azureRegistryNodes'; +import { AzureImageNode } from '../models/AzureRegistryNodes'; import { DockerHubImageNode } from '../models/dockerHubNodes'; import { AzureAccountWrapper } from './azureAccountWrapper'; import * as util from './util'; diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 410f81adc0..50266e5450 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -1,9 +1,9 @@ +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import * as moment from 'moment'; import * as path from 'path'; import * as request from 'request-promise'; import * as vscode from 'vscode'; -import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import { AsyncPool } from '../../utils/asyncpool'; import { MAX_CONCURRENT_REQUESTS } from '../../utils/constants' diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 4456dcfebd..80b122a105 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -1,18 +1,18 @@ import ContainerRegistryManagementClient = require('azure-arm-containerregistry'); +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; +import * as ContainerOps from 'azure-arm-containerregistry/lib/operations'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { TIMEOUT } from 'dns'; import * as keytarType from 'keytar'; import { ServiceClientCredentials } from 'ms-rest'; import * as path from 'path'; import * as vscode from 'vscode'; -import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; -import * as ContainerOps from '../../node_modules/azure-arm-containerregistry/lib/operations'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import { AsyncPool } from '../../utils/asyncpool'; import { MAX_CONCURRENT_REQUESTS, MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from '../../utils/constants' import * as dockerHub from '../utils/dockerHubUtils' import { getCoreNodeModule } from '../utils/utils'; -import { AzureLoadingNode, AzureNotSignedInNode, AzureRegistryNode } from './azureRegistryNodes'; +import { AzureLoadingNode, AzureNotSignedInNode, AzureRegistryNode } from './AzureRegistryNodes'; import { DockerHubOrgNode } from './dockerHubNodes'; import { NodeBase } from './nodeBase'; import { RegistryType } from './registryType'; @@ -138,12 +138,14 @@ export class RegistryRootNode extends NodeBase { // tslint:disable-next-line:prefer-for-of // Grandfathered in for (let i = 0; i < subs.length; i++) { subPool.addTask(async () => { - const client = new ContainerRegistryManagement(this.getCredentialByTenantId(subs[i].tenantId), subs[i].subscriptionId); - subsAndRegistries.push({ - 'subscription': subs[i], - 'registries': await client.registries.list(), - 'client': client - }); + try { + const client = new ContainerRegistryManagement(this.getCredentialByTenantId(subs[i].tenantId), subs[i].subscriptionId); + subsAndRegistries.push({ + 'subscription': subs[i], + 'registries': await client.registries.list(), + 'client': client + }); + } catch (err) { } ///cheat fix for the no repo error, remove later }); } await subPool.runAll(); @@ -161,15 +163,15 @@ export class RegistryRootNode extends NodeBase { if (registries[j].adminUserEnabled && !registries[j].sku.tier.includes('Classic')) { const resourceGroup: string = registries[j].id.slice(registries[j].id.search('resourceGroups/') + 'resourceGroups/'.length, registries[j].id.search('/providers/')); regPool.addTask(async () => { - let creds = await client.registries.listCredentials(resourceGroup, registries[j].name); + //let creds = 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, 'azureRegistryNode', iconPath, this._azureAccount); node.type = RegistryType.Azure; - node.password = creds.passwords[0].value; - node.userName = creds.username; + //node.password = creds.passwords[0].value; + //node.userName = creds.username; node.subscription = subscription; node.registry = registries[j]; azureRegistryNodes.push(node); diff --git a/explorer/utils/azureUtils.ts b/explorer/utils/azureUtils.ts index 8a55faa55a..f2bc09a525 100644 --- a/explorer/utils/azureUtils.ts +++ b/explorer/utils/azureUtils.ts @@ -1,6 +1,6 @@ import * as opn from 'opn'; import { AzureSession } from '../../typings/azure-account.api'; -import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from '../models/azureRegistryNodes'; +import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from '../models/AzureRegistryNodes'; export function browseAzurePortal(context?: AzureRegistryNode | AzureRepositoryNode | AzureImageNode): void { diff --git a/package.json b/package.json index fbd5ea054b..2d102fb2fc 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", "onCommand:vscode-docker.deleteAzureImage", + "onCommand:vscode-docker.pullFromAzure", "onView:dockerExplorer", "onDebugInitialConfigurations" ], @@ -261,6 +262,10 @@ "command": "vscode-docker.deleteAzureImage", "when": "view == dockerExplorer && viewItem == azureImageNode" }, + { + "command": "vscode-docker.pullFromAzure", + "when": "view == dockerExplorer && viewItem == azureImageNode" + }, { "command": "vscode-docker.browseDockerHub", "when": "view == dockerExplorer && viewItem == dockerHubImageTag" @@ -611,6 +616,11 @@ "title": "Deploy Image to Azure App Service", "category": "Docker" }, + { + "command": "vscode-docker.pullFromAzure", + "title": "Pull Image from Azure", + "category": "Docker" + }, { "command": "vscode-docker.dockerHubLogout", "title": "Docker Hub Logout", diff --git a/test/global.test.ts b/test/global.test.ts index ba7b808553..8572e6e563 100644 --- a/test/global.test.ts +++ b/test/global.test.ts @@ -2,7 +2,7 @@ import * as vscode from "vscode"; import * as path from "path"; import * as fse from "fs-extra"; import mocha = require("mocha"); -import { pathExists } from '../node_modules/@types/fs-extra'; +import { pathExists } from 'fs-extra'; export namespace constants { export const testOutputName = 'testOutput'; diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index ca98da0f5a..ef32062179 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -4,9 +4,9 @@ import request = require('request-promise'); import * as vscode from "vscode"; import { AzureImageNode, AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; -import { AzureImage } from "../Azure/models/image"; -import { Repository } from "../Azure/models/Repository"; import { AzureCredentialsManager } from '../azureCredentialsManager'; +import { AzureImage } from "./models/image"; +import { Repository } from "./models/repository"; const teleCmdId: string = 'vscode-docker.deleteAzureImage'; /** @@ -211,28 +211,12 @@ export async function getAzureImages(element: Repository): Promise * @param registry : the registry to get login credentials for * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username */ +///using for pull export async function loginCredentials(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - let node: AzureImageNode | AzureRepositoryNode; - if (context) { - node = context; - } - let username: string; - let password: string; - const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(subscription); - const resourceGroup: string = registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); - if (context) { - username = node.userName; - password = node.password; - } else if (registry.adminUserEnabled) { - let creds = await client.registries.listCredentials(resourceGroup, registry.name); - password = creds.passwords[0].value; - username = creds.username; - } else { - //grab the access token to be used as a password, and a generic username - let creds = await getRegistryTokens(registry); - password = creds.accessToken; - username = '00000000-0000-0000-0000-000000000000'; - } + //grab the access token to be used as a password, and a generic username + let creds = await getRegistryTokens(registry); + let password = creds.accessToken; + let username = '00000000-0000-0000-0000-000000000000'; return { password, username }; } diff --git a/utils/Azure/models/image.ts b/utils/Azure/models/image.ts index 0f047e3453..fd1617bf08 100644 --- a/utils/Azure/models/image.ts +++ b/utils/Azure/models/image.ts @@ -1,7 +1,7 @@ import { Registry } from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; -import { Repository } from '../models/repository'; +import { Repository } from './repository'; /** * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository diff --git a/utils/Azure/models/repository.ts b/utils/Azure/models/repository.ts index 5491269b39..4ef724cbe3 100644 --- a/utils/Azure/models/repository.ts +++ b/utils/Azure/models/repository.ts @@ -1,8 +1,8 @@ import { Registry } from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; -import * as acrTools from '../../../utils/Azure/acrTools'; -import { AzureCredentialsManager } from '../../AzureCredentialsManager'; +import { AzureCredentialsManager } from '../../azureCredentialsManager'; +import * as acrTools from '../acrTools'; /** * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository * accessToken can be used like a password, and the username can be '00000000-0000-0000-0000-000000000000' diff --git a/utils/azureCredentialsManager.ts b/utils/azureCredentialsManager.ts index a7839e3f78..4db067bc52 100644 --- a/utils/azureCredentialsManager.ts +++ b/utils/azureCredentialsManager.ts @@ -1,11 +1,11 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { Registry } from 'azure-arm-containerregistry/lib/models'; +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup, ResourceGroupListResult } from "azure-arm-resource/lib/resource/models"; import { ServiceClientCredentials } from 'ms-rest'; -import * as ContainerModels from '../node_modules/azure-arm-containerregistry/lib/models'; import { AzureAccount, AzureSession } from '../typings/azure-account.api'; -import { AsyncPool } from '../utils/asyncpool'; +import { AsyncPool } from './asyncpool'; import { MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from './constants'; /* Singleton for facilitating communication with Azure account services by providing extended shared functionality and extension wide access to azureAccount. Tool for internal use. From def61627c2cb449c6f3c154f8bedccecf7eb2b19 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Thu, 9 Aug 2018 18:14:43 -0700 Subject: [PATCH 26/77] added folder select --- commands/acr-build.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index 2da2f8d808..80a45c4d1f 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -3,6 +3,7 @@ import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; import { Registry } from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient } from 'azure-arm-resource'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import { Stream, Writable } from "stream"; import * as vscode from "vscode"; import { ImageNode } from "../explorer/models/imageNode"; import { AzureUtilityManager } from "../utils/azureUtilityManager"; @@ -12,7 +13,7 @@ let fs = require('fs'); let os = require('os'); let url = require('url'); -export async function queueBuild(context?: ImageNode): Promise { +export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { console.log("Obtaining Subscription and Client"); let subscription = await acquireSubscription(); @@ -25,11 +26,17 @@ export async function queueBuild(context?: ImageNode): Promise { let registry: Registry = await quickPickACRRegistry(subscription, resourceGroupName); let registryName = registry.name; + let folder: vscode.WorkspaceFolder; + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { + folder = vscode.workspace.workspaceFolders[0]; + } else { + folder = await (vscode).window.showWorkspaceFolderPick(); + } + let sourceLocation: string = folder.uri.path; console.log("Setting up temp file with 'sourceArchive.tar.gz' "); let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); console.log("Uploading Source Code"); - let sourceLocation: string = vscode.workspace.rootPath; sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); console.log("Setting up Build Request"); @@ -47,7 +54,7 @@ export async function queueBuild(context?: ImageNode): Promise { await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); } catch (error) { console.log('Build Failed'); - console.log(error.message); + vscode.window.showErrorMessage(error); } console.log(client.builds.list(resourceGroupName, registryName)); } From dc34528b408856c65df50dca4703ad31193c7c13 Mon Sep 17 00:00:00 2001 From: rsamai Date: Fri, 10 Aug 2018 17:39:57 -0700 Subject: [PATCH 27/77] Split the loginCredentials function, added docker login and docker pull and telemetry actions --- commands/azureCommands/delete-azure-image.ts | 3 +- commands/azureCommands/pull-from-azure.ts | 42 ++++++++++++++++++-- explorer/models/azureRegistryNodes.ts | 2 + explorer/models/imageNode.ts | 2 +- utils/Azure/acrTools.ts | 12 +++++- 5 files changed, 53 insertions(+), 8 deletions(-) diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index 58a2e0da23..73a1d3faa9 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -52,9 +52,10 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise tag = wholeName[1]; } - let creds = await acrTools.loginCredentials(subscription, registry); + let creds = await acrTools.loginCredentialsAccessToken(subscription, registry); username = creds.username; password = creds.password; let path = `/v2/_acr/${repoName}/tags/${tag}`; await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image + console.log("after await send requestoregistry"); } diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index a930410f96..74314bfc9f 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -6,9 +6,43 @@ import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; //FOR TELEMETRY DATA const teleCmdId: string = 'vscode-docker.image.pullFromAzure'; //const { exec } = require('child_process'); +import { activate } from '../../dockerExtension'; +import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; +import { Registry } from '../../node_modules/azure-arm-containerregistry/lib/models'; +import { Subscription } from '../../node_modules/azure-arm-resource/lib/subscription/models'; +import * as acrTools from '../../utils/Azure/acrTools'; +const teleAzureId: string = 'vscode-docker.pull.from.azure.azureContainerRegistry'; + +/* Pulls an image from Azure. The context would be the image node the user has right clicked on */ +export async function pullFromAzure(context?: AzureImageNode): Promise { + console.log("in pull from Azure"); + + // Step 1: Using loginCredentials() function to get the username and password. This takes care of users, even if they don't have the Azure CLI + let credentials; + try { + credentials = await acrTools.loginCredentialsRefreshToken(context.subscription, context.registry, context); + } catch (error) { + console.log(error); + } + let username = credentials.username; + let password = credentials.password; + let registry = context.registry.loginServer; + + const terminal = vscode.window.createTerminal("Docker"); + terminal.show(); + + // Step 2: docker login command + terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); + + // Step 3: docker pull command + console.log(context.repository); + terminal.sendText(`docker pull ${registry}/${context.label}`); + + //Acquiring telemetry data here + if (reporter) { + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + } -export async function pullFromAzure(context?: ImageNode): Promise { - //1. call loginCredentials(),which gives us username and password - //2. docker login with username and password - //3. docker pull } diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 50266e5450..20b72e568b 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -214,6 +214,7 @@ export class AzureRepositoryNode extends NodeBase { node.azureAccount = element.azureAccount; node.password = element.password; node.registry = element.registry; + node.repository = element.label; node.serverUrl = element.repository; node.subscription = element.subscription; node.userName = element.userName; @@ -247,6 +248,7 @@ export class AzureImageNode extends NodeBase { public serverUrl: string; public subscription: SubscriptionModels.Subscription; public userName: string; + public repository: string; ///HERE public getTreeItem(): vscode.TreeItem { let displayName: string = this.label; diff --git a/explorer/models/imageNode.ts b/explorer/models/imageNode.ts index f6934c0e3e..a1d6c2a502 100644 --- a/explorer/models/imageNode.ts +++ b/explorer/models/imageNode.ts @@ -39,5 +39,5 @@ export class ImageNode extends NodeBase { } } - // no children + // No children } diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index ef32062179..9f7bb1f5a9 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -211,8 +211,16 @@ export async function getAzureImages(element: Repository): Promise * @param registry : the registry to get login credentials for * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username */ -///using for pull -export async function loginCredentials(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { + +export async function loginCredentialsRefreshToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { + //grab the access token to be used as a password, and a generic username + let creds = await getRegistryTokens(registry); + let password = creds.refreshToken; + let username = '00000000-0000-0000-0000-000000000000'; + return { password, username }; +} + +export async function loginCredentialsAccessToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { //grab the access token to be used as a password, and a generic username let creds = await getRegistryTokens(registry); let password = creds.accessToken; From d26247d68e8626fc4849567ab626a0640cf74180 Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 13:47:07 -0700 Subject: [PATCH 28/77] Finished pull from azure right click feature --- commands/azureCommands/delete-azure-image.ts | 9 +++++++-- commands/azureCommands/pull-from-azure.ts | 14 ++------------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index 73a1d3faa9..37700f60c0 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -52,10 +52,15 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise tag = wholeName[1]; } - let creds = await acrTools.loginCredentialsAccessToken(subscription, registry); + let creds; + try { + creds = await acrTools.loginCredentialsAccessToken(context.subscription, context.registry, context); + } catch (error) { + console.log(error); + } + username = creds.username; password = creds.password; let path = `/v2/_acr/${repoName}/tags/${tag}`; await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image - console.log("after await send requestoregistry"); } diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 74314bfc9f..fa4d671afa 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -1,23 +1,13 @@ import vscode = require('vscode'); -import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; -import { ImageNode } from '../../explorer/models/imageNode'; import { reporter } from '../../telemetry/telemetry'; -import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; -//FOR TELEMETRY DATA const teleCmdId: string = 'vscode-docker.image.pullFromAzure'; -//const { exec } = require('child_process'); -import { activate } from '../../dockerExtension'; import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; -import { Registry } from '../../node_modules/azure-arm-containerregistry/lib/models'; -import { Subscription } from '../../node_modules/azure-arm-resource/lib/subscription/models'; import * as acrTools from '../../utils/Azure/acrTools'; -const teleAzureId: string = 'vscode-docker.pull.from.azure.azureContainerRegistry'; -/* Pulls an image from Azure. The context would be the image node the user has right clicked on */ +/* Pulls an image from Azure. The context is the image node the user has right clicked on */ export async function pullFromAzure(context?: AzureImageNode): Promise { - console.log("in pull from Azure"); - // Step 1: Using loginCredentials() function to get the username and password. This takes care of users, even if they don't have the Azure CLI + // Step 1: Using loginCredentials() function to get the username and password. This takes care of all users, even if they don't have the Azure CLI let credentials; try { credentials = await acrTools.loginCredentialsRefreshToken(context.subscription, context.registry, context); From a16d877a3c10325ea1e2a579889617f0da000f94 Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 13:55:15 -0700 Subject: [PATCH 29/77] deleted push to azure --- commands/azureCommands/push-to-azure.ts | 108 ------------------------ 1 file changed, 108 deletions(-) delete mode 100644 commands/azureCommands/push-to-azure.ts diff --git a/commands/azureCommands/push-to-azure.ts b/commands/azureCommands/push-to-azure.ts deleted file mode 100644 index 2a4ab84e60..0000000000 --- a/commands/azureCommands/push-to-azure.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* push-azure.ts - * - * Very basic integration for pushing to azure container registry instead of Docker hub - * Author : Esteban Rey - * Version 0.01 - * Updated 6/25/2018 - * - * Known Issues: - * - * Does not currently identify if resource groups/container registry exist. - * Is currently dependent on terminal installation of azure CLI - * Review best practices for await - * If user is not logged in no idea what to do - * login rocketpenguininterns.azurecr.io - Username: rocketPenguinInterns - Password: - Login Succeeded - */ - -import vscode = require('vscode'); -import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; -import { ImageNode } from '../../explorer/models/imageNode'; -import { reporter } from '../../telemetry/telemetry'; -import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; -//FOR TELEMETRY DATA -const teleCmdId: string = 'vscode-docker.image.pushToAzure'; -import * as exec from 'child_process'; - -export async function pushAzure(context?: ImageNode): Promise { - let imageToPush: Docker.ImageDesc; - let imageName: string = ""; - - if (context && context.imageDesc) { - imageToPush = context.imageDesc; - imageName = context.label; - } else { - const selectedItem: ImageItem = await quickPickImage(); - if (selectedItem) { - imageToPush = selectedItem.imageDesc; - imageName = selectedItem.label; - } - } - - if (imageToPush) { - const terminal = vscode.window.createTerminal(imageName); - - // 1. Registry Name - let options: vscode.InputBoxOptions = { - prompt: "Azure Container Registry Name?" - } - - let regName = await vscode.window.showInputBox(options); - terminal.sendText(`az acr login --name ${regName}`); - - // 2. Resource Group Name - options = { - prompt: "Resource Group Name?" - } - let resGroup = await vscode.window.showInputBox(options); - - // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? - // 4. Acquire full acrLogin (Needs Testing) - - let cont = (err: any, stdout: any, stderr: any): void => { - console.log(stdout); - let jsonStdout = JSON.parse(stdout); - let soughtsrvr: string = ""; - for (let currJsonStdout of jsonStdout) { - let srvrName: string = currJsonStdout.acrLoginServer; - let searchIndex: number = srvrName.search(`${regName}`); - if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? - soughtsrvr = srvrName; - break; - } - } - - if (soughtsrvr === '') { - vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); - return; - } - - let tagPrompts = async (): Promise => { - let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); - let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); - // 5. Tag image - terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); - // 6. Push image - terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); - terminal.show(); - } - - tagPrompts(); - - } - - //exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); - - } - - function streamToString(stream: any): Promise { - const chunks = [] - return new Promise((resolve, reject) => { - stream.on('data', chunks.push) - stream.on('error', reject) - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) - }) - } -} From 2c16571db88eb604abc5efa55242d3f82d2163ab Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 14:05:20 -0700 Subject: [PATCH 30/77] removed username and password from Azure Registry Node --- explorer/models/azureRegistryNodes.ts | 4 ---- explorer/models/registryRootNode.ts | 2 -- 2 files changed, 6 deletions(-) diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 20b72e568b..a9aafbeee0 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -23,11 +23,9 @@ export class AzureRegistryNode extends NodeBase { this._azureAccount = azureAccount; } - public password: string; public registry: ContainerModels.Registry; public subscription: SubscriptionModels.Subscription; public type: RegistryType; - public userName: string; public getTreeItem(): vscode.TreeItem { return { @@ -96,12 +94,10 @@ export class AzureRegistryNode extends NodeBase { node = new AzureRepositoryNode(repositories[i], "azureRepositoryNode"); node.accessTokenARC = accessTokenARC; node.azureAccount = element.azureAccount; - node.password = element.password; node.refreshTokenARC = refreshTokenARC; node.registry = element.registry; node.repository = element.label; node.subscription = element.subscription; - node.userName = element.userName; repoNodes.push(node); } } diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 80b122a105..7e0b628aac 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -170,8 +170,6 @@ export class RegistryRootNode extends NodeBase { }; let node = new AzureRegistryNode(registries[j].loginServer, 'azureRegistryNode', iconPath, this._azureAccount); node.type = RegistryType.Azure; - //node.password = creds.passwords[0].value; - //node.userName = creds.username; node.subscription = subscription; node.registry = registries[j]; azureRegistryNodes.push(node); From 57f39125c3e5990e20a36e48ab1f71df31f1f402 Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 14:17:31 -0700 Subject: [PATCH 31/77] Clean up --- explorer/models/azureRegistryNodes.ts | 2 +- explorer/models/registryRootNode.ts | 16 +++++++--------- utils/Azure/acrTools.ts | 3 +-- 3 files changed, 9 insertions(+), 12 deletions(-) diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index a9aafbeee0..4bf734d483 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -244,7 +244,7 @@ export class AzureImageNode extends NodeBase { public serverUrl: string; public subscription: SubscriptionModels.Subscription; public userName: string; - public repository: string; ///HERE + public repository: string; public getTreeItem(): vscode.TreeItem { let displayName: string = this.label; diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 7e0b628aac..3b8eac62fd 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -138,14 +138,13 @@ export class RegistryRootNode extends NodeBase { // tslint:disable-next-line:prefer-for-of // Grandfathered in for (let i = 0; i < subs.length; i++) { subPool.addTask(async () => { - try { - const client = new ContainerRegistryManagement(this.getCredentialByTenantId(subs[i].tenantId), subs[i].subscriptionId); - subsAndRegistries.push({ - 'subscription': subs[i], - 'registries': await client.registries.list(), - 'client': client - }); - } catch (err) { } ///cheat fix for the no repo error, remove later + const client = new ContainerRegistryManagement(this.getCredentialByTenantId(subs[i].tenantId), subs[i].subscriptionId); + subsAndRegistries.push({ + 'subscription': subs[i], + 'registries': await client.registries.list(), + 'client': client + }); + }); } await subPool.runAll(); @@ -163,7 +162,6 @@ export class RegistryRootNode extends NodeBase { if (registries[j].adminUserEnabled && !registries[j].sku.tier.includes('Classic')) { const resourceGroup: string = registries[j].id.slice(registries[j].id.search('resourceGroups/') + 'resourceGroups/'.length, registries[j].id.search('/providers/')); regPool.addTask(async () => { - //let creds = 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') diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 9f7bb1f5a9..b11cea1f8b 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -213,15 +213,14 @@ export async function getAzureImages(element: Repository): Promise */ export async function loginCredentialsRefreshToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - //grab the access token to be used as a password, and a generic username let creds = await getRegistryTokens(registry); let password = creds.refreshToken; let username = '00000000-0000-0000-0000-000000000000'; return { password, username }; } +/* Used in delete Azure image and is pending edits */ export async function loginCredentialsAccessToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - //grab the access token to be used as a password, and a generic username let creds = await getRegistryTokens(registry); let password = creds.accessToken; let username = '00000000-0000-0000-0000-000000000000'; From 6e18256a3aa47164f4b76174094a819d19e03d7a Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 6 Aug 2018 16:24:54 -0700 Subject: [PATCH 32/77] copied previous push to acr into new pull-from-azure.ts file --- commands/azureCommands/pull-from-azure.ts | 108 ++++++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 commands/azureCommands/pull-from-azure.ts diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts new file mode 100644 index 0000000000..6484d76372 --- /dev/null +++ b/commands/azureCommands/pull-from-azure.ts @@ -0,0 +1,108 @@ +/* push-azure.ts + * + * Very basic integration for pushing to azure container registry instead of Docker hub + * Author : Esteban Rey + * Version 0.01 + * Updated 6/25/2018 + * + * Known Issues: + * + * Does not currently identify if resource groups/container registry exist. + * Is currently dependent on terminal installation of azure CLI + * Review best practices for await + * If user is not logged in no idea what to do + * login rocketpenguininterns.azurecr.io + Username: rocketPenguinInterns + Password: + Login Succeeded + */ + +import vscode = require('vscode'); +import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; +import { ImageNode } from '../../explorer/models/imageNode'; +import { reporter } from '../../telemetry/telemetry'; +import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; +//FOR TELEMETRY DATA +const teleCmdId: string = 'vscode-docker.image.pushToAzure'; +const teleAzureId: string = 'vscode-docker.image.push.azureContainerRegistry'; + +const { exec } = require('child_process'); + +export async function pushAzure(context?: ImageNode): Promise { + let imageToPush: Docker.ImageDesc; + let imageName: string = ""; + + if (context && context.imageDesc) { + imageToPush = context.imageDesc; + imageName = context.label; + } else { + const selectedItem: ImageItem = await quickPickImage(); + if (selectedItem) { + imageToPush = selectedItem.imageDesc; + imageName = selectedItem.label; + } + } + + if (imageToPush) { + const terminal = vscode.window.createTerminal(imageName); + + // 1. Registry Name + let options: vscode.InputBoxOptions = { + prompt: "Azure Container Registry Name?" + } + + let regName = await vscode.window.showInputBox(options); + terminal.sendText(`az acr login --name ${regName}`); + + // 2. Resource Group Name + options = { + prompt: "Resource Group Name?" + } + let resGroup = await vscode.window.showInputBox(options); + + // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? + // 4. Acquire full acrLogin (Needs Testing) + let cont = function (err, stdout, stderr) { + console.log(stdout); + let jsonStdout = JSON.parse(stdout); + let soughtsrvr: string = ""; + for (let i = 0; i < jsonStdout.length; i++) { + let srvrName: string = jsonStdout[i].acrLoginServer; + let searchIndex: number = srvrName.search(`${regName}`); + if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? + soughtsrvr = srvrName; + break; + } + } + + if (soughtsrvr === '') { + vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); + return; + } + + let tagPrompts = async function () { + let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); + let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); + // 5. Tag image + terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); + // 6. Push image + terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); + terminal.show(); + } + + tagPrompts(); + + } + + exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); + } +} + +function streamToString(stream: any): Promise { + const chunks = [] + return new Promise((resolve, reject) => { + stream.on('data', chunks.push) + stream.on('error', reject) + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) + }) +} From 47642b162b4c65fcb4f158e36bd2caa623fdc59e Mon Sep 17 00:00:00 2001 From: rsamai Date: Thu, 9 Aug 2018 15:33:10 -0700 Subject: [PATCH 33/77] Made loginCredentials method in acrTools more efficient, added Pull Image from Azure option, temporary fix for no registries --- commands/azureCommands/pull-from-azure.ts | 106 +----------------- commands/azureCommands/push-to-azure.ts | 108 ++++++++++++++++++ dockerExtension.ts | 47 ++++---- explorer/deploy/webAppCreator.ts | 4 +- explorer/models/azureRegistryNodes.ts | 2 +- explorer/models/registryRootNode.ts | 5 +- explorer/utils/azureUtils.ts | 2 +- package.json | 130 ++++++++++++++-------- utils/Azure/acrTools.ts | 30 ++--- utils/Azure/models/image.ts | 2 +- utils/azureUtilityManager.ts | 5 +- 11 files changed, 245 insertions(+), 196 deletions(-) create mode 100644 commands/azureCommands/push-to-azure.ts diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 6484d76372..a930410f96 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -1,108 +1,14 @@ -/* push-azure.ts - * - * Very basic integration for pushing to azure container registry instead of Docker hub - * Author : Esteban Rey - * Version 0.01 - * Updated 6/25/2018 - * - * Known Issues: - * - * Does not currently identify if resource groups/container registry exist. - * Is currently dependent on terminal installation of azure CLI - * Review best practices for await - * If user is not logged in no idea what to do - * login rocketpenguininterns.azurecr.io - Username: rocketPenguinInterns - Password: - Login Succeeded - */ - import vscode = require('vscode'); import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; import { ImageNode } from '../../explorer/models/imageNode'; import { reporter } from '../../telemetry/telemetry'; import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; //FOR TELEMETRY DATA -const teleCmdId: string = 'vscode-docker.image.pushToAzure'; -const teleAzureId: string = 'vscode-docker.image.push.azureContainerRegistry'; - -const { exec } = require('child_process'); - -export async function pushAzure(context?: ImageNode): Promise { - let imageToPush: Docker.ImageDesc; - let imageName: string = ""; - - if (context && context.imageDesc) { - imageToPush = context.imageDesc; - imageName = context.label; - } else { - const selectedItem: ImageItem = await quickPickImage(); - if (selectedItem) { - imageToPush = selectedItem.imageDesc; - imageName = selectedItem.label; - } - } - - if (imageToPush) { - const terminal = vscode.window.createTerminal(imageName); - - // 1. Registry Name - let options: vscode.InputBoxOptions = { - prompt: "Azure Container Registry Name?" - } - - let regName = await vscode.window.showInputBox(options); - terminal.sendText(`az acr login --name ${regName}`); - - // 2. Resource Group Name - options = { - prompt: "Resource Group Name?" - } - let resGroup = await vscode.window.showInputBox(options); - - // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? - // 4. Acquire full acrLogin (Needs Testing) - let cont = function (err, stdout, stderr) { - console.log(stdout); - let jsonStdout = JSON.parse(stdout); - let soughtsrvr: string = ""; - for (let i = 0; i < jsonStdout.length; i++) { - let srvrName: string = jsonStdout[i].acrLoginServer; - let searchIndex: number = srvrName.search(`${regName}`); - if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? - soughtsrvr = srvrName; - break; - } - } - - if (soughtsrvr === '') { - vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); - return; - } - - let tagPrompts = async function () { - let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); - let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); - // 5. Tag image - terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); - // 6. Push image - terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); - terminal.show(); - } - - tagPrompts(); - - } - - exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); - } -} +const teleCmdId: string = 'vscode-docker.image.pullFromAzure'; +//const { exec } = require('child_process'); -function streamToString(stream: any): Promise { - const chunks = [] - return new Promise((resolve, reject) => { - stream.on('data', chunks.push) - stream.on('error', reject) - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) - }) +export async function pullFromAzure(context?: ImageNode): Promise { + //1. call loginCredentials(),which gives us username and password + //2. docker login with username and password + //3. docker pull } diff --git a/commands/azureCommands/push-to-azure.ts b/commands/azureCommands/push-to-azure.ts new file mode 100644 index 0000000000..2a4ab84e60 --- /dev/null +++ b/commands/azureCommands/push-to-azure.ts @@ -0,0 +1,108 @@ +/* push-azure.ts + * + * Very basic integration for pushing to azure container registry instead of Docker hub + * Author : Esteban Rey + * Version 0.01 + * Updated 6/25/2018 + * + * Known Issues: + * + * Does not currently identify if resource groups/container registry exist. + * Is currently dependent on terminal installation of azure CLI + * Review best practices for await + * If user is not logged in no idea what to do + * login rocketpenguininterns.azurecr.io + Username: rocketPenguinInterns + Password: + Login Succeeded + */ + +import vscode = require('vscode'); +import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; +import { ImageNode } from '../../explorer/models/imageNode'; +import { reporter } from '../../telemetry/telemetry'; +import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; +//FOR TELEMETRY DATA +const teleCmdId: string = 'vscode-docker.image.pushToAzure'; +import * as exec from 'child_process'; + +export async function pushAzure(context?: ImageNode): Promise { + let imageToPush: Docker.ImageDesc; + let imageName: string = ""; + + if (context && context.imageDesc) { + imageToPush = context.imageDesc; + imageName = context.label; + } else { + const selectedItem: ImageItem = await quickPickImage(); + if (selectedItem) { + imageToPush = selectedItem.imageDesc; + imageName = selectedItem.label; + } + } + + if (imageToPush) { + const terminal = vscode.window.createTerminal(imageName); + + // 1. Registry Name + let options: vscode.InputBoxOptions = { + prompt: "Azure Container Registry Name?" + } + + let regName = await vscode.window.showInputBox(options); + terminal.sendText(`az acr login --name ${regName}`); + + // 2. Resource Group Name + options = { + prompt: "Resource Group Name?" + } + let resGroup = await vscode.window.showInputBox(options); + + // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? + // 4. Acquire full acrLogin (Needs Testing) + + let cont = (err: any, stdout: any, stderr: any): void => { + console.log(stdout); + let jsonStdout = JSON.parse(stdout); + let soughtsrvr: string = ""; + for (let currJsonStdout of jsonStdout) { + let srvrName: string = currJsonStdout.acrLoginServer; + let searchIndex: number = srvrName.search(`${regName}`); + if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? + soughtsrvr = srvrName; + break; + } + } + + if (soughtsrvr === '') { + vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); + return; + } + + let tagPrompts = async (): Promise => { + let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); + let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); + // 5. Tag image + terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); + // 6. Push image + terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); + terminal.show(); + } + + tagPrompts(); + + } + + //exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); + + } + + function streamToString(stream: any): Promise { + const chunks = [] + return new Promise((resolve, reject) => { + stream.on('data', chunks.push) + stream.on('error', reject) + stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) + }) + } +} diff --git a/dockerExtension.ts b/dockerExtension.ts index b42f0d274b..225c0261fd 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -11,6 +11,7 @@ import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-azure-registry'; import { deleteRepository } from './commands/azureCommands/delete-repository'; +import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; import inspectImage from './commands/inspect-image'; @@ -38,7 +39,7 @@ import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper'; import * as util from "./explorer/deploy/util"; import { WebAppCreator } from './explorer/deploy/webAppCreator'; import { DockerExplorerProvider } from './explorer/dockerExplorer'; -import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes'; +import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/AzureRegistryNodes'; import { DockerHubImageNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; import { browseAzurePortal } from './explorer/utils/azureUtils'; import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; @@ -113,25 +114,29 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DOCKER_INSPECT_SCHEME, new DockerInspectDocumentContentProvider())); - registerCommand('vscode-docker.configure', configure); - registerCommand('vscode-docker.image.build', buildImage); - registerCommand('vscode-docker.image.inspect', inspectImage); - registerCommand('vscode-docker.image.remove', removeImage); - registerCommand('vscode-docker.image.push', pushImage); - registerCommand('vscode-docker.image.tag', tagImage); - registerCommand('vscode-docker.container.start', startContainer); - registerCommand('vscode-docker.container.start.interactive', startContainerInteractive); - registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); - registerCommand('vscode-docker.container.stop', stopContainer); - registerCommand('vscode-docker.container.restart', restartContainer); - registerCommand('vscode-docker.container.show-logs', showLogsContainer); - registerCommand('vscode-docker.container.open-shell', openShellContainer); - registerCommand('vscode-docker.container.remove', removeContainer); - registerCommand('vscode-docker.compose.up', composeUp); - registerCommand('vscode-docker.compose.down', composeDown); - registerCommand('vscode-docker.compose.restart', composeRestart); - registerCommand('vscode-docker.system.prune', systemPrune); - registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.configure', configure)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.build', buildImage)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.inspect', inspectImage)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.remove', removeImage)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.push', pushImage)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.tag', tagImage)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.start', startContainer)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.start.interactive', startContainerInteractive)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.start.azurecli', startAzureCLI)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.stop', stopContainer)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.restart', restartContainer)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.show-logs', showLogsContainer)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.open-shell', openShellContainer)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.remove', removeContainer)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.up', composeUp)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.down', composeDown)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.restart', composeRestart)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.system.prune', systemPrune)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.pullFromAzure', pullFromAzure)); + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createRegistry', createRegistry)); + + ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { if (context) { if (azureAccount) { const azureAccountWrapper = new AzureAccountWrapper(ctx, azureAccount); @@ -151,7 +156,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { } } - }); + })); registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout); registerCommand('vscode-docker.browseDockerHub', (context?: DockerHubImageNode | DockerHubRepositoryNode | DockerHubOrgNode) => { diff --git a/explorer/deploy/webAppCreator.ts b/explorer/deploy/webAppCreator.ts index c1511c4e48..2149c01390 100644 --- a/explorer/deploy/webAppCreator.ts +++ b/explorer/deploy/webAppCreator.ts @@ -9,9 +9,11 @@ import { ResourceManagementClient, ResourceModels, SubscriptionModels } from 'az import { Subscription } from 'azure-arm-resource/lib/subscription/models'; import WebSiteManagementClient = require('azure-arm-website'); import * as WebSiteModels from 'azure-arm-website/lib/models'; +import * as fs from 'fs'; +import * as path from 'path'; import * as vscode from 'vscode'; import { reporter } from '../../telemetry/telemetry'; -import { AzureImageNode } from '../models/azureRegistryNodes'; +import { AzureImageNode } from '../models/AzureRegistryNodes'; import { DockerHubImageNode } from '../models/dockerHubNodes'; import { AzureAccountWrapper } from './azureAccountWrapper'; import * as util from './util'; diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 4c3c5c3e02..8b171c2f0b 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -1,5 +1,5 @@ import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; -import { SubscriptionModels } from 'azure-arm-resource'; +import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import * as moment from 'moment'; import * as path from 'path'; import * as request from 'request-promise'; diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 5ec56dcddc..9dc70d35b8 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -1,5 +1,8 @@ +import ContainerRegistryManagementClient = require('azure-arm-containerregistry'); import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; +import * as ContainerOps from 'azure-arm-containerregistry/lib/operations'; import { SubscriptionModels } from 'azure-arm-resource'; +import { TIMEOUT } from 'dns'; import * as keytarType from 'keytar'; import { ServiceClientCredentials } from 'ms-rest'; import * as path from 'path'; @@ -11,7 +14,7 @@ import { AsyncPool } from '../../utils/asyncpool'; import * as acrTools from '../../utils/Azure/acrTools'; import * as dockerHub from '../utils/dockerHubUtils' import { getCoreNodeModule } from '../utils/utils'; -import { AzureLoadingNode, AzureNotSignedInNode, AzureRegistryNode } from './azureRegistryNodes'; +import { AzureLoadingNode, AzureNotSignedInNode, AzureRegistryNode } from './AzureRegistryNodes'; import { DockerHubOrgNode } from './dockerHubNodes'; import { NodeBase } from './nodeBase'; import { RegistryType } from './registryType'; diff --git a/explorer/utils/azureUtils.ts b/explorer/utils/azureUtils.ts index 8a55faa55a..f2bc09a525 100644 --- a/explorer/utils/azureUtils.ts +++ b/explorer/utils/azureUtils.ts @@ -1,6 +1,6 @@ import * as opn from 'opn'; import { AzureSession } from '../../typings/azure-account.api'; -import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from '../models/azureRegistryNodes'; +import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from '../models/AzureRegistryNodes'; export function browseAzurePortal(context?: AzureRegistryNode | AzureRepositoryNode | AzureImageNode): void { diff --git a/package.json b/package.json index 35ef6c8d68..045bf9d16b 100644 --- a/package.json +++ b/package.json @@ -52,16 +52,22 @@ "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", +<<<<<<< HEAD "onCommand:vscode-docker.delete-ACR-Registry", "onCommand:vscode-docker.delete-ACR-Repository", "onCommand:vscode-docker.delete-ACR-Image", +======= + "onCommand:vscode-docker.deleteAzureImage", + "onCommand:vscode-docker.pullFromAzure", +>>>>>>> Made loginCredentials method in acrTools more efficient, added Pull Image from Azure option, temporary fix for no registries "onView:dockerExplorer", "onDebugInitialConfigurations" ], "main": "./out/dockerExtension", "contributes": { "menus": { - "commandPalette": [{ + "commandPalette": [ + { "command": "vscode-docker.browseDockerHub", "when": "false" }, @@ -70,7 +76,8 @@ "when": "false" } ], - "editor/context": [{ + "editor/context": [ + { "when": "editorLangId == dockerfile", "command": "vscode-docker.image.build", "group": "docker" @@ -106,7 +113,8 @@ "group": "docker" } ], - "explorer/context": [{ + "explorer/context": [ + { "when": "resourceFilename =~ /[dD]ocker[fF]ile/", "command": "vscode-docker.image.build", "group": "docker" @@ -127,7 +135,8 @@ "group": "docker" } ], - "view/title": [{ + "view/title": [ + { "command": "vscode-docker.explorer.refresh", "when": "view == dockerExplorer", "group": "navigation" @@ -138,7 +147,8 @@ "group": "navigation" } ], - "view/item/context": [{ + "view/item/context": [ + { "command": "vscode-docker.container.start", "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, @@ -195,8 +205,20 @@ "when": "view == dockerExplorer && viewItem == dockerHubRootNode" }, { - "command": "vscode-docker.delete-ACR-Repository", - "when": "view == dockerExplorer && viewItem == azureRepositoryNode" + "command": "vscode-docker.deleteAzureImage", + "when": "view == dockerExplorer && viewItem == azureImageNode" + }, + { + "command": "vscode-docker.pullFromAzure", + "when": "view == dockerExplorer && viewItem == azureImageNode" + }, + { + "command": "vscode-docker.browseDockerHub", + "when": "view == dockerExplorer && viewItem == dockerHubImageTag" + }, + { + "command": "vscode-docker.browseDockerHub", + "when": "view == dockerExplorer && viewItem == dockerHubRepository" }, { "command": "vscode-docker.delete-ACR-Image", @@ -216,34 +238,40 @@ } ] }, - "debuggers": [{ - "type": "docker", - "label": "Docker", - "configurationSnippets": [{ - "label": "Docker: Attach to Node", - "description": "Docker: Attach to Node", - "body": { - "type": "node", - "request": "attach", - "name": "Docker: Attach to Node", - "port": 9229, - "address": "localhost", - "localRoot": "^\"\\${workspaceFolder}\"", - "remoteRoot": "/usr/src/app", - "protocol": "inspector" - } - }] - }], - "languages": [{ - "id": "dockerfile", - "aliases": [ - "Dockerfile" - ], - "filenamePatterns": [ - "*.dockerfile", - "Dockerfile" - ] - }], + "debuggers": [ + { + "type": "docker", + "label": "Docker", + "configurationSnippets": [ + { + "label": "Docker: Attach to Node", + "description": "Docker: Attach to Node", + "body": { + "type": "node", + "request": "attach", + "name": "Docker: Attach to Node", + "port": 9229, + "address": "localhost", + "localRoot": "^\"\\${workspaceFolder}\"", + "remoteRoot": "/usr/src/app", + "protocol": "inspector" + } + } + ] + } + ], + "languages": [ + { + "id": "dockerfile", + "aliases": [ + "Dockerfile" + ], + "filenamePatterns": [ + "*.dockerfile", + "Dockerfile" + ] + } + ], "configuration": { "type": "object", "title": "Docker configuration options", @@ -403,7 +431,8 @@ } } }, - "commands": [{ + "commands": [ + { "command": "vscode-docker.configure", "title": "Add Docker files to Workspace", "description": "Add Dockerfile, docker-compose.yml files", @@ -543,6 +572,11 @@ "title": "Deploy Image to Azure App Service", "category": "Docker" }, + { + "command": "vscode-docker.pullFromAzure", + "title": "Pull Image from Azure", + "category": "Docker" + }, { "command": "vscode-docker.dockerHubLogout", "title": "Docker Hub Logout", @@ -570,18 +604,22 @@ } ], "views": { - "dockerView": [{ - "id": "dockerExplorer", - "name": "Explorer", - "when": "config.docker.showExplorer == true" - }] + "dockerView": [ + { + "id": "dockerExplorer", + "name": "Explorer", + "when": "config.docker.showExplorer == true" + } + ] }, "viewsContainers": { - "activitybar": [{ - "icon": "images/docker.svg", - "id": "dockerView", - "title": "Docker" - }] + "activitybar": [ + { + "icon": "images/docker.svg", + "id": "dockerView", + "title": "Docker" + } + ] } }, "engines": { diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 0a5208fe76..0bb4ec39a1 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -5,9 +5,10 @@ import * as vscode from "vscode"; import { NULL_GUID } from "../../constants"; import { AzureImageNode, AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; -import { AzureImage } from "../Azure/models/image"; -import { Repository } from "../Azure/models/Repository"; import { AzureUtilityManager } from '../azureUtilityManager'; +import { AzureImage } from "./models/image"; +import { Repository } from "./models/repository"; +const teleCmdId: string = 'vscode-docker.deleteAzureImage'; /** * Developers can use this to visualize and list repositories on a given Registry. This is not a command, just a developer tool. @@ -214,27 +215,12 @@ export async function getAzureImages(element: Repository): Promise * @param registry : the registry to get login credentials for * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username */ +///using for pull export async function loginCredentials(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - let node: AzureImageNode | AzureRepositoryNode; - if (context) { - node = context; - } - let username: string; - let password: string; - - const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); - const resourceGroup: string = registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); - - if (registry.adminUserEnabled) { - let creds = await client.registries.listCredentials(resourceGroup, registry.name); - password = creds.passwords[0].value; - username = creds.username; - } else { - //grab the access token to be used as a password, and a generic username - let creds = await getRegistryTokens(registry); - password = creds.accessToken; - username = NULL_GUID; - } + //grab the access token to be used as a password, and a generic username + let creds = await getRegistryTokens(registry); + let password = creds.accessToken; + let username = '00000000-0000-0000-0000-000000000000'; return { password, username }; } diff --git a/utils/Azure/models/image.ts b/utils/Azure/models/image.ts index 0f047e3453..fd1617bf08 100644 --- a/utils/Azure/models/image.ts +++ b/utils/Azure/models/image.ts @@ -1,7 +1,7 @@ import { Registry } from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; -import { Repository } from '../models/repository'; +import { Repository } from './repository'; /** * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository diff --git a/utils/azureUtilityManager.ts b/utils/azureUtilityManager.ts index 90a0fde99f..74c7450fb4 100644 --- a/utils/azureUtilityManager.ts +++ b/utils/azureUtilityManager.ts @@ -1,12 +1,13 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { Registry } from 'azure-arm-containerregistry/lib/models'; import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { ServiceClientCredentials } from 'ms-rest'; import { MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from '../constants'; -import { AzureAccount } from '../typings/azure-account.api'; +import { AzureAccount, AzureSession } from '../typings/azure-account.api'; import { AsyncPool } from './asyncpool'; - +//C:\Users\t-rusama\Documents\GitHub\vscode-docker\constants.ts /* Singleton for facilitating communication with Azure account services by providing extended shared functionality and extension wide access to azureAccount. Tool for internal use. Authors: Esteban Rey L, Jackson Stokes From dc02f2b20f1a3c323b7766ba7e7271e40b5c8e03 Mon Sep 17 00:00:00 2001 From: rsamai Date: Fri, 10 Aug 2018 17:39:57 -0700 Subject: [PATCH 34/77] Split the loginCredentials function, added docker login and docker pull and telemetry actions --- commands/azureCommands/delete-azure-image.ts | 2 +- commands/azureCommands/pull-from-azure.ts | 42 ++++++++++++++++++-- explorer/models/imageNode.ts | 2 +- utils/Azure/acrTools.ts | 12 +++++- 4 files changed, 50 insertions(+), 8 deletions(-) diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index f8ec7a220e..f66fc6d8c5 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -53,7 +53,7 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise tag = wholeName[1]; } - let creds = await acrTools.loginCredentials(subscription, registry); + let creds = await acrTools.loginCredentialsAccessToken(subscription, registry); username = creds.username; password = creds.password; let path = `/v2/_acr/${repoName}/tags/${tag}`; diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index a930410f96..74314bfc9f 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -6,9 +6,43 @@ import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; //FOR TELEMETRY DATA const teleCmdId: string = 'vscode-docker.image.pullFromAzure'; //const { exec } = require('child_process'); +import { activate } from '../../dockerExtension'; +import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; +import { Registry } from '../../node_modules/azure-arm-containerregistry/lib/models'; +import { Subscription } from '../../node_modules/azure-arm-resource/lib/subscription/models'; +import * as acrTools from '../../utils/Azure/acrTools'; +const teleAzureId: string = 'vscode-docker.pull.from.azure.azureContainerRegistry'; + +/* Pulls an image from Azure. The context would be the image node the user has right clicked on */ +export async function pullFromAzure(context?: AzureImageNode): Promise { + console.log("in pull from Azure"); + + // Step 1: Using loginCredentials() function to get the username and password. This takes care of users, even if they don't have the Azure CLI + let credentials; + try { + credentials = await acrTools.loginCredentialsRefreshToken(context.subscription, context.registry, context); + } catch (error) { + console.log(error); + } + let username = credentials.username; + let password = credentials.password; + let registry = context.registry.loginServer; + + const terminal = vscode.window.createTerminal("Docker"); + terminal.show(); + + // Step 2: docker login command + terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); + + // Step 3: docker pull command + console.log(context.repository); + terminal.sendText(`docker pull ${registry}/${context.label}`); + + //Acquiring telemetry data here + if (reporter) { + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + } -export async function pullFromAzure(context?: ImageNode): Promise { - //1. call loginCredentials(),which gives us username and password - //2. docker login with username and password - //3. docker pull } diff --git a/explorer/models/imageNode.ts b/explorer/models/imageNode.ts index f6934c0e3e..a1d6c2a502 100644 --- a/explorer/models/imageNode.ts +++ b/explorer/models/imageNode.ts @@ -39,5 +39,5 @@ export class ImageNode extends NodeBase { } } - // no children + // No children } diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 0bb4ec39a1..d6182e468b 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -215,8 +215,16 @@ export async function getAzureImages(element: Repository): Promise * @param registry : the registry to get login credentials for * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username */ -///using for pull -export async function loginCredentials(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { + +export async function loginCredentialsRefreshToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { + //grab the access token to be used as a password, and a generic username + let creds = await getRegistryTokens(registry); + let password = creds.refreshToken; + let username = '00000000-0000-0000-0000-000000000000'; + return { password, username }; +} + +export async function loginCredentialsAccessToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { //grab the access token to be used as a password, and a generic username let creds = await getRegistryTokens(registry); let password = creds.accessToken; From e02d9d5c548bab33bb404fe2c7cb47a7fa195d3f Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 13:47:07 -0700 Subject: [PATCH 35/77] Finished pull from azure right click feature --- commands/azureCommands/delete-azure-image.ts | 8 +++++++- commands/azureCommands/pull-from-azure.ts | 14 ++------------ 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index f66fc6d8c5..c1655fdabf 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -53,7 +53,13 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise tag = wholeName[1]; } - let creds = await acrTools.loginCredentialsAccessToken(subscription, registry); + let creds; + try { + creds = await acrTools.loginCredentialsAccessToken(context.subscription, context.registry, context); + } catch (error) { + console.log(error); + } + username = creds.username; password = creds.password; let path = `/v2/_acr/${repoName}/tags/${tag}`; diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 74314bfc9f..fa4d671afa 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -1,23 +1,13 @@ import vscode = require('vscode'); -import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; -import { ImageNode } from '../../explorer/models/imageNode'; import { reporter } from '../../telemetry/telemetry'; -import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; -//FOR TELEMETRY DATA const teleCmdId: string = 'vscode-docker.image.pullFromAzure'; -//const { exec } = require('child_process'); -import { activate } from '../../dockerExtension'; import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; -import { Registry } from '../../node_modules/azure-arm-containerregistry/lib/models'; -import { Subscription } from '../../node_modules/azure-arm-resource/lib/subscription/models'; import * as acrTools from '../../utils/Azure/acrTools'; -const teleAzureId: string = 'vscode-docker.pull.from.azure.azureContainerRegistry'; -/* Pulls an image from Azure. The context would be the image node the user has right clicked on */ +/* Pulls an image from Azure. The context is the image node the user has right clicked on */ export async function pullFromAzure(context?: AzureImageNode): Promise { - console.log("in pull from Azure"); - // Step 1: Using loginCredentials() function to get the username and password. This takes care of users, even if they don't have the Azure CLI + // Step 1: Using loginCredentials() function to get the username and password. This takes care of all users, even if they don't have the Azure CLI let credentials; try { credentials = await acrTools.loginCredentialsRefreshToken(context.subscription, context.registry, context); From 086a574047115af0fd790ba500321cfe3456af33 Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 13:55:15 -0700 Subject: [PATCH 36/77] deleted push to azure --- commands/azureCommands/push-to-azure.ts | 108 ------------------------ 1 file changed, 108 deletions(-) delete mode 100644 commands/azureCommands/push-to-azure.ts diff --git a/commands/azureCommands/push-to-azure.ts b/commands/azureCommands/push-to-azure.ts deleted file mode 100644 index 2a4ab84e60..0000000000 --- a/commands/azureCommands/push-to-azure.ts +++ /dev/null @@ -1,108 +0,0 @@ -/* push-azure.ts - * - * Very basic integration for pushing to azure container registry instead of Docker hub - * Author : Esteban Rey - * Version 0.01 - * Updated 6/25/2018 - * - * Known Issues: - * - * Does not currently identify if resource groups/container registry exist. - * Is currently dependent on terminal installation of azure CLI - * Review best practices for await - * If user is not logged in no idea what to do - * login rocketpenguininterns.azurecr.io - Username: rocketPenguinInterns - Password: - Login Succeeded - */ - -import vscode = require('vscode'); -import { ExecuteCommandRequest } from 'vscode-languageclient/lib/main'; -import { ImageNode } from '../../explorer/models/imageNode'; -import { reporter } from '../../telemetry/telemetry'; -import { ImageItem, quickPickImage } from '../utils/quick-pick-image'; -//FOR TELEMETRY DATA -const teleCmdId: string = 'vscode-docker.image.pushToAzure'; -import * as exec from 'child_process'; - -export async function pushAzure(context?: ImageNode): Promise { - let imageToPush: Docker.ImageDesc; - let imageName: string = ""; - - if (context && context.imageDesc) { - imageToPush = context.imageDesc; - imageName = context.label; - } else { - const selectedItem: ImageItem = await quickPickImage(); - if (selectedItem) { - imageToPush = selectedItem.imageDesc; - imageName = selectedItem.label; - } - } - - if (imageToPush) { - const terminal = vscode.window.createTerminal(imageName); - - // 1. Registry Name - let options: vscode.InputBoxOptions = { - prompt: "Azure Container Registry Name?" - } - - let regName = await vscode.window.showInputBox(options); - terminal.sendText(`az acr login --name ${regName}`); - - // 2. Resource Group Name - options = { - prompt: "Resource Group Name?" - } - let resGroup = await vscode.window.showInputBox(options); - - // 3. Check for the existance of the resource group, if doesnt exist, create -- maybe close enough feature? - // 4. Acquire full acrLogin (Needs Testing) - - let cont = (err: any, stdout: any, stderr: any): void => { - console.log(stdout); - let jsonStdout = JSON.parse(stdout); - let soughtsrvr: string = ""; - for (let currJsonStdout of jsonStdout) { - let srvrName: string = currJsonStdout.acrLoginServer; - let searchIndex: number = srvrName.search(`${regName}`); - if (searchIndex === 0 && srvrName[regName.length] === '.') { // can names include . ? - soughtsrvr = srvrName; - break; - } - } - - if (soughtsrvr === '') { - vscode.window.showErrorMessage(`${regName} could not be found in resource group: ${resGroup}`); - return; - } - - let tagPrompts = async (): Promise => { - let repName = await vscode.window.showInputBox({ prompt: "Repository Name?" }); - let tag = await vscode.window.showInputBox({ prompt: "Tag?" }); - // 5. Tag image - terminal.sendText(`docker tag ${imageName} ${soughtsrvr}/${repName}:${tag}`); - // 6. Push image - terminal.sendText(`docker push ${soughtsrvr}/${repName}:${tag}`); - terminal.show(); - } - - tagPrompts(); - - } - - //exec(`az acr list --resource-group ${resGroup} --query "[].{acrLoginServer:loginServer}" --output json`, cont); - - } - - function streamToString(stream: any): Promise { - const chunks = [] - return new Promise((resolve, reject) => { - stream.on('data', chunks.push) - stream.on('error', reject) - stream.on('end', () => resolve(Buffer.concat(chunks).toString('utf8'))) - }) - } -} From 9cadc21e3ca03e5c3e6eff4d69288a60660e115f Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 14:17:31 -0700 Subject: [PATCH 37/77] Clean up --- explorer/models/azureRegistryNodes.ts | 2 ++ explorer/models/registryRootNode.ts | 14 +++++--------- utils/Azure/acrTools.ts | 3 +-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 8b171c2f0b..35a6b1f79e 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -252,6 +252,8 @@ export class AzureImageNode extends NodeBase { public registry: ContainerModels.Registry; public serverUrl: string; public subscription: SubscriptionModels.Subscription; + public userName: string; + public repository: string; public getTreeItem(): vscode.TreeItem { let displayName: string = this.label; diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 9dc70d35b8..4853c2eb00 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -141,15 +141,11 @@ export class RegistryRootNode extends NodeBase { for (let i = 0; i < subs.length; i++) { subPool.addTask(async () => { const client = new ContainerRegistryManagement(this.getCredentialByTenantId(subs[i].tenantId), subs[i].subscriptionId); - try { - let regs: ContainerModels.Registry[] = await client.registries.list(); - subsAndRegistries.push({ - 'subscription': subs[i], - 'registries': regs - }); - } catch (error) { - vscode.window.showErrorMessage(parseError(error).message); - } + subsAndRegistries.push({ + 'subscription': subs[i], + 'registries': await client.registries.list(), + }); + }); } await subPool.runAll(); diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index d6182e468b..edf82da402 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -217,15 +217,14 @@ export async function getAzureImages(element: Repository): Promise */ export async function loginCredentialsRefreshToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - //grab the access token to be used as a password, and a generic username let creds = await getRegistryTokens(registry); let password = creds.refreshToken; let username = '00000000-0000-0000-0000-000000000000'; return { password, username }; } +/* Used in delete Azure image and is pending edits */ export async function loginCredentialsAccessToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - //grab the access token to be used as a password, and a generic username let creds = await getRegistryTokens(registry); let password = creds.accessToken; let username = '00000000-0000-0000-0000-000000000000'; From dc77b1afcd1726be7c64523656bea73c3c0bee6d Mon Sep 17 00:00:00 2001 From: rsamai Date: Mon, 13 Aug 2018 18:27:20 -0700 Subject: [PATCH 38/77] Working again after rebasing and resolving merge conflicts --- commands/azureCommands/delete-repository.ts | 2 +- dockerExtension.ts | 47 ++++++++++----------- explorer/models/registryRootNode.ts | 13 ++++-- package.json | 5 --- 4 files changed, 33 insertions(+), 34 deletions(-) diff --git a/commands/azureCommands/delete-repository.ts b/commands/azureCommands/delete-repository.ts index c6fdcb5af0..2a4733bfd3 100644 --- a/commands/azureCommands/delete-repository.ts +++ b/commands/azureCommands/delete-repository.ts @@ -41,7 +41,7 @@ export async function deleteRepository(context?: AzureRepositoryNode): Promise { ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DOCKER_INSPECT_SCHEME, new DockerInspectDocumentContentProvider())); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.configure', configure)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.build', buildImage)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.inspect', inspectImage)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.remove', removeImage)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.push', pushImage)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.image.tag', tagImage)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.start', startContainer)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.start.interactive', startContainerInteractive)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.start.azurecli', startAzureCLI)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.stop', stopContainer)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.restart', restartContainer)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.show-logs', showLogsContainer)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.open-shell', openShellContainer)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.container.remove', removeContainer)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.up', composeUp)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.down', composeDown)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.compose.restart', composeRestart)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.system.prune', systemPrune)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.pullFromAzure', pullFromAzure)); - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createRegistry', createRegistry)); - - ctx.subscriptions.push(vscode.commands.registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { + registerCommand('vscode-docker.configure', configure); + registerCommand('vscode-docker.image.build', buildImage); + registerCommand('vscode-docker.image.inspect', inspectImage); + registerCommand('vscode-docker.image.remove', removeImage); + registerCommand('vscode-docker.image.push', pushImage); + registerCommand('vscode-docker.image.tag', tagImage); + registerCommand('vscode-docker.container.start', startContainer); + registerCommand('vscode-docker.container.start.interactive', startContainerInteractive); + registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); + registerCommand('vscode-docker.container.stop', stopContainer); + registerCommand('vscode-docker.container.restart', restartContainer); + registerCommand('vscode-docker.container.show-logs', showLogsContainer); + registerCommand('vscode-docker.container.open-shell', openShellContainer); + registerCommand('vscode-docker.container.remove', removeContainer); + registerCommand('vscode-docker.compose.up', composeUp); + registerCommand('vscode-docker.compose.down', composeDown); + registerCommand('vscode-docker.compose.restart', composeRestart); + registerCommand('vscode-docker.system.prune', systemPrune); + registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage); + registerCommand('vscode-docker.pullFromAzure', pullFromAzure); + + registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { if (context) { if (azureAccount) { const azureAccountWrapper = new AzureAccountWrapper(ctx, azureAccount); @@ -156,7 +155,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { } } - })); + }); registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout); registerCommand('vscode-docker.browseDockerHub', (context?: DockerHubImageNode | DockerHubRepositoryNode | DockerHubOrgNode) => { diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 4853c2eb00..e8c3ec73f0 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -141,10 +141,15 @@ export class RegistryRootNode extends NodeBase { for (let i = 0; i < subs.length; i++) { subPool.addTask(async () => { const client = new ContainerRegistryManagement(this.getCredentialByTenantId(subs[i].tenantId), subs[i].subscriptionId); - subsAndRegistries.push({ - 'subscription': subs[i], - 'registries': await client.registries.list(), - }); + try { + let regs: ContainerModels.Registry[] = await client.registries.list(); + subsAndRegistries.push({ + 'subscription': subs[i], + 'registries': regs + }); + } catch (error) { + vscode.window.showErrorMessage(parseError(error).message); + } }); } diff --git a/package.json b/package.json index 3856a9660a..fadb0b64a1 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,6 @@ "onCommand:vscode-docker.delete-ACR-Registry", "onCommand:vscode-docker.delete-ACR-Repository", "onCommand:vscode-docker.delete-ACR-Image", - "onCommand:vscode-docker.deleteAzureImage", "onCommand:vscode-docker.pullFromAzure", "onView:dockerExplorer", "onDebugInitialConfigurations" @@ -201,10 +200,6 @@ "command": "vscode-docker.dockerHubLogout", "when": "view == dockerExplorer && viewItem == dockerHubRootNode" }, - { - "command": "vscode-docker.deleteAzureImage", - "when": "view == dockerExplorer && viewItem == azureImageNode" - }, { "command": "vscode-docker.pullFromAzure", "when": "view == dockerExplorer && viewItem == azureImageNode" From 1f3b25a1d640b11d3b35b7caf920101f4d498e16 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Tue, 14 Aug 2018 12:57:01 -0700 Subject: [PATCH 39/77] Updates and fixes --- commands/acr-build.ts | 42 ++++++++++++++++++++++++------------- dockerExtension.ts | 4 ++-- explorer/models/taskNode.ts | 4 ++-- package.json | 8 ++----- 4 files changed, 34 insertions(+), 24 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index 80a45c4d1f..de21dd3a84 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -1,20 +1,18 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; import { Registry } from 'azure-arm-containerregistry/lib/models'; +import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; import { ResourceManagementClient } from 'azure-arm-resource'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; -import { Stream, Writable } from "stream"; +import * as fs from 'fs'; +import * as os from 'os'; +import * as tar from 'tar'; +import * as url from 'url'; import * as vscode from "vscode"; -import { ImageNode } from "../explorer/models/imageNode"; import { AzureUtilityManager } from "../utils/azureUtilityManager"; import { acquireResourceGroup, acquireSubscription, quickPickACRRegistry } from './utils/quick-pick-azure'; -let tar = require('tar'); -let fs = require('fs'); -let os = require('os'); -let url = require('url'); +const idPrecision = 6; export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { - console.log("Obtaining Subscription and Client"); let subscription = await acquireSubscription(); let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); @@ -33,20 +31,36 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { folder = await (vscode).window.showWorkspaceFolderPick(); } let sourceLocation: string = folder.uri.path; - console.log("Setting up temp file with 'sourceArchive.tar.gz' "); - let tarFilePath = url.resolve(os.tmpdir(), 'sourceArchive.tar.gz'); - console.log("Uploading Source Code"); + let relativeDockerPath = 'Dockerfile'; + if (dockerFileUri.path.indexOf(sourceLocation) !== 0) { + //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. + throw new TypeError("Source code path must be a parent of the Dockerfile path") + } else { + relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length); + } + + const opt: vscode.InputBoxOptions = { + prompt: 'Image name and tag in format :', + }; + const name: string = await vscode.window.showInputBox(opt); + + /* tslint:disable-next-line:insecure-random */ + let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); + console.log("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + + console.log("Uploading Source Code to " + tarFilePath); sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); console.log("Setting up Build Request"); let buildRequest: QuickBuildRequest = { 'type': 'QuickBuild', - 'imageNames': [], - 'isPushEnabled': false, + 'imageNames': [name], + 'isPushEnabled': true, 'sourceLocation': sourceLocation, 'platform': { 'osType': 'Linux' }, - 'dockerFilePath': 'DockerFile' + 'dockerFilePath': relativeDockerPath }; console.log("Queueing Build"); diff --git a/dockerExtension.ts b/dockerExtension.ts index 2687d83e0f..bc790eedf6 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -113,6 +113,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.image.remove', removeImage); registerCommand('vscode-docker.image.push', pushImage); registerCommand('vscode-docker.image.tag', tagImage); + registerCommand('vscode-docker.queueBuild', queueBuild); registerCommand('vscode-docker.container.start', startContainer); registerCommand('vscode-docker.container.start.interactive', startContainerInteractive); registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); @@ -125,7 +126,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.compose.down', composeDown); registerCommand('vscode-docker.compose.restart', composeRestart); registerCommand('vscode-docker.system.prune', systemPrune); - registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { if (context) { if (azureAccount) { @@ -157,7 +157,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage); registerCommand('vscode-docker.createRegistry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); - registerCommand('vscode-docker.queueBuild', queueBuild) + } activateLanguageClient(ctx); diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index a3ddc9059d..ccc3f65305 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -4,7 +4,7 @@ import * as vscode from 'vscode'; import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; -import { AzureCredentialsManager } from '../../utils/azureCredentialsManager'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { NodeBase } from './nodeBase'; /* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ @@ -36,7 +36,7 @@ export class TaskRootNode extends NodeBase { const buildTaskNodes: BuildTaskNode[] = []; let buildTasks: ContainerModels.BuildTask[] = []; - const client = AzureCredentialsManager.getInstance().getContainerRegistryManagementClient(element.subscription); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); const resourceGroup: string = acrTools.getResourceGroup(element.registry); buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); diff --git a/package.json b/package.json index 3f1ab63dd5..39ef2ad15d 100644 --- a/package.json +++ b/package.json @@ -284,10 +284,6 @@ "command": "vscode-docker.browseDockerHub", "when": "view == dockerExplorer && viewItem == dockerHubNamespace" }, - { - "command": "vscode-docker.queueBuild", - "when": "view == dockerExplorer && viewItem == localImageNode" - }, { "command": "vscode-docker.browseAzurePortal", "when": "view == dockerExplorer && viewItem == azureRegistryNode" @@ -717,9 +713,9 @@ "opn": "^5.1.0", "pom-parser": "^1.1.1", "request-promise": "^4.2.2", + "tar": "^4.4.6", "vscode-azureextensionui": "^0.16.6", "vscode-extension-telemetry": "0.0.18", - "vscode-languageclient": "^4.4.0", - "tar": "^4.4.6" + "vscode-languageclient": "^4.4.0" } } From 9736b40b39a4777260e12ff6c6e91730f962dcb4 Mon Sep 17 00:00:00 2001 From: rsamai Date: Tue, 14 Aug 2018 16:13:24 -0700 Subject: [PATCH 40/77] Fixes from PR comments -renamed loginCredentials functions -added await to docker commands in image pull -cleanup --- commands/azureCommands/delete-azure-image.ts | 9 ++------- commands/azureCommands/delete-repository.ts | 4 ++-- commands/azureCommands/pull-from-azure.ts | 7 +++---- explorer/deploy/webAppCreator.ts | 2 +- utils/Azure/acrTools.ts | 7 ++----- 5 files changed, 10 insertions(+), 19 deletions(-) diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts index c1655fdabf..7c93ca49d5 100644 --- a/commands/azureCommands/delete-azure-image.ts +++ b/commands/azureCommands/delete-azure-image.ts @@ -2,7 +2,7 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; import * as vscode from "vscode"; import * as quickPicks from '../../commands/utils/quick-pick-azure'; -import { AzureImageNode } from '../../explorer/models/AzureRegistryNodes'; +import { AzureImageNode } from '../../explorer/models/azureRegistryNodes'; import { reporter } from '../../telemetry/telemetry'; import * as acrTools from '../../utils/Azure/acrTools'; import { Repository } from "../../utils/Azure/models/repository"; @@ -53,12 +53,7 @@ export async function deleteAzureImage(context?: AzureImageNode): Promise tag = wholeName[1]; } - let creds; - try { - creds = await acrTools.loginCredentialsAccessToken(context.subscription, context.registry, context); - } catch (error) { - console.log(error); - } + let creds = await acrTools.acquireRegistryAccessToken(context.subscription, context.registry, context); username = creds.username; password = creds.password; diff --git a/commands/azureCommands/delete-repository.ts b/commands/azureCommands/delete-repository.ts index 2a4733bfd3..78cad929f2 100644 --- a/commands/azureCommands/delete-repository.ts +++ b/commands/azureCommands/delete-repository.ts @@ -2,7 +2,7 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; import * as vscode from "vscode"; import * as quickPicks from '../../commands/utils/quick-pick-azure'; -import { AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; +import { AzureRepositoryNode } from '../../explorer/models/azureRegistryNodes'; import { reporter } from '../../telemetry/telemetry'; import * as acrTools from '../../utils/Azure/acrTools'; import { Repository } from "../../utils/Azure/models/repository"; @@ -41,7 +41,7 @@ export async function deleteRepository(context?: AzureRepositoryNode): Promise { // Step 1: Using loginCredentials() function to get the username and password. This takes care of all users, even if they don't have the Azure CLI let credentials; try { - credentials = await acrTools.loginCredentialsRefreshToken(context.subscription, context.registry, context); + credentials = await acrTools.acquireRegistryLoginCredential(context.subscription, context.registry, context); } catch (error) { console.log(error); } @@ -22,11 +22,10 @@ export async function pullFromAzure(context?: AzureImageNode): Promise { terminal.show(); // Step 2: docker login command - terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); + await terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); // Step 3: docker pull command - console.log(context.repository); - terminal.sendText(`docker pull ${registry}/${context.label}`); + await terminal.sendText(`docker pull ${registry}/${context.label}`); //Acquiring telemetry data here if (reporter) { diff --git a/explorer/deploy/webAppCreator.ts b/explorer/deploy/webAppCreator.ts index 2149c01390..6538f4e567 100644 --- a/explorer/deploy/webAppCreator.ts +++ b/explorer/deploy/webAppCreator.ts @@ -13,7 +13,7 @@ import * as fs from 'fs'; import * as path from 'path'; import * as vscode from 'vscode'; import { reporter } from '../../telemetry/telemetry'; -import { AzureImageNode } from '../models/AzureRegistryNodes'; +import { AzureImageNode } from '../models/azureRegistryNodes'; import { DockerHubImageNode } from '../models/dockerHubNodes'; import { AzureAccountWrapper } from './azureAccountWrapper'; import * as util from './util'; diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index edf82da402..e17e8a31c2 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -2,13 +2,11 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; import request = require('request-promise'); import * as vscode from "vscode"; -import { NULL_GUID } from "../../constants"; import { AzureImageNode, AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; import { AzureUtilityManager } from '../azureUtilityManager'; import { AzureImage } from "./models/image"; import { Repository } from "./models/repository"; -const teleCmdId: string = 'vscode-docker.deleteAzureImage'; /** * Developers can use this to visualize and list repositories on a given Registry. This is not a command, just a developer tool. @@ -216,15 +214,14 @@ export async function getAzureImages(element: Repository): Promise * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username */ -export async function loginCredentialsRefreshToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { +export async function acquireRegistryLoginCredential(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { let creds = await getRegistryTokens(registry); let password = creds.refreshToken; let username = '00000000-0000-0000-0000-000000000000'; return { password, username }; } -/* Used in delete Azure image and is pending edits */ -export async function loginCredentialsAccessToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { +export async function acquireRegistryAccessToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { let creds = await getRegistryTokens(registry); let password = creds.accessToken; let username = '00000000-0000-0000-0000-000000000000'; From 8c36bb94092cf88d17456edfa990355bb83a715e Mon Sep 17 00:00:00 2001 From: rsamai Date: Tue, 14 Aug 2018 16:23:47 -0700 Subject: [PATCH 41/77] uncapitalize AzureRegistryNode --- commands/azureCommands/pull-from-azure.ts | 2 +- dockerExtension.ts | 2 +- explorer/models/registryRootNode.ts | 2 +- explorer/utils/azureUtils.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 5d498b0ed8..fdf6d7ac56 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -7,7 +7,7 @@ import * as acrTools from '../../utils/Azure/acrTools'; /* Pulls an image from Azure. The context is the image node the user has right clicked on */ export async function pullFromAzure(context?: AzureImageNode): Promise { - // Step 1: Using loginCredentials() function to get the username and password. This takes care of all users, even if they don't have the Azure CLI + // Step 1: Using loginCredentials function to get the username and password. This takes care of all users, even if they don't have the Azure CLI let credentials; try { credentials = await acrTools.acquireRegistryLoginCredential(context.subscription, context.registry, context); diff --git a/dockerExtension.ts b/dockerExtension.ts index c883402b2f..092ef9d5de 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -39,7 +39,7 @@ import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper'; import * as util from "./explorer/deploy/util"; import { WebAppCreator } from './explorer/deploy/webAppCreator'; import { DockerExplorerProvider } from './explorer/dockerExplorer'; -import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/AzureRegistryNodes'; +import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes'; import { DockerHubImageNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; import { browseAzurePortal } from './explorer/utils/azureUtils'; import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index e8c3ec73f0..4a8ec679a8 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -14,7 +14,7 @@ import { AsyncPool } from '../../utils/asyncpool'; import * as acrTools from '../../utils/Azure/acrTools'; import * as dockerHub from '../utils/dockerHubUtils' import { getCoreNodeModule } from '../utils/utils'; -import { AzureLoadingNode, AzureNotSignedInNode, AzureRegistryNode } from './AzureRegistryNodes'; +import { AzureLoadingNode, AzureNotSignedInNode, AzureRegistryNode } from './azureRegistryNodes'; import { DockerHubOrgNode } from './dockerHubNodes'; import { NodeBase } from './nodeBase'; import { RegistryType } from './registryType'; diff --git a/explorer/utils/azureUtils.ts b/explorer/utils/azureUtils.ts index f2bc09a525..8a55faa55a 100644 --- a/explorer/utils/azureUtils.ts +++ b/explorer/utils/azureUtils.ts @@ -1,6 +1,6 @@ import * as opn from 'opn'; import { AzureSession } from '../../typings/azure-account.api'; -import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from '../models/AzureRegistryNodes'; +import { AzureImageNode, AzureRegistryNode, AzureRepositoryNode } from '../models/azureRegistryNodes'; export function browseAzurePortal(context?: AzureRegistryNode | AzureRepositoryNode | AzureImageNode): void { From 6c06d7660ecfb2c309d3ec08ff026ed1e0cfa964 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Tue, 14 Aug 2018 16:47:11 -0700 Subject: [PATCH 42/77] Refactoring, prod. --- commands/acr-build.ts | 50 +++++++++++------------ commands/azureCommands/create-registry.ts | 8 ++-- commands/utils/quick-pick-azure.ts | 3 +- utils/Azure/acrTools.ts | 11 +++++ 4 files changed, 40 insertions(+), 32 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index de21dd3a84..7e7c87d2a1 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -8,12 +8,18 @@ import * as os from 'os'; import * as tar from 'tar'; import * as url from 'url'; import * as vscode from "vscode"; +import { getBlobInfo } from "../utils/Azure/acrTools"; import { AzureUtilityManager } from "../utils/azureUtilityManager"; import { acquireResourceGroup, acquireSubscription, quickPickACRRegistry } from './utils/quick-pick-azure'; const idPrecision = 6; +let status = vscode.window.createOutputChannel('status'); +status.show(); +// Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. +// The user is then asked to name & tag the image. A build is queued for the image in the selected registry. +// Selected source code must contain a path to the desired dockerfile. export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { - console.log("Obtaining Subscription and Client"); + status.appendLine("Obtaining Subscription and Client"); let subscription = await acquireSubscription(); let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); @@ -40,6 +46,7 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length); } + // Prompting for name so the image can then be pushed to a repository. const opt: vscode.InputBoxOptions = { prompt: 'Image name and tag in format :', }; @@ -47,13 +54,13 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { /* tslint:disable-next-line:insecure-random */ let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); - console.log("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); + status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); - console.log("Uploading Source Code to " + tarFilePath); + status.appendLine("Uploading Source Code to " + tarFilePath); sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); - console.log("Setting up Build Request"); + status.appendLine("Setting up Build Request"); let buildRequest: QuickBuildRequest = { 'type': 'QuickBuild', 'imageNames': [name], @@ -62,19 +69,18 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { 'platform': { 'osType': 'Linux' }, 'dockerFilePath': relativeDockerPath }; - - console.log("Queueing Build"); + status.appendLine("Queueing Build"); try { await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); } catch (error) { - console.log('Build Failed'); + status.appendLine('Build Failed'); vscode.window.showErrorMessage(error); } - console.log(client.builds.list(resourceGroupName, registryName)); + status.appendLine('Success'); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { - console.log(" Sending source code to temp file"); + status.appendLine(" Sending source code to temp file"); try { tar.c( { @@ -83,38 +89,28 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis [sourceLocation] ).pipe(fs.createWriteStream(tarFilePath)); } catch (error) { - console.log(error); + status.appendLine(error); } - console.log(" Getting Build Source Upload Url "); + status.appendLine(" Getting Build Source Upload Url "); let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); let upload_url = sourceUploadLocation.uploadUrl; let relative_path = sourceUploadLocation.relativePath; - console.log(" Getting blob info from upload URl "); + status.appendLine(" Getting blob info from Upload Url "); // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); - console.log(" Creating Blob service "); + status.appendLine(" Creating Blob Service "); let blob: BlobService = createBlobServiceWithSas(host, sasToken); - console.log(" Creating Block Blob "); + status.appendLine(" Creating Block Blob "); try { blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); } catch (error) { - console.log(error); + status.appendLine(" Failed to create Block Blob "); + vscode.window.showErrorMessage(error); } - console.log(" Success "); + status.appendLine(" Success "); return relative_path; } - -function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { - let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); - let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); - let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); - let containerName: string = items[1]; - let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); - let sasToken: string = items[4].slice(items[4].search('[?]') + 1); - let host: string = accountName + '.blob.' + endpointSuffix; - return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; -} diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index e615ea8ad4..7ebf94f43e 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -6,6 +6,7 @@ import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import * as vscode from "vscode"; import { reporter } from '../../telemetry/telemetry'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { acquireLocation, acquireResourceGroup, acquireSubscription } from "../utils/quick-pick-azure" const teleAzureId: string = 'vscode-docker.create.registry.azureContainerRegistry'; const teleCmdId: string = 'vscode-docker.createRegistry'; import * as opn from 'opn'; @@ -18,7 +19,8 @@ export async function createRegistry(): Promise { try { subscription = await acquireSubscription(); - resourceGroup = await acquireResourceGroup(subscription); + const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + resourceGroup = await acquireResourceGroup(subscription, resourceGroupClient); } catch (error) { return; @@ -96,7 +98,7 @@ async function acquireRegistryName(client: ContainerRegistryManagementClient): P } return registryName; } - +/* async function acquireSubscription(): Promise { const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); if (subs.length === 0) { @@ -179,7 +181,7 @@ async function acquireResourceGroup(subscription: SubscriptionModels.Subscriptio } while (!resourceGroupName); return resourceGroup; } - +*/ /*Creates a new resource group within the current subscription */ async function createNewResourceGroup(loc: string, resourceGroupClient: ResourceManagementClient): Promise { let promptMessage = 'Resource group name?'; diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index 5421e5ecfc..0d159ac475 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -66,7 +66,6 @@ export async function acquireResourceGroup(subscription: Subscription, resourceG //Acquire each subscription's data simultaneously let resourceGroup; let resourceGroupName; - //const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); let resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); let resourceGroupNames: string[] = []; @@ -90,7 +89,7 @@ export async function acquireResourceGroup(subscription: Subscription, resourceG return resourceGroup; } -async function acquireLocation(resourceGroup: ResourceGroup, subscription: SubscriptionModels.Subscription): Promise { +export async function acquireLocation(resourceGroup: ResourceGroup, subscription: SubscriptionModels.Subscription): Promise { let locations: SubscriptionModels.Location[] = await AzureUtilityManager.getInstance().getLocationsBySubscription(subscription); let locationNames: string[] = []; let placeHolder: string; diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index f31653e39a..df6e27206d 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -283,3 +283,14 @@ export function getRegistrySubscription(registry: Registry): SubscriptionModels. }); return subscription; } + +export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { + let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); + let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); + let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); + let containerName: string = items[1]; + let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); + let sasToken: string = items[4].slice(items[4].search('[?]') + 1); + let host: string = accountName + '.blob.' + endpointSuffix; + return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; +} From 3eed8a1c92bcd643ebd7e3eaea3eeb360b9aa3cd Mon Sep 17 00:00:00 2001 From: jvstokes Date: Thu, 16 Aug 2018 14:18:51 -0700 Subject: [PATCH 43/77] Flexible OSType --- commands/acr-build.ts | 56 +++++++++++++++++++++---------------------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index 7e7c87d2a1..9486e50cfe 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -13,12 +13,12 @@ import { AzureUtilityManager } from "../utils/azureUtilityManager"; import { acquireResourceGroup, acquireSubscription, quickPickACRRegistry } from './utils/quick-pick-azure'; const idPrecision = 6; let status = vscode.window.createOutputChannel('status'); -status.show(); // Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. // The user is then asked to name & tag the image. A build is queued for the image in the selected registry. // Selected source code must contain a path to the desired dockerfile. export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { + status.show(); status.appendLine("Obtaining Subscription and Client"); let subscription = await acquireSubscription(); let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); @@ -41,7 +41,7 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { let relativeDockerPath = 'Dockerfile'; if (dockerFileUri.path.indexOf(sourceLocation) !== 0) { //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. - throw new TypeError("Source code path must be a parent of the Dockerfile path") + throw new Error("Source code path must be a parent of the Dockerfile path"); } else { relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length); } @@ -52,45 +52,38 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { }; const name: string = await vscode.window.showInputBox(opt); - /* tslint:disable-next-line:insecure-random */ - let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); - status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); - let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + let tarFilePath = getTempSourceArchivePath(); status.appendLine("Uploading Source Code to " + tarFilePath); sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); + let osType = os.type() + if (osType === 'Windows_NT') { + osType = 'Windows' + } + status.appendLine("Setting up Build Request"); let buildRequest: QuickBuildRequest = { 'type': 'QuickBuild', 'imageNames': [name], 'isPushEnabled': true, 'sourceLocation': sourceLocation, - 'platform': { 'osType': 'Linux' }, + 'platform': { 'osType': osType }, 'dockerFilePath': relativeDockerPath }; status.appendLine("Queueing Build"); - try { - await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); - } catch (error) { - status.appendLine('Build Failed'); - vscode.window.showErrorMessage(error); - } + await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); status.appendLine('Success'); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { status.appendLine(" Sending source code to temp file"); - try { - tar.c( - { - gzip: true - }, - [sourceLocation] - ).pipe(fs.createWriteStream(tarFilePath)); - } catch (error) { - status.appendLine(error); - } + tar.c( + { + gzip: true + }, + [sourceLocation] + ).pipe(fs.createWriteStream(tarFilePath)); status.appendLine(" Getting Build Source Upload Url "); let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); @@ -105,12 +98,17 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis let blob: BlobService = createBlobServiceWithSas(host, sasToken); status.appendLine(" Creating Block Blob "); - try { - blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); - } catch (error) { - status.appendLine(" Failed to create Block Blob "); - vscode.window.showErrorMessage(error); - } + + blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + status.appendLine(" Success "); return relative_path; } + +function getTempSourceArchivePath(): string { + /* tslint:disable-next-line:insecure-random */ + let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); + status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + return tarFilePath; +} From 341f09719c5e206e0ed67f6a9a0ce6542ef134fc Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 16 Aug 2018 14:32:33 -0700 Subject: [PATCH 44/77] Estebanreyl/ready for production (#55) * began updating * Reorganized create registry and delete azure image * continued improvements * Began updating login * being credentials update * further updates * Finished updating, need to test functionality now * Updated requests, things all work now * Applied some nit fixes * Updates to naming * maintain UtilityManager standards * Updated Prompts * Updated imports and naming / standarized telemetry * Added explorer refresh capabilities on delete/add --- commands/azureCommands/create-registry.ts | 207 ++--------- commands/azureCommands/delete-azure-image.ts | 71 ---- .../azureCommands/delete-azure-registry.ts | 49 --- commands/azureCommands/delete-image.ts | 57 +++ commands/azureCommands/delete-registry.ts | 41 +++ commands/azureCommands/delete-repository.ts | 51 ++- commands/azureCommands/pull-from-azure.ts | 13 +- commands/utils/quick-pick-azure.ts | 186 ++++++++-- constants.ts | 3 + dockerExtension.ts | 8 +- explorer/dockerExplorer.ts | 4 + explorer/models/azureRegistryNodes.ts | 6 +- explorer/models/registryRootNode.ts | 1 - explorer/models/taskNode.ts | 2 +- explorer/utils/azureUtils.ts | 12 - package.json | 6 +- utils/Azure/acrTools.ts | 336 ++++++------------ utils/Azure/models/image.ts | 10 +- utils/Azure/models/repository.ts | 23 +- utils/azureUtilityManager.ts | 6 + 20 files changed, 453 insertions(+), 639 deletions(-) delete mode 100644 commands/azureCommands/delete-azure-image.ts delete mode 100644 commands/azureCommands/delete-azure-registry.ts create mode 100644 commands/azureCommands/delete-image.ts create mode 100644 commands/azureCommands/delete-registry.ts diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index e615ea8ad4..9032ed4375 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -1,217 +1,60 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { RegistryNameStatus } from "azure-arm-containerregistry/lib/models"; -import { ResourceManagementClient, SubscriptionModels } from 'azure-arm-resource'; +import { SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import * as vscode from "vscode"; +import { dockerExplorerProvider } from '../../dockerExtension'; +import { UserCancelledError } from '../../explorer/deploy/wizard'; import { reporter } from '../../telemetry/telemetry'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; -const teleAzureId: string = 'vscode-docker.create.registry.azureContainerRegistry'; -const teleCmdId: string = 'vscode-docker.createRegistry'; -import * as opn from 'opn'; +import { quickPickLocation, quickPickResourceGroup, quickPickSKU, quickPickSubscription } from '../utils/quick-pick-azure'; +const teleCmdId: string = 'vscode-docker.create-ACR-Registry'; -/* Creates a new registry based on user input/selection of features, such as location */ -export async function createRegistry(): Promise { - let subscription: SubscriptionModels.Subscription; - let resourceGroup: ResourceGroup; - let location: string; - - try { - subscription = await acquireSubscription(); - resourceGroup = await acquireResourceGroup(subscription); - - } catch (error) { - return; - } +/* Creates a new Azure container registry based on user input/selection of features */ +export async function createRegistry(): Promise { + const subscription: SubscriptionModels.Subscription = await quickPickSubscription(); + const resourceGroup: ResourceGroup = await quickPickResourceGroup(true, subscription); const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + const registryName: string = await acquireRegistryName(client); + const sku: string = await quickPickSKU(); + const location = await quickPickLocation(subscription); - let registryName: string; - try { - registryName = await acquireRegistryName(client); - } catch (error) { - return; - } - - const sku: string = await acquireSKU(); - location = await acquireLocation(resourceGroup, subscription); - - client.registries.beginCreate(resourceGroup.name, registryName, { 'sku': { 'name': sku }, 'location': location }).then((response): void => { - vscode.window.showInformationMessage(response.name + ' has been created succesfully!'); - }, (error): void => { - vscode.window.showErrorMessage(error.message); - }) - - //Acquiring telemetry data here + const registry = await client.registries.beginCreate(resourceGroup.name, registryName, { + 'sku': { 'name': sku }, + 'location': location + }); + vscode.window.showInformationMessage(registry.name + ' has been created succesfully!'); + dockerExplorerProvider.refreshRegistries(); if (reporter) { - /* __GDPR__ - "command" : { - "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ reporter.sendTelemetryEvent('command', { command: teleCmdId }); - - if (registryName.toLowerCase().indexOf('azurecr.io')) { - /* __GDPR__ - "command" : { - "command" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - reporter.sendTelemetryEvent('command', { - command: teleAzureId - }); - } } + return registryName; } -// INPUT HELPERS -async function acquireSKU(): Promise { - let skus: string[] = ["Standard", "Basic", "Premium"]; - let sku: string; - sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU' }); - if (sku === undefined) { throw new Error('User exit'); } - - return sku; -} - +/** Acquires a new registry name from a user, validating that the name is unique */ async function acquireRegistryName(client: ContainerRegistryManagementClient): Promise { let opt: vscode.InputBoxOptions = { ignoreFocusOut: false, - prompt: 'Registry name? ' + prompt: 'New Registry name? ' }; let registryName: string = await vscode.window.showInputBox(opt); + if (!registryName) { throw new UserCancelledError(); } let registryStatus: RegistryNameStatus = await client.registries.checkNameAvailability({ 'name': registryName }); + while (!registryStatus.nameAvailable) { opt = { ignoreFocusOut: false, - prompt: `The registry name '${registryName}' is unavailable. Try again: ` + prompt: `The Registry name '${registryName}' is unavailable. Try again: ` } registryName = await vscode.window.showInputBox(opt); + if (!registryName) { throw new UserCancelledError(); } - if (registryName === undefined) { throw new Error('user Exit'); } registryStatus = await client.registries.checkNameAvailability({ 'name': registryName }); } return registryName; } - -async function acquireSubscription(): Promise { - const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); - if (subs.length === 0) { - vscode.window.showErrorMessage("You do not have any subscriptions. You can create one in your Azure Portal", "Open Portal").then(val => { - if (val === "Open Portal") { - opn('https://portal.azure.com/'); - } - }); - } - - let subsNames: string[] = []; - for (let sub of subs) { - subsNames.push(sub.displayName); - } - let subscriptionName: string; - subscriptionName = await vscode.window.showQuickPick(subsNames, { 'canPickMany': false, 'placeHolder': 'Choose a subscription to be used' }); - if (subscriptionName === undefined) { throw new Error('User exit'); } - - return subs.find(sub => { return sub.displayName === subscriptionName }); -} - -async function acquireLocation(resourceGroup: ResourceGroup, subscription: SubscriptionModels.Subscription): Promise { - let locations: SubscriptionModels.Location[] = await AzureUtilityManager.getInstance().getLocationsBySubscription(subscription); - let locationNames: string[] = []; - let placeHolder: string; - - for (let loc of locations) { - locationNames.push(loc.displayName); - } - - locationNames.sort((loc1: string, loc2: string): number => { - return loc1.localeCompare(loc2); - }); - - if (resourceGroup === undefined) { - placeHolder = "Choose location for your new resource group"; - } else { - placeHolder = resourceGroup.location; - - //makes placeholder the Display Name version of the location's name - locations.forEach((locObj: SubscriptionModels.Location): string => { - if (locObj.name === resourceGroup.location) { - placeHolder = locObj.displayName; - return; - } - }); - } - let location: string; - do { - location = await vscode.window.showQuickPick(locationNames, { 'canPickMany': false, 'placeHolder': placeHolder }); - if (location === undefined) { throw new Error('User exit'); } - } while (!location); - return location; -} - -async function acquireResourceGroup(subscription: SubscriptionModels.Subscription): Promise { - //Acquire each subscription's data simultaneously - let resourceGroup; - let resourceGroupName; - const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); - let resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); - - let resourceGroupNames: string[] = []; - resourceGroupNames.push('+ Create new resource group'); - for (let resGroupName of resourceGroups) { - resourceGroupNames.push(resGroupName.name); - } - - do { - resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, { 'canPickMany': false, 'placeHolder': 'Choose a Resource Group to be used' }); - if (resourceGroupName === undefined) { throw new Error('user Exit'); } - if (resourceGroupName === '+ Create new resource group') { - let loc = await acquireLocation(resourceGroup, subscription); - resourceGroupName = await createNewResourceGroup(loc, resourceGroupClient); - } - resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); - resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); - - if (!resourceGroupName) { vscode.window.showErrorMessage('You must select a valid resource group'); } - } while (!resourceGroupName); - return resourceGroup; -} - -/*Creates a new resource group within the current subscription */ -async function createNewResourceGroup(loc: string, resourceGroupClient: ResourceManagementClient): Promise { - let promptMessage = 'Resource group name?'; - - let opt: vscode.InputBoxOptions = { - ignoreFocusOut: false, - prompt: promptMessage - }; - - let resourceGroupName: string; - let resourceGroupStatus: boolean; - - while (opt.prompt) { - resourceGroupName = await vscode.window.showInputBox(opt); - resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); - if (!resourceGroupStatus) { - opt.prompt = null; - } else { - opt.prompt = `The resource group '${resourceGroupName}' already exists. Try again: `; - } - } - - let newResourceGroup: ResourceGroup = { - name: resourceGroupName, - location: loc, - }; - - //Potential error when two clients try to create same resource group name at once - try { - await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); - } catch (error) { - vscode.window.showErrorMessage(`The resource group '${resourceGroupName}' already exists. Try again: `); - } - return resourceGroupName; -} diff --git a/commands/azureCommands/delete-azure-image.ts b/commands/azureCommands/delete-azure-image.ts deleted file mode 100644 index 7c93ca49d5..0000000000 --- a/commands/azureCommands/delete-azure-image.ts +++ /dev/null @@ -1,71 +0,0 @@ -import { Registry } from "azure-arm-containerregistry/lib/models"; -import { SubscriptionModels } from 'azure-arm-resource'; -import * as vscode from "vscode"; -import * as quickPicks from '../../commands/utils/quick-pick-azure'; -import { AzureImageNode } from '../../explorer/models/azureRegistryNodes'; -import { reporter } from '../../telemetry/telemetry'; -import * as acrTools from '../../utils/Azure/acrTools'; -import { Repository } from "../../utils/Azure/models/repository"; -import { AzureUtilityManager } from '../../utils/azureUtilityManager'; - -const teleCmdId: string = 'vscode-docker.deleteACRImage'; - -/** Function to delete an Azure repository and its associated images - * @param context : if called through right click on AzureRepositoryNode, the node object will be passed in. See azureRegistryNodes.ts for more info - */ -export async function deleteAzureImage(context?: AzureImageNode): Promise { - if (!AzureUtilityManager.getInstance().waitForLogin()) { - vscode.window.showErrorMessage('You are not logged into Azure'); - return; - } - let registry: Registry; - let subscription: SubscriptionModels.Subscription; - let repoName: string; - let username: string; - let password: string; - let tag: string; - if (!context) { - registry = await quickPicks.quickPickACRRegistry(); - subscription = acrTools.getRegistrySubscription(registry); - let repository: Repository = await quickPicks.quickPickACRRepository(registry); - repoName = repository.name; - const image = await quickPicks.quickPickACRImage(repository); - tag = image.tag; - } - - //ensure user truly wants to delete image - let opt: vscode.InputBoxOptions = { - ignoreFocusOut: true, - placeHolder: 'No', - value: 'No', - prompt: 'Are you sure you want to delete this image? Enter Yes to continue: ' - }; - let answer = await vscode.window.showInputBox(opt); - answer = answer.toLowerCase(); - if (answer !== 'yes') { return; } - - if (context) { - repoName = context.label; - subscription = context.subscription; - registry = context.registry; - let wholeName = repoName.split(':'); - repoName = wholeName[0]; - tag = wholeName[1]; - } - - let creds = await acrTools.acquireRegistryAccessToken(context.subscription, context.registry, context); - - username = creds.username; - password = creds.password; - let path = `/v2/_acr/${repoName}/tags/${tag}`; - await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); //official call to delete the image - reportTelemetry(); -} - -function reportTelemetry(): void { - if (reporter) { - reporter.sendTelemetryEvent('command', { - command: teleCmdId - }); - } -} diff --git a/commands/azureCommands/delete-azure-registry.ts b/commands/azureCommands/delete-azure-registry.ts deleted file mode 100644 index 6d03c9cc31..0000000000 --- a/commands/azureCommands/delete-azure-registry.ts +++ /dev/null @@ -1,49 +0,0 @@ -import { Registry } from "azure-arm-containerregistry/lib/models"; -import * as vscode from "vscode"; -import { quickPickACRRegistry } from '../../commands/utils/quick-pick-azure'; -import { AzureRegistryNode } from '../../explorer/models/azureRegistryNodes'; -import { SubscriptionModels } from "../../node_modules/azure-arm-resource"; -import { reporter } from '../../telemetry/telemetry'; -import * as acrTools from '../../utils/Azure/acrTools'; -import { AzureUtilityManager } from '../../utils/AzureUtilityManager'; - -const teleCmdId: string = 'vscode-docker.deleteAzureRegistry'; - -/** Delete a registry and all it's associated nested items - * @param context : the AzureRegistryNode the user right clicked on to delete - */ -export async function deleteAzureRegistry(context?: AzureRegistryNode): Promise { - let registry: Registry; - if (context) { - registry = context.registry; - } else { - registry = await quickPickACRRegistry(); - } - - let opt: vscode.InputBoxOptions = { - ignoreFocusOut: true, - placeHolder: 'No', - value: 'No', - prompt: 'Are you sure you want to delete this registry and its associated images? Enter yes to continue: ' - }; - let answer = await vscode.window.showInputBox(opt); - - answer = answer.toLowerCase(); - if (answer !== 'yes') { return; } - - let subscription: SubscriptionModels.Subscription = acrTools.getRegistrySubscription(registry); - let resourceGroup: string = acrTools.getResourceGroupName(registry); - const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); - - await client.registries.beginDeleteMethod(resourceGroup, registry.name); - vscode.window.showInformationMessage('Successfully deleted registry ' + registry.name); - telemetryReport(); -} - -function telemetryReport(): void { - if (reporter) { - reporter.sendTelemetryEvent('command', { - command: teleCmdId - }); - } -} diff --git a/commands/azureCommands/delete-image.ts b/commands/azureCommands/delete-image.ts new file mode 100644 index 0000000000..7e49333189 --- /dev/null +++ b/commands/azureCommands/delete-image.ts @@ -0,0 +1,57 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import * as vscode from "vscode"; +import { dockerExplorerProvider } from '../../dockerExtension'; +import { UserCancelledError } from "../../explorer/deploy/wizard"; +import { AzureImageNode, AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; +import { reporter } from '../../telemetry/telemetry'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureImage } from "../../utils/Azure/models/image"; +import { Repository } from "../../utils/Azure/models/repository"; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import * as quickPicks from '../utils/quick-pick-azure'; + +const teleCmdId: string = 'vscode-docker.delete-ACR-Image'; + +/** Function to delete an Azure hosted image + * @param context : if called through right click on AzureImageNode, the node object will be passed in. See azureRegistryNodes.ts for more info + */ +export async function deleteAzureImage(context?: AzureImageNode): Promise { + if (!AzureUtilityManager.getInstance().waitForLogin()) { + vscode.window.showErrorMessage('You are not logged into Azure'); + throw new Error('User is not logged into azure'); + } + let registry: Registry; + let repoName: string; + let tag: string; + + if (!context) { + registry = await quickPicks.quickPickACRRegistry(); + const repository: Repository = await quickPicks.quickPickACRRepository(registry, 'Choose the Repository of the image you want to delete'); + repoName = repository.name; + const image: AzureImage = await quickPicks.quickPickACRImage(repository, 'Choose the Image you want to delete'); + tag = image.tag; + } else { + registry = context.registry; + let wholeName: string[] = context.label.split(':'); + repoName = wholeName[0]; + tag = wholeName[1]; + } + + const shouldDelete = await quickPicks.confirmUserIntent('Are you sure you want to delete this image? Enter Yes to continue: '); + if (shouldDelete) { + const { acrAccessToken } = await acrTools.acquireACRAccessTokenFromRegistry(registry, `repository:${repoName}:*`); + const path = `/v2/_acr/${repoName}/tags/${tag}`; + await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, acrAccessToken); + vscode.window.showInformationMessage(`Successfully deleted image ${tag}`); + if (context) { + dockerExplorerProvider.refreshNode(context.parent); + } + } else { + throw new UserCancelledError(); + } + if (reporter) { + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + } +} diff --git a/commands/azureCommands/delete-registry.ts b/commands/azureCommands/delete-registry.ts new file mode 100644 index 0000000000..1ea89aa4a6 --- /dev/null +++ b/commands/azureCommands/delete-registry.ts @@ -0,0 +1,41 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { SubscriptionModels } from "azure-arm-resource"; +import * as vscode from "vscode"; +import { dockerExplorerProvider } from '../../dockerExtension'; +import { UserCancelledError } from "../../explorer/deploy/wizard"; +import { AzureRegistryNode } from '../../explorer/models/AzureRegistryNodes'; +import { reporter } from '../../telemetry/telemetry'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { confirmUserIntent, quickPickACRRegistry } from '../utils/quick-pick-azure'; + +const teleCmdId: string = 'vscode-docker.delete-ACR-Registry'; + +/** Delete a registry and all it's associated nested items + * @param context : the AzureRegistryNode the user right clicked on to delete + */ +export async function deleteAzureRegistry(context?: AzureRegistryNode): Promise { + let registry: Registry; + if (context) { + registry = context.registry; + } else { + registry = await quickPickACRRegistry(false, 'Choose the Registry you want to delete'); + } + const shouldDelete = await confirmUserIntent('Are you sure you want to delete this registry and its associated images? Enter yes to continue: '); + if (shouldDelete) { + let subscription: SubscriptionModels.Subscription = acrTools.getSubscriptionFromRegistry(registry); + let resourceGroup: string = acrTools.getResourceGroupName(registry); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + await client.registries.beginDeleteMethod(resourceGroup, registry.name); + vscode.window.showInformationMessage(`Successfully deleted registry ${registry.name}`); + dockerExplorerProvider.refreshRegistries(); + } else { + throw new UserCancelledError(); + } + + if (reporter) { + reporter.sendTelemetryEvent('command', { + command: teleCmdId + }); + } +} diff --git a/commands/azureCommands/delete-repository.ts b/commands/azureCommands/delete-repository.ts index 78cad929f2..667bfc3025 100644 --- a/commands/azureCommands/delete-repository.ts +++ b/commands/azureCommands/delete-repository.ts @@ -1,13 +1,15 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; -import { SubscriptionModels } from 'azure-arm-resource'; +import { Context } from "mocha"; import * as vscode from "vscode"; -import * as quickPicks from '../../commands/utils/quick-pick-azure'; -import { AzureRepositoryNode } from '../../explorer/models/azureRegistryNodes'; +import { dockerExplorerProvider } from '../../dockerExtension'; +import { UserCancelledError } from "../../explorer/deploy/wizard"; +import { AzureRegistryNode, AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; import { reporter } from '../../telemetry/telemetry'; import * as acrTools from '../../utils/Azure/acrTools'; import { Repository } from "../../utils/Azure/models/repository"; +import { confirmUserIntent, quickPickACRRegistry, quickPickACRRepository } from '../utils/quick-pick-azure'; -const teleCmdId: string = 'vscode-docker.deleteACRRepository'; +const teleCmdId: string = 'vscode-docker.delete-ACR-Repository'; /** * function to delete an Azure repository and its associated images * @param context : if called through right click on AzureRepositoryNode, the node object will be passed in. See azureRegistryNodes.ts for more info @@ -15,41 +17,28 @@ const teleCmdId: string = 'vscode-docker.deleteACRRepository'; export async function deleteRepository(context?: AzureRepositoryNode): Promise { let registry: Registry; - let subscription: SubscriptionModels.Subscription; let repoName: string; if (context) { repoName = context.label; - subscription = context.subscription; registry = context.registry; } else { - registry = await quickPicks.quickPickACRRegistry(); - subscription = acrTools.getRegistrySubscription(registry); - const repository: Repository = await quickPicks.quickPickACRRepository(registry); + registry = await quickPickACRRegistry(); + const repository: Repository = await quickPickACRRepository(registry, 'Choose the Repository you want to delete'); repoName = repository.name; } - - // Ensure user truly wants to delete registry - let opt: vscode.InputBoxOptions = { - ignoreFocusOut: true, - placeHolder: 'No', - value: 'No', - prompt: 'Are you sure you want to delete this repository and its associated images? Enter Yes to continue: ' - }; - - let answer = await vscode.window.showInputBox(opt); - answer = answer.toLowerCase(); - if (answer !== 'yes') { return; } - - let creds = await acrTools.acquireRegistryAccessToken(subscription, registry); - const username: string = creds.username; - const password: string = creds.password; - let path = `/v2/_acr/${repoName}/repository`; - await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, username, password); - reportTelemetry(); -} - -function reportTelemetry(): void { + const shouldDelete = await confirmUserIntent('Are you sure you want to delete this repository and its associated images? Enter yes to continue: '); + if (shouldDelete) { + const { acrAccessToken } = await acrTools.acquireACRAccessTokenFromRegistry(registry, `repository:${repoName}:*`); + const path = `/v2/_acr/${repoName}/repository`; + await acrTools.sendRequestToRegistry('delete', registry.loginServer, path, acrAccessToken); + vscode.window.showInformationMessage(`Successfully deleted repository ${Repository}`); + if (context) { + dockerExplorerProvider.refreshNode(context.parent); + } + } else { + throw new UserCancelledError(); + } if (reporter) { reporter.sendTelemetryEvent('command', { command: teleCmdId diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index fdf6d7ac56..903ad6f32f 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -8,15 +8,10 @@ import * as acrTools from '../../utils/Azure/acrTools'; export async function pullFromAzure(context?: AzureImageNode): Promise { // Step 1: Using loginCredentials function to get the username and password. This takes care of all users, even if they don't have the Azure CLI - let credentials; - try { - credentials = await acrTools.acquireRegistryLoginCredential(context.subscription, context.registry, context); - } catch (error) { - console.log(error); - } - let username = credentials.username; - let password = credentials.password; - let registry = context.registry.loginServer; + const credentials = await acrTools.loginCredentials(context.registry); + const username = credentials.username; + const password = credentials.password; + const registry = context.registry.loginServer; const terminal = vscode.window.createTerminal("Docker"); terminal.show(); diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index ae7b49f092..af0951eaa3 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -1,58 +1,190 @@ -import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; import { Registry } from 'azure-arm-containerregistry/lib/models'; +import { ResourceGroup } from 'azure-arm-resource/lib/resource/models'; +import { Location, Subscription } from 'azure-arm-resource/lib/subscription/models'; +import * as opn from 'opn'; import * as vscode from "vscode"; +import { skus } from '../../constants' +import { UserCancelledError } from '../../explorer/deploy/wizard'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureImage } from "../../utils/Azure/models/image"; -import { Repository } from "../../utils/Azure/models/Repository"; +import { Repository } from "../../utils/Azure/models/repository"; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; -/** - * function to allow user to pick a desired image for use - * @param repository the repository to look in - * @returns an AzureImage object (see azureUtils.ts) - */ -export async function quickPickACRImage(repository: Repository): Promise { - const repoImages: AzureImage[] = await acrTools.getAzureImages(repository); +export async function quickPickACRImage(repository: Repository, prompt?: string): Promise { + const placeHolder = prompt ? prompt : 'Choose Image to Use'; + const repoImages: AzureImage[] = await acrTools.getImagesByRepository(repository); let imageListNames: string[] = []; for (let tempImage of repoImages) { imageListNames.push(tempImage.tag); } - let desiredImage = await vscode.window.showQuickPick(imageListNames, { 'canPickMany': false, 'placeHolder': 'Choose the image you want to delete' }); + let desiredImage = await vscode.window.showQuickPick(imageListNames, { 'canPickMany': false, 'placeHolder': placeHolder }); if (!desiredImage) { return; } const image = repoImages.find((myImage): boolean => { return desiredImage === myImage.tag }); return image; } -/** - * function to allow user to pick a desired repository for use - * @param registry the registry to choose a repository from - * @returns a Repository object (see azureUtils.ts) - */ -export async function quickPickACRRepository(registry: Registry): Promise { - const myRepos: Repository[] = await acrTools.getAzureRepositories(registry); +export async function quickPickACRRepository(registry: Registry, prompt?: string): Promise { + const placeHolder = prompt ? prompt : 'Choose Registry to Use'; + const myRepos: Repository[] = await acrTools.getRepositoriesByRegistry(registry); let rep: string[] = []; for (let repo of myRepos) { rep.push(repo.name); } - let desiredRepo = await vscode.window.showQuickPick(rep, { 'canPickMany': false, 'placeHolder': 'Choose the repository from which your desired image exists' }); + let desiredRepo = await vscode.window.showQuickPick(rep, { 'canPickMany': false, 'placeHolder': placeHolder }); if (!desiredRepo) { return; } const repository = myRepos.find((currentRepo): boolean => { return desiredRepo === currentRepo.name }); return repository; } -/** - * function to let user choose a registry for use - * @returns a Registry object - */ -export async function quickPickACRRegistry(): Promise { - //first get desired registry +export async function quickPickACRRegistry(canCreateNew: boolean = false, prompt?: string): Promise { + const placeHolder = prompt ? prompt : 'Choose Registry to Use'; let registries = await AzureUtilityManager.getInstance().getRegistries(); - let reg: string[] = []; + const reg: string[] = []; + if (canCreateNew) { reg.push('+ Create new registry'); } for (let registryName of registries) { reg.push(registryName.name); } - let desired = await vscode.window.showQuickPick(reg, { 'canPickMany': false, 'placeHolder': 'Choose the Registry from which your desired image exists' }); - if (!desired) { return; } + let desired: string = await vscode.window.showQuickPick(reg, { + 'canPickMany': false, + 'placeHolder': placeHolder + }); + + if (!desired) { + throw new UserCancelledError(); + } else if (canCreateNew && desired === reg[0]) { + desired = String(await vscode.commands.executeCommand("vscode-docker.create-ACR-Registry")); + registries = await AzureUtilityManager.getInstance().getRegistries(); // Reload + } + const registry = registries.find((currentReg): boolean => { return desired === currentReg.name }); return registry; } + +export async function quickPickSKU(): Promise { + let sku: string; + sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU to use' }); + if (!sku) { throw new UserCancelledError(); } + return sku; +} + +export async function quickPickSubscription(): Promise { + const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); + if (subs.length === 0) { + vscode.window.showErrorMessage("You do not have any subscriptions. You can create one in your Azure Portal", "Open Portal").then(val => { + if (val === "Open Portal") { + opn('https://portal.azure.com/'); + } + }); + throw new Error('User has no azure subscriptions'); + } + + let subsNames: string[] = []; + for (let sub of subs) { + subsNames.push(sub.displayName); + } + let subscriptionName: string; + subscriptionName = await vscode.window.showQuickPick(subsNames, { + 'canPickMany': false, + 'placeHolder': 'Choose a subscription to use' + }); + if (!subscriptionName) { throw new UserCancelledError(); } + + return subs.find(sub => { return sub.displayName === subscriptionName }); +} + +export async function quickPickLocation(subscription: Subscription): Promise { + let locations: Location[] = await AzureUtilityManager.getInstance().getLocationsBySubscription(subscription); + let locationNames: string[] = []; + + for (let loc of locations) { + locationNames.push(loc.displayName); + } + + locationNames.sort((loc1: string, loc2: string): number => { + return loc1.localeCompare(loc2); + }); + + let location: string = await vscode.window.showQuickPick(locationNames, { + 'canPickMany': false, + 'placeHolder': 'Choose a Location to use' + }); + if (!location) { throw new UserCancelledError(); } + return location; +} + +export async function quickPickResourceGroup(canCreateNew?: boolean, subscription?: Subscription): Promise { + let resourceGroups = await AzureUtilityManager.getInstance().getResourceGroups(subscription); + let resourceGroupNames: string[] = []; + + if (canCreateNew) { resourceGroupNames.push('+ Create new resource group'); } + for (let resGroupName of resourceGroups) { + resourceGroupNames.push(resGroupName.name); + } + + let resourceGroupName = await vscode.window.showQuickPick(resourceGroupNames, { + 'canPickMany': false, + 'placeHolder': 'Choose a Resource Group to use' + }); + if (!resourceGroupName) { throw new UserCancelledError(); } + + let resourceGroup: ResourceGroup; + if (canCreateNew && resourceGroupName === '+ Create new Resource Group') { + if (!subscription) { + subscription = await quickPickSubscription(); + } + const loc = await quickPickLocation(subscription); + resourceGroup = await createNewResourceGroup(loc, subscription); + } else { + resourceGroup = resourceGroups.find(resGroup => { return resGroup.name === resourceGroupName; }); + } + return resourceGroup; +} + +/** Requests confirmation for an action and returns true only in the case that the user types in yes + * @param yesOrNoPrompt Should be a yes or no question + */ +export async function confirmUserIntent(yesOrNoPrompt: string): Promise { + //ensure user truly wants to delete image + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: true, + placeHolder: 'No', + value: 'No', + prompt: yesOrNoPrompt + }; + let answer = await vscode.window.showInputBox(opt); + if (!answer) { throw new UserCancelledError(); } + + answer = answer.toLowerCase(); + return answer === 'yes'; +} +/*Creates a new resource group within the current subscription */ +async function createNewResourceGroup(loc: string, subscription?: Subscription): Promise { + const resourceGroupClient = AzureUtilityManager.getInstance().getResourceManagementClient(subscription); + + let opt: vscode.InputBoxOptions = { + ignoreFocusOut: false, + prompt: 'New Resource Group name?' + }; + + let resourceGroupName: string; + let resourceGroupStatus: boolean; + + while (opt.prompt) { + resourceGroupName = await vscode.window.showInputBox(opt); + if (!resourceGroupName) { throw new UserCancelledError(); } + + resourceGroupStatus = await resourceGroupClient.resourceGroups.checkExistence(resourceGroupName); + if (!resourceGroupStatus) { + opt.prompt = undefined; + } else { + opt.prompt = `The Resource Group '${resourceGroupName}' already exists. Try again: `; + } + } + + let newResourceGroup: ResourceGroup = { + name: resourceGroupName, + location: loc, + }; + + return await resourceGroupClient.resourceGroups.createOrUpdate(resourceGroupName, newResourceGroup); +} diff --git a/constants.ts b/constants.ts index d099293e16..bc3b47a639 100644 --- a/constants.ts +++ b/constants.ts @@ -13,3 +13,6 @@ export namespace keytarConstants { //Credentials Constants export const NULL_GUID = '00000000-0000-0000-0000-000000000000'; + +//Azure Container Registries +export const skus = ["Standard", "Basic", "Premium"]; diff --git a/dockerExtension.ts b/dockerExtension.ts index 092ef9d5de..233d495ebe 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -6,10 +6,10 @@ import * as opn from 'opn'; import * as path from 'path'; import * as vscode from 'vscode'; import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExtensionVariables, UserCancelledError } from 'vscode-azureextensionui'; -import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient'; +import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; import { createRegistry } from './commands/azureCommands/create-registry'; -import { deleteAzureImage } from './commands/azureCommands/delete-azure-image'; -import { deleteAzureRegistry } from './commands/azureCommands/delete-azure-registry'; +import { deleteAzureImage } from './commands/azureCommands/delete-image'; +import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; import { deleteRepository } from './commands/azureCommands/delete-repository'; import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; import { buildImage } from './commands/build-image'; @@ -171,7 +171,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.delete-ACR-Registry', deleteAzureRegistry); registerCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); registerCommand('vscode-docker.delete-ACR-Repository', deleteRepository); - registerCommand('vscode-docker.createRegistry', createRegistry); + registerCommand('vscode-docker.create-ACR-Registry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); } diff --git a/explorer/dockerExplorer.ts b/explorer/dockerExplorer.ts index 3c9ecc7ea8..abca40be95 100644 --- a/explorer/dockerExplorer.ts +++ b/explorer/dockerExplorer.ts @@ -35,6 +35,10 @@ export class DockerExplorerProvider implements vscode.TreeDataProvider this._onDidChangeTreeData.fire(this._registriesNode); } + public refreshNode(element: NodeBase): void { + this._onDidChangeTreeData.fire(element); + } + public getTreeItem(element: NodeBase): vscode.TreeItem { return element.getTreeItem(); } diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 35a6b1f79e..14cbbb627d 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -109,6 +109,7 @@ export class AzureRegistryNode extends NodeBase { node.registry = element.registry; node.repository = element.label; node.subscription = element.subscription; + node.parent = element; registryChildNodes.push(node); } } @@ -136,6 +137,7 @@ export class AzureRepositoryNode extends NodeBase { public registry: ContainerModels.Registry; public repository: string; public subscription: SubscriptionModels.Subscription; + public parent: NodeBase; public getTreeItem(): vscode.TreeItem { return { @@ -223,6 +225,7 @@ export class AzureRepositoryNode extends NodeBase { node.registry = element.registry; node.serverUrl = element.repository; node.subscription = element.subscription; + node.parent = element; node.created = moment(new Date(JSON.parse(manifest.history[0].v1Compatibility).created)).fromNow(); imageNodes.push(node); } @@ -252,8 +255,7 @@ export class AzureImageNode extends NodeBase { public registry: ContainerModels.Registry; public serverUrl: string; public subscription: SubscriptionModels.Subscription; - public userName: string; - public repository: string; + public parent: NodeBase; public getTreeItem(): vscode.TreeItem { let displayName: string = this.label; diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 4a8ec679a8..049ed79bb9 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -11,7 +11,6 @@ import { parseError } from 'vscode-azureextensionui'; import { keytarConstants, MAX_CONCURRENT_REQUESTS, MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from '../../constants'; import { AzureAccount } from '../../typings/azure-account.api'; import { AsyncPool } from '../../utils/asyncpool'; -import * as acrTools from '../../utils/Azure/acrTools'; import * as dockerHub from '../utils/dockerHubUtils' import { getCoreNodeModule } from '../utils/utils'; import { AzureLoadingNode, AzureNotSignedInNode, AzureRegistryNode } from './azureRegistryNodes'; diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index eb6a74b21d..191e9beaa2 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -1,7 +1,7 @@ +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import * as opn from 'opn'; import * as vscode from 'vscode'; -import * as ContainerModels from '../../node_modules/azure-arm-containerregistry/lib/models'; import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; diff --git a/explorer/utils/azureUtils.ts b/explorer/utils/azureUtils.ts index 8a55faa55a..c31c1a9aa1 100644 --- a/explorer/utils/azureUtils.ts +++ b/explorer/utils/azureUtils.ts @@ -15,15 +15,3 @@ export function browseAzurePortal(context?: AzureRegistryNode | AzureRepositoryN } } - -export function openAzurePortal(): void { - - /* - let url: string = `${session.environment.portalUrl}/${tenantId}/#resource${context.registry.id}`; - if (context.contextValue === 'azureImageNode' || context.contextValue === 'azureRepositoryNode') { - url = `${url}/repository`; - } - opn(url); - }*/ - -} diff --git a/package.json b/package.json index fadb0b64a1..166f7d51bf 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "onCommand:vscode-docker.compose.restart", "onCommand:vscode-docker.configure", "onCommand:vscode-docker.createWebApp", - "onCommand:vscode-docker.createRegistry", + "onCommand:vscode-docker.create-ACR-Registry", "onCommand:vscode-docker.system.prune", "onCommand:vscode-docker.dockerHubLogout", "onCommand:vscode-docker.browseDockerHub", @@ -193,7 +193,7 @@ "when": "view == dockerExplorer && viewItem =~ /^(azureImageNode|dockerHubImageTag)$/" }, { - "command": "vscode-docker.createRegistry", + "command": "vscode-docker.create-ACR-Registry", "when": "view == dockerExplorer && viewItem == azureRegistryRootNode" }, { @@ -521,7 +521,7 @@ "category": "Docker" }, { - "command": "vscode-docker.createRegistry", + "command": "vscode-docker.create-ACR-Registry", "title": "Create Registry", "category": "Docker" }, diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index e17e8a31c2..bbc404f412 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -1,243 +1,86 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; import request = require('request-promise'); -import * as vscode from "vscode"; -import { AzureImageNode, AzureRepositoryNode } from '../../explorer/models/AzureRegistryNodes'; -import { AzureAccount, AzureSession } from "../../typings/azure-account.api"; +import { NULL_GUID } from "../../constants"; +import { AzureSession } from "../../typings/azure-account.api"; import { AzureUtilityManager } from '../azureUtilityManager'; import { AzureImage } from "./models/image"; import { Repository } from "./models/repository"; +//General helpers /** - * Developers can use this to visualize and list repositories on a given Registry. This is not a command, just a developer tool. - * @param registry : the registry whose repositories you want to see - * @returns allRepos : an array of Repository objects that exist within the given registry + * @param registry gets the subscription for a given registry + * @returns a subscription object */ -export async function getAzureRepositories(registry: Registry): Promise { - const allRepos: Repository[] = []; - let repo: Repository; - let azureAccount: AzureAccount = AzureUtilityManager.getInstance().getAccount(); - if (!azureAccount) { - return []; - } - const { accessToken, refreshToken } = await getRegistryTokens(registry); - if (accessToken && refreshToken) { - - await request.get('https://' + registry.loginServer + '/v2/_catalog', { - auth: { - bearer: accessToken - } - }, (err, httpResponse, body) => { - if (body.length > 0) { - const repositories = JSON.parse(body).repositories; - for (let tempRepo of repositories) { - repo = new Repository(registry, tempRepo, accessToken, refreshToken); - allRepos.push(repo); - } - } - }); - } - //Note these are ordered by default in alphabetical order - return allRepos; +export function getSubscriptionFromRegistry(registry: Registry): SubscriptionModels.Subscription { + let subscriptionId = registry.id.slice('/subscriptions/'.length, registry.id.search('/resourceGroups/')); + const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); + let subscription = subs.find((sub): boolean => { + return sub.subscriptionId === subscriptionId; + }); + return subscription; } - -/** - * @param registry gets the registry - * @returns a string, the resource group name - */ export function getResourceGroupName(registry: Registry): any { return registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); } -/** - * @param registry : the registry to get credentials for - * @returns : the updated refresh and access tokens which can be used to generate a header for an API call - */ -export async function getRegistryTokens(registry: Registry): Promise<{ refreshToken: any, accessToken: any }> { - const subscription = getRegistrySubscription(registry); - const tenantId: string = subscription.tenantId; - let azureAccount: AzureAccount = AzureUtilityManager.getInstance().getAccount(); - - const session: AzureSession = azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase()); - const { accessToken } = await acquireARMToken(session); - - //regenerates in case they have expired - if (accessToken) { - let refreshTokenACR; - let accessTokenACR; - - await request.post('https://' + registry.loginServer + '/oauth2/exchange', { - form: { - grant_type: 'access_token', - service: registry.loginServer, - tenant: tenantId, - access_token: accessToken - } - }, (err, httpResponse, body) => { - if (body.length > 0) { - refreshTokenACR = JSON.parse(body).refresh_token; - } else { - return; - } - }); - - await request.post('https://' + registry.loginServer + '/oauth2/token', { - form: { - grant_type: 'refresh_token', - service: registry.loginServer, - scope: 'registry:catalog:*', - refresh_token: refreshTokenACR - } - }, (err, httpResponse, body) => { - if (body.length > 0) { - accessTokenACR = JSON.parse(body).access_token; - } else { - return; - } - }); - if (refreshTokenACR && accessTokenACR) { - return { 'refreshToken': refreshTokenACR, 'accessToken': accessTokenACR }; +//Registry item management +/** List images under a specific Repository */ +export async function getImagesByRepository(element: Repository): Promise { + let allImages: AzureImage[] = []; + let image: AzureImage; + let tags: string[]; + const { acrAccessToken } = await acquireACRAccessTokenFromRegistry(element.registry, 'repository:' + element.name + ':pull'); + await request.get('https://' + element.registry.loginServer + '/v2/' + element.name + '/tags/list', { + auth: { + bearer: acrAccessToken + } + }, (err, httpResponse, body) => { + if (err) { throw (err) } + if (body.length > 0) { + tags = JSON.parse(body).tags; } - } - vscode.window.showErrorMessage('Could not generate tokens'); -} - -export async function acquireARMToken(localSession: AzureSession): Promise<{ accessToken: string; }> { - return new Promise<{ accessToken: string; }>((resolve, reject) => { - const credentials: any = localSession.credentials; - const environment: any = localSession.environment; - // tslint:disable-next-line:no-function-expression // Grandfathered in - credentials.context.acquireToken(environment.activeDirectoryResourceId, credentials.username, credentials.clientId, function (err: any, result: { accessToken: string; }): void { - if (err) { - reject(err); - } else { - resolve({ - accessToken: result.accessToken - }); - } - }); }); -} -/** Function used to create header for http request to acr */ -export function getAuthorizationHeader(username: string, password: string): string { - let auth; - if (username === '00000000-0000-0000-0000-000000000000') { - auth = 'Bearer ' + password; - } else { - auth = ('Basic ' + (encode(username + ':' + password).trim())); + for (let tag of tags) { + image = new AzureImage(element, tag); + allImages.push(image); } - return auth; -} - -/** - * First encodes to base 64, and then to latin1. See online documentation to see typescript encoding capabilities - * see https://nodejs.org/api/buffer.html#buffer_buf_tostring_encoding_start_end for details {Buffers and Character Encodings} - * current character encodings include: ascii, utf8, utf16le, ucs2, base64, latin1, binary, hex. Version v6.4.0 - * @param str : the string to encode for api URL purposes - */ -export function encode(str: string): string { - let bufferB64 = new Buffer(str); - let bufferLat1 = new Buffer(bufferB64.toString('base64')); - return bufferLat1.toString('latin1'); + return allImages; } - -/** - * Lots of https requests but they must be separate from getTokens because the forms are different - * @param element the repository where the desired images are - * @returns a list of AzureImage objects from the given repository (see azureUtils.ts) - */ -export async function getAzureImages(element: Repository): Promise { - let allImages: AzureImage[] = []; - let image: AzureImage; - let tags; - let azureAccount: AzureAccount = AzureUtilityManager.getInstance().getAccount(); - let tenantId: string = element.subscription.tenantId; - let refreshTokenACR; - let accessTokenACR; - const session: AzureSession = azureAccount.sessions.find((s, i, array) => s.tenantId.toLowerCase() === tenantId.toLowerCase()); - const { accessToken } = await acquireARMToken(session); - if (accessToken) { - await request.post('https://' + element.registry.loginServer + '/oauth2/exchange', { - form: { - grant_type: 'access_token', - service: element.registry.loginServer, - tenant: tenantId, - access_token: accessToken - } - }, (err, httpResponse, body) => { - if (body.length > 0) { - refreshTokenACR = JSON.parse(body).refresh_token; - } else { - return []; - } - }); - - await request.post('https://' + element.registry.loginServer + '/oauth2/token', { - form: { - grant_type: 'refresh_token', - service: element.registry.loginServer, - scope: 'repository:' + element.name + ':pull', - refresh_token: refreshTokenACR - } - }, (err, httpResponse, body) => { - if (body.length > 0) { - accessTokenACR = JSON.parse(body).access_token; - } else { - return []; - } - }); - - await request.get('https://' + element.registry.loginServer + '/v2/' + element.name + '/tags/list', { - auth: { - bearer: accessTokenACR - } - }, (err, httpResponse, body) => { - if (err) { return []; } - - if (body.length > 0) { - tags = JSON.parse(body).tags; +/** List repositories on a given Registry. */ +export async function getRepositoriesByRegistry(registry: Registry): Promise { + const allRepos: Repository[] = []; + let repo: Repository; + const { acrRefreshToken, acrAccessToken } = await acquireACRAccessTokenFromRegistry(registry, "registry:catalog:*"); + await request.get('https://' + registry.loginServer + '/v2/_catalog', { + auth: { + bearer: acrAccessToken + } + }, (err, httpResponse, body) => { + if (body.length > 0) { + const repositories = JSON.parse(body).repositories; + for (let tempRepo of repositories) { + repo = new Repository(registry, tempRepo, acrAccessToken, acrRefreshToken); + allRepos.push(repo); } - }); - - for (let tag of tags) { - image = new AzureImage(element, tag); - allImages.push(image); } - } - return allImages; -} - -/** Acquires login credentials for a registry in the form of refresh tokens and NULL_GUID - * @param subscription : the subscription the registry is on - * @param registry : the registry to get login credentials for - * @param context : if command is invoked through a right click on an AzureRepositoryNode. This context has a password and username - */ - -export async function acquireRegistryLoginCredential(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - let creds = await getRegistryTokens(registry); - let password = creds.refreshToken; - let username = '00000000-0000-0000-0000-000000000000'; - return { password, username }; -} + }); -export async function acquireRegistryAccessToken(subscription: SubscriptionModels.Subscription, registry: Registry, context?: AzureImageNode | AzureRepositoryNode): Promise<{ password: string, username: string }> { - let creds = await getRegistryTokens(registry); - let password = creds.accessToken; - let username = '00000000-0000-0000-0000-000000000000'; - return { password, username }; + //Note these are ordered by default in alphabetical order + return allRepos; } - -/** +/** Sends a custon html request to a registry * @param http_method : the http method, this function currently only uses delete * @param login_server: the login server of the registry * @param path : the URL path * @param username : registry username, can be in generic form of 0's, used to generate authorization header * @param password : registry password, can be in form of accessToken, used to generate authorization header */ -export async function sendRequestToRegistry(http_method: string, login_server: string, path: string, username: string, password: string): Promise { +export async function sendRequestToRegistry(http_method: string, login_server: string, path: string, bearerAccessToken: string): Promise { let url: string = `https://${login_server}${path}`; - let header = getAuthorizationHeader(username, password); + let header = 'Bearer ' + bearerAccessToken; let opt = { headers: { 'Authorization': header }, http_method: http_method, @@ -245,19 +88,72 @@ export async function sendRequestToRegistry(http_method: string, login_server: s } if (http_method === 'delete') { await request.delete(opt); - vscode.window.showInformationMessage('Successfully deleted item'); } } -/** - * @param registry gets the subscription for a given registry - * @returns a subscription object +//Credential management +/** Obtains registry username and password compatible with docker login */ +export async function loginCredentials(registry: Registry): Promise<{ password: string, username: string }> { + const subscription: Subscription = getSubscriptionFromRegistry(registry); + const session: AzureSession = AzureUtilityManager.getInstance().getSession(subscription) + const { aadAccessToken, aadRefreshToken } = await acquireAADTokens(session); + const acrRefreshToken = await acquireACRRefreshToken(registry.loginServer, session.tenantId, aadRefreshToken, aadAccessToken); + return { 'password': acrRefreshToken, 'username': NULL_GUID }; +} +/** Obtains tokens for using the Docker Registry v2 Api + * @param registry The targeted Azure Container Registry + * @param scope String determining the scope of the access token + * @returns acrRefreshToken: For use as a Password for docker registry access , acrAccessToken: For use with docker API */ -export function getRegistrySubscription(registry: Registry): SubscriptionModels.Subscription { - let subscriptionId = registry.id.slice('/subscriptions/'.length, registry.id.search('/resourceGroups/')); - const subs = AzureUtilityManager.getInstance().getFilteredSubscriptionList(); - let subscription = subs.find((sub): boolean => { - return sub.subscriptionId === subscriptionId; +export async function acquireACRAccessTokenFromRegistry(registry: Registry, scope: string): Promise<{ acrRefreshToken: string, acrAccessToken: string }> { + const subscription: Subscription = getSubscriptionFromRegistry(registry); + const session: AzureSession = AzureUtilityManager.getInstance().getSession(subscription); + const { aadAccessToken, aadRefreshToken } = await acquireAADTokens(session); + const acrRefreshToken = await acquireACRRefreshToken(registry.loginServer, session.tenantId, aadRefreshToken, aadAccessToken); + const acrAccessToken = await acquireACRAccessToken(registry.loginServer, scope, acrRefreshToken) + return { acrRefreshToken, acrAccessToken }; +} +/** Obtains refresh and access tokens for Azure Active Directory. */ +export async function acquireAADTokens(session: AzureSession): Promise<{ aadAccessToken: string, aadRefreshToken: string }> { + return new Promise<{ aadAccessToken: string, aadRefreshToken: string }>((resolve, reject) => { + const credentials: any = session.credentials; + const environment: any = session.environment; + credentials.context.acquireToken(environment.activeDirectoryResourceId, credentials.username, credentials.clientId, (err: any, result: any) => { + if (err) { + reject(err); + } else { + resolve({ + aadAccessToken: result.accessToken, + aadRefreshToken: result.refreshToken, + }); + } + }); }); - return subscription; +} +/** Obtains refresh tokens for Azure Container Registry. */ +export async function acquireACRRefreshToken(registryUrl: string, tenantId: string, aadRefreshToken: string, aadAccessToken: string): Promise { + const acrRefreshTokenResponse = await request.post(`https://${registryUrl}/oauth2/exchange`, { + form: { + grant_type: "refresh_token", + service: registryUrl, + tenant: tenantId, + refresh_token: aadRefreshToken, + access_token: aadAccessToken, + }, + }); + + return JSON.parse(acrRefreshTokenResponse).refresh_token; + +} +/** Gets an ACR accessToken by using an acrRefreshToken */ +export async function acquireACRAccessToken(registryUrl: string, scope: string, acrRefreshToken: string): Promise { + const acrAccessTokenResponse = await request.post(`https://${registryUrl}/oauth2/token`, { + form: { + grant_type: "refresh_token", + service: registryUrl, + scope, + refresh_token: acrRefreshToken, + }, + }); + return JSON.parse(acrAccessTokenResponse).access_token; } diff --git a/utils/Azure/models/image.ts b/utils/Azure/models/image.ts index fd1617bf08..40f6c3f8b3 100644 --- a/utils/Azure/models/image.ts +++ b/utils/Azure/models/image.ts @@ -1,20 +1,14 @@ import { Registry } from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; -import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; import { Repository } from './repository'; -/** - * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository - * accessToken can be used like a password, and the username can be '00000000-0000-0000-0000-000000000000' - */ +/** Class Azure Image: Used locally, Organizes data for managing images */ export class AzureImage { public registry: Registry; public repository: Repository; public tag: string; public subscription: SubscriptionModels.Subscription; public resourceGroupName: string; - public accessToken?: string; - public refreshToken?: string; public password?: string; public username?: string; @@ -24,8 +18,6 @@ export class AzureImage { this.tag = tag; this.subscription = repository.subscription; this.resourceGroupName = repository.resourceGroupName; - if (repository.accessToken) { this.accessToken = repository.accessToken; } - if (repository.refreshToken) { this.refreshToken = repository.refreshToken; } if (repository.password) { this.password = repository.password; } if (repository.username) { this.username = repository.username; } } diff --git a/utils/Azure/models/repository.ts b/utils/Azure/models/repository.ts index 16b541d36a..bbdda80206 100644 --- a/utils/Azure/models/repository.ts +++ b/utils/Azure/models/repository.ts @@ -1,35 +1,22 @@ import { Registry } from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; -import { AzureAccount, AzureSession } from '../../../typings/azure-account.api'; -import * as acrTools from '../../../utils/Azure/acrTools'; -/** - * class Repository: used locally as of August 2018, primarily for functions within azureUtils.ts and new commands such as delete Repository - * accessToken can be used like a password, and the username can be '00000000-0000-0000-0000-000000000000' - */ +import * as acrTools from '../acrTools'; + +/** Class Azure Repository: Used locally, Organizes data for managing Repositories */ export class Repository { public registry: Registry; public name: string; public subscription: SubscriptionModels.Subscription; public resourceGroupName: string; - public accessToken?: string; - public refreshToken?: string; public password?: string; public username?: string; - constructor(registry: Registry, repository: string, accessToken?: string, refreshToken?: string, password?: string, username?: string) { + constructor(registry: Registry, repository: string, password?: string, username?: string) { this.registry = registry; this.resourceGroupName = acrTools.getResourceGroupName(registry); - this.subscription = acrTools.getRegistrySubscription(registry); + this.subscription = acrTools.getSubscriptionFromRegistry(registry); this.name = repository; - if (accessToken) { this.accessToken = accessToken; } - if (refreshToken) { this.refreshToken = refreshToken; } if (password) { this.password = password; } if (username) { this.username = username; } } - - public async setTokens(registry: Registry): Promise { - let tokens = await acrTools.getRegistryTokens(registry); - this.accessToken = tokens.accessToken; - this.refreshToken = tokens.refreshToken; - } } diff --git a/utils/azureUtilityManager.ts b/utils/azureUtilityManager.ts index fd4abae68c..48e65b939a 100644 --- a/utils/azureUtilityManager.ts +++ b/utils/azureUtilityManager.ts @@ -39,6 +39,12 @@ export class AzureUtilityManager { throw new Error('Azure account is not present, you may have forgotten to call setAccount'); } + public getSession(subscription: SubscriptionModels.Subscription): AzureSession { + const tenantId: string = subscription.tenantId; + const azureAccount: AzureAccount = this.getAccount(); + return azureAccount.sessions.find((s) => s.tenantId.toLowerCase() === tenantId.toLowerCase()); + } + public getFilteredSubscriptionList(): SubscriptionModels.Subscription[] { return this.getAccount().filters.map(filter => { return { From 1ee4f107badb2202f25d7a8b50ac21ca54b552b1 Mon Sep 17 00:00:00 2001 From: jvstokes <40584422+jvstokes@users.noreply.github.com> Date: Sun, 19 Aug 2018 16:01:53 -0700 Subject: [PATCH 45/77] Jackson/quick build dev (#46) * added acrbuild stuff * added acrbuild stuff * Update to match utility manager class * Added quick pick for selecting resource group and registry * clean * Added subscription support * utility bug fix * added folder select * Updates and fixes * Refactoring, prod. * Flexible OSType --- commands/acr-build.ts | 114 ++++++++++++++++++++++++++ dockerExtension.ts | 4 +- explorer/models/azureRegistryNodes.ts | 2 +- explorer/models/taskNode.ts | 1 - package.json | 17 ++++ utils/Azure/acrTools.ts | 11 +++ 6 files changed, 146 insertions(+), 3 deletions(-) create mode 100644 commands/acr-build.ts diff --git a/commands/acr-build.ts b/commands/acr-build.ts new file mode 100644 index 0000000000..3d9859beaa --- /dev/null +++ b/commands/acr-build.ts @@ -0,0 +1,114 @@ +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; +import { Registry } from 'azure-arm-containerregistry/lib/models'; +import { ResourceManagementClient } from 'azure-arm-resource'; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as fs from 'fs'; +import * as os from 'os'; +import * as tar from 'tar'; +import * as url from 'url'; +import * as vscode from "vscode"; +import { getBlobInfo } from "../utils/Azure/acrTools"; +import { AzureUtilityManager } from "../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickResourceGroup, quickPickSubscription } from './utils/quick-pick-azure'; +const idPrecision = 6; +let status = vscode.window.createOutputChannel('status'); + +// Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. +// The user is then asked to name & tag the image. A build is queued for the image in the selected registry. +// Selected source code must contain a path to the desired dockerfile. +export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { + status.show(); + status.appendLine("Obtaining Subscription and Client"); + let subscription = await quickPickSubscription(); + let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); + + let resourceGroup = await quickPickResourceGroup(false, subscription); + let resourceGroupName = resourceGroup.name; + + let registry: Registry = await quickPickACRRegistry(false); + let registryName = registry.name; + + let folder: vscode.WorkspaceFolder; + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { + folder = vscode.workspace.workspaceFolders[0]; + } else { + folder = await (vscode).window.showWorkspaceFolderPick(); + } + let sourceLocation: string = folder.uri.path; + + let relativeDockerPath = 'Dockerfile'; + if (dockerFileUri.path.indexOf(sourceLocation) !== 0) { + //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. + throw new Error("Source code path must be a parent of the Dockerfile path"); + } else { + relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length); + } + + // Prompting for name so the image can then be pushed to a repository. + const opt: vscode.InputBoxOptions = { + prompt: 'Image name and tag in format :', + }; + const name: string = await vscode.window.showInputBox(opt); + + let tarFilePath = getTempSourceArchivePath(); + + status.appendLine("Uploading Source Code to " + tarFilePath); + sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); + + let osType = os.type() + if (osType === 'Windows_NT') { + osType = 'Windows' + } + + status.appendLine("Setting up Build Request"); + let buildRequest: QuickBuildRequest = { + 'type': 'QuickBuild', + 'imageNames': [name], + 'isPushEnabled': true, + 'sourceLocation': sourceLocation, + 'platform': { 'osType': osType }, + 'dockerFilePath': relativeDockerPath + }; + status.appendLine("Queueing Build"); + await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); + status.appendLine('Success'); +} + +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { + status.appendLine(" Sending source code to temp file"); + tar.c( + { + gzip: true + }, + [sourceLocation] + ).pipe(fs.createWriteStream(tarFilePath)); + + status.appendLine(" Getting Build Source Upload Url "); + let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + let upload_url = sourceUploadLocation.uploadUrl; + let relative_path = sourceUploadLocation.relativePath; + + status.appendLine(" Getting blob info from Upload Url "); + // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. + let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); + + status.appendLine(" Creating Blob Service "); + let blob: BlobService = createBlobServiceWithSas(host, sasToken); + + status.appendLine(" Creating Block Blob "); + + blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + + status.appendLine(" Success "); + return relative_path; +} + +function getTempSourceArchivePath(): string { + /* tslint:disable-next-line:insecure-random */ + let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); + status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + return tarFilePath; +} diff --git a/dockerExtension.ts b/dockerExtension.ts index 233d495ebe..6e30574dcc 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -7,6 +7,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExtensionVariables, UserCancelledError } from 'vscode-azureextensionui'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; +import { queueBuild } from './commands/acr-build'; import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; @@ -120,6 +121,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.image.remove', removeImage); registerCommand('vscode-docker.image.push', pushImage); registerCommand('vscode-docker.image.tag', tagImage); + registerCommand('vscode-docker.queueBuild', queueBuild); registerCommand('vscode-docker.container.start', startContainer); registerCommand('vscode-docker.container.start.interactive', startContainerInteractive); registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); @@ -134,7 +136,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.system.prune', systemPrune); registerCommand('vscode-docker.deleteAzureImage', deleteAzureImage); registerCommand('vscode-docker.pullFromAzure', pullFromAzure); - registerCommand('vscode-docker.createWebApp', async (context?: AzureImageNode | DockerHubImageNode) => { if (context) { if (azureAccount) { @@ -173,6 +174,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.delete-ACR-Repository', deleteRepository); registerCommand('vscode-docker.create-ACR-Registry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); + } activateLanguageClient(ctx); diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 14cbbb627d..c38d997c7b 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -208,7 +208,7 @@ export class AzureRepositoryNode extends NodeBase { pool.addTask(async () => { let data: string; try { - data = await request.get('https://' + element.repository + '/v2/' + element.label + `/manifests/${tags}`, { + data = await request.get('https://' + element.repository + '/v2/' + element.label + `/manifests/${tag}`, { auth: { bearer: accessTokenARC } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 191e9beaa2..0faca22a3b 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -38,7 +38,6 @@ export class TaskRootNode extends NodeBase { const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); const resourceGroup: string = acrTools.getResourceGroupName(element.registry); - buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); if (buildTasks.length === 0) { vscode.window.showInformationMessage(`You do not have any Build Tasks in the registry, '${element.registry.name}'. You can create one with ACR Build. `, "Learn More").then(val => { diff --git a/package.json b/package.json index 166f7d51bf..d02ed5a126 100644 --- a/package.json +++ b/package.json @@ -49,6 +49,7 @@ "onCommand:vscode-docker.create-ACR-Registry", "onCommand:vscode-docker.system.prune", "onCommand:vscode-docker.dockerHubLogout", + "onCommand:vscode-docker.queuebuild", "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", @@ -78,6 +79,11 @@ "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "editorLangId == dockerfile", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename == docker-compose.yml", "command": "vscode-docker.compose.up", @@ -115,6 +121,11 @@ "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "resourceFilename =~ /[dD]ocker[fF]ile/", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", "command": "vscode-docker.compose.up", @@ -593,6 +604,11 @@ "command": "vscode-docker.delete-ACR-Image", "title": "Delete Azure Image", "category": "Docker" + }, + { + "command": "vscode-docker.queueBuild", + "title": "Lightweight Build", + "category": "Docker" } ], "views": { @@ -662,6 +678,7 @@ "opn": "^5.1.0", "pom-parser": "^1.1.1", "request-promise": "^4.2.2", + "tar": "^4.4.6", "vscode-azureextensionui": "^0.16.6", "vscode-extension-telemetry": "0.0.18", "vscode-languageclient": "^4.4.0" diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index bbc404f412..31ff278a4f 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -157,3 +157,14 @@ export async function acquireACRAccessToken(registryUrl: string, scope: string, }); return JSON.parse(acrAccessTokenResponse).access_token; } + +export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { + let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); + let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); + let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); + let containerName: string = items[1]; + let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); + let sasToken: string = items[4].slice(items[4].search('[?]') + 1); + let host: string = accountName + '.blob.' + endpointSuffix; + return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; +} From 0a4dcb29cd303e2cb0c6dbd9c5e6e4011988420a Mon Sep 17 00:00:00 2001 From: jvstokes Date: Mon, 20 Aug 2018 10:42:34 -0700 Subject: [PATCH 46/77] added ID --- commands/acr-build.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/commands/acr-build.ts b/commands/acr-build.ts index 3d9859beaa..cbb064a62e 100644 --- a/commands/acr-build.ts +++ b/commands/acr-build.ts @@ -43,7 +43,8 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. throw new Error("Source code path must be a parent of the Dockerfile path"); } else { - relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length); + relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length + 1); + console.log(relativeDockerPath); } // Prompting for name so the image can then be pushed to a repository. @@ -72,8 +73,10 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { 'dockerFilePath': relativeDockerPath }; status.appendLine("Queueing Build"); - await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); - status.appendLine('Success'); + let queuedBuild = await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); + let buildId = queuedBuild.buildId + + status.appendLine('Successfully queued build with ID ' + buildId); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { From c280b12257cf0b6eb624a07a683d74da497ac871 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Thu, 23 Aug 2018 17:05:24 -0700 Subject: [PATCH 47/77] Move build to azure commands --- commands/acr-build.ts | 117 ----------------- commands/azureCommands/acr-build.ts | 197 ++++++++++++++++++++++++++++ 2 files changed, 197 insertions(+), 117 deletions(-) delete mode 100644 commands/acr-build.ts create mode 100644 commands/azureCommands/acr-build.ts diff --git a/commands/acr-build.ts b/commands/acr-build.ts deleted file mode 100644 index cbb064a62e..0000000000 --- a/commands/acr-build.ts +++ /dev/null @@ -1,117 +0,0 @@ -import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; -import { Registry } from 'azure-arm-containerregistry/lib/models'; -import { ResourceManagementClient } from 'azure-arm-resource'; -import { BlobService, createBlobServiceWithSas } from "azure-storage"; -import * as fs from 'fs'; -import * as os from 'os'; -import * as tar from 'tar'; -import * as url from 'url'; -import * as vscode from "vscode"; -import { getBlobInfo } from "../utils/Azure/acrTools"; -import { AzureUtilityManager } from "../utils/azureUtilityManager"; -import { quickPickACRRegistry, quickPickResourceGroup, quickPickSubscription } from './utils/quick-pick-azure'; -const idPrecision = 6; -let status = vscode.window.createOutputChannel('status'); - -// Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. -// The user is then asked to name & tag the image. A build is queued for the image in the selected registry. -// Selected source code must contain a path to the desired dockerfile. -export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { - status.show(); - status.appendLine("Obtaining Subscription and Client"); - let subscription = await quickPickSubscription(); - let client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); - const resourceGroupClient = new ResourceManagementClient(AzureUtilityManager.getInstance().getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId); - - let resourceGroup = await quickPickResourceGroup(false, subscription); - let resourceGroupName = resourceGroup.name; - - let registry: Registry = await quickPickACRRegistry(false); - let registryName = registry.name; - - let folder: vscode.WorkspaceFolder; - if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { - folder = vscode.workspace.workspaceFolders[0]; - } else { - folder = await (vscode).window.showWorkspaceFolderPick(); - } - let sourceLocation: string = folder.uri.path; - - let relativeDockerPath = 'Dockerfile'; - if (dockerFileUri.path.indexOf(sourceLocation) !== 0) { - //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. - throw new Error("Source code path must be a parent of the Dockerfile path"); - } else { - relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length + 1); - console.log(relativeDockerPath); - } - - // Prompting for name so the image can then be pushed to a repository. - const opt: vscode.InputBoxOptions = { - prompt: 'Image name and tag in format :', - }; - const name: string = await vscode.window.showInputBox(opt); - - let tarFilePath = getTempSourceArchivePath(); - - status.appendLine("Uploading Source Code to " + tarFilePath); - sourceLocation = await uploadSourceCode(client, registryName, resourceGroupName, sourceLocation, tarFilePath); - - let osType = os.type() - if (osType === 'Windows_NT') { - osType = 'Windows' - } - - status.appendLine("Setting up Build Request"); - let buildRequest: QuickBuildRequest = { - 'type': 'QuickBuild', - 'imageNames': [name], - 'isPushEnabled': true, - 'sourceLocation': sourceLocation, - 'platform': { 'osType': osType }, - 'dockerFilePath': relativeDockerPath - }; - status.appendLine("Queueing Build"); - let queuedBuild = await client.registries.queueBuild(resourceGroupName, registryName, buildRequest); - let buildId = queuedBuild.buildId - - status.appendLine('Successfully queued build with ID ' + buildId); -} - -async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string): Promise { - status.appendLine(" Sending source code to temp file"); - tar.c( - { - gzip: true - }, - [sourceLocation] - ).pipe(fs.createWriteStream(tarFilePath)); - - status.appendLine(" Getting Build Source Upload Url "); - let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); - let upload_url = sourceUploadLocation.uploadUrl; - let relative_path = sourceUploadLocation.relativePath; - - status.appendLine(" Getting blob info from Upload Url "); - // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. - let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); - - status.appendLine(" Creating Blob Service "); - let blob: BlobService = createBlobServiceWithSas(host, sasToken); - - status.appendLine(" Creating Block Blob "); - - blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); - - status.appendLine(" Success "); - return relative_path; -} - -function getTempSourceArchivePath(): string { - /* tslint:disable-next-line:insecure-random */ - let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); - status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); - let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); - return tarFilePath; -} diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts new file mode 100644 index 0000000000..982bda9a62 --- /dev/null +++ b/commands/azureCommands/acr-build.ts @@ -0,0 +1,197 @@ +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { BuildGetLogResult, QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; +import { Build, Registry } from 'azure-arm-containerregistry/lib/models'; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as fs from 'fs'; +import * as os from 'os'; +import * as path from 'path'; +import * as process from 'process'; +import { Readable, Writable } from 'stream'; +import * as tar from 'tar'; +import * as url from 'url'; +import * as vscode from "vscode"; +import { ResourceGroup } from '../../node_modules/azure-arm-resource/lib/resource/models'; +import { getBlobInfo, getResourceGroupName } from "../../utils/Azure/acrTools"; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickResourceGroup, quickPickSubscription } from '../utils/quick-pick-azure'; +const idPrecision = 6; +const status = vscode.window.createOutputChannel('status'); +const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; +// Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. +// The user is then asked to name & tag the image. A build is queued for the image in the selected registry. +// Selected source code must contain a path to the desired dockerfile. +export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { + status.show(); + status.appendLine("Obtaining Subscription and initializing management client"); + const subscription = await quickPickSubscription(); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + const registry: Registry = await quickPickACRRegistry(true); + status.appendLine("Selected registry: " + registry.name); + + const resourceGroupName = getResourceGroupName(registry); + let folder: vscode.WorkspaceFolder; + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { + folder = vscode.workspace.workspaceFolders[0]; + } else { + folder = await (vscode).window.showWorkspaceFolderPick(); + } + let sourceLocation: string = folder.uri.path; + let relativeDockerPath = 'Dockerfile'; + if (dockerFileUri.path.indexOf(sourceLocation) !== 0) { + //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. + throw new Error("Source code path must be a parent of the Dockerfile path"); + } else { + relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length + 1); + } + + // Prompting for name so the image can then be pushed to a repository. + const opt: vscode.InputBoxOptions = { + prompt: 'Image name and tag in format :', + }; + const name: string = await vscode.window.showInputBox(opt); + + let tarFilePath = getTempSourceArchivePath(); + + status.appendLine("Uploading Source Code to " + tarFilePath); + let uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); + + let osType = os.type() + if (osType === 'Windows_NT') { + osType = 'Windows' + } + status.appendLine("Setting up Build Request"); + console.log(dockerFileUri.path.substring(4)); + let buildRequest: QuickBuildRequest = { + 'type': 'QuickBuild', + 'imageNames': [name], + 'isPushEnabled': true, + 'sourceLocation': uploadedSourceLocation, + 'platform': { 'osType': 'Linux' }, + 'dockerFilePath': 'Dockerfile' //relativeDockerPath + }; + status.appendLine("Queueing Build"); + await client.registries.queueBuild(resourceGroupName, registry.name, buildRequest); + status.appendLine('Success'); +} + +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { + status.appendLine(" Sending source code to temp file"); + let source = sourceLocation.substring(1); + process.chdir(source); + await fs.readdir(source, (err, items) => { + items = filter(items); + tar.c( + {}, + items + ).pipe(fs.createWriteStream(tarFilePath)); + process.chdir(current); + }); + let current = process.cwd(); + + status.appendLine(" Getting Build Source Upload Url "); + let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + let upload_url = sourceUploadLocation.uploadUrl; + let relative_path = sourceUploadLocation.relativePath; + + status.appendLine(" Getting blob info from Upload Url "); + // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. + let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); + status.appendLine(" Creating Blob Service "); + let blob: BlobService = createBlobServiceWithSas(host, sasToken); + status.appendLine(" Creating Block Blob "); + blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + return relative_path; +} + +function getTempSourceArchivePath(): string { + /* tslint:disable-next-line:insecure-random */ + let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); + status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + return tarFilePath; +} + +function filter(list: string[]): string[] { + for (let file of list) { + if (vcsIgnoreList.indexOf(file) !== -1) { + list.splice(list.indexOf(file), 1); + } + } + return list; +} + +async function streamLogs(client: ContainerRegistryManagementClient, resourceGroupName: string, registry: Registry, build: Build): Promise { + const temp: BuildGetLogResult = await client.builds.getLogLink(resourceGroupName, registry.name, build.buildId); + const link = temp.logLink; + let blobInfo = getBlobInfo(link); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); + let stream: Readable = new Readable(); + try { + stream = blob.createReadStream(blobInfo.containerName, blobInfo.blobName, (error, response) => { + if (response) { + console.log(response.name + 'has Completed'); + } else { + console.log(error); + } + }); + console.log(stream); + } catch (error) { + console.log('a' + error); + } + stream.on('data', (chunk) => { + status.appendLine(chunk.toString()); + status.show(); + }); + +} + +async function streamLogs2(client: ContainerRegistryManagementClient, resourceGroupName: string, registry: Registry, build: Build): Promise { + const temp: BuildGetLogResult = await client.builds.getLogLink(resourceGroupName, registry.name, build.buildId); + const link = temp.logLink; + let blobInfo = getBlobInfo(link); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); + let stream: Readable = blob.createReadStream(blobInfo.containerName, blobInfo.blobName, (error, response) => { + if (response) { + status.appendLine(response.name + 'has Completed'); + } else { + status.appendLine(error.message); + } + status.show(); + }); + + stream.on('data', (chunk) => { + status.appendLine(chunk.toString()); + console.log(chunk.toString()); + status.show(); + }); + +} + +export async function streamLogs3(client: ContainerRegistryManagementClient, resourceGroupName: string, registry: Registry, build: Build): Promise { + const temp: BuildGetLogResult = await client.builds.getLogLink(resourceGroupName, registry.name, build.buildId); + return new Promise((resolve, reject) => { + const link = temp.logLink; + let blobInfo = getBlobInfo(link); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); + let stream: Readable = new Readable(); + stream = blob.createReadStream(blobInfo.containerName, blobInfo.blobName, (error, response) => { + if (response) { + //.appendLine(chunk.toString()); + status.show(); + } else { + status.appendLine(error.message); + reject(); + } + status.show(); + }); + + stream.on('data', (chunk) => { + status.appendLine(chunk.toString()); + status.show(); + }); + stream.on('finish', () => { + resolve(); + }); + + }); +} From f5f3ef391cd205bca3fc735a415e33cc2134b8a9 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Thu, 23 Aug 2018 17:07:28 -0700 Subject: [PATCH 48/77] cleanup --- commands/azureCommands/acr-build.ts | 88 ++--------------------------- dockerExtension.ts | 3 +- package.json | 2 +- 3 files changed, 7 insertions(+), 86 deletions(-) diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 982bda9a62..2e635d6acd 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -4,19 +4,17 @@ import { Build, Registry } from 'azure-arm-containerregistry/lib/models'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as fs from 'fs'; import * as os from 'os'; -import * as path from 'path'; import * as process from 'process'; -import { Readable, Writable } from 'stream'; import * as tar from 'tar'; import * as url from 'url'; import * as vscode from "vscode"; -import { ResourceGroup } from '../../node_modules/azure-arm-resource/lib/resource/models'; import { getBlobInfo, getResourceGroupName } from "../../utils/Azure/acrTools"; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; -import { quickPickACRRegistry, quickPickResourceGroup, quickPickSubscription } from '../utils/quick-pick-azure'; +import { quickPickACRRegistry, quickPickSubscription } from '../utils/quick-pick-azure'; const idPrecision = 6; const status = vscode.window.createOutputChannel('status'); const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; + // Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. // The user is then asked to name & tag the image. A build is queued for the image in the selected registry. // Selected source code must contain a path to the desired dockerfile. @@ -60,14 +58,14 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { osType = 'Windows' } status.appendLine("Setting up Build Request"); - console.log(dockerFileUri.path.substring(4)); + console.log(relativeDockerPath); let buildRequest: QuickBuildRequest = { 'type': 'QuickBuild', 'imageNames': [name], 'isPushEnabled': true, 'sourceLocation': uploadedSourceLocation, 'platform': { 'osType': 'Linux' }, - 'dockerFilePath': 'Dockerfile' //relativeDockerPath + 'dockerFilePath': 'Dockerfile' }; status.appendLine("Queueing Build"); await client.registries.queueBuild(resourceGroupName, registry.name, buildRequest); @@ -77,6 +75,7 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { status.appendLine(" Sending source code to temp file"); let source = sourceLocation.substring(1); + let current = process.cwd(); process.chdir(source); await fs.readdir(source, (err, items) => { items = filter(items); @@ -86,7 +85,6 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis ).pipe(fs.createWriteStream(tarFilePath)); process.chdir(current); }); - let current = process.cwd(); status.appendLine(" Getting Build Source Upload Url "); let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); @@ -119,79 +117,3 @@ function filter(list: string[]): string[] { } return list; } - -async function streamLogs(client: ContainerRegistryManagementClient, resourceGroupName: string, registry: Registry, build: Build): Promise { - const temp: BuildGetLogResult = await client.builds.getLogLink(resourceGroupName, registry.name, build.buildId); - const link = temp.logLink; - let blobInfo = getBlobInfo(link); - let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); - let stream: Readable = new Readable(); - try { - stream = blob.createReadStream(blobInfo.containerName, blobInfo.blobName, (error, response) => { - if (response) { - console.log(response.name + 'has Completed'); - } else { - console.log(error); - } - }); - console.log(stream); - } catch (error) { - console.log('a' + error); - } - stream.on('data', (chunk) => { - status.appendLine(chunk.toString()); - status.show(); - }); - -} - -async function streamLogs2(client: ContainerRegistryManagementClient, resourceGroupName: string, registry: Registry, build: Build): Promise { - const temp: BuildGetLogResult = await client.builds.getLogLink(resourceGroupName, registry.name, build.buildId); - const link = temp.logLink; - let blobInfo = getBlobInfo(link); - let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); - let stream: Readable = blob.createReadStream(blobInfo.containerName, blobInfo.blobName, (error, response) => { - if (response) { - status.appendLine(response.name + 'has Completed'); - } else { - status.appendLine(error.message); - } - status.show(); - }); - - stream.on('data', (chunk) => { - status.appendLine(chunk.toString()); - console.log(chunk.toString()); - status.show(); - }); - -} - -export async function streamLogs3(client: ContainerRegistryManagementClient, resourceGroupName: string, registry: Registry, build: Build): Promise { - const temp: BuildGetLogResult = await client.builds.getLogLink(resourceGroupName, registry.name, build.buildId); - return new Promise((resolve, reject) => { - const link = temp.logLink; - let blobInfo = getBlobInfo(link); - let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); - let stream: Readable = new Readable(); - stream = blob.createReadStream(blobInfo.containerName, blobInfo.blobName, (error, response) => { - if (response) { - //.appendLine(chunk.toString()); - status.show(); - } else { - status.appendLine(error.message); - reject(); - } - status.show(); - }); - - stream.on('data', (chunk) => { - status.appendLine(chunk.toString()); - status.show(); - }); - stream.on('finish', () => { - resolve(); - }); - - }); -} diff --git a/dockerExtension.ts b/dockerExtension.ts index 96c66c394f..cfb6a0c804 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -122,7 +122,6 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { registerCommand('vscode-docker.image.remove', removeImage); registerCommand('vscode-docker.image.push', pushImage); registerCommand('vscode-docker.image.tag', tagImage); - registerCommand('vscode-docker.queueBuild', queueBuild); registerCommand('vscode-docker.container.start', startContainer); registerCommand('vscode-docker.container.start.interactive', startContainerInteractive); registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); @@ -170,12 +169,12 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider())); if (azureAccount) { + registerCommand('vscode-docker.queueBuild', queueBuild); registerCommand('vscode-docker.delete-ACR-Registry', deleteAzureRegistry); registerCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); registerCommand('vscode-docker.delete-ACR-Repository', deleteRepository); registerCommand('vscode-docker.create-ACR-Registry', createRegistry); AzureUtilityManager.getInstance().setAccount(azureAccount); - } activateLanguageClient(ctx); diff --git a/package.json b/package.json index 0589e9138a..8c3f898e41 100644 --- a/package.json +++ b/package.json @@ -607,7 +607,7 @@ }, { "command": "vscode-docker.queueBuild", - "title": "Lightweight Build", + "title": "Cloud Build", "category": "Docker" } ], From b255dad5d114d30f97e44b8d668368a7862a939a Mon Sep 17 00:00:00 2001 From: jvstokes Date: Thu, 23 Aug 2018 17:15:12 -0700 Subject: [PATCH 49/77] Relative dockerfile support --- commands/azureCommands/acr-build.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 2e635d6acd..181ccf47ff 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -1,6 +1,6 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import { BuildGetLogResult, QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; -import { Build, Registry } from 'azure-arm-containerregistry/lib/models'; +import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; +import { Registry } from 'azure-arm-containerregistry/lib/models'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as fs from 'fs'; import * as os from 'os'; @@ -58,14 +58,13 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { osType = 'Windows' } status.appendLine("Setting up Build Request"); - console.log(relativeDockerPath); let buildRequest: QuickBuildRequest = { 'type': 'QuickBuild', 'imageNames': [name], 'isPushEnabled': true, 'sourceLocation': uploadedSourceLocation, 'platform': { 'osType': 'Linux' }, - 'dockerFilePath': 'Dockerfile' + 'dockerFilePath': relativeDockerPath }; status.appendLine("Queueing Build"); await client.registries.queueBuild(resourceGroupName, registry.name, buildRequest); From 34a27870ab27c1f9fc55f6209f4a6567b828bc95 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Fri, 24 Aug 2018 14:59:57 -0700 Subject: [PATCH 50/77] Removed await, updating list in enumeration --- commands/azureCommands/acr-build.ts | 11 ++++++----- dockerExtension.ts | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 181ccf47ff..8db9922581 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -76,13 +76,13 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis let source = sourceLocation.substring(1); let current = process.cwd(); process.chdir(source); - await fs.readdir(source, (err, items) => { + fs.readdir(source, (err, items) => { + process.chdir(current); items = filter(items); tar.c( {}, items ).pipe(fs.createWriteStream(tarFilePath)); - process.chdir(current); }); status.appendLine(" Getting Build Source Upload Url "); @@ -109,10 +109,11 @@ function getTempSourceArchivePath(): string { } function filter(list: string[]): string[] { + let result = []; for (let file of list) { - if (vcsIgnoreList.indexOf(file) !== -1) { - list.splice(list.indexOf(file), 1); + if (vcsIgnoreList.indexOf(file) === -1) { + result.push(file); } } - return list; + return result; } diff --git a/dockerExtension.ts b/dockerExtension.ts index cfb6a0c804..154e001199 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -8,7 +8,7 @@ import * as path from 'path'; import * as vscode from 'vscode'; import { AzureUserInput, createTelemetryReporter, IActionContext, registerCommand, registerUIExtensionVariables, UserCancelledError } from 'vscode-azureextensionui'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; -import { queueBuild } from './commands/acr-build'; +import { queueBuild } from './commands/azureCommands/acr-build'; import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; From 6a954ebdf1db56e5d4c4c387a3b63b4aac2aa433 Mon Sep 17 00:00:00 2001 From: jvstokes Date: Wed, 29 Aug 2018 15:48:54 -0700 Subject: [PATCH 51/77] fixed chdir --- commands/azureCommands/acr-build.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 8db9922581..55fbd12620 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -1,6 +1,6 @@ -import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; -import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models"; -import { Registry } from 'azure-arm-containerregistry/lib/models'; +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry/lib/containerRegistryManagementClient'; +import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models/quickBuildRequest"; +import { Registry } from 'azure-arm-containerregistry/lib/models/registry'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as fs from 'fs'; import * as os from 'os'; @@ -77,12 +77,12 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis let current = process.cwd(); process.chdir(source); fs.readdir(source, (err, items) => { - process.chdir(current); items = filter(items); tar.c( {}, items ).pipe(fs.createWriteStream(tarFilePath)); + process.chdir(current); }); status.appendLine(" Getting Build Source Upload Url "); From 71f6fa63b5e234cd1ac629fc9289ef2e3e893e9f Mon Sep 17 00:00:00 2001 From: jvstokes Date: Wed, 29 Aug 2018 17:36:50 -0700 Subject: [PATCH 52/77] flexible ostype --- commands/azureCommands/acr-build.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 55fbd12620..0166795515 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -42,6 +42,8 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length + 1); } + let osType: string = await vscode.window.showQuickPick(['Linux', 'Windows'], { 'canPickMany': false, 'placeHolder': 'Linux' }); + // Prompting for name so the image can then be pushed to a repository. const opt: vscode.InputBoxOptions = { prompt: 'Image name and tag in format :', @@ -53,17 +55,13 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { status.appendLine("Uploading Source Code to " + tarFilePath); let uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); - let osType = os.type() - if (osType === 'Windows_NT') { - osType = 'Windows' - } status.appendLine("Setting up Build Request"); let buildRequest: QuickBuildRequest = { 'type': 'QuickBuild', 'imageNames': [name], 'isPushEnabled': true, 'sourceLocation': uploadedSourceLocation, - 'platform': { 'osType': 'Linux' }, + 'platform': { 'osType': osType }, 'dockerFilePath': relativeDockerPath }; status.appendLine("Queueing Build"); From d90ed09e9e1b069185a7d3156637dac95f4ada4a Mon Sep 17 00:00:00 2001 From: rsamai Date: Tue, 4 Sep 2018 16:10:23 -0700 Subject: [PATCH 53/77] Rutusamai/Show Build Task properties (#70) * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show is working- final * removed run build task stuff * cleanup * addressed esteban's comments * buildTask = context.task.name rather than label * fixes from Bin's comments --- commands/azureCommands/pull-from-azure.ts | 4 +- commands/azureCommands/show-buildTask.ts | 43 +++++++ .../task-utils/showTaskManager.ts | 37 ++++++ commands/utils/quick-pick-azure.ts | 11 ++ dockerExtension.ts | 5 + explorer/models/azureRegistryNodes.ts | 2 +- explorer/models/taskNode.ts | 34 ++++-- package.json | 114 +++++++++++------- utils/Azure/acrTools.ts | 18 ++- 9 files changed, 207 insertions(+), 61 deletions(-) create mode 100644 commands/azureCommands/show-buildTask.ts create mode 100644 commands/azureCommands/task-utils/showTaskManager.ts diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 037a615a8d..699871c94c 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -5,8 +5,8 @@ import * as acrTools from '../../utils/Azure/acrTools'; /* Pulls an image from Azure. The context is the image node the user has right clicked on */ export async function pullFromAzure(context?: AzureImageTagNode): Promise { - // Step 1: Using loginCredentials function to get the username and password. This takes care of all users, even if they don't have the Azure CLI - const credentials = await acrTools.loginCredentials(context.registry); + // Step 1: Using getLoginCredentials function to get the username and password. This takes care of all users, even if they don't have the Azure CLI + const credentials = await acrTools.getLoginCredentials(context.registry); const username = credentials.username; const password = credentials.password; const registry = context.registry.loginServer; diff --git a/commands/azureCommands/show-buildTask.ts b/commands/azureCommands/show-buildTask.ts new file mode 100644 index 0000000000..1b5e83ab67 --- /dev/null +++ b/commands/azureCommands/show-buildTask.ts @@ -0,0 +1,43 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import { BuildTaskNode } from "../../explorer/models/taskNode"; +import { ext } from '../../extensionVariables'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickBuildTask, quickPickSubscription } from '../utils/quick-pick-azure'; +import { openTask } from "./task-utils/showTaskManager"; + +export async function showBuildTaskProperties(context?: BuildTaskNode): Promise { + let subscription: Subscription; + let registry: Registry; + let resourceGroup: ResourceGroup; + let buildTask: string; + + if (context) { // Right click + subscription = context.susbscription; + registry = context.registry; + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + buildTask = context.task.name; + } else { // Command palette + subscription = await quickPickSubscription(); + registry = await quickPickACRRegistry(); + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + buildTask = (await quickPickBuildTask(registry, subscription, resourceGroup)).name; + } + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let item: any = await client.buildTasks.get(resourceGroup.name, registry.name, buildTask); + + try { + const steps = await client.buildSteps.get(resourceGroup.name, registry.name, buildTask, `${buildTask}StepName`); + item.properties = steps; + } catch (error) { + ext.outputChannel.append(error); + ext.outputChannel.append("Build Step not available for this image due to update in API"); + } + + let indentation = 1; + let replacer; + openTask(JSON.stringify(item, replacer, indentation), buildTask); +} diff --git a/commands/azureCommands/task-utils/showTaskManager.ts b/commands/azureCommands/task-utils/showTaskManager.ts new file mode 100644 index 0000000000..7a3ec4be58 --- /dev/null +++ b/commands/azureCommands/task-utils/showTaskManager.ts @@ -0,0 +1,37 @@ +import * as vscode from 'vscode'; + +export class TaskContentProvider implements vscode.TextDocumentContentProvider { + public static scheme: string = 'task'; + private onDidChangeEvent: vscode.EventEmitter = new vscode.EventEmitter(); + + constructor() { } + + public provideTextDocumentContent(uri: vscode.Uri): string { + return decodeBase64(JSON.parse(uri.query).content); + } + + get onDidChange(): vscode.Event { + return this.onDidChangeEvent.event; + } + + public update(uri: vscode.Uri, message: string): void { + this.onDidChangeEvent.fire(uri); + } +} + +export function decodeBase64(str: string): string { + return Buffer.from(str, 'base64').toString('utf8'); +} + +export function encodeBase64(str: string): string { + return Buffer.from(str, 'ascii').toString('base64'); +} + +export function openTask(content: string, title: string): void { + const scheme = 'task'; + let query = JSON.stringify({ 'content': encodeBase64(content) }); + let uri: vscode.Uri = vscode.Uri.parse(`${scheme}://authority/${title}.json?${query}#idk`); + vscode.workspace.openTextDocument(uri).then((doc) => { + return vscode.window.showTextDocument(doc, vscode.ViewColumn.Active + 1, true); + }); +} diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index 52de004309..974155aa3e 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Registry } from 'azure-arm-containerregistry/lib/models'; +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceGroup } from 'azure-arm-resource/lib/resource/models'; import { Location, Subscription } from 'azure-arm-resource/lib/subscription/models'; import * as opn from 'opn'; @@ -33,6 +34,16 @@ export async function quickPickACRRepository(registry: Registry, prompt?: string return desiredRepo.data; } +export async function quickPickBuildTask(registry: Registry, subscription: Subscription, resourceGroup: ResourceGroup, prompt?: string): Promise { + const placeHolder = prompt ? prompt : 'Choose a Build Task'; + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let buildTasks: ContainerModels.BuildTask[] = await client.buildTasks.list(resourceGroup.name, registry.name); + const quickpPickBTList = buildTasks.map(buildTask => >{ label: buildTask.name, data: buildTask }); + let desiredBuildTask = await ext.ui.showQuickPick(quickpPickBTList, { 'canPickMany': false, 'placeHolder': placeHolder }); + return desiredBuildTask.data; +} + export async function quickPickACRRegistry(canCreateNew: boolean = false, prompt?: string): Promise { const placeHolder = prompt ? prompt : 'Select registry to use'; let registries = await AzureUtilityManager.getInstance().getRegistries(); diff --git a/dockerExtension.ts b/dockerExtension.ts index 76d0c353a1..e83d086f29 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -15,6 +15,8 @@ import { deleteAzureImage } from './commands/azureCommands/delete-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; import { deleteRepository } from './commands/azureCommands/delete-repository'; import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; +import { showBuildTaskProperties } from './commands/azureCommands/show-buildTask'; +import { TaskContentProvider } from './commands/azureCommands/task-utils/showTaskManager'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; import inspectImage from './commands/inspect-image'; @@ -45,6 +47,7 @@ import { DockerExplorerProvider } from './explorer/dockerExplorer'; import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes'; import { connectCustomRegistry, disconnectCustomRegistry } from './explorer/models/customRegistries'; import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; +import { BuildTaskNode } from './explorer/models/taskNode'; import { browseAzurePortal } from './explorer/utils/browseAzurePortal'; import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; import { ext } from "./extensionVariables"; @@ -125,6 +128,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(YAML_MODE_ID, new DockerComposeCompletionItemProvider(), '.')); ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DOCKER_INSPECT_SCHEME, new DockerInspectDocumentContentProvider())); + ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(TaskContentProvider.scheme, new TaskContentProvider())); if (azureAccount) { AzureUtilityManager.getInstance().setAccount(azureAccount); @@ -202,6 +206,7 @@ function registerDockerCommands(azureAccount: AzureAccount): void { registerAzureCommand('vscode-docker.create-ACR-Registry', createRegistry); registerAzureCommand('vscode-docker.queueBuild', queueBuild); registerAzureCommand('vscode-docker.pullFromAzure', pullFromAzure); + registerAzureCommand('vscode-docker.show-ACR-buildTask', showBuildTaskProperties); } async function consolidateDefaultRegistrySettings(): Promise { diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index b8416d67f5..9ee700476d 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -49,7 +49,7 @@ export class AzureRegistryNode extends NodeBase { }; //Pushing single TaskRootNode under the current registry. All the following nodes added to registryNodes are type AzureRepositoryNode - let taskNode = new TaskRootNode("Build Tasks", "taskRootNode", element.subscription, element.azureAccount, element.registry, iconPath); + let taskNode = new TaskRootNode("Build Tasks", element.subscription, element.azureAccount, element.registry, iconPath); registryChildNodes.push(taskNode); if (!this.azureAccount) { diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 0faca22a3b..8c6efe111a 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -1,21 +1,23 @@ import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; -import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; +import { SubscriptionModels } from 'azure-arm-resource'; import * as opn from 'opn'; import * as vscode from 'vscode'; -import { AzureAccount, AzureSession } from '../../typings/azure-account.api'; +import { AzureAccount } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { NodeBase } from './nodeBase'; /* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ export class TaskRootNode extends NodeBase { + public static readonly contextValue: string = 'taskRootNode'; + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + public readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; constructor( public readonly label: string, - public readonly contextValue: string, public subscription: SubscriptionModels.Subscription, public readonly azureAccount: AzureAccount, public registry: ContainerModels.Registry, - public readonly iconPath: any = {} + public readonly iconPath: any = null, ) { super(label); } @@ -26,7 +28,7 @@ export class TaskRootNode extends NodeBase { return { label: this.label, collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, - contextValue: this.contextValue, + contextValue: TaskRootNode.contextValue, iconPath: this.iconPath } } @@ -48,7 +50,7 @@ export class TaskRootNode extends NodeBase { } for (let buildTask of buildTasks) { - let node = new BuildTaskNode(buildTask.name, "buildTaskNode"); + let node = new BuildTaskNode(buildTask, element.registry, element.subscription, element); buildTaskNodes.push(node); } return buildTaskNodes; @@ -56,11 +58,25 @@ export class TaskRootNode extends NodeBase { } export class BuildTaskNode extends NodeBase { + public static readonly contextValue: string = 'buildTaskNode'; + public label: string; constructor( - public readonly label: string, - public readonly contextValue: string, + public task: ContainerModels.BuildTask, + public registry: ContainerModels.Registry, + public susbscription: SubscriptionModels.Subscription, + public parent: NodeBase + ) { - super(label); + super(task.name); + } + + public getTreeItem(): vscode.TreeItem { + return { + label: this.label, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: BuildTaskNode.contextValue, + iconPath: null + } } } diff --git a/package.json b/package.json index 104c8adea1..6efee07294 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", + "onCommand:vscode-docker.show-ACR-buildTask", "onCommand:vscode-docker.delete-ACR-Registry", "onCommand:vscode-docker.delete-ACR-Repository", "onCommand:vscode-docker.delete-ACR-Image", @@ -66,7 +67,8 @@ "main": "./out/dockerExtension", "contributes": { "menus": { - "commandPalette": [{ + "commandPalette": [ + { "command": "vscode-docker.browseDockerHub", "when": "false" }, @@ -79,7 +81,8 @@ "when": "never" } ], - "editor/context": [{ + "editor/context": [ + { "when": "editorLangId == dockerfile", "command": "vscode-docker.image.build", "group": "docker" @@ -120,7 +123,8 @@ "group": "docker" } ], - "explorer/context": [{ + "explorer/context": [ + { "when": "resourceFilename =~ /[dD]ocker[fF]ile/", "command": "vscode-docker.image.build", "group": "docker" @@ -146,7 +150,8 @@ "group": "docker" } ], - "view/title": [{ + "view/title": [ + { "command": "vscode-docker.explorer.refresh", "when": "view == dockerExplorer", "group": "navigation" @@ -157,7 +162,8 @@ "group": "navigation" } ], - "view/item/context": [{ + "view/item/context": [ + { "command": "vscode-docker.container.start", "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, @@ -237,6 +243,10 @@ "command": "vscode-docker.delete-ACR-Image", "when": "view == dockerExplorer && viewItem == azureImageNode" }, + { + "command": "vscode-docker.show-ACR-buildTask", + "when": "view == dockerExplorer && viewItem == buildTaskNode" + }, { "command": "vscode-docker.delete-ACR-Registry", "when": "view == dockerExplorer && viewItem == azureRegistryNode" @@ -259,34 +269,40 @@ } ] }, - "debuggers": [{ - "type": "docker", - "label": "Docker", - "configurationSnippets": [{ - "label": "Docker: Attach to Node", - "description": "Docker: Attach to Node", - "body": { - "type": "node", - "request": "attach", - "name": "Docker: Attach to Node", - "port": 9229, - "address": "localhost", - "localRoot": "^\"\\${workspaceFolder}\"", - "remoteRoot": "/usr/src/app", - "protocol": "inspector" - } - }] - }], - "languages": [{ - "id": "dockerfile", - "aliases": [ - "Dockerfile" - ], - "filenamePatterns": [ - "*.dockerfile", - "Dockerfile" - ] - }], + "debuggers": [ + { + "type": "docker", + "label": "Docker", + "configurationSnippets": [ + { + "label": "Docker: Attach to Node", + "description": "Docker: Attach to Node", + "body": { + "type": "node", + "request": "attach", + "name": "Docker: Attach to Node", + "port": 9229, + "address": "localhost", + "localRoot": "^\"\\${workspaceFolder}\"", + "remoteRoot": "/usr/src/app", + "protocol": "inspector" + } + } + ] + } + ], + "languages": [ + { + "id": "dockerfile", + "aliases": [ + "Dockerfile" + ], + "filenamePatterns": [ + "*.dockerfile", + "Dockerfile" + ] + } + ], "configuration": { "type": "object", "title": "Docker configuration options", @@ -446,7 +462,8 @@ } } }, - "commands": [{ + "commands": [ + { "command": "vscode-docker.configure", "title": "Add Docker files to workspace", "description": "Add Dockerfile, docker-compose.yml files", @@ -610,6 +627,11 @@ "title": "Browse in the Azure Portal", "category": "Docker" }, + { + "command": "vscode-docker.show-ACR-buildTask", + "title": "Show Build Task Properties", + "category": "Docker" + }, { "command": "vscode-docker.delete-ACR-Registry", "title": "Delete Azure Registry", @@ -637,18 +659,22 @@ } ], "views": { - "dockerView": [{ - "id": "dockerExplorer", - "name": "Explorer", - "when": "config.docker.showExplorer == true" - }] + "dockerView": [ + { + "id": "dockerExplorer", + "name": "Explorer", + "when": "config.docker.showExplorer == true" + } + ] }, "viewsContainers": { - "activitybar": [{ - "icon": "images/docker.svg", - "id": "dockerView", - "title": "Docker" - }] + "activitybar": [ + { + "icon": "images/docker.svg", + "id": "dockerView", + "title": "Docker" + } + ] } }, "engines": { diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 90cf7a32b4..bd341f66c7 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -6,18 +6,19 @@ import * as assert from 'assert'; import { Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import { NULL_GUID } from "../../constants"; import { getCatalog, getTags, TagInfo } from "../../explorer/models/commonRegistryUtils"; import { ext } from '../../extensionVariables'; import { AzureSession } from "../../typings/azure-account.api"; -import { addUserAgent } from "../addUserAgent"; import { AzureUtilityManager } from '../azureUtilityManager'; import { AzureImage } from "./models/image"; import { Repository } from "./models/repository"; //General helpers -/** Gets the subscription for a given registry +/** + * @param registry gets the subscription for a given registry * @returns a subscription object */ export function getSubscriptionFromRegistry(registry: Registry): SubscriptionModels.Subscription { @@ -29,10 +30,17 @@ export function getSubscriptionFromRegistry(registry: Registry): SubscriptionMod return subscription; } -export function getResourceGroupName(registry: Registry): string { +export function getResourceGroupName(registry: Registry): any { return registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); } +//Gets resource group object from registry and subscription +export async function getResourceGroup(registry: Registry, subscription: Subscription): Promise { ///to do: move to acr tools + let resourceGroups: ResourceGroup[] = await AzureUtilityManager.getInstance().getResourceGroups(subscription); + const resourceGroupName = getResourceGroupName(registry); + return resourceGroups.find((res) => { return res.name === resourceGroupName }); +} + //Registry item management /** List images under a specific Repository */ export async function getImagesByRepository(element: Repository): Promise { @@ -61,7 +69,7 @@ export async function getRepositoriesByRegistry(registry: Registry): Promise { +export async function getLoginCredentials(registry: Registry): Promise<{ password: string, username: string }> { const subscription: Subscription = getSubscriptionFromRegistry(registry); const session: AzureSession = AzureUtilityManager.getInstance().getSession(subscription) const { aadAccessToken, aadRefreshToken } = await acquireAADTokens(session); From a6825ae7b3a18e24935b80c65e21124ec6867722 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 6 Sep 2018 18:00:25 -0700 Subject: [PATCH 54/77] Merge branch 'dev' of https://github.com/AzureCR/vscode-docker into master4 --- commands/azureCommands/acr-build.ts | 117 +++++++++++++ commands/azureCommands/delete-repository.ts | 2 +- commands/azureCommands/pull-from-azure.ts | 22 +++ commands/azureCommands/show-buildTask.ts | 43 +++++ .../task-utils/showTaskManager.ts | 37 +++++ commands/utils/quick-pick-azure.ts | 12 +- dockerExtension.ts | 12 ++ explorer/deploy/webAppCreator.ts | 2 + explorer/models/azureRegistryNodes.ts | 19 ++- explorer/models/imageNode.ts | 2 +- explorer/models/registryRootNode.ts | 2 +- explorer/models/rootNode.ts | 66 ++++---- explorer/models/taskNode.ts | 82 +++++++++ images/dark/buildTasks_dark.svg | 1 + images/light/buildTasks_light.svg | 1 + package.json | 156 +++++++++++------- utils/Azure/acrTools.ts | 29 +++- utils/Azure/models/repository.ts | 28 ++-- utils/azureUtilityManager.ts | 1 + 19 files changed, 509 insertions(+), 125 deletions(-) create mode 100644 commands/azureCommands/acr-build.ts create mode 100644 commands/azureCommands/pull-from-azure.ts create mode 100644 commands/azureCommands/show-buildTask.ts create mode 100644 commands/azureCommands/task-utils/showTaskManager.ts create mode 100644 explorer/models/taskNode.ts create mode 100644 images/dark/buildTasks_dark.svg create mode 100644 images/light/buildTasks_light.svg diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts new file mode 100644 index 0000000000..0166795515 --- /dev/null +++ b/commands/azureCommands/acr-build.ts @@ -0,0 +1,117 @@ +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry/lib/containerRegistryManagementClient'; +import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models/quickBuildRequest"; +import { Registry } from 'azure-arm-containerregistry/lib/models/registry'; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as fs from 'fs'; +import * as os from 'os'; +import * as process from 'process'; +import * as tar from 'tar'; +import * as url from 'url'; +import * as vscode from "vscode"; +import { getBlobInfo, getResourceGroupName } from "../../utils/Azure/acrTools"; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickSubscription } from '../utils/quick-pick-azure'; +const idPrecision = 6; +const status = vscode.window.createOutputChannel('status'); +const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; + +// Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. +// The user is then asked to name & tag the image. A build is queued for the image in the selected registry. +// Selected source code must contain a path to the desired dockerfile. +export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { + status.show(); + status.appendLine("Obtaining Subscription and initializing management client"); + const subscription = await quickPickSubscription(); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + const registry: Registry = await quickPickACRRegistry(true); + status.appendLine("Selected registry: " + registry.name); + + const resourceGroupName = getResourceGroupName(registry); + let folder: vscode.WorkspaceFolder; + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { + folder = vscode.workspace.workspaceFolders[0]; + } else { + folder = await (vscode).window.showWorkspaceFolderPick(); + } + let sourceLocation: string = folder.uri.path; + let relativeDockerPath = 'Dockerfile'; + if (dockerFileUri.path.indexOf(sourceLocation) !== 0) { + //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. + throw new Error("Source code path must be a parent of the Dockerfile path"); + } else { + relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length + 1); + } + + let osType: string = await vscode.window.showQuickPick(['Linux', 'Windows'], { 'canPickMany': false, 'placeHolder': 'Linux' }); + + // Prompting for name so the image can then be pushed to a repository. + const opt: vscode.InputBoxOptions = { + prompt: 'Image name and tag in format :', + }; + const name: string = await vscode.window.showInputBox(opt); + + let tarFilePath = getTempSourceArchivePath(); + + status.appendLine("Uploading Source Code to " + tarFilePath); + let uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); + + status.appendLine("Setting up Build Request"); + let buildRequest: QuickBuildRequest = { + 'type': 'QuickBuild', + 'imageNames': [name], + 'isPushEnabled': true, + 'sourceLocation': uploadedSourceLocation, + 'platform': { 'osType': osType }, + 'dockerFilePath': relativeDockerPath + }; + status.appendLine("Queueing Build"); + await client.registries.queueBuild(resourceGroupName, registry.name, buildRequest); + status.appendLine('Success'); +} + +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { + status.appendLine(" Sending source code to temp file"); + let source = sourceLocation.substring(1); + let current = process.cwd(); + process.chdir(source); + fs.readdir(source, (err, items) => { + items = filter(items); + tar.c( + {}, + items + ).pipe(fs.createWriteStream(tarFilePath)); + process.chdir(current); + }); + + status.appendLine(" Getting Build Source Upload Url "); + let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + let upload_url = sourceUploadLocation.uploadUrl; + let relative_path = sourceUploadLocation.relativePath; + + status.appendLine(" Getting blob info from Upload Url "); + // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. + let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); + status.appendLine(" Creating Blob Service "); + let blob: BlobService = createBlobServiceWithSas(host, sasToken); + status.appendLine(" Creating Block Blob "); + blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + return relative_path; +} + +function getTempSourceArchivePath(): string { + /* tslint:disable-next-line:insecure-random */ + let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); + status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + return tarFilePath; +} + +function filter(list: string[]): string[] { + let result = []; + for (let file of list) { + if (vcsIgnoreList.indexOf(file) === -1) { + result.push(file); + } + } + return result; +} diff --git a/commands/azureCommands/delete-repository.ts b/commands/azureCommands/delete-repository.ts index e66db1d65d..b1ff281e89 100644 --- a/commands/azureCommands/delete-repository.ts +++ b/commands/azureCommands/delete-repository.ts @@ -26,7 +26,7 @@ export async function deleteRepository(context?: AzureRepositoryNode): Promise { + + // Step 1: Using getLoginCredentials function to get the username and password. This takes care of all users, even if they don't have the Azure CLI + const credentials = await acrTools.getLoginCredentials(context.registry); + const username = credentials.username; + const password = credentials.password; + const registry = context.registry.loginServer; + + const terminal = vscode.window.createTerminal("Docker"); + terminal.show(); + + // Step 2: docker login command + await terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); + + // Step 3: docker pull command + await terminal.sendText(`docker pull ${registry}/${context.label}`); +} diff --git a/commands/azureCommands/show-buildTask.ts b/commands/azureCommands/show-buildTask.ts new file mode 100644 index 0000000000..1b5e83ab67 --- /dev/null +++ b/commands/azureCommands/show-buildTask.ts @@ -0,0 +1,43 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import { BuildTaskNode } from "../../explorer/models/taskNode"; +import { ext } from '../../extensionVariables'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickBuildTask, quickPickSubscription } from '../utils/quick-pick-azure'; +import { openTask } from "./task-utils/showTaskManager"; + +export async function showBuildTaskProperties(context?: BuildTaskNode): Promise { + let subscription: Subscription; + let registry: Registry; + let resourceGroup: ResourceGroup; + let buildTask: string; + + if (context) { // Right click + subscription = context.susbscription; + registry = context.registry; + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + buildTask = context.task.name; + } else { // Command palette + subscription = await quickPickSubscription(); + registry = await quickPickACRRegistry(); + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + buildTask = (await quickPickBuildTask(registry, subscription, resourceGroup)).name; + } + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let item: any = await client.buildTasks.get(resourceGroup.name, registry.name, buildTask); + + try { + const steps = await client.buildSteps.get(resourceGroup.name, registry.name, buildTask, `${buildTask}StepName`); + item.properties = steps; + } catch (error) { + ext.outputChannel.append(error); + ext.outputChannel.append("Build Step not available for this image due to update in API"); + } + + let indentation = 1; + let replacer; + openTask(JSON.stringify(item, replacer, indentation), buildTask); +} diff --git a/commands/azureCommands/task-utils/showTaskManager.ts b/commands/azureCommands/task-utils/showTaskManager.ts new file mode 100644 index 0000000000..7a3ec4be58 --- /dev/null +++ b/commands/azureCommands/task-utils/showTaskManager.ts @@ -0,0 +1,37 @@ +import * as vscode from 'vscode'; + +export class TaskContentProvider implements vscode.TextDocumentContentProvider { + public static scheme: string = 'task'; + private onDidChangeEvent: vscode.EventEmitter = new vscode.EventEmitter(); + + constructor() { } + + public provideTextDocumentContent(uri: vscode.Uri): string { + return decodeBase64(JSON.parse(uri.query).content); + } + + get onDidChange(): vscode.Event { + return this.onDidChangeEvent.event; + } + + public update(uri: vscode.Uri, message: string): void { + this.onDidChangeEvent.fire(uri); + } +} + +export function decodeBase64(str: string): string { + return Buffer.from(str, 'base64').toString('utf8'); +} + +export function encodeBase64(str: string): string { + return Buffer.from(str, 'ascii').toString('base64'); +} + +export function openTask(content: string, title: string): void { + const scheme = 'task'; + let query = JSON.stringify({ 'content': encodeBase64(content) }); + let uri: vscode.Uri = vscode.Uri.parse(`${scheme}://authority/${title}.json?${query}#idk`); + vscode.workspace.openTextDocument(uri).then((doc) => { + return vscode.window.showTextDocument(doc, vscode.ViewColumn.Active + 1, true); + }); +} diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index 9d5280f3fa..0b4d25164a 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -3,6 +3,7 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Registry } from 'azure-arm-containerregistry/lib/models'; +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceGroup } from 'azure-arm-resource/lib/resource/models'; import { Location, Subscription } from 'azure-arm-resource/lib/subscription/models'; import * as opn from 'opn'; @@ -33,6 +34,16 @@ export async function quickPickACRRepository(registry: Registry, prompt?: string return desiredRepo.data; } +export async function quickPickBuildTask(registry: Registry, subscription: Subscription, resourceGroup: ResourceGroup, prompt?: string): Promise { + const placeHolder = prompt ? prompt : 'Choose a Build Task'; + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let buildTasks: ContainerModels.BuildTask[] = await client.buildTasks.list(resourceGroup.name, registry.name); + const quickpPickBTList = buildTasks.map(buildTask => >{ label: buildTask.name, data: buildTask }); + let desiredBuildTask = await ext.ui.showQuickPick(quickpPickBTList, { 'canPickMany': false, 'placeHolder': placeHolder }); + return desiredBuildTask.data; +} + export async function quickPickACRRegistry(canCreateNew: boolean = false, prompt?: string): Promise { const placeHolder = prompt ? prompt : 'Select registry to use'; let registries = await AzureUtilityManager.getInstance().getRegistries(); @@ -152,7 +163,6 @@ async function createNewResourceGroup(loc: string, subscription?: Subscription): }; let resourceGroupName: string = await ext.ui.showInputBox(opt); - let newResourceGroup: ResourceGroup = { name: resourceGroupName, location: loc, diff --git a/dockerExtension.ts b/dockerExtension.ts index 3032fc11d5..f395638887 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -9,10 +9,14 @@ import * as request from 'request-promise-native'; import * as vscode from 'vscode'; import { AzureUserInput, createTelemetryReporter, IActionContext, registerCommand as uiRegisterCommand, registerUIExtensionVariables, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; +import { queueBuild } from './commands/azureCommands/acr-build'; import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; import { deleteRepository } from './commands/azureCommands/delete-repository'; +import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; +import { showBuildTaskProperties } from './commands/azureCommands/show-buildTask'; +import { TaskContentProvider } from './commands/azureCommands/task-utils/showTaskManager'; import { buildImage } from './commands/build-image'; import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; import inspectImage from './commands/inspect-image'; @@ -45,9 +49,13 @@ import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from './exp import { ContainerNode } from './explorer/models/containerNode'; import { connectCustomRegistry, disconnectCustomRegistry } from './explorer/models/customRegistries'; import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; +<<<<<<< HEAD import { ImageNode } from './explorer/models/imageNode'; import { NodeBase } from './explorer/models/nodeBase'; import { RootNode } from './explorer/models/rootNode'; +======= +import { BuildTaskNode } from './explorer/models/taskNode'; +>>>>>>> dev import { browseAzurePortal } from './explorer/utils/browseAzurePortal'; import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; import { ext } from "./extensionVariables"; @@ -128,6 +136,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(YAML_MODE_ID, new DockerComposeCompletionItemProvider(), '.')); ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DOCKER_INSPECT_SCHEME, new DockerInspectDocumentContentProvider())); + ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(TaskContentProvider.scheme, new TaskContentProvider())); if (azureAccount) { AzureUtilityManager.getInstance().setAccount(azureAccount); @@ -227,6 +236,9 @@ function registerDockerCommands(azureAccount: AzureAccount): void { registerAzureCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); registerAzureCommand('vscode-docker.delete-ACR-Repository', deleteRepository); registerAzureCommand('vscode-docker.create-ACR-Registry', createRegistry); + registerAzureCommand('vscode-docker.queueBuild', queueBuild); + registerAzureCommand('vscode-docker.pullFromAzure', pullFromAzure); + registerAzureCommand('vscode-docker.show-ACR-buildTask', showBuildTaskProperties); } export async function deactivate(): Promise { diff --git a/explorer/deploy/webAppCreator.ts b/explorer/deploy/webAppCreator.ts index 3af5f72a5a..0178732964 100644 --- a/explorer/deploy/webAppCreator.ts +++ b/explorer/deploy/webAppCreator.ts @@ -9,6 +9,8 @@ import { ResourceManagementClient, ResourceModels, SubscriptionModels } from 'az import { Subscription } from 'azure-arm-resource/lib/subscription/models'; import WebSiteManagementClient = require('azure-arm-website'); import * as WebSiteModels from 'azure-arm-website/lib/models'; +import * as fs from 'fs'; +import * as path from 'path'; import * as vscode from 'vscode'; import { addExtensionUserAgent } from 'vscode-azureextensionui'; import { reporter } from '../../telemetry/telemetry'; diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 11ef615885..9ee700476d 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -13,6 +13,7 @@ import { AzureImage } from '../../utils/Azure/models/image'; import { Repository } from '../../utils/Azure/models/repository'; import { formatTag, getCatalog, getTags } from './commonRegistryUtils'; import { NodeBase } from './nodeBase'; +import { TaskRootNode } from './taskNode'; export class AzureRegistryNode extends NodeBase { constructor( @@ -39,8 +40,17 @@ export class AzureRegistryNode extends NodeBase { } } - public async getChildren(element: AzureRegistryNode): Promise { - const repoNodes: AzureRepositoryNode[] = []; + public async getChildren(element: AzureRegistryNode): Promise { + const registryChildNodes: NodeBase[] = []; + + let iconPath = { + light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'buildTasks_light.svg'), + dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'buildTasks_dark.svg') + }; + + //Pushing single TaskRootNode under the current registry. All the following nodes added to registryNodes are type AzureRepositoryNode + let taskNode = new TaskRootNode("Build Tasks", element.subscription, element.azureAccount, element.registry, iconPath); + registryChildNodes.push(taskNode); if (!this.azureAccount) { return []; @@ -54,14 +64,13 @@ export class AzureRegistryNode extends NodeBase { element.subscription, element.registry, element.label); - repoNodes.push(node); + registryChildNodes.push(node); } //Note these are ordered by default in alphabetical order - return repoNodes; + return registryChildNodes; } } - export class AzureRepositoryNode extends NodeBase { constructor( public readonly label: string, diff --git a/explorer/models/imageNode.ts b/explorer/models/imageNode.ts index 65035f1ab4..c880ff9ed8 100644 --- a/explorer/models/imageNode.ts +++ b/explorer/models/imageNode.ts @@ -45,5 +45,5 @@ export class ImageNode extends NodeBase { } } - // no children + // No children } diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index a7582be760..4a20c08b89 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -6,7 +6,6 @@ import * as assert from 'assert'; import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; -import { ServiceClientCredentials } from 'ms-rest'; import * as vscode from 'vscode'; import { parseError } from 'vscode-azureextensionui'; import { keytarConstants, MAX_CONCURRENT_REQUESTS, MAX_CONCURRENT_SUBSCRIPTON_REQUESTS } from '../../constants'; @@ -151,6 +150,7 @@ export class RegistryRootNode extends NodeBase { } catch (error) { vscode.window.showErrorMessage(parseError(error).message); } + }); } await subPool.runAll(); diff --git a/explorer/models/rootNode.ts b/explorer/models/rootNode.ts index 2c3960a575..4f0c086f0e 100644 --- a/explorer/models/rootNode.ts +++ b/explorer/models/rootNode.ts @@ -58,7 +58,6 @@ export class RootNode extends NodeBase { // clearInterval(this._imageDebounceTimer); // return; // } - clearInterval(this._imageDebounceTimer); if (refreshInterval > 0) { @@ -103,18 +102,21 @@ export class RootNode extends NodeBase { } - public async getChildren(element: NodeBase): Promise { - - if (element.contextValue === 'imagesRootNode') { - return this.getImages(); - } - if (element.contextValue === 'containersRootNode') { - return this.getContainers(); - } - if (element.contextValue === 'registriesRootNode') { - return this.getRegistries() + public async getChildren(element: RootNode): Promise { + switch (element.contextValue) { + case 'imagesRootNode': { + return this.getImages(); + } + case 'containersRootNode': { + return this.getContainers(); + } + case 'registriesRootNode': { + return this.getRegistries(); + } + default: { + break; + } } - } private async getImages(): Promise { @@ -127,19 +129,15 @@ export class RootNode extends NodeBase { return []; } - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < images.length; i++) { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - if (!images[i].RepoTags) { + for (let image of images) { + if (!image.RepoTags) { let node = new ImageNode(`:`, this.eventEmitter); - node.imageDesc = images[i]; + node.imageDesc = image; imageNodes.push(node); } else { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let j = 0; j < images[i].RepoTags.length; j++) { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - let node = new ImageNode(`${images[i].RepoTags[j]}`, this.eventEmitter); - node.imageDesc = images[i]; + for (let repoTag of image.RepoTags) { + let node = new ImageNode(`${repoTag}`, this.eventEmitter); + node.imageDesc = image; imageNodes.push(node); } } @@ -163,7 +161,6 @@ export class RootNode extends NodeBase { // clearInterval(this._containerDebounceTimer); // return; // } - clearInterval(this._containerDebounceTimer); if (refreshInterval > 0) { @@ -181,15 +178,13 @@ export class RootNode extends NodeBase { if (this._containerCache.length !== containers.length) { needToRefresh = true; } else { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < this._containerCache.length; i++) { - let ctr: Docker.ContainerDesc = this._containerCache[i]; - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let j = 0; j < containers.length; j++) { + for (let cachedContainer of this._containerCache) { + let ctr: Docker.ContainerDesc = cachedContainer; + for (let cont of containers) { // 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) { + if (ctr.Id === cont.Id && + ctr.Image === cont.Image && + ctr.State === cont.State) { found = true; break; } @@ -225,9 +220,8 @@ export class RootNode extends NodeBase { return []; } - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < containers.length; i++) { - if (['exited', 'dead'].includes(containers[i].State)) { + for (let container of containers) { + if (['exited', 'dead'].includes(container.State)) { contextValue = "stoppedLocalContainerNode"; iconPath = { light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'stoppedContainer.svg'), @@ -241,8 +235,8 @@ export class RootNode extends NodeBase { }; } - let containerNode: ContainerNode = new ContainerNode(`${containers[i].Image} (${containers[i].Names[0].substring(1)}) (${containers[i].Status})`, contextValue, iconPath); - containerNode.containerDesc = containers[i]; + let containerNode: ContainerNode = new ContainerNode(`${container.Image} (${container.Names[0].substring(1)}) (${container.Status})`, contextValue, iconPath); + containerNode.containerDesc = container; containerNodes.push(containerNode); } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts new file mode 100644 index 0000000000..8c6efe111a --- /dev/null +++ b/explorer/models/taskNode.ts @@ -0,0 +1,82 @@ +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; +import { SubscriptionModels } from 'azure-arm-resource'; +import * as opn from 'opn'; +import * as vscode from 'vscode'; +import { AzureAccount } from '../../typings/azure-account.api'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { NodeBase } from './nodeBase'; + +/* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ +export class TaskRootNode extends NodeBase { + public static readonly contextValue: string = 'taskRootNode'; + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + public readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + constructor( + public readonly label: string, + public subscription: SubscriptionModels.Subscription, + public readonly azureAccount: AzureAccount, + public registry: ContainerModels.Registry, + public readonly iconPath: any = null, + ) { + super(label); + } + + public name: string; + + public getTreeItem(): vscode.TreeItem { + return { + label: this.label, + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + contextValue: TaskRootNode.contextValue, + iconPath: this.iconPath + } + } + + /* Making a list view of BuildTaskNodes, or the Build Tasks of the current registry */ + public async getChildren(element: TaskRootNode): Promise { + const buildTaskNodes: BuildTaskNode[] = []; + let buildTasks: ContainerModels.BuildTask[] = []; + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); + const resourceGroup: string = acrTools.getResourceGroupName(element.registry); + buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); + if (buildTasks.length === 0) { + vscode.window.showInformationMessage(`You do not have any Build Tasks in the registry, '${element.registry.name}'. You can create one with ACR Build. `, "Learn More").then(val => { + if (val === "Learn More") { + opn('https://aka.ms/acr/buildtask'); + } + }) + } + + for (let buildTask of buildTasks) { + let node = new BuildTaskNode(buildTask, element.registry, element.subscription, element); + buildTaskNodes.push(node); + } + return buildTaskNodes; + } +} + +export class BuildTaskNode extends NodeBase { + public static readonly contextValue: string = 'buildTaskNode'; + public label: string; + + constructor( + public task: ContainerModels.BuildTask, + public registry: ContainerModels.Registry, + public susbscription: SubscriptionModels.Subscription, + public parent: NodeBase + + ) { + super(task.name); + } + + public getTreeItem(): vscode.TreeItem { + return { + label: this.label, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: BuildTaskNode.contextValue, + iconPath: null + } + } +} diff --git a/images/dark/buildTasks_dark.svg b/images/dark/buildTasks_dark.svg new file mode 100644 index 0000000000..618310ec7c --- /dev/null +++ b/images/dark/buildTasks_dark.svg @@ -0,0 +1 @@ +Manufacture_16x \ No newline at end of file diff --git a/images/light/buildTasks_light.svg b/images/light/buildTasks_light.svg new file mode 100644 index 0000000000..27f50db2ce --- /dev/null +++ b/images/light/buildTasks_light.svg @@ -0,0 +1 @@ +Manufacture_16x \ No newline at end of file diff --git a/package.json b/package.json index 5a4dfabdfe..3aece97c5b 100644 --- a/package.json +++ b/package.json @@ -50,23 +50,25 @@ "onCommand:vscode-docker.create-ACR-Registry", "onCommand:vscode-docker.system.prune", "onCommand:vscode-docker.dockerHubLogout", + "onCommand:vscode-docker.queuebuild", "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", + "onCommand:vscode-docker.show-ACR-buildTask", "onCommand:vscode-docker.delete-ACR-Registry", "onCommand:vscode-docker.delete-ACR-Repository", "onCommand:vscode-docker.delete-ACR-Image", "onCommand:vscode-docker.connectCustomRegistry", "onCommand:vscode-docker.setRegistryAsDefault", "onCommand:vscode-docker.disconnectCustomRegistry", + "onCommand:vscode-docker.pullFromAzure", "onView:dockerExplorer", "onDebugInitialConfigurations" ], "main": "./out/dockerExtension", "contributes": { "menus": { - "commandPalette": [ - { + "commandPalette": [{ "command": "vscode-docker.browseDockerHub", "when": "false" }, @@ -79,12 +81,16 @@ "when": "never" } ], - "editor/context": [ - { + "editor/context": [{ "when": "editorLangId == dockerfile", "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "editorLangId == dockerfile", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename == docker-compose.yml", "command": "vscode-docker.compose.up", @@ -116,12 +122,16 @@ "group": "docker" } ], - "explorer/context": [ - { + "explorer/context": [{ "when": "resourceFilename =~ /[dD]ocker[fF]ile/", "command": "vscode-docker.image.build", "group": "docker" }, + { + "when": "resourceFilename =~ /[dD]ocker[fF]ile/", + "command": "vscode-docker.queueBuild", + "group": "docker" + }, { "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", "command": "vscode-docker.compose.up", @@ -138,8 +148,7 @@ "group": "docker" } ], - "view/title": [ - { + "view/title": [{ "command": "vscode-docker.explorer.refresh", "when": "view == dockerExplorer", "group": "navigation" @@ -150,8 +159,7 @@ "group": "navigation" } ], - "view/item/context": [ - { + "view/item/context": [{ "command": "vscode-docker.container.start", "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, @@ -215,6 +223,26 @@ "command": "vscode-docker.delete-ACR-Image", "when": "view == dockerExplorer && viewItem == azureImageTagNode" }, + { + "command": "vscode-docker.pullFromAzure", + "when": "view == dockerExplorer && viewItem == azureImageTagNode" + }, + { + "command": "vscode-docker.browseDockerHub", + "when": "view == dockerExplorer && viewItem == dockerHubImageTag" + }, + { + "command": "vscode-docker.browseDockerHub", + "when": "view == dockerExplorer && viewItem == dockerHubRepository" + }, + { + "command": "vscode-docker.delete-ACR-Image", + "when": "view == dockerExplorer && viewItem == azureImageNode" + }, + { + "command": "vscode-docker.show-ACR-buildTask", + "when": "view == dockerExplorer && viewItem == buildTaskNode" + }, { "command": "vscode-docker.delete-ACR-Registry", "when": "view == dockerExplorer && viewItem == azureRegistryNode" @@ -241,40 +269,34 @@ } ] }, - "debuggers": [ - { - "type": "docker", - "label": "Docker", - "configurationSnippets": [ - { - "label": "Docker: Attach to Node", - "description": "Docker: Attach to Node", - "body": { - "type": "node", - "request": "attach", - "name": "Docker: Attach to Node", - "port": 9229, - "address": "localhost", - "localRoot": "^\"\\${workspaceFolder}\"", - "remoteRoot": "/usr/src/app", - "protocol": "inspector" - } - } - ] - } - ], - "languages": [ - { - "id": "dockerfile", - "aliases": [ - "Dockerfile" - ], - "filenamePatterns": [ - "*.dockerfile", - "Dockerfile" - ] - } - ], + "debuggers": [{ + "type": "docker", + "label": "Docker", + "configurationSnippets": [{ + "label": "Docker: Attach to Node", + "description": "Docker: Attach to Node", + "body": { + "type": "node", + "request": "attach", + "name": "Docker: Attach to Node", + "port": 9229, + "address": "localhost", + "localRoot": "^\"\\${workspaceFolder}\"", + "remoteRoot": "/usr/src/app", + "protocol": "inspector" + } + }] + }], + "languages": [{ + "id": "dockerfile", + "aliases": [ + "Dockerfile" + ], + "filenamePatterns": [ + "*.dockerfile", + "Dockerfile" + ] + }], "configuration": { "type": "object", "title": "Docker configuration options", @@ -440,8 +462,7 @@ } } }, - "commands": [ - { + "commands": [{ "command": "vscode-docker.configure", "title": "Add Docker files to workspace", "description": "Add Dockerfile, docker-compose.yml files", @@ -585,6 +606,11 @@ "title": "Deploy Image to Azure App Service", "category": "Docker" }, + { + "command": "vscode-docker.pullFromAzure", + "title": "Pull Image from Azure", + "category": "Docker" + }, { "command": "vscode-docker.dockerHubLogout", "title": "Docker Hub Logout", @@ -600,6 +626,11 @@ "title": "Browse in the Azure Portal", "category": "Docker" }, + { + "command": "vscode-docker.show-ACR-buildTask", + "title": "Show Build Task Properties", + "category": "Docker" + }, { "command": "vscode-docker.delete-ACR-Registry", "title": "Delete Azure Registry", @@ -624,25 +655,26 @@ "command": "vscode-docker.disconnectCustomRegistry", "title": "Disconnect from Private Registry", "category": "Docker" + }, + { + "command": "vscode-docker.queueBuild", + "title": "Cloud Build", + "category": "Docker" } ], "views": { - "dockerView": [ - { - "id": "dockerExplorer", - "name": "Explorer", - "when": "config.docker.showExplorer == true" - } - ] + "dockerView": [{ + "id": "dockerExplorer", + "name": "Explorer", + "when": "config.docker.showExplorer == true" + }] }, "viewsContainers": { - "activitybar": [ - { - "icon": "images/docker.svg", - "id": "dockerView", - "title": "Docker" - } - ] + "activitybar": [{ + "icon": "images/docker.svg", + "id": "dockerView", + "title": "Docker" + }] } }, "engines": { @@ -671,7 +703,6 @@ "@types/node": "^8.0.34", "@types/request-promise-native": "^1.0.15", "adm-zip": "^0.4.11", - "azure-storage": "^2.8.1", "cross-env": "^5.2.0", "gulp": "^3.9.1", "mocha": "5.2.0", @@ -686,6 +717,7 @@ "azure-arm-resource": "^2.0.0-preview", "azure-arm-website": "^1.0.0-preview", "dockerfile-language-server-nodejs": "^0.0.19", + "azure-storage": "^2.8.1", "dockerode": "^2.5.1", "fs-extra": "^6.0.1", "glob": "7.1.2", @@ -695,6 +727,8 @@ "pom-parser": "^1.1.1", "request-promise-native": "^1.0.5", "vscode-azureextensionui": "^0.17.0", + "request-promise": "^4.2.2", + "tar": "^4.4.6", "vscode-extension-telemetry": "0.0.18", "vscode-languageclient": "^4.4.0" } diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 8a7386034a..bd341f66c7 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -6,18 +6,19 @@ import * as assert from 'assert'; import { Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import { NULL_GUID } from "../../constants"; import { getCatalog, getTags, TagInfo } from "../../explorer/models/commonRegistryUtils"; import { ext } from '../../extensionVariables'; import { AzureSession } from "../../typings/azure-account.api"; -import { addUserAgent } from "../addUserAgent"; import { AzureUtilityManager } from '../azureUtilityManager'; import { AzureImage } from "./models/image"; import { Repository } from "./models/repository"; //General helpers -/** Gets the subscription for a given registry +/** + * @param registry gets the subscription for a given registry * @returns a subscription object */ export function getSubscriptionFromRegistry(registry: Registry): SubscriptionModels.Subscription { @@ -29,10 +30,17 @@ export function getSubscriptionFromRegistry(registry: Registry): SubscriptionMod return subscription; } -export function getResourceGroupName(registry: Registry): string { +export function getResourceGroupName(registry: Registry): any { return registry.id.slice(registry.id.search('resourceGroups/') + 'resourceGroups/'.length, registry.id.search('/providers/')); } +//Gets resource group object from registry and subscription +export async function getResourceGroup(registry: Registry, subscription: Subscription): Promise { ///to do: move to acr tools + let resourceGroups: ResourceGroup[] = await AzureUtilityManager.getInstance().getResourceGroups(subscription); + const resourceGroupName = getResourceGroupName(registry); + return resourceGroups.find((res) => { return res.name === resourceGroupName }); +} + //Registry item management /** List images under a specific Repository */ export async function getImagesByRepository(element: Repository): Promise { @@ -61,7 +69,7 @@ export async function getRepositoriesByRegistry(registry: Registry): Promise { +export async function getLoginCredentials(registry: Registry): Promise<{ password: string, username: string }> { const subscription: Subscription = getSubscriptionFromRegistry(registry); const session: AzureSession = AzureUtilityManager.getInstance().getSession(subscription) const { aadAccessToken, aadRefreshToken } = await acquireAADTokens(session); @@ -155,3 +163,14 @@ export async function acquireACRAccessToken(registryUrl: string, scope: string, }); return acrAccessTokenResponse.access_token; } + +export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { + let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); + let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); + let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); + let containerName: string = items[1]; + let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); + let sasToken: string = items[4].slice(items[4].search('[?]') + 1); + let host: string = accountName + '.blob.' + endpointSuffix; + return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; +} diff --git a/utils/Azure/models/repository.ts b/utils/Azure/models/repository.ts index be87acd391..a6b3e51f30 100644 --- a/utils/Azure/models/repository.ts +++ b/utils/Azure/models/repository.ts @@ -8,19 +8,19 @@ import * as acrTools from '../acrTools'; /** Class Azure Repository: Used locally, Organizes data for managing Repositories */ export class Repository { - public registry: Registry; - public name: string; - public subscription: SubscriptionModels.Subscription; - public resourceGroupName: string; - public password?: string; - public username?: string; + public registry: Registry; + public name: string; + public subscription: SubscriptionModels.Subscription; + public resourceGroupName: string; + public password?: string; + public username?: string; - constructor(registry: Registry, repository: string, password?: string, username?: string) { - this.registry = registry; - this.resourceGroupName = acrTools.getResourceGroupName(registry); - this.subscription = acrTools.getSubscriptionFromRegistry(registry); - this.name = repository; - if (password) { this.password = password; } - if (username) { this.username = username; } - } + constructor(registry: Registry, repository: string, password?: string, username?: string) { + this.registry = registry; + this.resourceGroupName = acrTools.getResourceGroupName(registry); + this.subscription = acrTools.getSubscriptionFromRegistry(registry); + this.name = repository; + if (password) { this.password = password; } + if (username) { this.username = username; } + } } diff --git a/utils/azureUtilityManager.ts b/utils/azureUtilityManager.ts index d22de4c12c..d03f6b66bb 100644 --- a/utils/azureUtilityManager.ts +++ b/utils/azureUtilityManager.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { Registry } from 'azure-arm-containerregistry/lib/models'; import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; From 9c4d36703bcd694c354063a4b846550c9dbcafdf Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 6 Sep 2018 18:04:19 -0700 Subject: [PATCH 55/77] merge --- dockerExtension.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/dockerExtension.ts b/dockerExtension.ts index f395638887..236b4fa5da 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -49,13 +49,9 @@ import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from './exp import { ContainerNode } from './explorer/models/containerNode'; import { connectCustomRegistry, disconnectCustomRegistry } from './explorer/models/customRegistries'; import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; -<<<<<<< HEAD import { ImageNode } from './explorer/models/imageNode'; import { NodeBase } from './explorer/models/nodeBase'; import { RootNode } from './explorer/models/rootNode'; -======= -import { BuildTaskNode } from './explorer/models/taskNode'; ->>>>>>> dev import { browseAzurePortal } from './explorer/utils/browseAzurePortal'; import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; import { ext } from "./extensionVariables"; From 216bb2fb54820a60936d8612c32476aca6aa10c1 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 6 Sep 2018 18:08:24 -0700 Subject: [PATCH 56/77] missed small changes --- dockerExtension.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/dockerExtension.ts b/dockerExtension.ts index 236b4fa5da..5857b917c3 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -52,6 +52,7 @@ import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from import { ImageNode } from './explorer/models/imageNode'; import { NodeBase } from './explorer/models/nodeBase'; import { RootNode } from './explorer/models/rootNode'; +import { BuildTaskNode } from './explorer/models/taskNode'; import { browseAzurePortal } from './explorer/utils/browseAzurePortal'; import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; import { ext } from "./extensionVariables"; From 32332abc6f5c352327ca1e54f69c66ce893c654f Mon Sep 17 00:00:00 2001 From: rsamai Date: Thu, 13 Sep 2018 16:57:33 -0700 Subject: [PATCH 57/77] Rutusamai/Run a Build Task (#71) * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show is working- final * run is working for right click * run build task works through command pallette. added success notification * removed show build task * cleanup- matched quickpick buils task and tasknode withshow build task branch * removed showTaskManager * spacing * outputChannel and null icon * merge to update with dev --- commands/azureCommands/run-buildTask.ts | 43 ++++++++++++++++++++++++ commands/azureCommands/show-buildTask.ts | 2 +- dockerExtension.ts | 2 ++ explorer/models/taskNode.ts | 2 +- package.json | 10 ++++++ 5 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 commands/azureCommands/run-buildTask.ts diff --git a/commands/azureCommands/run-buildTask.ts b/commands/azureCommands/run-buildTask.ts new file mode 100644 index 0000000000..bcc962ee6b --- /dev/null +++ b/commands/azureCommands/run-buildTask.ts @@ -0,0 +1,43 @@ +import { BuildTaskBuildRequest } from "azure-arm-containerregistry/lib/models"; +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import vscode = require('vscode'); +import { BuildTaskNode } from "../../explorer/models/taskNode"; +import { ext } from '../../extensionVariables'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickBuildTask, quickPickSubscription } from '../utils/quick-pick-azure'; + +export async function runBuildTask(context?: BuildTaskNode): Promise { + let buildTaskName: string; + let subscription: Subscription; + let resourceGroup: ResourceGroup; + let registry: Registry; + + if (context) { // Right Click + subscription = context.subscription; + registry = context.registry; + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + buildTaskName = context.task.name; + } else { // Command Palette + subscription = await quickPickSubscription(); + registry = await quickPickACRRegistry(); + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + buildTaskName = (await quickPickBuildTask(registry, subscription, resourceGroup)).name; + } + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let buildRequest: BuildTaskBuildRequest = { + 'type': 'BuildTask', + 'buildTaskName': buildTaskName + }; + + try { + await client.registries.queueBuild(resourceGroup.name, registry.name, buildRequest); + } catch (err) { + ext.outputChannel.append(err); + } + vscode.window.showInformationMessage(`Successfully ran the Build Task, ${buildTaskName}`); + +} diff --git a/commands/azureCommands/show-buildTask.ts b/commands/azureCommands/show-buildTask.ts index 1b5e83ab67..5ac52dc778 100644 --- a/commands/azureCommands/show-buildTask.ts +++ b/commands/azureCommands/show-buildTask.ts @@ -15,7 +15,7 @@ export async function showBuildTaskProperties(context?: BuildTaskNode): Promise< let buildTask: string; if (context) { // Right click - subscription = context.susbscription; + subscription = context.subscription; registry = context.registry; resourceGroup = await acrTools.getResourceGroup(registry, subscription); buildTask = context.task.name; diff --git a/dockerExtension.ts b/dockerExtension.ts index 5857b917c3..43e2dfeaca 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -15,6 +15,7 @@ import { deleteAzureImage } from './commands/azureCommands/delete-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; import { deleteRepository } from './commands/azureCommands/delete-repository'; import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; +import { runBuildTask } from './commands/azureCommands/run-buildTask'; import { showBuildTaskProperties } from './commands/azureCommands/show-buildTask'; import { TaskContentProvider } from './commands/azureCommands/task-utils/showTaskManager'; import { buildImage } from './commands/build-image'; @@ -235,6 +236,7 @@ function registerDockerCommands(azureAccount: AzureAccount): void { registerAzureCommand('vscode-docker.create-ACR-Registry', createRegistry); registerAzureCommand('vscode-docker.queueBuild', queueBuild); registerAzureCommand('vscode-docker.pullFromAzure', pullFromAzure); + registerAzureCommand('vscode-docker.run-ACR-BuildTask', runBuildTask); registerAzureCommand('vscode-docker.show-ACR-buildTask', showBuildTaskProperties); } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 8c6efe111a..47b0188997 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -64,7 +64,7 @@ export class BuildTaskNode extends NodeBase { constructor( public task: ContainerModels.BuildTask, public registry: ContainerModels.Registry, - public susbscription: SubscriptionModels.Subscription, + public subscription: SubscriptionModels.Subscription, public parent: NodeBase ) { diff --git a/package.json b/package.json index 3aece97c5b..7d88062aaa 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "onCommand:vscode-docker.explorer.refresh", "onCommand:vscode-docker.show-ACR-buildTask", "onCommand:vscode-docker.delete-ACR-Registry", + "onCommand:vscode-docker.run-ACR-BuildTask", "onCommand:vscode-docker.delete-ACR-Repository", "onCommand:vscode-docker.delete-ACR-Image", "onCommand:vscode-docker.connectCustomRegistry", @@ -239,6 +240,10 @@ "command": "vscode-docker.delete-ACR-Image", "when": "view == dockerExplorer && viewItem == azureImageNode" }, + { + "command": "vscode-docker.run-ACR-BuildTask", + "when": "view == dockerExplorer && viewItem == buildTaskNode" + }, { "command": "vscode-docker.show-ACR-buildTask", "when": "view == dockerExplorer && viewItem == buildTaskNode" @@ -626,6 +631,11 @@ "title": "Browse in the Azure Portal", "category": "Docker" }, + { + "command": "vscode-docker.run-ACR-BuildTask", + "title": "Run a Build Task", + "category": "Docker" + }, { "command": "vscode-docker.show-ACR-buildTask", "title": "Show Build Task Properties", From ec20087fae168c255b55d3a662786f3f5e0a3b52 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Thu, 13 Sep 2018 19:13:14 -0500 Subject: [PATCH 58/77] Estebanreyl/build logs final (#72) * "Merge branch 'estebanreyl/buildlogsWithAccesibility' of https://github.com/AzureCR/vscode-docker into estebanreyl/buildlogsWithAccesibility" This reverts commit e645cbf5058c9bc838ee41a67cd6c5aee9945702, reversing changes made to fc4a477220c7819c9249557ba0d7570f5d0c1e0c. * Refactored and organized log view into readable components. * moved storage * Began incorporating icons * Added icons * Further standarization * added logs * Added download log capabilities * Updated Copy * Updated inner div size alignment * Added resizing script * header sort by is only clickable on text now * fixed header table * Fix minor display issues * Identified filtering strategy * Begin adding sorting * Merge with dev * Added proper filtering * Nit loading improvements * Added accesibility, key only behaviour * accesibility retouches and enable right click from tasknode * merges * fix module name --- .../acr-build-logs-utils/logFileManager.ts | 69 ++++ .../acr-build-logs-utils/logScripts.js | 319 +++++++++++++++ .../acr-build-logs-utils/resizable.js | 166 ++++++++ .../fabric-components/css/vscmdl2-icons.css | 71 ++++ .../fonts/vscmdl2-icons-d3699964.woff | Bin 0 -> 2792 bytes .../fabric-components/fonts/vscmdl2-icons.ttf | Bin 0 -> 5308 bytes .../microsoft-ui-fabric-assets-license.pdf | Bin 0 -> 467888 bytes .../acr-build-logs-utils/style/stylesheet.css | 387 ++++++++++++++++++ .../acr-build-logs-utils/tableDataManager.ts | 131 ++++++ .../acr-build-logs-utils/tableViewManager.ts | 262 ++++++++++++ commands/azureCommands/acr-build-logs.ts | 65 +++ commands/azureCommands/pull-from-azure.ts | 2 +- dockerExtension.ts | 5 +- explorer/models/taskNode.ts | 3 - package.json | 131 +++--- utils/Azure/acrTools.ts | 1 + utils/Azure/models/repository.ts | 28 +- 17 files changed, 1564 insertions(+), 76 deletions(-) create mode 100644 commands/azureCommands/acr-build-logs-utils/logFileManager.ts create mode 100644 commands/azureCommands/acr-build-logs-utils/logScripts.js create mode 100644 commands/azureCommands/acr-build-logs-utils/resizable.js create mode 100644 commands/azureCommands/acr-build-logs-utils/style/fabric-components/css/vscmdl2-icons.css create mode 100644 commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff create mode 100644 commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf create mode 100644 commands/azureCommands/acr-build-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf create mode 100644 commands/azureCommands/acr-build-logs-utils/style/stylesheet.css create mode 100644 commands/azureCommands/acr-build-logs-utils/tableDataManager.ts create mode 100644 commands/azureCommands/acr-build-logs-utils/tableViewManager.ts create mode 100644 commands/azureCommands/acr-build-logs.ts diff --git a/commands/azureCommands/acr-build-logs-utils/logFileManager.ts b/commands/azureCommands/acr-build-logs-utils/logFileManager.ts new file mode 100644 index 0000000000..f377d78538 --- /dev/null +++ b/commands/azureCommands/acr-build-logs-utils/logFileManager.ts @@ -0,0 +1,69 @@ +import { BlobService, createBlobServiceWithSas } from 'azure-storage'; +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import { getBlobInfo } from '../../../utils/Azure/acrTools'; + +export class LogContentProvider implements vscode.TextDocumentContentProvider { + public static scheme: string = 'purejs'; + private onDidChangeEvent: vscode.EventEmitter = new vscode.EventEmitter(); + + constructor() { } + + public provideTextDocumentContent(uri: vscode.Uri): string { + return decodeBase64(JSON.parse(uri.query).log); + } + + get onDidChange(): vscode.Event { + return this.onDidChangeEvent.event; + } + + public update(uri: vscode.Uri, message: string): void { + this.onDidChangeEvent.fire(uri); + } + +} + +export function decodeBase64(str: string): string { + return Buffer.from(str, 'base64').toString('ascii'); +} + +export function encodeBase64(str: string): string { + return Buffer.from(str, 'ascii').toString('base64'); +} + +/** Loads log text from remote url using azure blobservices */ +export function accessLog(url: string, title: string, download: boolean): void { + let blobInfo = getBlobInfo(url); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); + blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, async (error, text, result, response) => { + if (response) { + if (download) { + downloadLog(text, title); + } else { + openLogInNewWindow(text, title); + } + } else if (error) { + throw error; + } + }); +} + +function openLogInNewWindow(content: string, title: string): void { + const scheme = 'purejs'; + let query = JSON.stringify({ 'log': encodeBase64(content) }); + let uri: vscode.Uri = vscode.Uri.parse(`${scheme}://authority/${title}.log?${query}#idk`); + vscode.workspace.openTextDocument(uri).then((doc) => { + return vscode.window.showTextDocument(doc, vscode.ViewColumn.Active + 1, true); + }); +} + +export async function downloadLog(content: string, title: string): Promise { + let uri = await vscode.window.showSaveDialog({ + filters: { 'Log': ['.log', '.txt'] }, + defaultUri: vscode.Uri.file(`${title}.log`) + }); + fs.writeFile(uri.fsPath, content, + (err) => { + if (err) { throw err; } + }); +} diff --git a/commands/azureCommands/acr-build-logs-utils/logScripts.js b/commands/azureCommands/acr-build-logs-utils/logScripts.js new file mode 100644 index 0000000000..e8db8245e8 --- /dev/null +++ b/commands/azureCommands/acr-build-logs-utils/logScripts.js @@ -0,0 +1,319 @@ +// Global Variables +const status = { + 'Succeeded': 4, + 'Queued': 3, + 'Error': 2, + 'Failed': 1 +} + +var currentN = 4; +var currentDir = "asc" +var triangles = { + 'down': ' ', + 'up': ' ' +} + +document.addEventListener("scroll", function () { + var translate = "translate(0," + this.lastChild.scrollTop + "px)"; + let fixedItems = this.querySelectorAll(".fixed"); + for (item of fixedItems) { + item.style.transform = translate; + } +}); + +// Main +let content = document.querySelector('#core'); +const vscode = acquireVsCodeApi(); +setLoadMoreListener(); +setInputListeners(); +loading(); + +document.onkeydown = function (event) { + if (event.key === "Enter") { // The Enter/Return key + document.activeElement.onclick(event); + } +}; + +/* Sorting + * PR note, while this does not use a particularly quick algorithm + * it allows a low stuttering experience that allowed rapid testing. + * I will improve it soon.*/ +function sortTable(n, dir = "asc", holdDir = false) { + currentN = n; + let table, rows, switching, i, x, y, shouldSwitch, switchcount = 0; + let cmpFunc = acquireCompareFunction(n); + table = document.getElementById("core"); + switching = true; + //Set the sorting direction to ascending: + + while (switching) { + switching = false; + rows = table.querySelectorAll(".holder"); + for (i = 0; i < rows.length - 1; i++) { + shouldSwitch = false; + x = rows[i].getElementsByTagName("TD")[n + 1]; + y = rows[i + 1].getElementsByTagName("TD")[n + 1]; + if (dir == "asc") { + if (cmpFunc(x, y)) { + shouldSwitch = true; + break; + } + } else if (dir == "desc") { + if (cmpFunc(y, x)) { + shouldSwitch = true; + break; + } + } + } + if (shouldSwitch) { + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + switchcount++; + } else { + /*If no switching has been done AND the direction is "asc", set the direction to "desc" and run the while loop again.*/ + if (switchcount == 0 && dir == "asc" && !holdDir) { + dir = "desc"; + switching = true; + } + } + } + if (!holdDir) { + let sortColumns = document.querySelectorAll(".sort"); + if (sortColumns[n].innerHTML === triangles['down']) { + sortColumns[n].innerHTML = triangles['up']; + } else if (sortColumns[n].innerHTML === triangles['up']) { + sortColumns[n].innerHTML = triangles['down']; + } else { + for (cell of sortColumns) { + cell.innerHTML = ' '; + } + sortColumns[n].innerHTML = triangles['down']; + } + } + currentDir = dir; +} + +function acquireCompareFunction(n) { + switch (n) { + case 0: //Name + case 1: //Build Task + return (x, y) => { + return x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase() + } + case 2: //Status + return (x, y) => { + return status[x.dataset.status] > status[y.dataset.status];; + } + case 3: //Created time + return (x, y) => { + if (x.dataset.createdtime === '') return true; + if (y.dataset.createdtime === '') return false; + let dateX = new Date(x.dataset.createdtime); + let dateY = new Date(y.dataset.createdtime); + return dateX > dateY; + } + case 4: //Elapsed time + return (x, y) => { + if (x.innerHTML === '') return true; + if (y.innerHTML === '') return false; + return Number(x.innerHTML.substring(0, x.innerHTML.length - 1)) > Number(y.innerHTML.substring(0, y.innerHTML.length - 1)); + } + case 5: //OS Type + return (x, y) => { + return x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase() + } + default: + throw 'Could not acquire Compare function, invalid n'; + } +} + +// Event Listener Setup +window.addEventListener('message', event => { + const message = event.data; // The JSON data our extension sent + if (message.type === 'populate') { + content.insertAdjacentHTML('beforeend', message.logComponent); + + let item = content.querySelector(`#btn${message.id}`); + setSingleAccordion(item); + + let panel = item.nextElementSibling; + + const logButton = panel.querySelector('.openLog'); + setLogBtnListener(logButton, false); + const downloadlogButton = panel.querySelector('.downloadlog'); + setLogBtnListener(downloadlogButton, true); + + const digestClickables = panel.querySelectorAll('.copy'); + setDigestListener(digestClickables); + + } else if (message.type === 'endContinued') { + sortTable(currentN, currentDir, true); + loading(); + } else if (message.type === 'end') { + window.addEventListener("resize", manageWidth); + manageWidth(); + setTableSorter(); + loading(); + } + + if (message.canLoadMore) { + const loadBtn = document.querySelector('.loadMoreBtn'); + loadBtn.style.display = 'flex'; + } + +}); + +function setSingleAccordion(item) { + item.onclick = function (event) { + this.classList.toggle('active'); + this.querySelector('.arrow').classList.toggle('activeArrow'); + let panel = this.nextElementSibling; + if (panel.style.maxHeight) { + panel.style.display = 'none'; + panel.style.maxHeight = null; + let index = openAccordions.indexOf(panel); + if (index > -1) { + openAccordions.splice(index, 1); + } + } else { + openAccordions.push(panel); + setAccordionTableWidth(); + panel.style.display = 'table-row'; + let paddingTop = +panel.style.paddingTop.split('px')[0]; + let paddingBottom = +panel.style.paddingBottom.split('px')[0]; + panel.style.maxHeight = (panel.scrollHeight + paddingTop + paddingBottom) + 'px'; + } + }; +} + +function setTableSorter() { + let tableHeader = document.querySelector("#tableHead"); + let items = tableHeader.querySelectorAll(".colTitle"); + for (let i = 0; i < items.length; i++) { + items[i].onclick = () => { + sortTable(i); + }; + } +} + +function setLogBtnListener(item, download) { + item.onclick = (event) => { + vscode.postMessage({ + logRequest: { + 'id': event.target.dataset.id, + 'download': download + } + }); + }; +} + +function setLoadMoreListener() { + let item = document.querySelector("#loadBtn"); + item.onclick = function () { + const loadBtn = document.querySelector('.loadMoreBtn'); + loadBtn.style.display = 'none'; + loading(); + vscode.postMessage({ + loadMore: true + }); + }; +} + +function setDigestListener(digestClickables) { + for (digest of digestClickables) { + digest.onclick = function (event) { + vscode.postMessage({ + copyRequest: { + 'text': event.target.parentNode.dataset.digest, + } + }); + }; + } +} + +function manageWidth() { + // let headerCells = document.querySelectorAll("#headerTable th"); + // let topRow = document.querySelector("#core tr"); + // let topRowCells = topRow.querySelectorAll("td"); + // for (let i = 0; i < topRowCells.length; i++) { + // let width = parseInt(getComputedStyle(topRowCells[i]).width); + // headerCells[i].style.width = width + "px"; + // } + setAccordionTableWidth(); +} + +let openAccordions = []; + +function setAccordionTableWidth() { + let headerCells = document.querySelectorAll("#core thead tr th"); + let topWidths = []; + for (let cell of headerCells) { + topWidths.push(parseInt(getComputedStyle(cell).width)); + } + for (acc of openAccordions) { + let cells = acc.querySelectorAll(".innerTable th, .innerTable td"); // 4 items + const cols = acc.querySelectorAll(".innerTable th").length + 1; //Account for arrowHolder + const rows = cells.length / cols; + //cells[0].style.width = topWidths[0]; + for (let row = 0; row < rows; row++) { + for (let col = 1; col < cols - 1; col++) { + let cell = cells[row * cols + col]; + cell.style.width = topWidths[col - 1] + "px" + } + } + } +} + +function setInputListeners() { + const inputFields = document.querySelectorAll("input"); + const loadBtn = document.querySelector('.loadMoreBtn'); + for (let inputField of inputFields) { + inputField.addEventListener("keyup", function (event) { + if (event.key === "Enter") { + clearLogs(); + loading(); + loadBtn.style.display = 'none'; + vscode.postMessage({ + loadFiltered: { + filterString: getFilterString(inputFields) + } + }); + } + }); + } +} + +/*interface Filter + image?: string; + buildId?: string; + buildTask?: string; +*/ +function getFilterString(inputFields) { + let filter = {}; + if (inputFields[0].value.length > 0) { //Build Id + filter.buildId = inputFields[0].value; + } else if (inputFields[1].value.length > 0) { // Build Task id + filter.buildTask = inputFields[1].value; + } else if (inputFields[2].value.length > 0) { //Image + filter.image = inputFields[2].value; + } + return filter; +} + +function clearLogs() { + let items = document.querySelectorAll("#core tbody"); + for (let item of items) { + item.remove(); + } +} +var shouldLoad = false; + +function loading() { + const loader = document.querySelector('#loadingDiv'); + if (shouldLoad) { + loader.style.display = 'flex'; + } else { + loader.style.display = 'none'; + } + shouldLoad = !shouldLoad; +} diff --git a/commands/azureCommands/acr-build-logs-utils/resizable.js b/commands/azureCommands/acr-build-logs-utils/resizable.js new file mode 100644 index 0000000000..909f35c0e2 --- /dev/null +++ b/commands/azureCommands/acr-build-logs-utils/resizable.js @@ -0,0 +1,166 @@ +// **** ADAPTED FROM HTML DEMO AT **** +// DEMO = http://bz.var.ru/comp/web/resizable.html +// JS = http://bz.var.ru/comp/web/resizable-tables.js +// ******* ORIGINAL SCRIPT HEADER ******* +// Resizable Table Columns. +// version: 1.0 +// +// (c) 2006, bz +// +// 25.12.2006: first working prototype +// 26.12.2006: now works in IE as well but not in Opera (Opera is @#$%!) +// 27.12.2006: changed initialization, now just make class='resizable' in table and load script +//===================================================== +//Changelog +//-Removed cookies +//-limited functionality to manually selected table + +function preventEvent(e) { + let ev = e || window.event; + if (ev.preventDefault) ev.preventDefault(); + else ev.returnValue = false; + if (ev.stopPropagation) + ev.stopPropagation(); + return false; +} + +function getStyle(x, styleProp) { + let y; + if (x.currentStyle) + y = x.currentStyle[styleProp]; + else if (window.getComputedStyle) + y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp); + return y; +} + +function getWidth(x) { + return document.defaultView.getComputedStyle(x, null).getPropertyValue("width"); +} + +// main class prototype +function ColumnResize(table) { + this.id = table.id; + // private data + let self = this; + + let dragColumns = table.rows[0].cells; // first row columns, used for changing of width + if (!dragColumns) return; // return if no table exists or no one row exists + + let dragColumnNo; // current dragging column + let dragX; // last event X mouse coordinate + + let saveOnmouseup; // save document onmouseup event handler + let saveOnmousemove; // save document onmousemove event handler + let saveBodyCursor; // save body cursor property + + // do changes columns widths + // returns true if success and false otherwise + this.changeColumnWidth = function (no, w) { + if (no === 0) return false; // Ignores first item + if (no === dragColumns.length - 1) return false; // Ignores last item + + if (parseInt(dragColumns[no].style.width) <= -w) return false; + if (dragColumns[no + 1] && parseInt(dragColumns[no + 1].style.width) <= w) return false; + + dragColumns[no].style.width = parseInt(dragColumns[no].style.width) + w + 'px'; + if (dragColumns[no + 1]) + dragColumns[no + 1].style.width = parseInt(dragColumns[no + 1].style.width) - w + 'px'; + + return true; + } + + // do drag column width + this.columnDrag = function (e) { + var e = e || window.event; + var X = e.clientX || e.pageX; + if (!self.changeColumnWidth(dragColumnNo, X - dragX)) { + // stop drag! + self.stopColumnDrag(e); + } + + dragX = X; + // prevent other event handling + preventEvent(e); + return false; + } + + // stops column dragging + this.stopColumnDrag = function (e) { + var e = e || window.event; + if (!dragColumns) return; + + // restore handlers & cursor + document.onmouseup = saveOnmouseup; + document.onmousemove = saveOnmousemove; + document.body.style.cursor = saveBodyCursor; + preventEvent(e); + } + + // init data and start dragging + this.startColumnDrag = function (e) { + var e = e || window.event; + + // if not first button was clicked + //if (e.button != 0) return; + + // remember dragging object + dragColumnNo = (e.target || e.srcElement).parentNode.parentNode.cellIndex; + dragX = e.clientX || e.pageX; + + // set up current columns widths in their particular attributes + // do it in two steps to avoid jumps on page! + let colWidth = []; + for (let i = 0; i < dragColumns.length; i++) { + colWidth[i] = parseInt(getWidth(dragColumns[i])); + } + for (let i = 0; i < dragColumns.length; i++) { + dragColumns[i].width = ""; // for sure + dragColumns[i].style.width = colWidth[i] + "px"; + } + + saveOnmouseup = document.onmouseup; + document.onmouseup = self.stopColumnDrag; + + saveBodyCursor = document.body.style.cursor; + document.body.style.cursor = 'w-resize'; + + // fire! + saveOnmousemove = document.onmousemove; + document.onmousemove = self.columnDrag; + + preventEvent(e); + } + + // prepare table header to be draggable + // it runs during class creation + for (let i = 1; i < dragColumns.length - 1; i++) { + dragColumns[i].innerHTML = "
" + + "
" + + "
" + + dragColumns[i].innerHTML + + "
"; + dragColumns[i].firstChild.firstChild.onmousedown = this.startColumnDrag; + } +} + +// select all tables and make resizable those that have 'resizable' class +let resizableTables = []; + +function ResizableColumns() { + + var tables = document.getElementsByTagName('table'); + for (var i = 0; tables.item(i); i++) { + if (tables[i].className.match(/resizable/)) { + // generate id + if (!tables[i].id) tables[i].id = 'table' + (i + 1); + // make table resizable + resizableTables[resizableTables.length] = new ColumnResize(tables[i]); + } + } +} + +try { + window.addEventListener('load', ResizableColumns, false); +} catch (e) { + window.onload = ResizableColumns; +} diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/css/vscmdl2-icons.css b/commands/azureCommands/acr-build-logs-utils/style/fabric-components/css/vscmdl2-icons.css new file mode 100644 index 0000000000..cd0e84c017 --- /dev/null +++ b/commands/azureCommands/acr-build-logs-utils/style/fabric-components/css/vscmdl2-icons.css @@ -0,0 +1,71 @@ +/* + Your use of the content in the files referenced here is subject to the terms of the license at https://aka.ms/fabric-assets-license +*/ + +@font-face { + font-family: 'VSC MDL2 Assets'; + src: url('../fonts/vscmdl2-icons-d3699964.woff') format('woff'); +} + +.ms-Icon { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-family: 'VSC MDL2 Assets'; + font-style: normal; + font-weight: normal; + speak: none; +} + +.ms-Icon--ChevronDown:before { + cursor: pointer; + content: "\E70D"; +} + +.ms-Icon--ChevronRight:before { + cursor: pointer; + content: "\E76C"; +} + +.ms-Icon--Clear:before { + content: "\E894"; +} + +.ms-Icon--OpenInNewWindow:before { + cursor: pointer; + content: "\E8A7"; +} + +.ms-Icon--Copy:before { + cursor: pointer; + content: "\E8C8"; +} + +.ms-Icon--StatusErrorFull:before { + color: var(--vscode-list-errorForeground); + content: "\EB90"; +} + +.ms-Icon--CompletedSolid:before { + color: var(--vscode-list-warningForeground); + content: "\EC61"; +} + +.ms-Icon--SkypeCircleClock:before { + color: #CCCCCC; + content: "\EF7E"; +} + +.ms-Icon--CaretSolidDown:before { + content: "\F08E"; +} + +.ms-Icon--MSNVideosSolid:before { + color: var(--vscode-activityBarBadge-foreground); + content: "\F2DA"; +} + +.ms-Icon--CriticalErrorSolid:before { + color: var(--vscode-list-invalidItemForeground); + content: "\F5C9"; +} diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff b/commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff new file mode 100644 index 0000000000000000000000000000000000000000..6c579dbdbb31cd67233f298992444bea615de776 GIT binary patch literal 2792 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 literal 0 HcmV?d00001 diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf b/commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..03770761ccffad6d5f2b7f840c157945d6b57e31 GIT binary patch literal 5308 zcmds5eQX=$8Gqip^H-WCb=@STN#iqVNXds&l9qO5pp>T73N0%MRBu+ z(-08aUpwpGd!FZgK7P-~dpU&|5!I1NA|3kbaBpAdt@r*Kk`HkoiY67~G56s~BDR-E zSQnkl%Dy*z&k%`m?01eE6UpLt-#ZTe1au-3O2!~BH4{nauwOfom>z#4=f4kp5%=b} zs>I57|K)2$zD?M_Iu5}q<`uzne;eY->^=28JeKc4@GS{FsiVW6(F) zL*QQqFQ=5G`ox_6F!*b!W?j&xReW)-eZ#U$1BLqYuX-(sy%Mh-Nk|!&%okMAzFX`) zwgwWe)dIDDtH?Z4JO+N3w#)^zQf$-t>qGxc9zxvA!QI~Sn;LKky?A+!HCqfWY& z44kv488K5FV6!x^gv82vpvLF*xLuMcFzUWB@7q7}EMvzbFR%;u=Lgq6>l5#|y{B82 zcWHxpcBrR2&pJckcjv|S-9sa75qUac~GBd+H4Ue>i^5)D;^9*oT_}Pp37Z@!T3@j8N z?_Do2b3a_+(H3gvg0@gw2-qTn2s=Eo3(!K59$sBW0uF2qPEtLnQ#ijzT$4DZ9-^)O zkiR|T51kVKT6|u3zH|e*Cg)~ONH^Jb0rsBfth-2+NI zJ6cQ<6GRf-2R$XO*A6uwB;pT>w~vbDqHvq|r1<1$Q7rNo1+WAt;5q9#ODpjHVjdss zund}{gW|(*R!okH4;NDiAU-lG(M4=A1r_)Z;KuZrfbXT%phNz)Ev$pJ`FS=W4{kRU z%<{H_?3I(|e@qvdMRGyEY@W1)CDSaCWST`2Zzhw-W8!`01tLE+!5{bH53&A0e@A-= z-mM#s%}=OI;z77>KY6lf3hfuD1Wq!`X33ODdh?TZX1c*4&YO(pOu=O4oXPQW_lVI< z9q`{LwF;{n)(CZi)OPuXupn&Ny!tv}Lx7h81>uF3=ooXq^Wp=0_B`<7JLS1!$I5dR zj=9V1z}@Ai%TM3U4p<9VFD<2b%yCjA%iJ=-BK4{mPQ-`dgGBeRfD+qiMPpPQC`({N>dc~-{UVx+v7BAgovgnSI`EaKwpHAFS|K;ZDWH4d4wDz=pYS6LceW(g2Oo zQTi!!v)$}*_G9+0uns%iQ!QO>)!ycaY9NcT2v&ZrWmi~QoW_@rLo@)r z2P`q+9}dk(7H@KBf$GGRLyM5#>(CPD#~s>5YsH^Aw3}MQ*BshIY4Myxd#O_zbLbUx zi}avFuN03;1&6MqTfFgKJ|*{Ux@xPuPm8AYj6R-~cj{?FPb*nXPle?jiG)0)O~kVq zc}UHu=}9#f-kpPR-{9Upc}FIrW-~+TL@uGE7mFA32h?;1KIOjfwtl|HJ=hM0)d^jd zwT!ID*|ZWPz5v-2f@-N~A{Rq@s_6ApVp`s)T`8-{F<_Zb1Rj<{ zYpKVy)P$T?GugBj<)bhk0FmZ>U1L>XqlPG1HOc2Dts!bmpGqZkWnoZMPq9m-rg=I# zx{EcJHF8-wrt&m-h2v_%xVWMi!ql{*&KrVupgwVJOhbjkpTFDm6v@;_o9HUq3Thu| z6s0ujl)*htS;%&RO5=N#uL>j@s1${0MSyGOIMoLH4E$Q)QjJg z^$Ww_;!cIpIUc7M+S69QI1&X^9DFZu^S<$V_bwSdV_`PXDULA)^c4%<>xh+;Y*k_! zd=7qiwRk1$idD3^OvetNBNg$pSi8Z;EIsaD0Uj>l_26*~XlfQVZe^oX9bU1zVZcY) z%9+=~MmGj8^PS+Rqu5DRaoHBUmwa-$=0XYYqMZXr^4VSG7HRPxe0+ F=)V_d(H8&! literal 0 HcmV?d00001 diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf b/commands/azureCommands/acr-build-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf new file mode 100644 index 0000000000000000000000000000000000000000..47153a3b8090f343b8f3064c1523769d8d4d2992 GIT binary patch literal 467888 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�v-kPquQLS$%k5&0&zO}1B*gv$3e5zNo znD6l?8AMfhGN&gP>img#`qoh=!=rYEw^GiKg19dHDz)pn8 zKlTL@xo&8Q?+%zCq>)X zNyVd^hE}@CH}vn
    J;=rO*Ig)`QZo*2+;#RmCU_3C;i%T%(tj-&{yZNp@0h$Z-D z&LOIG;`i$xt=Ax(Jqcz~=a)(Z;8fOz+(Dyyg?gST6FVG<58gUI!J8%A-8<~u*peU; z3!~T5E$g_ha-%``*NfcLhRqLuCJv+S`6_*!TAHC#ihPVc;K!2*jDJdPTwV?=AISE? zBaE`BTuVBW%5>A(*rV-R44s6QyCtd+@}Rg?jkQUYwfy-k<&IzNU1GoLrccKI_%p*h z13xpJctC$XuY&KufV-vC_#85z*`IbjxYt^iH>}aX-n3Bn%B^3sJL1S^=a4w>IBZSd zIV3Bk_{e4cRzkL?wsOS?sPM$u*n1FiZIUG%yS7+EG)sD>o4*JMl!lPUTq7Y z6>1A-lz)i{!uwJ6B6+K}bDK9-3++cwa*GZ1tmIohhB)v@o@O-8RQ4TtuYP|b7xrt< za#&pT$2+?XJ6#1@jp^h-P5rdE3=-iI%PfXv%>7D!oylF7`#{&RyRj>WSeo6DDuGU= zoz6GDmC zOj)6($7tMsN3~2Cq6H1bi&OCdQ9wN965r97>{H~?k}g6ges(JNo&AD9v*6IDKf*o{ z2lpeh8u@&*2Jf~P!~_%&LkOs;#LBg5Ie%>c;Sdy5%ejlrx%**+#s^`8Pc-qX;<}L(ySi(T;#N%+rmgpJWN1kk0_FM^Kh`ds_euk zLwkmHzd|x^HE?AoZlkLpeIw5{?`~?c<7TT0scxfrSWvp&c$S@7w}@?nmi1 z@Wqse{KAJO(MajI4erN--^XB~78TsIaqn4TVa^H*NS7Y{Y?d(VyB2Dd79VkEcbGwB z0j?V5|MYqyL#4ZMGSN<)^5=P&;7ac}6M7;*q1s$$y~o^!Hai18F0k;Tq?4%pkXhqY zMQ*79R@!wfdH=rULdA=FuMm34+Ba&>Atn^lC)Lok!y;}n$yr$SP7!K6`@N|GThcyj zhJs{XN@~@sEL!g$+T-DhLmJ8fxd-p9>3oNTkF#BdkuWJy2!w%KZYGQ~`zwv2ipm;T z`=p?M1?yi))Os}5Pi>#6@X@|tHReUA^04b%`h(5h*YF=js9Q{1G{9)J!T7yeOjYKu z7^hDhg{~sDvy}Ri^7r$GX1X;Fi|r97qCT7Y!D9hV8#2}}tX27Q_ItiH)>vMJ$*Now-y+HswkFq3zk+BjHQ>P^IQ+^s>b`XnJRL0?te_a^S8?x z=XSoh@YQ7HiZtqm*IwOro1VJH^Z5%A_GS2o?3vCg7b%g?8uNOb!WB^~+jV{!)7{7; z8a%kJQRQl)&Nb^YT~4gnr(h5O6z~@?{56Ka_?C>yQjRM29~DX-z9<_SXY2X?Ir8wX zs;=w_*8;Lx(`ZevuH^VK2)xewtku#9`*{P{+4c>}hhA0u{@y#v;gMFy6`*qxH;iQsjFciOuh64LoYoth_8_djfpgkPX8pql6IAsY_j_7n4X2}?9unTj zJBx;Bg3$zSri^AYg-B}Fk!Y?{nKYXMb<_OcF-ZTzC-^TuP39ZVZlxRYp@3W|&@Ytd z(r@2i9GAnB(49(BI8_bSb4?rk;*&@lhxk6h3!h`+fDX7BC{kcvv4Q;}@Dse;?QO?K z<#%9B+_s4Xaf|CkX=-v@oLb1lspK%`ZCbacA>enf)`p$sTkZNjZQ5DS1Et68(%}ah zdO9Byd6F6mS7O5K>t#i)y=~!>7{pn&6~^f8Ib`W32u%D9M6aVBJ2e@kBig9DiD{SD ze_$S*VNWCzfv&FuWzIUxpz&@TR@WM&0B8o0E(N1K>;gLeqq7#&-+vgH?XDcyK$p|Q z367I5opFfDW}**M&Njq8JGEw=L!{XEiwZavDeQQ7YN?yHB6X^TZW!tdISEdMz)nfT z;ELteAC}$88pmK&A;A6^1A)yG+sdV>yM)OATeE)H(aW)?2u;ykhr_O7@OUS`3I z|H<+*^YZ?EC&!eeoqeaH7fxAHI1AJ`IU^PTI;wVr2lWG3t@?3dB; zpBSi$GM_aoUV5T-%piDKUX$}D(l3ftLKAgMgQ@u$b;8f!V}jysy2LLm?df%?6E6;Z z7|7%Kr9*(qz-3BxAOEA1Qluxok@L4Gh~N+UwJVp|HMkml77T77!>x3SAg;GoGn?OQ zBp(Hof9*|8fxNouLX*I}Lrkn`h^{GBmXdirFsz8Y>rwWuTUn;NFsXcrt3<2q?w`L3 z6tsS}aNeo>_{cA4ovl&=KmMGcw*imEq-fXl7P}hbRx20wsVtZO&(7TdDSXptkImN= z@=GjI$99ZA@|k%OLlRZT1{}LoM8u|2$p0x@LP@w$qM> z=&&GoxG$bEiUAoSjxN=RA0wd%IN)Ps1Zl6U-YHO-zht*%#~~{P_BmS()vnV*rzPHPU7dm=SaQe5Eb`kK zytRt^(RoVQw8=}DfP-g(c*+(jd_G@@AJJ1O^gr!ld7j;(F#sl)zf|f(pb^vYH89`G z)5!h#0-m>yXfp>Z#xZVixXvuvuaqgKB!(=|Kj3w5sm$YC>e4bc$l82TK3mimi#usw zGEBYxxtT>H>m1UoaW-hL+KCk+HWbs_;?i0hBAP322_p0c+P4?nc z{5ZS?<$FSEg(>FYSGrn6{^Ch#RxTJeC7V;TTo-++I{%eit=3*b+qlfjPcDT`ltO-w zyx`(|e;qD$KLKJ!1eo#)0KBio4I1h=snMz08fLzZ(l7;Q!X+ijogNxl zdnR?2+feFKio|tJ3)sBm{U}Jt)HPi*jZp&1Eh;jhfZqnW-tX{AY?zfgnzc>1lBZ4E z(Lq^PeN%~MLLlR=feHTakhP`MrP9V4zH(cBSL=~)376m71}`UF7qG}XTs?NLo2s0; z8QS~eOGvMV{Bi89=odD3=D+EO%z3rNfgkM;$_i zVu^{vN;0`mEaY+D{8?`H|Gd2XKx&=2`R0|^S503jy?nRa)N8!GZD2iUbWIgKjzOQO zao=M1xZPY%Z0~_Ez5DB(UDNlUj9==;J`G7$7v6tn=pFIQBb{Wcv^UmDI6=WScVV3K zx#Aqg5K%%nxlgsg>qvbsTfL}hT8CfT)H#|}c%ZX|+8wWiDf4*nWBW9biSjwM|2pfu z06pFlE2XdOluMtyy{4N*x<2fC=(SCdcvS4w$!~=|h>iIESpKv@vF^HY+iT3e?8;&E z`1ZBNFxDH7@jlbPwF=E=`6A-dAW$^*<*~s7JLuyd+nqUQM1f3)h=VMx;>l+lL8ZlG z6B1k%4MC*^=htza?9`G? z>*}N^XiN1_9>GNX!j9wyg~MS(Q{N7T<4I!Cq~wDfIWcSVkgk`6yzPF+e_|Bua&%CV zr)LvJ_01(LlIdP2>J+KghO)Arz5bik$5?NgTduobcbG4)MxAbJzPm?FEJDxNw62*B z{Zk)KzZ;b?tN7Uq92FTuT=bH#O?f~Ws}sJTVPq+b1_rutdNIQ9^L^l5&r zT*8m7Cz|V$u1&Xehb@EbR91Y}Au? zBfmn{hc+;9B>Ib%mdIpXjX6pz>Jf-nf1pJFMi@PIR`878vT7FQkVC?{;EJxs%*>*X zp{pm@rr-EMFXG<__ce)!$*={jn{^o0pG~OkKIHD~%XR5I$Yy$IP`}ql(>Hx^4uSr> z_WHfX8*#VQKH{|2GGpomMsLgzjLT`Vgt22H;foTHcyMsX7;gDkK+`2h_w-{YpIr0Y zN5#lfYz0X-@1p{hMoODJeJtq_Hqt``#U%D=6abLlnAr)e*~MTbA97fOgIY8E{9pp< z#1#6Ld1VJVOOD>VlMHrM@d#jxX zxlxq%n?q6c7p;b=jIbr`?(d1K$38E+=Y)i7D2sF7lJItl>cEYWohiP|p4g9f3Zn+v z9Xq|o_k81?BkyX*-RK*{9`trhq4@FU^cj?9)hZbPT$#Q6r($woz&3=0v3!@?Dd;hS z3uT>V5plB_%6GbGm^?_TFXqm+@{2wqV+Pp!h}goSJ7^BkIm~SFCI|+Af%IQqaF*0J!|J!YYq3v`xxO&FP3Cc?ttg{esXzSD}%Gg8zyXS9BHh?^aD=1d{0@AU_%*BB`ty zN81W<)fafkK?C53H<@>VMLiFlkaLrOQnN0Qgj7Ec01-D->3hu=m^etEAvgUmB;`0x zqFS&VDuF5z-i`gHShl*^H=W&OF^6FA-fiPLomAZI{5N*?NbYQTyP+nR|Hc!YKWyQU z4(45e{QKt+BK@_k$S`zXGH_qe*89~?k*GR)g50Y(MBtI%YUoRxn|!_uj8`Xg+KH)f zJa25ozB3%v1RG&|B?>3!U6SCCoOB8v_Zcfkgv>CF3pXyGLnIKH=MdB2t-6-q*N6_E zoI~y-Fh*;D3XLXe;LsZ6TAEx8O5C1$GU9~7D2ISX->%*3w~ux?a3vIP9SP$fksc(S zAP+c-S z9~sPT)nA?KI6aU=Zm1(c3v{JCwS5YIkKhm7X6_W69`0`SVtnWn7g^iqC}D-Fn)>b5 zfswEUeavZl`gjR9E}TR9%vQ0BSHT>PHNw}nk+Vz(@Nq!vo!F6<<8Tc7^##u+>h&}q zZ}sh2d#^o^fwUnNOXU%|on~a|@OS6c-!~~1z*i&~K1f)E8$SQV@F^}{Z*G@eukAAi z`C7sw$R^=;)(r>1shGN!$!~3kWF4D`5==Y=7`~V@Pff>ZmAK#R0km%iTIq;|JHpN9 z#Synr*1fYsgYl?8y;J6>yapd$F!!Cb@D1ed_zApLarSDxqz66^!sx$t|2Ooagy`4X z{YZ+K*VAj7*CYxjpkVI4RhJrBpx}MP0SD97dT79<#wC6X3fqTaLLW0-tRD1>6Qf-C z0hZpZ-&;MS-fptbWYe(cV&oW6BUiBHbO3u}nWFz~PhWF9zbNl#|9SHN>isGyZqdEN zq=Seb9rVF$4gvwF=K{dm<3PUx9nZ$CKE&9F9JvGFUXJYmjP9VAsc(bg0)0p}((Dd@ zd(&!l^i{XYQl}56QB#c&ur;pt$JW!1vXw?-2hhA_Z?>?0D!yy03bC=+^(;N z@nc+sQ?>9W;_o3$?uKtFSMM;6 ztP{+6gdRSSIV6PtFzaB!y&vuSVCz^d#;+gu%hmq@09lp})vzauPValCETc%8Dt-8I z&uhf^dP2cyg>a)S1ix&vUiX!SfiV@~TCn^F2npzFC!@qDY;fVZ?GhxJC01a`CFKc? z%veN{PD*eMFawl2%QVFRdVb&0kDEixKj!f5U^0TSWhOCl%5E}l+&ksK47Y~z_?Qp4 z#|N5l^wI;zEek!KFR{-vKNzk}Fg8K-Y1=O2P4;*)$_Z@8gcKv+jTPt?ye` zg_aDSh&%GPYkDKWJwXCGkFC8ubsfhpnLPfZ_wF5@uK=T$v5WugoN2yy$|Z`Vfrl>2 z9VHn&+K>*A&G*gx5#Z_nMs*Fa8f~RA+cY?u{LkIrW{`D95tJTeub&Ec4e;aCUc zE{5Kc|6hEr3&#%NmP-HPCjSIFI}OTSWDPoz71=`Pl;O!(xrPQ4Z)U!_!T%qu{tsYX zjAQ{k0et}MO&f6JC}Le7ikxElC5&Kc>kxCQrlI-mqw2p&=Xc_*U0wnvAMlVT6!s5J zj|2Vpw5xM5xD6!_6aNp^%{SW?S$jg^z3$5CzSNI5>4AN1`vnU4)%)f~G{Ana8RIa; z_uy0R3$HJB!}!_0OMW3;hXB14}xCj1CnFc=EGp z1YIUe=a6+ca`DedYI>?z>D~xCNA7I?2Tc`WVF{JXD}J9?Gz~J%@>heWC7SvcLkKzB zPk-;<9tIJAV5kkjwnXDi|1L&ARLCwdm^bxxVSPrKq-3k4|F5=*A{^ol`224u02?OjI`6dv( z#3Q6(O%bG9EkkFUyC;L4(xgJDxK0z0M+*RD0g|H`B}^kBi?<^2>BIAUY650gh&vD! zk4-vAotkJQo{&b#VV(yBWjeG=Eda-(*;&c%Y4c5+W6$qrv(ig`pWF+A9+LBke1mr2 z`pT6{JnWcWt?_08Vj$)9CgIL5lQ<9n5B6wiCqzb*nUtO}k8q!HIHsT=pL)%!1H(9r z-f4-0Td+yNpB(~)YdC=6i3sEk(UPHp1;RL#Bd(NLB!U`4*D3Iv* z&P%hB&X*TM>6yTDJL*!FV!N@P_k=OC7cj&xc*ozt>g|oNkELk_c~}O7Uw3LPK_RyoGS{ z=eVOHgeIZ6pdK7@#1s_wKlEa7^eL%6J}n*5e7-&;j_UW($YQpm+wEK^-M^TK3^V&| zA0QvFli8kPvmsoWP!(sdjw~R}_dS^SC@bzfy$$FzJAet@>+EIHL53TkO6LvU*|h4% zEm1HWY#^7yK72!^H)ecV$9f*LJpMQ3Fb*%Wc#55@qZ~VcquF-$^iWDx$N|fk`bL!8 z|L_>(Lbgy>GIpdki-0p#AERdo-#eIMFa9=Q;jE za{e(A3>GlQ?t+47VsZ+NAixzaLjYm*v4uk6IFv|`CnP)mwxVOci`|Dj64cZrVEFp` z9O5#0iq(-SxVA+u4VDa}-j3sid2FE=CP4Oo@bQ_DB7{3FQ4|39`DuONxi`;>U3t^z>)`f1OeV!iN7p%1(ISUG9Hf|bjMH9z>R(22HY z7&}AmBD$3c4dz7L#VtR9qonC5e}gE64t7MtCKi70ZsDtin;lGdfZpJ7ijB|FLwB;X zq87#dHV@#v@P|al_3g(=K~tm|A}50n&YCw~T@&sCA_=ZXgQ7pYERj>bc~S6bK;%Vt z3+)MmBS^{e5OvtT_Q`pm}lI5*B;7xiJ#5FcaIkptCKF#+oKo7 zcd?ttXV&=DT>-L}`nqvSndK>J2lAvokGv&fUlOUSJ@o{#CL$i>rw7KW^SG!yK#u-2 zbm(k$_eA|`8Ck_OXRisEJMJ~~E<~dyjo5;9%h_|mXVAAzinKXeqzkoyIfo$oxHR7# zOOuG-$VSCrdziH*9$pfrW`hB-2nqNLX;okSugNj?e$3!$vvK(i{$3c=XBM!Uzt-)L zu4sot7!W{TCfXV=-07vRnmw%%c zfV2-rHC$-#8zjxn#EJSBJh?V@`ChzbHFoJ>nThe(@~CMa`wc%YNVMZ*Z;{<6AnHLkdpdOEv1{^BqITa#x|mANGvWEawcV^jl3Ty z362>(;61Fpo2j1Fw=T4`#3XLs%@j#nG=UQ2F#3`vz#QKHI!t0{aP}i4IJ^NnEn5(M`OV;7SQe1OkPBIkzsq?^;^O+)XQB&Vi4dmI zpXO!IT|kzbqMpyC>x!%w^zViIYp&w<%LQkJeIxkp=|tUk=QYnhjU|a?H66RD%Odpa zEMG6+6U$YqA}tPgi~`&A?g@lQtl77Yejy4*FN9mYCZVKR{%d-Hee_80$3>x?>>?fA zKxIJgB75AclUjphHzYMW*l}-0<9RrcxEP}%GDJX!$BS8TgZ0P;K0gu(YfU{*R(hwv zitTP(k50$TT6-517Kj5d2cDk4!p!(L6#H2Tc+W&-^C81e2s;aVhJQswQLLl;?`;qP zTnQfS`;(*hlaL@H4NMsZkYGQPknSBU{mrF5c4Qq`4e<{%swU%38F?9!6(96CFNn_z zgIl&Ve|gO+QhZ_b0x);<<2UZmk++mEBQJ!l#SpvJXomQ>;hP;$T{$gqj*SBP9d?Mr z6_L}Cgm+@H&jK@zrqNJD%XDpKEjka`gnP@T zpgFRIT|7D)gh#svTo($_FBei#fUaE-I)~6}z#;*CfszX{#)YGv34)Z0) zG!DWW$P@g25{E}|pQi5JRr+u2y>(PoUE4Rh5fxDcQ96~7mhRYqQqnCg-Q6H;5J7U& zAPv&p-O}B;DXC2(EgRULg}3+pKF{;M-x%LHyv2>?Y6mY(h~sXWrdZ`v z`{rtoUk`$JyQj767l@}gn9lJMYwi{BQ=W>c0Fkjjs-oF9i^_Dzy@^& z=W_Y|xpd>zT%1+4mCq|imo>B-5`^J&Y|)v41VRjo1sFOBA%IHz z`HhkSoxtej_b5So`Rw6854u!L)#%$WI!+`xo}Oh}3cf2eSnZ+V8LGdyxB$aBOBIM= zB9CNj^syOmOxYf1__p6gY^JgAk@dMkJlTF|sIK9||7LH}o*xH=%$h^=m+Fn2TycVpdXCYQG<-57s>Ei~hjUbEQ z&F9N5E9ECoj-Rr|tu6b?CksX$AkU4xLe%>1ulCrAAfX;kf;ZdVJ%w>p3-160JW+o! za?3cHXV`V2Qe|b^$#Cc;Ibe96kHfKyZr|5xICZhE{mwA&_6-hLrPVaE)$cp3yPyk@ zk^1uZR0AWmluvsS>Sad6n*ER+AhfRvnY}hugu;AA+cFqMG!( zSMBV#X8>({R?t^o%{$bj$J%!(rp`70Z0~Fgm@}p=7l{RtjeEELIba$!m2B;nw-Gjb zsb#56SBTB(u&_cK;2Dl1=E+sqcb1vyd9z2>$%ZbjHKHCL0wZrX*7Y37>k8hu zjMFvEvgesMqg|Jull=m{oxQ>>5xEl3#=weEdPJ;Pdqc(?^ZvtEDCGIMZR_e_-dbzS z9)5!(FWL)zAtZ7$M@v?(Q2U)x^!NfIdiTJ>^do^Y1DKvePVb-u{0XobCtb7NyO5UU zkzLtd%0~wP3wHss+E-V;a)g9>l`qdil9ia^ig>v3cu25L9VZCR&WnTT-TQybO87a` zyXd44(p+T9NQ>uH+DW2bg~e6hl~@4ZFHleYZZhI(x*5l@rF}2n^`eq;&!Y|!x7uSy z0tkAO;cqpN#idyzjE~1}AO7<~=*wAg@E=C|vTs`W6g6o-Ec zH!Bhl7v8#ZfQ54%4^xrw>&>QLpw2#ew}5R^BXc30Zq9k&WXLehFd(1@IQM1Y*QmgL zUQdSAoj;*k(U11CUc{cSK$b-y>_2yV%*y3R?Q3l?%8j(%V1`qy zH>B1nhGNd<^cCzpnd`6d$Y{N!w5=kTSvTlq^__eoPwIw%mrJ`Df+R4^KyCBs@0AU! zYyBUJ+2UA8QmcaQLeqMc2othoof^V!Qo1_hA3X@of~Gs`N`Nq{N8UDsY~hzw!fulG z9gJnHn}OBwQVu)T+XZB_@(h6#Oqzp9BP*8h;jV)| zC%bZ7KjIkmL(6rtVhMY4$3sHbH=A2EjH;`rG{4J4(l1&oDZAc%ZwL8O7&lWpkpGxM zV`etr=T7x!=p8rI}_7a*+^JtNb$zlOI z&+egc$Z6;BC3{+PxA(_18JWXq-m3-EEjkP@W0{_zSO>dzQS7&BGj(>VfC>9Ci<4n= z>_fh@!*E~&Opd`SBQT>HS*C2S`!#8Uz~m8Tr>ZvX**Q3E_iQK-Hx8WqQEFJYi>HH_ zomE&Gg#5ZgxKXAwJ~zNyY9(QpS0SZI2LF z^tTpK5wl8xdMY4z2z4uoIz)9UzZ>7}}vGLxHsX0P%`tP&c2!j8b@?Z$cy?A!Cry&0zuoUhcA6y4F1B3?RH z*9&@qN4{JAeDauBq27FX_@g$JzQk}H81SfJ9b4*{*Cfh^GVL=N*~XhY9DN>e9_GIR z_npReo|0Ne*Cm@d#d&HncCN@;+`WiV0-fx?)5lx(hEmXGCj)xSDMVnBrd(# zx+cmqRAuP6t32D)@adXLs)oi_#z3@wlnYV_P_Lz9?MxSCd_uU$IxNM*Q$X@~c5xJ| zR1OT>%G172?)IT|U48h*AQvn+A~Nn@Y*-?W>HGP$T%I*#ZH^Dg$?1iovzh>J{sxrz zKWRq-dkp~mI!%UrHyPNp@q2mY14t+UKEx;AN#CalCjy>H(Pd!ojA0)u^Y@3~|=I zZaf6!x)Q+0=X`*5Egy5}LfXKm_X z`9hB-wlBSHOoDI@!lpj1)7dn6Sqf6dT@I98s)GUh;%h(BdU-m|+OjSM(?7bbt@!-q zgKyab0zpWuWx>9f;;dn|8tO*SQ(i?^8xjckZS|X!Z*QP2JJw7dA3*x_JT5X z{o|A1OyUMue`UD=!Ad|M;o>Erm|y!_zv3FAO5{a<@I7!h*h{fW1ZOy%OLBw~SQYS% zMQ)Lvc6**CEPK$!a4p%C=l z;6eFk+U|%!E6SSz>#C6>k{DiiG7EXy3}y#kKSLr=dLP>1`M91<#WeS zQ%<3_0%{`$nq0*9U znA1HIzQR~}q+b@lT4OZ2I$qPHN&ri@JEEHHq}92Awm2-+z0?Z7wr{(aNSEq#yWva& z_M7fwWKp>{r-H4W-cBg!3$;5Q3?)K!g`5gJ+-sJ1it5&YEgSrLT?ORGy*7*6Fwu4x^6R}Nq_+c;sH}2#nw9Y0gq-B8 zOFf|=7Vr^-r9$LCp_BX&)BddjDI>x*_;T(gLVe81^X%wxmc#b(9V4LbhCR9s48Inn zEs)~IcMPkqH7CmmSxD|FdKQ_s%sd2?1{NbdEM_6Sj?#DK2()TGtUhugwO0-Q{3_@d zD1t+$68UA}7*KteTiYJ0$2)Ns_;_QmE?aCd)o6F#9=#JJ&2W<1mUB0o%v>GtKx`T* z>=#Ic(}cWX`LOn+VCGgI@f<7wRW`C3pATxU_OFrEg9u`Yd;p(MGQB8FZ6AfDB3_A? z>~df8OwE(Plu<{5veeEJKAd0Sk6v8bZ>KVd zd(l#CvK&U9IEKsr034f()>*krvD)?@(RaJtA*zK#1ICWI^P)3#H}v~t;OBHYt*+p3 z+*}2%`7RqFG2-&D*?r}iVo~-_IHvUjT*)OLgK3MUD9Lx1Z>Esd)Ik4o;ERkQ#Q?A! zfWCrj^N1?xJV2aS!;WpyZF}PS#^mz=_A0nBwSEMwdwPsYhn7LWi8c2_NP@2KDzkiF z7~Squ#N3wKh>e(<(SuLe9|c)^$mL?q-&3$;A4`Zz*znxYvttBMgPsVlc-Ws6(cn8u z_6DNF^*#?6>E|O)ibQ%r$OVpEuR||VkZ_%{eGci0Psj=QL5IF~6!{>GjW;^CW7($+h? zLe=W36)jB)akchc#FSJ)RGxmOBxj_Rm(#JhtlEt@i-R~n;L+yJ)}#|wiE~Udc74R+ zY+e>=*A&V^8MvMC_H11=UyAf^w)r2fw0IxNq5V`>6;qupa*QX#)nKaHknmlv4DAXD zM9s|KJwE4{`LVHe($##*we9!>Y6K;Zg)9)Rw@s@0(p3JE zk6bgog@E`p*E349H);<*p+cJE%BKBVD0XyAY?eSa{cUtm@qsFd4#!VpDj>8#c$%L+ zh9RBn$5vjQDit($5fXRZasixJLdwbjbLK-=&{^p>yW%aHBCFT*3q(tD)}wn5#1|d9 z7CL@jLNTBnbXNaLZI87fb_F{g==WWF@Ko>!5pES%msCC8S$9lWxtw2>9F}`1& z{ql6(faRjKf1vyRNP>&XhoPu?_)m9oICKWT%b_Dr3U{cX3e7!e_3?>yj{`_ha^=YH z@bmlnNl@V^N*&Pf+A?^pazB z;tN^%-6C16HhsvkigwgZo7T6@DmLwWVez2rp_y&<{N_m3MSs>+sOY2a;;MBU?bW6W zt;sWfCYQ}|{=%5gGHF*PYA5K+W-j-`Dq$~Id`I11PvAuUtW>VNqTpPNanNA|`eNxZ zRCleUstKupS>_F8GY?u7Bluzzw+x>Z6J%zzRiho9yko{?p4eGL}7hok;@%~}Z?axbz)%cMrW+H`PMY!ySS^JUeYzog~U-q{g_zEtQ3$CmqzEWcfNs@+cj|2?Kf81PpI=x|xOC2QbsZ zM0=w?@&0Uu-!@Rl`mMg?f#3tqjTUA=jpm#nQGl{bCTpO{akgHMEnAj)yp=ynyayWy z29?@PHl2Vr9|`=cf(c-6^?D`~lW%0u4Q5Q^t=QdKxL$^u4`pr_V?=!Mkw8!8C78RA z9-{y`2HY%*2GWI*PTVyZ?hZbP zSN5SENMYy@NJSwl0_|=xvTA4W$F=$svm-HwY$MTrlwp_kwbE+yaYAYJy2fgtcXBs5 z-*<6EK}$5@qb#DB3N5`(tCEPcI63xEIAA2A#}~DGOf9Jgd$7B+zX02Kd1J@5-M-Rd zO1v^*BA#_hOG)<~aOou|iGO|CI=9(U%WAk+I5Ij`29cP0qEs<)ep9hSzXz)vXHmc<<1b3)Z{g@&=a;=31uo$i6*yaW`?u zzpo*2iF_aRePptvzdAea3Hl-wji;By`P2nwCT<{@OeCY(pId7c}($h z+?&Ew^!as6Pt&yVDh^p!oyqEPT^_Z8{ikmXOhZ}3dNDv=b2R4dq*xQwK6}?&*WKYB zhl)}4>sXpz?iZnW-w)45Du(qVowBJ4)+1XZXF?H2^7Yf#q7AjPjGG^gt0fcG9f`cs z%dJwiaS3!;uwMUs&cDOpXRXz#tXnDJWr@SK^HhV;glXv=0ZL_R{XWz(>&Mjo)WMg{ zW3ebV6E}^?_goV*q(BgD+K215@SQFw>{ws;vYxW_PMVA;cb{*sjr%!yEQm3%qI;~a zzpR~XO3C`w(CKNF8OldS9PP-De2&v4<3ev)Pz4mw*ip(Fq|`{+fD}rZcXbXdyHJ&b z-W$G$<+fb(oSioSzlM zH@IXn%D600F>{!_kqlwg3C5HlBqR;D9h!TvyqU4d7n*mGGp{JFpl&VT5%%p8I&B5X+CVEY)6S~8tM+;_mMyMh(0FV9) z;#ce^WbFIu+s%;-E@~~4j8%;ijgznT_nD%JH|rwAL!y1-Xi%f>35_4mBd7Tb`2%gN zHwd9-+I&1`ckL``x)QCIiZeSbjgBQ)TB_rRLUXpJXv9s4+m(dxuJ|Q82{O#TzGi*Y4t52tn7(Xi0A_D#+#&RvidOvgFxRM6jrJgjE`K&KY%Rd;OWXyr(JpKbPeJ@gR%IOWphdZ z`LNoa8-Z7dfq!S3KQQs#s$~L2X!(b-SD+EF$QFbw>06wmh8dg0vxPWD8B2H6H_9+` zsiM(C3~^~@pZQLn7FLI6$QR4KB37#n#^A*+AW24@*%Akj z$o>V!5DF=UT&N=^V+jD{vR|Ns(zE>E_1LHks!bDB_34dG;hSGB$xPUN`{1zBO^eUx zCXu)cHRm;=8uJS^MiDXd^7U(}ZiNktnYyyMxiLnc=%~A z-`BZis-k2H^x^HrD(?noeKIpgyip6ccg>Szii5nukvvE#ho%WRCLf*NseL9_2JPDu zsA7@K)*!5SN3MNr8QnPEv}+kxGjT9IMuxBDqK+Ziy@~?oSqp#tv)*Yp;jAao(2D@G zMG9|Fe9*43vS$@cBzuPsy!V)8g+u1x(~}VfcIGmwbxe7(vo^On!!CRuCt~23TQbq{ zW9aeJ{1@`R7n1d{q*M_bb@WSqB;k5(_TK+$E}l!|1K>y)w0X~)Y8o_%mYibVY>*q8 zT4JV;!*oR4@sn)n2gu$YC`U$4`!MXxYZG;+QK$62GYc(daPOn{} zI{rkDQ?_Lv?6FN`p&Qa!R;`up@DiKW`^#?V>1xTC@RI>^YhFjDCM8>);(C+?L9A*u z>t}BvG~P)jOTwKR{@xRag#;^yyys95tJH-Rz(&JYus!90Es)>JUM0u@7} zCvVJ8|AL5+)wcOyTFNWbktoiul!m1NhpU11#Nb;(1kCGi)px#YYG9P{SGmYbFRX3r2RkxlsaMfo*vz|Q3n z-Z6(1=0*4;CD~2!vEwJD??JQVM!_DsU1j*IpEGW5RQ%bB`TgBK2>-0m{ji{3lrN_j2qkMLB3T-X~-qQ!&NGKrE0F$c4mN z?xfg90qESrJait!mFm0dVlK}}+sErlGICs$M#vZx??2cjcY?)@-6$>1Y^3?e{+&1bimOQcarJp z6E)dAg0m4+)E{SA72$g`n+IkGoAzW~Fyk_iJPouu1NW2_QZQj-xWMKD!@gYGfiP#2 z6bV-GC&s9`O^JX)7?vcik2oL}e&hY8C*fyk%fP-BNC?}|-_KYW{aOix|0S+3m(F?+ z0hw1SkdfF!G_(sez`Ia-=|#I?a6WoqrlS@ScWJ=-C4>u|gp@c~;W7iGzmg2^JGLV6h(@)D}KMhVMz;6tGFm9`e9%L;%T0Ht-sO zfdTw)MU4$@_K+92ew)ncPd=X+e#$T^xxJpq`wox?4*Et4!K~i(0VmS9LfOhag5f+ov`89I(WK+8%SUpCcaKQ1G@1n2 zICX>u)B+z2aLKSshvJkoRSl%NKMBvuU$xU;1U4)vysy^8?^&Ryo3POs^!@#7Sp2<+ zbpU!oD0#YVk|M}kmTz11jyO|qk8WBn{4B#Z9V+fLVWS$ZhS&KF@fXd9c8>2)GrznN z?V#dEsgATi|8$q+#&Hxl%r$?{k6M#{hW?1-v%QiOEkCALfguDVilHXOL5qa7mdf|+ zT7E;+g0++`cd_Kat(M~VLhY4e6?nm5=u(zBh#uEn}nlq0Av)im%)QM$2&rt0naq;1z>a|^Jz7Zr>bSe@SB-0}1Y z!|JlCcZuJ2+zH~`$za4uGo<%^>$Nye;_*ZdCX$Kn2^Q7ZvTTpEFX!9SWvBbEl_#gi zDIrvrS8WG@+@!fs`y1Ypurq=bE}=B1ez`l<^*xXu|~8k(=F}h&=7lYctprD7pVz|EN%<$APTOJj@5 z)wu>3re~L4bUjMK{<8i{Q=|fZ!MyKos`!%!kC)659UWy=acbjzsB6?~G=2vXya?|P z-IX*+1=2M0L@N~;Y+P(|?Ww6L;D@K1lhO8u`Yw`aY0m+l<8y%GC*e;kz=5w4c@nAl zSwfQ}FVQUIm~40^LsOy!4FDhfyWfWX-@w2B6$t$Q%-`y9Zmmf1q>-O!fM0aSc_Z~d zjPvik*1v+QhxP|*1gfG^rA`eGtuhqWYcdgam3*jp-GBXjH6Xn{`t2rUNu%g8ZWLqLxU;Ny7MGL__{t@M^1}@)@Vx6AWi~3aCilMTYYaMH-T)N z?~U66jn?(XA}e5B;&!h7gUJY-=uXl{2$6idgC z1PQ1|E;y~#FA)DJTUJgCB{;}iZO7z;c+|Q`wwR?O6%*%lM|tqNLBUQ0cYuK6*Cc6J zCh5ebQV(Oc-_?yf5T|GWKQ()5S(+P@Vt`k9*@=Bow=FYs&Ht*hVB6K)E;2hgX->nj zb$eSb){OSsYa}GS8RA zXB?1W&p9eh{2kquS|*Zf0q<;kW_dStPJkPdyW<+KDZ9ri;es@Zlu7cjI>WU%5v`m2 zz-^ziw9vzN>>DKdo>|ThhFjPfM)*nZ0&`SC+~+Eu^Q}qS$Gl$d+0m<* zk>+s8GHL6VXX?*^j)BdFKW*h|L*5-x4Vr)r5_DNAA{#eGgqj6`wu3ib=ROzS^kvg` z`wK0f(Zos6ye!aiGdS+hZfUA+4&SRCEEZsUDNkYt;ZrQdm5&ul1L}5r9W!5fTRKDH zFOyA@yzUogEiLWfBLXL>nKmx@zrAGlVO833P0Y#7cC#T)3JKAGCwDOfyhw`TC6_k}hs= zH=8lU{an~*+Nvk0o-nAlU9sK5K2hby$zNUB9GVa_D8)k=nwoIVng9JVr~If38Cd&F z+7w5~$UvP@J(m!ycQU!VLq-;L(WU8KR{1r%0LQHV`11ML*O`uSt)JfxwJiPVICM2T z(I|6ij*EwUM#zKMjm+CQ^!RT#mLZ#I1M z{8{wOc9$JlkgPRk?yA;$;*R|tDyzuC71S9Z_`KAbQdHFk54ADNq`KpntfDr}CQ_iY zNQjg5U09fxpiun@^-xoU)CH+aqQ%Ul-1+E9Kx<7<_w_n4XH=LJ`COd1(h)khsREbiQSEyeG z!|O^OtVE|BL-_EySa-;9$8BCwB@e)@m^Miw8v(C!c&V7_cUb2Pq}rA_&Rlab!}yUu zVK?jkbm|gGgqy=-KK=+A-`XsHx5eaR+VgFW!;-W-PjsH@fo1uI?xE}~7qx-A)aT5y zd+(4Y_3dBhTSI9KH)#&k#O{cbz-w!3-)>nc9q_-=np4t(x^h_hSj0{zxjBEDWU`^u zi=pg!E|7OxLa2*K8LN&BEoOhKbyX~*EzR;tJED-d;>34Y1Sqpeg|j?r%y*KUHlIsn z7(1BmIx7XQ?MIZAKTPRk)56##A0c5e1@d$Dq#+~*93rLrq-2L}#JJ1>5zcujz8u|223UKS^ z$)Z zLlJI6t;SKL>xVq6%aCLFsp|hVm|psy4a9%G=>N(8kC*WQy*2?@86Zh`6fcemZlJ@>`6*4oEji>W% zBbdH58ce=zJ3(&mF4Ro>WTe|>VdedG$jG)>yj{IPu+M8ASW+$z$?X-%7s=K`uw>^; z{Iv0qSKl4FaJ6sm*7EEgGlR455Oyyt*0;mgbs=vV8~q1xRPFDs*#M)R{Jx8h)CTVM z0nggRXt+KCCbmlW6BvyoEz+GVh4z?c?S=^fKVX)Z`*Zuxyd5v_b93Jc1OaJ7(%$t~ zArljS=3W2M8#2CNwEiKa?Q~n@qhqp8U0?4de)!+RZ=w(H{kb$A)4{tKQcEcCA*AQ` zcM0NZ|8-^in3+8LKH=>zN8}^jZfGkG@EH|V2cKQfHrhw_^fr0N5YWJHn7YN$ZvXyk z;z$`llV`V5qOR)-A{5t0Mi5%3Jqx-m&3`?u;LZ-z+f3Xncy;aWjz|A1LH<1dKTzbp z(CV?5hobSqt9M@uFP|MD&Yf6=StUufj}FYEsot?NG8*K*s~IRMYXvhSe(z5eHah+Voq(u2@C8(GkmxLtA} zQ*0l3?!9_?`p{%)q-j6??pG|FLzm^4F6Y>oB$Lgxuu{;z;DXG2mWsT!)lP1 zz@_EL@VX{)-I(9&5BszJe=#arH2C@ky!e|+bOH&tUxqOJEhB%EvI1?9xxa0du+m7qxWEsY8q?J zN$*r7KcNranPFqM^1lNQ-7GTKJK6;rapleX`T>pw@PEuz+E@G@=LqyFHjZeTr-CHex*zOtKje+ji2CQPfAfWS zW!tjLyJ0~B<>SVhb(?N`QS-gWoON8;e`r>lyCk^IIP9yTg@!BJHg91~d854y#2wxm zJSVE|=xu)?o4#Vo0TjLc#ZjuoJ_BB|nr-QK|NG>%A6nvyF?P~Jvqtaycb?%i3W{iG zh$WR2P(sh3)t2-}Q(It_PQdz~mccCQr9%IX2+v)zoPa1!42?p9Pi0K+ihkk6=lSH#1f@dl7DYr-1R?AkcBSL%A;s=lV?4MtJ097$bx&s%<$bP zJGfLtMUvd|^UaYOr{38Qug4$`3w0w%LHJqrKf?syr!{$_G%T98Bb9#dpR4~Yc*s89 zWlsv37x%-+IZB=3{=yyESH=q;iuC@h91v{%OI!#u9+33<%D-;(lUpWJvC*!T^B8l& zGx+Z-#+u_VbQ;*S7Kf8&<_EegUpc+}%;EUzd;itbf8P2xnOQf2Sspdy39{MCEqwBM z&_{^Mp>yv`>mM0QoKKk#y#(au`XN+-i^za^ZXu}=%o%U#@Bo)O_u513qUG?&xnSP( z8o)N(EaXZ%+QwhHYVDd%6LEeCByeDqI@wKMm)&e_FPCcO19CBdbrGgj&toQJLKx$8 z`0pA^zELhG|C`Y{u9S0#xad_fD7#xBG~8lXm6AMYcAO=q1=ryQSJs(d4JyGnu(4)m z{2Yj`M_(z;N>H*o9d~c4iDC-cOT&~8KJQt$uvcd4xf&&7#K(}lTKR7BYCDkbHqW~x~c;#91=;P!8QQ+8C^y>aGKHSd0i zDF`kJ(1{3RPF%br|Lnk^OUOv*(?ubl3>}GMB!L<`&+vkMVx%)Ip~l!tV^sc6rfA!e z4b_!YR(BhXtP5mJqgC&{Wdd45C_7UnZW!;-m%{e112-|=_QKRrqb!*BJ#Cq&V65sq zN|XYLTh95#bHjO)o)kA+@{OvkRo375pY|&gD%A;`t`>n83PRVI64*jB8)(K`*XH#u z!W65wysJIm0nh=f6B6J z2BPXU*(^koQU-lc_2^b!ktx`8;_;w5n-oLd!p8A^$-bUy{PGY_Y8H9M(BtPc!@L5B z^P$(B8r;^g{VK2ay=%D4ilj5B6uTG_gocAZ^iJD;@q{-ENwDsqbCgoTdiyCEUJzqt zke*0PGwzKdOPw=sGqbjNKcNo^GWKzKMO~1T$z$v`>IO>Sr+0Vo#$xY<4;FNwmh<3pr;(@Xj|}6x6YOi;=stzupWRU0G_AIf z?;=b{Sea*Y0*(@S6I7cPjBwY;eNKy+*j{rM287|B+-6o3vi9HJ|YOS~F2C z1&$SZG+V+nI99laDp0~ESg? z-giZa6?Hfh72{#-ft;oKmFSE`ZNhPddqR)cMNUbs6|A!PfzD4OO3Fz!i!h$5O90l= zl2Sr5D_XmC=2~zr{@kJAwY#E!#KI4k^X2P;{R-_@m2@ew0Ez%he;ujC;k?1}L(|O| zJUPDL#2WtxIf4mIp7GTAaep|8DMNY10WQi>h_sVuA$tJcb zKI60~fH~Xd=W6SU+3bXprd&P@i4InZ3T`AZSl2ryPsAul#oQb^XeZ3!p~U882O&W{*v^0pWtCv)H^B)ZNP z6h=D1Hl*$i#svimRAY$#&~nWkuN8u!Knb}?E4_yKF7oiztBk1dL3H*p0=|>M8?i(# z8*t5EAiex4qABK~f!Z?!p}ijwhp)7F{M(H&$-9OCP-c{B@TPSB{A&`EH>`~>o=5P9 zza8T%t}~l4nu{=oxVY!zAr?QbBqs5P|BuMn9h#zFmPR|#7^;ft9^-3)NanS`xh$`#DJYiXQ+TN({i^=##-@|#$@2gAy~p;KMk(#kw1VmwPV_C{+Y$tGTUK=cH34V*KQJ7nM&TU{R7d$ZgN-bU;(mMldFJMhm@zk^Q}e4hwE+ST z$X~=mmy69v-m0UDYGp$jxwVnZPg5qe zE`4|jar(T!=T1;x1vCtc4g<#tvd731)g(DL5$p-cNFI&c4@pK{NZl~}81EG`q{TS7d>X8^l zm9lT}@tWv^@o0aVQ8iB5?wks)TuqhJpkOCh%qtxCPke7n_6Mwy&u-{!nQRW5`aXm+ z2G&sE5gJCwALqg>T=BUoWIoOVJ$)C-sQdc1d4rtmJ3b zkT!PWla>ydHjQG%d|@_;*GGkzc$_`8q8i0aGvJ%q_ufIWhir19XyD7t#RF-!coTj( zLAvps@gBEPfB%|_SEF*9BlfRj(F$!xPM(Gtf0Z6MW}2^V_Yq+#4=u1>Ab8>(tG*YH zZka1iu`t?8h}3Uu{{ki*qMne#`3gaHyVi5?cp zGUYuu7ABEt`Io}Vq2sc7+9IyFRbe)v19g>8QQ+y>S1~0?wU;m?;CB45m{N*Vr>?JH z+liY&aR|mldaMC`#59{}7IT%2uBztz9%IfSRSSbc7Jdq1#2CoLeP>l^UHv%*F&zgu)gq?Y{-{vF6gK^%$4~| z2u=s7IwAb{#uCex<{VNuM_hodXNjnu%O>G^P|RrlKSbzU#&HI5VE=mu zPc@yXdLm}BJlUBHsQ+% zz5IOYn&gnH-jzq>7v;yBem6;*)Exxe1Cz~p_H70F3?)MLhSYMwJqaV*ZXDJltnBGYAf%Ke`9|ouXvGfg?4ojuT z67pNyA)4lrGxZBJ)q(s4I?Yo)7)337b^J~0498jFeFO?OcyV1V8yA9T~t(co>1LX6tmvAzuOi^0`S6mm@t{-d6y54oksXkj5q5X#OH=C!M;yg7CI&WmD0F^$92Nk|Ap$$D_N_OQM&twr5aG9>ff#Q~pq4W9jtr z56r59$&W5-zd#H@kBec{Sx-fAtt+Gt2y7k@?`QQO(|;8tn6t0#+nAyd2Tf1MXT_+g3f~bggY<6{j$MP=q2-BX8k8nWK><9>!I){iI!x zs5CWfe-8O}IYS_}*n9T`^5acQd0k6`6<^QOo@yz=K0)u40?xAf*m(_MsW__HIpmA9 z@nUI5$MbEb)pup(pYK)fDoEsOhwvQ0q0H1tk861qCpfZ+wD%j&uED+bvAQa;4c_2Q zF>6bAE5-%bq1jkRo_;$GXNJDb`4MkC%DTfkrk`N`xnuJnnI)~NfeY5TCsDbUVm3iG zZR<`qXFDZs-*ZU^*jqaQ9rwqRwH_$6DMW@qWfRDl#V z%=0BLxaqzKe=0+du3K@=Ypvcuj^Ohb2Qh~DEWGlvOxp#{+u*h>M78|D5p`>MU2_AE zv1A-uf#k5%t|F8FV9#L8-2?-L@Kh*zzFanygGAG}BZ5S6vpzvV+Nc8rKTU(#_eWK1 zIdT%yWl>*Y(XDA`m&7!x)VQ`3&CSId4RiIiOGiQ61!ZgYLuRvDeGlJ`uLxA&)AM1R z7i{Ofo%e&q`MMZ>l-ukb!6o4vkg{z4`smdev17ZEeWYA+Ui-mm>vy9rB~FtXO=Lr+ zZg2+a(9mGXd&y4P4Ri;GPUkM_k__suZ|xht6sACisObc`=Jxq$gU&F-kO537n!vok zfiP;do6ZqSVMhuO-<~)>b48NGU3#SE6q=_Utt=mMYE6TDVD((f zcQDvw^MO)**-2J_q|uWvzM#8z9rtCt=twsrdOyt2x3_Ibk{0}MGBH-W7assR%~f`K zzc~N2n4>5m_9o)z$UO9jF6OCUKEbvcxN<6C-t^>AC#4PRGH+p(k_ugM1m79i-e9=o z@q2%RI_EeFm%h;TXT21u^G-v2^5>;X^!rrZnC?Tm5>ogMEc(etM9$>uIk8dKJ^MS{ zK#==6DP4uvyfbjF)FO!fS3}sG4e6{wYSriE z*GzVCnkVsO`a12NYtvbO5{kly!$*#}!?|s4nvW(pBsd&xj~BSQ=V$l4Xz#qejV%6$ zDez$efCP4c^bQC@h3>+_TJJ_1eCsgs7up8_&F9k}l)MKvK5cSi6tPq;L&|(_t&OBK z3=N8uNsL^{HOIbI#?s0zNse=x;u`fo&K8|TE z==)-b<&p+dqW1EV4#RxTySN1LNNDRExn?^pQ(5l)U!1*lR2=EMHQG2q0|^9oNpN=! z4grEoUB5D38~H16)f-QC?SxVuX;w`R`#=9_cAv(~-q{?ThS-K(j3tLlApKYLqv zR_=UHY*d2aAIK78uykyj7~H&Vt5CSJ@YgJ|rZG)>LutPRwhWi-C>(?XKq>^@mhef) zjRx8hF@8#y@dw&2%8#{7>xnPb8jZ4qAC!+*WSmTlQjixAcr**AwZ}c_RLhUOW-~jR zUOPBA%BdHLy1XP|l&Grkbw70sRta#w9u8F*k&Zh$eP{h9BUrit1pBf#)NK{n&mMH@C=-%5vx=Utu<%o zGMq}6yvCiX-t`!<@>u();Qw?x`lp7aeyTu#nTYm(o0L+h*DIPS z6AQIu3)xYE3S}U%%gk^~lQzdH&^pv#!YchGOWA0Dg2;e@{fRXCk?3S=DOcvEk#QSK z&U=@`ayN+rZaBID$($(Me6E?#CYFjdiON-+SSu1#yi3#1YL1a*x%nf|^Te3){T0`C z^EbTJeuY;4S?QN`3#y>}8HjMrh>1&-GsZzAAHfm9<0MLOQ;-~5xd~CIinkRO(Morn z*f;(@!h<7;uKu@6`Q=NcEW^%JiUee)a69Jj6{h=@rOy=M)EldU!6jiMQjTw=EBnNX z_T0}e`CLX_=H2-?%len>^k&V~)z%yvupQ{mmKp2WpxKN=+$?K6B>8F2U;8O-F{%yi zDYa9A*}@=LeCUPCm){&`wr9hD^4Iqo8D=JNMpibK@RIka6A-07>*7ewU6BjECX(<1 zvgsmcj2^CfB|Wj-E*be=QA+&IUK!feJT=wN#UP~#7~p*oJ{M5hAejQBFs&jg#BNId z9rFh;k_0#0)Xo@UcU#-L)WJ%aNupF4J2DiDZu?rhooRoaRt@bvM-`vuuQs5Fyy3oW z%Lo}?%IRr$PH)+Im_QQXfNCX-iP8#&YD=@ZrJ?=_mIPVm@DpfI^4C{nc;TL zx>b}B*BtM@qLsre!>2a=rR?>3H*U8+|9BAi?|NT>s3Xn{g{BT4$u||L|F;StMmcye z8C^qjCLHFQ)V4$J;2qNHN$8=yTZagF$Q-9LB;?zpPs0toB>* z*J(ShOJ`gzh;f7CLFkc#ARsDDN7&p!9R)CC;9$Dd)y2R|Nd*1h zekADSpr|I}#@W9e`X2VL=)G3WwyAz*wivYIdW63_E45fPEo;=t%6j>=P6mC#F?E^) zY*|`so?zWt^BFIx2d#`U0sc*TF50D_bumQqchFA!*S0=p(LBcRhS7(EFA#pRwSd8BJ^5m zhzZbOhY-bX#XAj^8wyq_TK6 z?oPb}p6&SRO)uBdok~Mf#&Ba3wMiOLqBFt!v8fg(XI+PefX=-iIjqL~cj)bD62!ef zy_I=0^*K&ImkQb3k<`G1*xP5H_b)4^R)dA^Fy~ZN#y?{qAE=={P9W>aRq$@fkZW!s zpt55R^%EHwXh=Ex7KC|@Ty;!J=H(%L=^)9@l{?}_t=h##ImL6Fo(o_tEx&m;yHyuY zyA-Fm%Nm8OanD;GTudyvK3XsCNSofP7X6hC(P|a+Po*q?0bpqR?Z0MKZQ*Q*wc5OM z6j=-YzuaRX-snH^_4Y>QL%>K*;eU6A7^;5PuEbum^7=2D{wKc&|^l)BZ{?aQ}hp}z{N#oorHO40;VMWF-ApbDaiGrm3 zdN93Y!2mjpK{0Pxsa}^l0yOj;C@deYukZ;yNjaH+`gA4X6JJT2xGV~t$Wl_{SMlV_ z1{ndX0m=L;khZ%0Z!RelWhpqCxgb`BY4x_kKHU85wezEzcPk)C`{0}{K_&CHScMkF z`SMZyAtYFB;Gqx;d!&(+5#)TdF@r%kiiF7yT|W!=`+49aRJGjw<`ZVT*<2%i>K@Tc zeB-YN?@QKHqn~}EdQzhzc-S7kL4%E7H`G7x@8h$z)7fk*i*7V*g}Qi#dCu+Pk~@e} z|Dc>cb1DBmBxLw0Jn|v`8#StlqX^N)L~h-L{Yf@fYtMxZg6P997WRf@%@#pk==Hp8 zJwp7CNCAW{96bD^Q5Q2d90jRSx$O*gvyBX$2_#0OU?wseL$5L?Y8jj~)DLudo`d7O zMJnAMZIF#Me4jZcu@Z+~w^Xz((jXTjHnm8pVd>A`)CYD0*soJwaU3yI$h52{#>}G# zBFgX-_5bA=O3WyC;RgoK)EvX^rE(?^%Knuv$p6z~SLzAMW{l|ty;T`8p{oPVX@`{E zOj6Mc6dCFf9^Fs5le-{pc*&wD3$n!_5BaV(u(uX1E@K#TIRW>+z(z>#IoupCk2PA= z#AgOHL??>Hq(T|xW%1FNIVQjYmiyoJmYt84fiYJoasSj@0^a}G0x40`n{?;ZSLbvP zmt))ZsezIET=VYH=Qg}oyimj8MAJ6wP^D#RSODdO072E9Zln}r6H_5^v&r}SFmMdV zP?f))Dv*6z~r&@j0A0T89R;ss# z#11J-zND1T^hfOQMWWA0@6&M-j?F%p25sY>VOPSR7%f+QxYHwQf@mf4lA}zQ9t|S= zYL*u8e|*g)#Q|DdnN-FLaY!P%%f*V-Gyzuq8DQ;P5k3p81`?o( zzxP;Ek~=u)ZE@B;^DFSl#n*mY`?>@^t(yqjzWcgk%|c0{328wiVndF6kbdYVTW^ z#@5Aj--kd<2Ocb&F(LYtpslwsUo5l7iI3ftIo^w(&`Z7M7x_>RTW`6`#&s!VrT_jR zh>eYZ0^SAaDir;7?Nw+$Rw4q=G3~VQR%iR)HgiCj`7`VP=`A>0z9V~X71{i!DemNF z!K?H%Ipb@UUjd8@3a@e69i4$M+mED_ZVvuOJwiow!x9x{F1mj=^i#MwMIyDHf)vtR z0M#gdvGcm9U6jBym4jq++QRSm#@@4KU+)zU$`F~CzM9{wo<^c?o zvxIuzk2Gp3?r#&ITdKBRq-CK0>Mpn3_51;P=i-lgRBg$=xT?rO_RgZCpHlFM@a5ep zfcz0@K6`cxXgqS!i1qJ!1||sias2-PMNYSG_*x{6-|GGWdf^u-_Zr37d3CWR9@7=U z1W=U!b`b8X*}QMg9iAH6_H`7|CHWy?bno6%*G3tv#hBxA8G=T-R#%9p3il$M38bH@ zH$X2SYF~K$EsXuYo5w0v{}dWv;we{p5a41hV6IgWj~arF1V2ur?+Wj%**|UjM<}w< zCgBzqk~L|qPEh91yh3B0%Z(NPf!f%CBd7b_)jBG>xb}4Y!7UXa7Xv1=@n-@6USx38 z({F%Wk*XOro%)at6CH7KT_7UGqOB-7?^Qyq*o^S_L0k&8pQMuB)gQ1t=+va;RT$ zR0By@4#S88f@wv_39a&Nw61RE3y&eS;zWAQtD1;VdahyJs@6uX%t7=4{7duj9IQk3 z`SUV5#J7LWVg3F3UxOdb*+|;Ji%fXEpS5@fxS3a`@wYuldiep2+ZflXGG=``D~j=y z0WB)YeJ(~uKiS;S=K_eN;o_81zOB~!8Sm4*H-a1*Rhx0@m8_P?(#v@)R7t{-+-IW5 zITK>GAk|6}OaGqzUQ@@U)0GmX5?cD`L*E!{E;e8B7Ia9LZ`x(Z*nTOrv8J5Q~wh>xB4e^?nRI;zp^7xc#UIiK+dVO3jVu$L*Z7c9E(__q&2r@ ziyYXLcPCAf-?g~Ty{bGrphxGylTfkMY+Grfwke(;V|rf^CkZKb5luL}LSIZHt1W13 zo|+J8$1zZgKyY{*^sNh@%2Qe>gEc2n3i7NwZxYrGd30fmf70!K2fC3sxgPbnhH$Ij zo(~IPRF1p~kC|&t=v|`nn!QazN;L77!QbWCg1?5+$%~0qw`wZgV+|0&Oh?Z43djal zu8X1VpjJoTJ8mwoqgfTqGIm`Jy6ovVTrqDqoq!Py;R%5FSUW@;TS z9p@j8an;v~xTSx)E&s0AsB!fMWz|CNLY1Vd6rJM+elY?$kNq zbWL{saG7QNW!%ybmWzeF+NsM)PY-pHoT*eb|na*M70lWvPR-t2UfnlO>4W9vgH@n~Ej`N{cS;0l>j^9l4&vwO{ z)Oq}Ca)@6 z6F5S+dsmUA9Hmh?5uQ}+Xw(g=m+SwXQ9A6@>X|;0-;*;;Q>VpY2uIG9GWk06vORDr z$$VZDKp?9LkF_edRmDS7?HAE)aQ?PwID#U=TaTD`y_&=S0m(?}u#4h9mG)$GsGa;L z8X9SF6NC9IXYw1PAT;TjYIKQ~u~7f0Nmfs*M8=(AL}-|tsE`)NX;}%W!Gx_hPT@lxwQGtL?~i{z%Iew(w6FnU1|70`M$n` z&K@3zo}H}%@^nex@MGv%cL}F+{`jI+DoNR9>qlZFc?f^tZE#G_2w!wT>NJDv)9!`D zIpkuyDgs9O9E9*orWKUxFv6(y^Tf-4 zZd4Y!PE`>7a*^^kvZeZGq4qD`l4;iE=1}GZ2KOJ*GOwZ7oKWNYNYikl>i+4CeZ3e*&`?L?XuY? zZ++4{?VaW~@eENUnmbmm0h~++J`Vz*D?*PA*0tBdee@lT?IaTSjLX=BZTjG{6Feur zLPFt=;2cg`=D~5U_O2yvTc5T*X@4%2A6`g`)R{f_VMpO4h9q>dD{&!&cz30-?3B1?90fW^{ z$h+@7^UCj6EM|q07UDzpZ=#|TAON-M0po$vYhb3ki#o}lwgiihHKd9-`=z`sWAjvI z)D~{A7m(fKCN<(jh*=swKQ)Y($tH+2;Frta7!n0@+3*||6c$L7(hU{xb{@#yX=98E zD&&=a|1=}pxdO2%=hI+^ma_TT-7xS~m452|PHV9n{7N?sWdlr4zPxXhcCyo9Oq%fR z)O+ru)%pjr3-!$zwLBxK(a8M7u;>*t=6Fn!nlcA`bk%nyj3uu@d$sCX3g{r0C8e=c z0O?nvcz+#s3SHkgP_0o(qZc~Suod2#Vs8F#&Ta2Z^v-`SvOP++pfnm6?a%mZIHMH_ z#oy~s-xl0bJ_wRpawl;-Q2@WPbMWJCj)W=5t|O5Nma0{}4F+Fp2J1-mI_vR!QqI=w z@5GH5ZCUnl&Gp6&eU&T1|CI7E=ly15{^_Kn#>9G@MqLdoVSuS`%a;kW%G26K* z&<_wLWvu93@kp~Yli!7)_pB$mdK8&crinL6yf&q)JflP+_3V#WBC1fnF<#6IhMnuV$mB7-kv@v=D8-LZ#T(@V@t}3GBlg0^TG-C=Q)ps(8Sh(i5p*djEb!13Od_vh3;45S9S;xX|d__<(o#%x*Cbb&FzAxdBjuz)1 zC7{N&vn!!9Z(@v>o9%h|Gk+zoap8fg*^DxBp|}F|q1kU_$5*t>EbR-PMLVBEe0AW? z3%d4e&%20~p2tt*n)pn7q+QML8-w+D;cy1-U&LsC2ykN{aTET+)RS=2C?B+D2yN3^ z9yaF`lx46CtD94F zazApxVM$0yyY@hm#Da%(Q;v-qVTDg+fBd`ivB>a+lPaO9`VY_uy9G(4a9^dCuf@R| zS~oBE+hlF+{)U{Mi-@cQUVJLja+z{#t$CA~*OHUsu)pbp9)1ZPfsXjv7qaW(8m+CF z%%)I;L<+We=6d)TKz%p%)$@(f=Nx(*I)JE={{#+>K>?KgoLns}!4Zp7D=3};>118O z@4r{I_w<{hN2OlEkWK0&9V!+oKuPs&S|Hkj_rE!K@!5v0?h)Phsea)M?Nixz^;IrV z;s~=Yac$rh^%*o}GvV@@y^i_aXo-tOqY86njH5i~neIs$A5&LopyoI)v^SOE=w!tI zhVYjrWItzSwppY4A*oOndz(?l+o|7a`9%0;!aqnYzT5=qj_5Io9ZJu7^AoJzL=aIB zU4SV69k@E{YEy71KH-k`ccm7#Z`s@`Ysaqg%3WY@@VGs&khujK6)GAr&!+qBeVpO;Ui$9Y{} zMfz$%x_-2XNym6f|ui)1wp3+*t#-S{WX)Renw5p z^hdgqvrMIXGQa(s_MOmuA}>wmC-qSPQnHMNKR~UtFW)R#(;WY<+r)4EXep?a+Y6Ix8Igr$v$NDb}_#RuWgSr(AWn`)Qcr2?x1(NoF$B1`DqHHhXCVa|QZ3Zz4@Eu(7!Hg!6q_@tQBZ zCn22lwT&N%5jnYXA^U(|My&)ELvcwjdq{gr5RL3-7(OqCcJV2W-FKUaw5z3RX(PEu ze%CZvKF^oFa=M&!xVcXOsiM~27Q?x4+Jtlw{4hwHtdW8`S8T}^lhq$|h{1cLq)0P) zkUmQ+Ws7mvJ=!d;aypv>ET1IC<*B`Us;Y&_-NTRlbbYM$&%l5Q^Z;bN8Xvv>?naYx z5Pg8|A9}f#LOo3^eqRp2vmRZSp~gT0caZLKiVo%~1$3z{7CX0Z7(|~76S}AyciMPs z8f_I78sasEuj(QSXtT<$C_)VDCCF9L7Y`Na$;(Vy=E{WTN!0M-$s_rEt#)?$6CChf zW4Ie+Tpj4#HD7b*5liU~hHQL#-T~(2_a3IeGJCIx9e+J%+>JkQCV3Y1UG~F!ST2SF zbo~7)4|B6EG6>0i4g4Ch-ZxpgY5bJ>l3b&Srs4KPMK{DBVphQYW=`w-;}GJ?z!!>Y zfkDbNstASc>t}0{7qc+Y8L}wv!M9z~Kha{#oufK-^Var*qZCqmzeSIP5{nWhV$61X zU`^ad^~B=8OG$-GZ?(=jpFL^5OGoTJ0-%D_ynvF3bTw z{v_(WtUOYNp1n3*?JI7BIm(YgBP;5eg?NYFEyM0YcvYo^B#|ZvC??XlUVnq6?9e~n z)S3&>@-aS9SN^igf;dh4<_&bkg;Sni7i*-#7`11<>!{xg)Q{{=+)aGc&^YrMT-Ph< z#P3$YF~hi>(f?@mEeRBY`|xAQlV{X5m^O;vjt2fuO+ndsic&WMs(f9JYI8TwQS)VP zq$Blb{`My>`BTkPfoLRqtMPifcJ10HPYCbgBGloB9T$Ia+U5EkVvohw~ z(b4hF3E@tn@J3A;p|7Ran3^xQ%&5$FNHR4rH`4e{N#7hy$)IM31q}+gXHf+&n`i0i zEvz=iswFMlP=lZ8zC;f5YMSmr0qyO>(fpnkd%z;Va*{6O01i3gAvxQ|^ZO$TAi3l} z7v~!FeC|KjDs_yN@nGT4E(1+_cQ?LkflBh(Z>T@TlQl!^1?LRalzP0&<9F?kbj4q+ zk(`J9mL28xPlCd2{@T~lIX`?DDu*tq^i7vWN!(!~bBGV=L{-DW>r6!(hV~!g6sMlG z@0R@1d~>W`74*99vaqn=(Z(tW(&t|Uf1MsC(#9UIcQXqcq5AOK#C)l>U#59_tG>SK z>qVYx)XtU6TKCLuMPJCVyJXq8Yw0TgGgDuv|A@+Trne#{gR<{V6w+mJOvd$*|6Q-m zdRiU!AE4&YuAuf!Lwf*opNF7;f|`Y$z>yjuXtDYJ&mom`FgrMFUo&HXptYPx6cg(5 z5+6quWL8gh1$mLI4c_JSLp0id;~S2$W)Ifwwj6ve z`H?)|!-IvwzxG?hN_dq%PH8NAU%3Tc(qmw}?^)7ujucQHJd5 z#jo#cy48wOFV z793B0TJ!KHIJ0!cYo~W8iQl$57iqFTp4a`4sxZL3E z&epg|gWRno9fac@^xrhrPIoG=xA-AVeMvX>Nt`+jiO{&SU0*7(APoL)S(`)3e1Vmo zpWhZCndH<(gES1#=$B=Ed+DiR#5b-l6x#hd`URq37A5#%U_Dx{4`nkD-pldlyOz-G zKm!=0DtH9fq0ivpQp?h=X_wRL!A^Tq)$X}A?aa0 z+5kB2ofo>czG)}`o6K{uSU;HXZ=;9YH#j`nKUMk5ig-YG#`x#ZS0}G91rMPIN23O- z8EZg~z(o&jPDB0ztHC%JwTAT5O@;NOkK(S*D(5n;5}q)#AcD`j*^An5Cl*wLc}D<9 zhGPj~=3e52AO6Z!_3I?urLfvwY$A)#DuW^Nd#{&5LXm#TvUD<6nmhNrptbPF>5sZH zFabKSc_Bt3TWwY*^DfP~_3MRT7aEarBtN8Wdh2eDTNW!L{hAK7zRs)2n;K)MlDQy0 zzSyE3*)5ztehK-~U^HlmrlU$ab9HR3LG1_(_=Hxy02ZvBdK))kdt z<|Mb~Pa(W28QT(0b%n{|$ldo~n3S1WUj?q)FtKfCf!)$A6h}Af`WvXuXq-aBlrhU1 zCFa@(DZOxXmq*(U+@>#sL?kb>-z(M!J*{`86^AL}Nm6H<+59@a7EfnCtPUX3cZL>~imtZe+VzH}dis|;4ia`GlyA;FWZvgB+Ml2P@S)(bY=Jax;?m! zVlVuSbzNp{EAo)Px{gA8u5A#+5NSJj@!q&)5bwce7-a>JImCkh&;F zk=~)bkW7QB600?)z0#Jr`#wj$C6e8m^-6dzg{cqpMA9ClHfz5vYVV7+G8fglX%|@# zcpnMwgVP<>uHL0AL(JUS~A8fCZ+k_EdAm;EUq#+Qq^92QL!J|1ak}b z8^M#z<|gNO3mu#rN@^&9IYkG#1Geicj|;z4Md$Y$EKj@R~9m?s<)=R@E~us2&dgkYm0AgRh5|OZy(jQ2mPb&FEd{5Ii~9hErnEo#1^qkJ^9e!yhgA5y@@sh zYpxZ|PrNZvmGCG9y61@>NqExxL&ylehZ+Cp2T^Q5kJG0B!1AM;Gkb6H89Xo!=Y_?T z^@w4uuM}Rd2zUMT&C_XGSHQcWv{O7#Z5jRp^zhdidL{rwFgw6ftyC$(Zl_PRzpjWm zGVE$m#p<`{L|ST7-UbRd%MiwcPWtWA{AyMH*YhnFI_+s3Y_0=lP`1zubuxPVgCv~o zsZUD4I%|pyOe_Q2ucg{?^#9l2mLz=_2*a(LTv$%bF{4j?UM+F%-s8FghG`WKbf#tg zzLK0pSvqT?_AIe;YSfo(->2f-XMfbKF6z-ftV6f7(#dI$J1aZX_s)y3YC$J2LBdpj zfM5z_ZU>DmwtpqX2P3nlTfU)oHCE|bxd+Pvr~#lAa*d^ZJlV%KQYolRGx40~2 zPn>(vjUA+h>;M8<<1of0wDCpDfLry5mVn*iK zUY*U0h?Al0A&|6ffG5KEU?)U;pu#U#ps%Cd?20BH>MvxS-g56-p{)lkMyhN1oWc^W z)K2-rfzfBWuT-EDZ9>{?_v*Iz5np(ruKfTlgZWdcE60gECn3GeW=nNTz)v#!PeogE z#ryRZ9K#;%ZeuwJGMz-ph;{`TQ8z=wM?lqD_9`sn{wWcVs+D|k-G9X)4w__3U*N3} zZrnYz@%L>{Lq@JQY74msdes-A2LN0_7>YbUwos8JL`2+%Fx?N7n;x<9JB~JN4siiR zxd{#43mY?e_I;rBRByY?7f}y&!HK$1bC=17l0GWK^)Qn$^uWYvzH@kea#P-Q3j2yO zw5O?#I_fP<07^fr#_u<&GavWlgDoib&N1hJ+ z0~IjWTzJNYqnhuhJQ9nV@x0B}dJ98wF@k7BH=O?J z7qwMdVC@6TI{OdqBSSn}_3f=r?S^rm8OazOoyiAQ2WjflG^{Cic91_*SeB(jCT4<` zo|Jy*qI4BFsywTCF()WAQRd&ixPBI+Npr>?CS%IPjDLDjb2Rq2KA60S-9Gm{df%My zl_Xos1)1kZr=WzUU}!=+d(~$`v_3}ll^@uK;e8=>mGT;&89S=*k}4gSI=2^k7X}#J zv3f>CMhZN*oqcpe5B3)ci}p4DJ#bwhZVa07&O!RmCe$L%w@T8c-dT|P2j?GBereXs zJu$-!TnAX43{Jj0WpY_S+C4^%RH0C-sMSXXJgd`?E^xYQv1N?*y5;G}RP)NluuC7h zOI=x#=9d6Iqbk?Db#L?W)dt#YPAC3!0z#uQeiE7ez_muxOszU&AB78qxxfvyC28Ev zUA1sl{3TwR7ANKq?WH0NgP8Uf<4!33oFC1B*;z*d^GlVtC$`b*#encwo0lMaWGX@7 zVvc4#al}&;zSX?^o$b%lL3-R*Tt zj*i|>kFMF!Syw=vT#|GyABN&{NdOWKd^OBkIlU+mB*6yI!6_+?eXsxXbwB4ssqJpz zJIwh8#9uYphy_LR&LfN=UG|$;Y){(`-xY(zIvvA;hYEnb>P7UupeLZqq3lg z5CaDPIe7vF)B*BEVEl`V7zTA!f)K~769<3BHV}b1Jk_Om2wPWM+!e`U--_6dXQMVE z21FymhTzG`$`fTyH(@ncYb_MHIu^wD3y5e$!@BM}DlbLW(1r!S3T~0(Qt_cdG5_8qGbwtgX=wF5q#lzDa4BM z6)UA7fx?qDi7-U2C8^OX%p$m}OpK|}(3MY&6FN9-Tf6kroQ(f~O#TF$;GoUb-J#Z5 zDD^+dsVIpM?c4=zbtWh}zlrpzaw>SHM;(K#86!;LZ4*a$A*i-b+t%XM=}5jh?k;?` zfQwnFVnRkMsNAh|#e8MKmoDFzf^qfi81enOECtGRC2h1aYP;C4bW>=YMx_%dLZmz2 zZ4r$-bpsA_UZi@Mt7H_yT9IMrd?_F(vUxdRQ{2-lXz-eB_>4qrcs}0^(PR^)o{qe1 z?BAv@sjL4ww8(gpEuAGAB#sB0;Bb@UaFhAUR`K~$S^H6DvHu$tmt^<4vjcTTJb%k2 zzymTO(Dra(Qh)zte;`7l;%qw~I-EXDo97w_jVnZ_~kTR)eRyiHI4i1YpwLXtPBJ=)9Gwk{(M*OBqBN@<+q54jN%AzHy6OG z!^jSs+;n&@JZE70-=prwxJ?O*px~oKQ3w2vv-%3F5s&9~hEcnNXpEDFQCD|8h72Hl zHK9ii#iV{^_eKC4X)40&9RiX&1v>KycQuu|iFevFlY_-N z5tUsniJdRC5gTWC`)%*Eu={$cB6+l>QUb=H!H%&q&p{5{FXVHzX;!xC=JF%G@hGP# z{4nXW$xe1hVvsu6RJ&`ZgPkkB1E1xT2C8Th6EyaNrMg7iI^GuwR@G9?by0J-g^|#* zt}n|I>c67ylmbt{{SttbXmd?Zt>I6ezM(m(*1j$O%1QVgq;9%KvJ-NX zYvuJW(0^Mq+#l$(|CdQ7bbD@PpQ;of{jyQm|0Mr>WHlWo9nAIThml_o75^Odi{fm{ zaxu1{LvUDUSM!F_t2AL9CzIFg#ad?qh01i@=#Cq@W(Vf=5Nq{X2yVpBxVV@o*~`Wk7?0?Y7x~kSo$Zt1 zI>!f%$r|O)(oEwi!W$XSDyh(Bd0_W1{{i|@p3{|qd;>+*t8Q;-sx%FRER2B9H(Sd? zeF~YzoYg09_@OkXbG+G@9G?OzdA8MrF+7~rCR>Cp3kC(KH{?rIQ_Y2CkM;{p=o2yX z4kM;4+S%1iCT`3Q1cdJ<{{W#JS`--YfB~6PmL$a`#>w#59-D@~YfE-ypq*Odr zb#%om&3E6+aL#I4LYi!ZbXyGkJ)_hgXNUe$I0F4miV6p(Rxjt>UIT4e#z$n0tQqAJ zZ%E6s711RpWn)YKz`6Nddpai?He5mUKz_alSDiIQn2F2tC)bEc=p`{vSLtDHtl~Jh z$?^+^JV+Tz6RVmYL7F7^s6f#9mum4DJo-e7$o>!S9-K@j735zOug>A zPHX*4zLK4qY-u1R&x!n`osC9M!c-@fwLQ)3M;`dO$y8leYFn`SVg1$Rv1*Ylk0-5f zT)R|#LWWa(>stGktpfBKs#KGPJ7rcMDQ%3-aN6#%ef~~B<;3+wv8(CH;)SvmJgjBA zwmEe-+?|%;se@5)pw@y_y69n9ONw^HjkTx?Y-{O&u+5D7!=zO2Wi#^FW;|SP2>5%$y@iwQ08vh1FE%k~L zo|@8lF%b>!rc@E;@R3Ll;a7F!;T-C$BAq6cL)EK?0kW&u+1`7?0s1&_H`c5)^U@ZWn$77h|T#{ zd|_7V1ism;s0E*i*;8|{I(hKRiVUV0p6QKqkGL;VJjtcut7LXOb20h_71-w*IHxzj ze}J$(yt}4T(wKZdtcg_YGH4)?fnIXmMnNtF@X7rlddq4}7vxoz3-TCA$e)m_qX`9K zrLGEh8|G^LiI4bfNT_VN)2N z?{V8p!*r8TxMMzgZPi-QpefuDMxf0>E9UFPI`4HR#?lzR?eRvacW^`eTg3PPgz<6& zMV<8kleUzON7dvX(JP6KY?z7V3RAupp$GqL2^eHFWU|*@fjVO)8=~|NOEzI>-<}87 zU7&*W1%S1(|MzB7`kx#16qth|e8G}8fAM1kQq`Q7xBqRYHYdM_3#mV^tHfDhtL<&W z`od%Ug2Nor!d`rM0u$g7+%vUS58@a}44o{M`z zdraY!xNh%qv-Kqu^w75T+dR^q5=-YuUAd1B5twecPtI?zZVwGKSwARvS>gr+A@miL zL`a3Z&%OI@pJ4S5FbOin-kiQ)xFPg3wrB3N?yaoOc)E3}n+CE$dZ_hUVQHQu z`V}K7B(U)+FLc(F$4ct~+RwB7lfQvC_Gl)Em{K>xx;O$U=#5$;Hq^)}4@3CNs+ z#MfZw7Jod+SNv(sTA4yYyDqwKRHj{OYhx|KU=@cHcuUR~4_|kMkO==Y#^m)oyTO3<7}}> z*C$Y{(92uXOc;#LS!35>OzoM?$kYdyc#;EnmZe4s%VEN`KA+3la-|s#TXuC6PE=Iy z%y#$Da4uoKo0yD*_bp`t-a>jlk8K#59rUwEsV>5 z8LN7t)b@=ft*%@#THuOH-6Per+?`u3r%dg}Pv)eHsxcKkMt9afhj6xz^zBfx{6tG3 z!a*{$u{%pu^YpQ#(`!aGFwh*p3Ldswd&juX%KVY#+JM;ikBW6fizD$JAi#_q9(Bx4iM(EsyEplp@0F7*#F%H z+`L|=W9b&VvL|HSgX-s6AORu+fj}L^pgceg65t*C|Mx0J)3!dKxJrU86d=e?xay?u z2sLy50F1bH;FoiUEBReco#^|f)p4q(EG%bNz0*v(9<+;{Xo(r#Nu1c@;o&JhzYleb z2v0WY?0m|Zo@>hDm6lY-s$ zc#5gg|B~zYz>U7_#D0(r(*6%o_Xjri@GD74_81XViMM}g!NFpK2FWnJfXQ_XE5ZhZ z9t0;*qOXG1eDsYu1}TlSIZUF@B`6Ic%CX^1$sbdao4DQ=JL(C!Ul(rjcojES7+3HQ`FRS}5A1g_vu|C^cwkfBUg*%=o65X-8t90c z3ZTmOWHlzy_Ft#?VHSy;td*;|r6&hqZDPjX_VzeY{^T(zOC|LJDON2XFK$cKxGk`X z)&)Rl@Luc)cZi@?+V5dm?NRn#XO6K45jUy+1SD8+QrxKPGe$9F>y6m!Qj=p2Cr@>@ z-aGlzb#7V~T?*o7=h2i$pwLrl&Y4Ub)k6r)8e~x6>Uoif{LEnsyEh9gzQidejG#h3 zJ4U`bTaK)4+clSya5~$(WgQ@&HfIO91@9{W{Slg%-Iw*z^!b5fA)oE9qA#j2Ie*;) ze7M#>(>xkU$8-xAi|jb3J$(Gf3E0WM1!`1{JQj!OVMT$dWi5?LWOGas7(3bRNMCEd zS{$A$wj2_3WA)I1&{GFhr0sNNg@$Lkwo1~TQ92loA_S_cxbAgJLB%>tibUrPLrq+wg{L=!V0?qnP2DJ7 zL%dGjjzK5_2%Mc&+Z22|$Dc^*qs+8zgB5c>GXpI5zcSmv5^>X+tzcxee2-!5F;xR! zI;5shYsUf`*zLw}>>k(-bNnN1_eaHLrkqB(B7>N>g{iD2}M zu9m&;MJH67khT=!i4qw}fpPnhz0))E>hqP`Xh71eeK~eXCfmpj(Et_J`{`+#a~Su0 zMGqkF}#t z7>s7<_+f}$4|%{F)B!{n%h1AY`Fpe@3k-{;Tx^$}FyMHwwqv(NZWGDL55^CAOH4Q)AF%-kbQ7rL~P&Kvp=6=6$d@|T7U ziR1bcDVJ-Vr!C?A=|FoX^Ecl@UmS^JqU6cVk=#sp(A*vkNTAlZ=x(>&s%@#c*ovOs z+r6}qV`EV~o7cSkzs_{y=fArw57#w9Xjks9a7@Te`sF(uEHb*6Y`YpPw6u1w?sDQy zeT>N>QyoR{SUf6JVl8mJc}@azY_F91(|^a*DxSv$Ncvhe-DR_A`qqvl?QV`9^F97eqXSQs=)$G!y3$`Z6ciOb@A%8%Sp*q~k|Id)1*dNQqPM zE~ut3W_~DEe&`*W?1-A^fKpt?K#{$GAW-S8?Wt@?3(a7qqU*LGGl(*>fP)j8&7Zx| zXpZrzw-pE$jfQ)LX993yJ6AGp-8-x<=RG_6MvTr>56Q~*pPs;{L+>lZDko}AxL*Vf z7N4p~*JyXBGIr(Vpz-F=cxcj&f40qHHEjVfW2@2=SDsoG^17iN%+e?jne;e2j&XQL zkn2ur&60ajN3g0EpM7g{^BC$3l)7WDP8d;7ui%%(!{2%AFO0FP*FY)enCavfEYPo)7 z3pKqx!gl<(FP$R$Ao~);1;YOSu=f@~aeeQ$U=s-L!JPm>8c1-Lgy6y5Avle@yK8^| z!QCxLSf8gJa4H2RzV-+A}V)XaUa>eZ_^b!Td*KDBqBsy^Ml>9hB@*SEg45YMiQ zR078jWwss{PpEPFEqy3Y)3o@sWD!5_Pi-X?H@j0c_UxQ@xRg=8S-J;iYS)TRU60!~ zO*1=JX*I8_HJ6tsMEidBZ%Y}B_>uiwMZfxK_# zTb+CY4+c@c)A4YzwJuCgsj;~uSd7nn4b9<$9!$kO)(09WFnlG|S+0Goo0@XZZ`TOZ zh*itCNEt&B4(TDSdVo{>k~&Ie>99bU-)3G?WiijkNCL9(Jkrr)xtz5j%rzvl3Wj989%Q9@satJaBUmqSLgm<=@_`MvFPoj66xaJ-wIY(TxG z714_WeKV-2*+U^ZzZuEU*#p7AGK|Nk(^AIz!sL2C-Xg{$b%Y*p*{k;hwaosRymW0% z-i>0JciF+wUK2{(PnUx3bSi!bvQ%o|B?2L|=4=Pij|CHR^7# zyXAUK%5Pf1?vo-#)#mx*o{+h%;nI=c>k7pZbUe32NrZ_%6H;1eeqhVKsmp6m_m#I0 zX0O}OYf5h8ZA)(oq!*DLWdysob>;f`zJJ25WDEJ=VH>BncjtOaI09DweeQrD5@XcpnEtYlaNtuP8L6;4*+{wu0!z@G|d#a;t*q z$I~a0{3KbbZ(O9xoMrHIAP?g+FtRXQ#?B4&i!SHSs#4@antt6@Zu{Q^xp^v#`X}ss zs~fpd9}1@7K;@x)^#p%}JZv%DtHR?9th?fgL@86=>&8dlDXF0p33f+RlQXp=@5!mm zs!>q>u$PCq?uOyHcD75r z&1-9Z&F`jqX%~j-^`b7@cS8s)j-QdJP-)=XUV8`pvBwl}cx7q9F;Vkj#Atex?`#7w z!4QFjTL(6-(-xu7mMjmBURJdT$JxX`-oh_#c`BvWolEF-BAq)wegwsAHdaqGq#$}} zun;5W0Tv^DneDK1i*#b5(5;gm${aZlkc?kl5wf>Mj0{@gst9-V&B<0N&2&kVjag0! zwwnHF+uPNih9d4KYF)1jt)XkIS8MfEj7j1khi2|K*%lqb*CQFMbO2Bn5 zeagtmQoYzVdIo$Nds1+}-jw~31z{;?DXply1HhBjnwdKa?vDc)27(Z4yIa_KX{wG~ z#MG8Jz)i?SJySpYM7yOs2_=cVu#_NjM9ak4YiIthW@)pZ$UWHr+|DWOWR)8HiCc)|YaGl|k%>6yf{LQI1@iC!9 zk!ent?L(Y%r9=6c>#e&h*eY#rN|Dc$A87_f8Xlh_GNgQ0*4|-6TwAgQ-KS^BYS*m? zq5w3*1~8nchUjsfNtI|8&FT{w);pC*P)K+d#v1Y$z%7ksZYw-dd%y~Cc@s;Qp5*?|-W`FzHJNgk<9e`2 z8!o+739KSy$PoXpI#fk@4!ZK&>fBQWZe0wedg)!Xj>}&={%5OQt^0ujn;7J0&Wk+l zao@Lft?A#)i4^ene9w1^-Tj`{e`5d2ens1fx;)Ow;uUBn@!M|5$5I4nkAr1R1Szyp z)+zG%HWH-SM(MafhpdtB3aYEnh=s3CKm~EYP(yYbO2oQRYv@(`DS$fmQD{II_IhE*ulzH9qD-ErOFN_BUX+Qy!3wpv|n8W#&hRu35G znIb;qw8$t+M>~r*eWL1fuI;f<<04r-}1NmazQ9qLSqXIQf#2@4oi)}XV z2g!z_3Du1e{-~AxJ4XATsr_#{OL6~?I7{(<;{11KDQ-^g{{_xc{QRH(d(KkE0AsjS z);3^C($m@k08mf>u)yI7Q~)A^5a12GM*u%KBdh^P@D>4nNM&XJTgwFRm%tCeH*0U2 zZzj$z=Jqs9pY2RMolTel0Hhy?|Mk_rKal=w|3^~dzx5IMc>sWzo zl=XN^O;}L=QD&kgpK9&p7e3>soZyy|=}0P?CDMX9TL=LoU?^Pr`4N4uQD#mFPh0A+ znq0rk+~kLpPxN1yNi^Xqfm}>{bY1|<_2Et}DOO!@35LsCC#eTr z3^LpDpC=&9bnlwacc1JA2tEQF-@;R|5yG^-ZkeFWR^vw8G5YHwM>86#UzB2GcX#H( z$S73d9j8tBp$rq%?mp>fRvP6v78mB_1J9~(Zn-(4rCQO2@asu<;l*2dpN%7e{EsBCM-035exICCf0%_ z-F=6TpM}YPf}ifcZXaa7*M!pJ#M7>ZXs1~71$@znvS(mw7tCKhZ5HgaN_0$jlb-yr z>knRq{#8M2uq z(6W&Q(v8?PyR#9feEa;LWQ_hZOf&y4`V0~0uLLSZs0;nSUxaV0(}G{ zOgLDp422GCP>VYEtWbPtM8GpXUd;meSZD2T+w9bkZq^fv(Ad={7R%21)_=dzhq8MBzZ`gph=t;~Br0|PpsLh?bb(8)cyav7HzYx}S)}y*q_>FW zqJ15HU7uiW$8$Rax(=bxjJk>n`j`N9Du9cwIjoS@y`jbt3iY;KI!+dFdp)^2UB*V_JOZIa?s-ncAXm`gU*5VTOMaS6>!5HPl0J*qA z$C?P=$cC6>6*9y*H{$r#D`5a9yo;b7=HtVYn(kofVv3SUI3R&77azN>CwjL}#c)L& z<(3di#$up{#=ZD!=x;pShrSIT*Hk#xqxWbj`80^lVdjwthtkvHKq6N+K|AnhrfHOs z_9xKWI5zo~&{X!yA>yvZBw2B>y5t=fy?i%FfbvVFah4x&|AI7^a1|ydJAX%wVGU9I zIbYdhXCR%kC(VwSBXcf(V!+DPl)mGDtP2vwo#Q^~EA9g zPAEyb97Y2Nl?J^Dt9@RB7wU*py~GLg9O z6v~q7G8(UBEmI`-5GNAfGm)ri3gewDRnil;36L1BFmU@t@|4HUf?Tgyd6FE6k3dR^ zKp1s-0~O9;>+GI$*lJ8rtrxInExw^iafuso4oEMAI)ssH%$(P_I$$3`!drR&gfkS!FQN5?4Cd-o6 zU~4`dBl*ogg@%Zj;1BE*CWTgu^%`M6%iaUgs(ob6<_4#=6aPpfEbp6bx&srNP%1p0H2Uqj&i$b!vaEBoXaxG=4|aB(kb^wHfZq7jVyJ zq!m54W9&o@eAjp@jCiDZi$iGT&lGRXZSX~CAS`%9Mca-saRL1bUX67qO1IqMF04@m z)123zXRJ>+J@KOV*L#zsMBXqkUIQ(yaGryeAb*lR#es@50Eie52s?W=68To%$u_jhu`0t)6pzX ziQ#Pycqa@#0j+Pzg<5e5=FdJD&#n#M;2ItDoI9l)&9#jOM2T`I4>WfX%?Wdq$h(At4YFb zu4m(`&eYPro;4Lk1F`N<{@X12?Qe18xsod5)W%q*hq?IA zlu+Pa{oLJ%d7DdU^jFXH>4FHA_mPnX-(~u<__T6ChYu8IfEo`1o9gcs zefMd&Yywx-!!cUMk`(X;JrHF=5l0_km`7M-=YqtF8lUZ%d`I@|W8A2Qd>Jk?W46ARXZ`!5 zltT&q4iujAL%I%;w_YEs!@+jAAo=Aebvpddn1pgaIEKehmI6iGrw!)7Sc(@4^ zTb%_#oNU55;IpP3>wHcHS6+^zQkI~-e;6hmR zyvpzR1O3^v9M4q3VF>yzAxmdJK-92x%W0al#8>-@y%4o6bJ*KI+tZTnN$^JQJyv!? zM&yKO1f}R5!0)4^7xjQc)MG+8Hye=%hpojDk9KId70LLUcx2@)`!%I8nL~jZ@4v$V zxLp*mkZ%z~X_|s5VHXn{a!ghWxa4m&{LM%BX+r+|1yDWx&qR_k-m0_WRC?PuVK+xw zT^|hGs?Kj;OkmNZ)b5jmzc*R7t_9yITut{SoEf41u@WVa7eckGA^V=jIikMna%1u< z>v6e!R{CrE8+Q-kI8po!1K7Oo0*_FP)EpRyXroC=KBf8a=?@EWaQey7pykmfMi;Gi zaaR;|++b~l-M(d9HEChXN|IAweCt#32x#{)Woy5E zy+dV68uj4i)cEl-C%pb{GhCFR{z~J#bIZ;fOH@HZ%qD!-k!ztGvT~3U7(di$A8pVP z(z>hcOVQDA$#b4!S8J--m2J;n>M0?~Z1~lT%H4i9$7Nkfu6DaSB&{3@iqkW+dk-PC z%Rsa)WQ|O6P_2?Nesk#(Mlx*@@0Zp|TDlk^Pele#a|nY#mM+_qT!ZdM+n}frVQ1Q# z;4Gpo=5s+5gtjcRrN7ay1k+yIN;p=!Sc4r-)@l#&f8%>vj+MqrlboG;YSkx6^90v! z0-~sK+{B0}KRiiw2hXd{XL7t0QAXrXeQBmi_=4rOLE5-<#?3@?Mfgno_+vVgnFlT&>t| zX>{~^A;8_kY0_Gwf&|)}fXkC!@9yxo6OXHhaF{wwt>kF7PaQ|=t3uBs;m%US zv(}aa$ux8aDuxEjXS1hI81?)pJxYc>e*K$HfFC%lpS4`30#EH@NQ~Z!g?t*^wzHTv zSist-tUR+m-$9B@SKI?;!37uSv&@_ZO>jascoyucI>f4BB}+kiL(qv|orPqzQubv|3#T?ih_{S^yA=Y9cvh5pL}sWc-1- zTwXm>G^au0F1wa^kk?&U%F#Ox5QTFToy8x~Z_(wf>v&wI!54_f-lc;IID@!KR!zKk zr>T7I`0sA~W(YzS+};<$*Qvle5k_be7@B_203ttJe;?0hN<&}%5Tq;?di$Z1D4HlU zK#P1i;CMLcq#LtC-W<9*jSC|;3{lEdUbBa`Q8Ur+rU+?MWaWqh|J)3@P4^1jUQ$k_ z*_G?9fwY=A)(qCx8d$ybpDVtk3fIBeM{Qb7s;ZRJY>QnKAmUfr%kX@{$>{z<@x(P? zJM#BVcN0hHh3J`CVXX3_ZbG*Jf=QA5`i{sVr$UY&RHBzGDE0%(`6`NM@VZUiWII-` z+ySMqfXlRF_qP4yGAusbCJ0hhDdU+f_~jc5zw%gvx6EN(&}4qfH=zGeBip%t)^wP`I{F3=3S12(1A_ zGb=Mu>brbWqxrLA17(@q*3bj_DYkO_tVM~Q`Idy=)q;p2DU+dfhGp7IE9<{}2fOUX z3iD4DysHTic0_SuI?!%p&n3NeA>H3_8-T4f32?{CdrY|+bX?SF<^JoEjpq+nQejX( z39U=&NH*8IuJwHh=OQFcp-2=^Fv6iD>fPOaG!x>4FWP0>L2sL3CFA|5osY6Yw|zWK zA9j(ksE2vHqBK9H;1Y7(X4oM^m_5}^VPU70TNgq1` zggq(-7?)k8Czs?LEBGDOYXYf^z1f$qLGjwS*y7W4q{E6J_K%_dELT+BCVo59xRA}& zKU?&?TiM5`Lb87XEfE=!a*dQgaEqS*DP*;X5s>>CPQN=KrX~m_=IrC{ICXBl@!r~* zgNucP{SG)Jgdl5dfc7aH?MMJHpZ*)$*GS8#7o;i*CGm5@i+Yw)bzauYCfyNh?v*$Nlm5YUJ< z$P@tWFwM+4H=KHh5$;0*25HPN%Kkjqc#mROQU{$FhM4;}l<5{vR#mbpa#odwXrPr> z=Fbey);{w&4-!s!mJTX=+OJ?pS+t~y>KpHrI{N{W=SAs{wK9r3E4B4fGQ_)qyy729 zA<~&?b&AV``gZ6p_s-rq*^*vUiB}&$FZMuz(?pu|dpbJ&p5&mcOVF$|-H8S% zGq6yXB*Sgqb^R3Xqg287-}ZucO7HV+tzY$aa`{ZY&g6N(rrO57W z3V4ilx^H*UfKV6Dm60whX^<#TCOM`@=O4W<%na>@$#%_%jv0UYA$sqUadcFY(MHJf zyONM!sN`P|+suDN-DO4{d7l~$*kOb@6)15hbF{wwQ};$u;2Ai2C-2$-S5PmUP9un| zY`F;p9VQ!zoNQiG!mGJ!&Yn-AJLpIy8P+bjyb1DA$Cy8?9BFQ1x$4+`Ps}-B4T`6y zlzG%*xI%^qDfnY(f4+u-;|Sg*2mJspSBdRXJNyICAHJ^r080&kmcwD^fNI0{EF5yc za?fLe@~ySJB>Ta2y5Xfq;t1AL6EG-!R=Z2j_*)%~t3XXme=aVYTl;j~w7s2$$+hol zNwQxzH5j&!0m_(hKia9ZsGg*G6=6=IIaDF$6DsyD+2DQ-gN z($uoQb7*_2Wr9*5Blp<>15Kh-P51^J*3>Whvg3R(9Pgd(ZCh%t*U!}L@bCK!7u@C< zhs~YKPZJp0n>M}ME1MQ)B3nv)iMywUNz>i%?wpBP3H|jxaHzF~<}dxsv|b(QkTO+S zatv)%z3yB&@kWU}?U1lC2Y)B^^xO=Wc=cIo|D-{GPq7^BiM+4pKm@74Co&${S+HY# z2r`^@443y(cQd2$*O)8X!!~a{GKZCaXLgabX%q$yv<*D&cXAIj(0u=qCqm3m^0@4g z@5?{GG|I5}HCc%UDbKkgXU#a{MmymT*xWkeF{;-jL2AD%7@Q-NX|*2}B6UnEb6fT4 zDmM8i*fKq^6cc_!RJ0qA?i+r*q$e|vIVRI_q6j!KfxG)&ATHljP$66)9(l*6I&<+< zjp(!cpD;1f9V3ge+&YjLN?2}#_J0NKOPMPBG{E5y>d-TVgQ!zlj34@`R6Y0sa=Lxh z`3u@(j{VTB!^6$5ve0fk9DEy&HCE)e@gkY>mcq$7!PPLA0W!s;-(HV=*>2=S#N-dt zwwNkAom9h;OSHj5?+6@FEmTie1VoGALTDFghiz#Q)fe_>m}%JBL^mXsS*|FA1V6L( z>?UaC@q|rfYs?oG#(4G^Etm%WcZka^RYz zx;zD})p=$4{wUpW2r#)Yl-&M~65V(%@bqQg?Wq-P^v*z*j&f2U&e>A~1)wohl){~Q zR)1)^Cly+h)b6gUeHyS0%H_(YvAw-{fGf&?>Sl!HO`zm1426U#qR)z-#$V|+T6Zp| zzu5uDWlQuX^BxB4(f$E=%->bSY=4AF+{V19IBe+UBfbw)sNf1ZS31&VRu5YP%bXXo zy3!YGP6>6>$s54pOo|P7s;jWPSSSRvAbF3LC_G>Kp(z~=RUS)NNR}b=&i2C##!iMV zCQffVZ(R%^el?%uNn%pg{6>&#K;GHm<@64gg~O$7!4#4oG$I_*ta|@eX;}MeJy76J z0_AR0bqT&(ON{-% za*NB`A0x8b7A)zfs8eFX%Mwr#ew((p>ouGcp|wx_i3V!PyZZ^I>!q3+zn?E>!LVN& zZpssV;(6~@^J}>-&o1t@qF75! zo6UOZr;Yv9o{!qr$3ax|?WtnvCBLe)&$+^0Q>M>~pw;cdMrVSXS|0v)r#|e-P*jI! z(YDS$zf^pZ?6V#~V%96+*v7MJ5TjZB{Dv7NeUd`@^Cz2xfj1Dts3NQ1NwnJ6e|DUd z0n^7eI7psfhV|-FuV1oL^b4foVol2#uPT2YFt_yQ&uYQpLb`~OWuBB(K?NKrLwKcY z)3S1=0L3;2uX?V+nv2%63UuLer5Vd&{$XW^0@L)8XYZADrf`O*q$&K%Wn09z94(PJ z!z;Giv{$kjb_Rx`>vip1D8wmuE`K=A|vS-|OvliW{AUZO;I!7HTls=b=vVF?}K z@{1qeYnZN_vuaq|1&q*$T$e;!T}i9;mMkkzw2%i1hYjJ9i1wMUt)>H{TQXND*A0vcq+@4}W4FqKh zgWq3#xRmnw_<8i;x|XqP;h?Lzu{n7!dWn|828Zh481%=wVa0T z*sF(LIxl>4OE>_NR!LKu10R`uC$(Fs!I5GleF5?HJqp4?4CQ5uSz^g#NvLEfBf5E< zs~OEHSE?EW>x)r)eXeIttNob=uFKlzsraXL=#rhNgAm2C-8^3Dnwu_9K)huRv;bT> z{;e#Rqxic4&*ffCGSQ^wd(0znaGTIBuTO%vWq-Lpq%Ae>)4BsULsL%R%lz~3-d7ui zW|VFTInOKwmW{p!1#qFX^!7h+jlIW>k|s8@`}1SDx*xbQXEPcJ?_eXI&gVdn`30hll{gbp-(;s zSxlUOwla*dT-A2i%|G2Nf7yTm$E$NQ2=epSky;F&BSE@*j*VVULu~tv+F!zhTROZ@a+hFlQ0``XiSx9S*!a{8)05Bi!i5Snuavm`lKOx>qY&vA-fp5&sK zJJX@8hP;lRk&OjyR4LUbSJq^-3$vp0R9aS2Jv4u(c_uzz$&EtOOMj<+&=_NBu{)U0 z8(wP7)H|{lB{y6h8K~|YtFWxkae7z~MVV#enf$I@o07v+z>dp79xo1e#=fn%PyYGv z{V!az?b!5W%5@RQZr*V2nhJ-&m8aeg{Tf9Ru9t5KItfM&kgK~QJ15`7&{-4T-bpf6 zkKZb<(Ha&cEQzS-OQlU>E?86fl|``=>{aeSZ!JoyZ7;Cms^OotY*n03oHV;p|bz`5*9)lt^OE^8RiZNVc6yV ztV0z~rSwyMLH_gS8eZNssinnVcnrlo^Z{*)PUTF_r45{=QI!$-iYopm-Y*9=p1DJg zF#BV27aM!}M2{cT_On^uWbNAtxCwYi*9I}B_LU!cFzlxR2mDmvJu1$HWwk5r8>#I= z@v=~Fgja!D?aPwPm$dD6pfd+!Dkbdo!!F#cZ7y~#)4_DMA6l_IIL6@*<+Cb?Uc38` zb^^%vwYfYO4><6paoPRV?Uq5+52%#%<-**4%8PygjqxOUsF)pIS{={ojt4a^$gVn< zH5tnNS8%=jpq2CtubxlL3s`>p3O*G#Hg=_CX?*m{9*<~g3fDqJ$VQ~_f1hn7P}DIT z)%RpWHFm#x`RN&6!2aGm?Wr1GCPa2k696qXdW6TmE0vqjXeLvxpKCTI6-4+>hnZ4 z1-=@bY|3aFR;)|XAlGz{wWKQY;5qwNaRLsciN%$Yw8+IiJmbrN*wy)W9}i!b-DB~M zojJ4Y&ELltlj?Nd@?QXKGExg2p~q9CG~zPtcr2g9*0%tlSsSK;cqB9en@E22LV!BB zL!%&ZJ-m>Gd%^7dDaT|d7kT==oX3v$L!wbSR3#N<0UO$?02~yY3J=H7D}W26R%omd zJRy$MdJPliy5t|F*&9OpHl&1;W@BI7Lh|N2DC1eBbobOCvuQw|9NK`&<5#-2ID3Yq z{cqdv<2;cbnQCP(`9gE0qH1e@`qgXnD0b;S`YE_+wwcEzJbObVh)Kq1P^fj=AvM_y z#1nTW;p43=KDzb$iMs&=0lOwmaJ?q@!kSNZ+C?2OQV5DqcQ`OWy4Otm$}`xg`h%Z6 zFn2-ycRvMQaJ7&c~@hpM8{H8jhjNNM-b zT@C7Z&Z$F=tBQ<+Ia&N7S_=Kt1oBfPRZ|3n*4IUcBl+Tamggm-+7URKCo!T>Z>R50 z3~)m1cqZ-Vu7M}ztKU+IByUD->kid(9K~2Jq$3)g_%gUx?OwaIUk!kggr*31U&^)+ zG{_V$(l_>$do*GfDi-Y6BSK`6^S4E8y=Fe9lhSyV|4a-htH#(QH(dXfIyW}8P)>^1 zN6~t_>v~U}?L#@44TcA9cF=S`N(s}OihIb1G>fORDz+VCXHrvAY6(cOO48gTX-9pL zo_eWOS1J%@0m_WSC$&FKnzP&;3PC)HU%9Q0Uoa({fOQW_^t)e17V6krIHZvL;EgY7 z&ibjba%{-WZ0PW{S-2mbq`5bWvMSoUI6WW8DXjo_JToRRNX`{iVl}vd~U0`pT?afnRExVQrWi7 z_lj4YEBQOq*yGhlPUAN1HPun(hNn2nBCuMnIbrDvn2oGy6D=Zj4rwwCUkyLaa_4>3 zQzyA%m))|avfSo|8R_$h&YbVstb6MT^b)&TQZutL5lYMBKm-!#ZE5@YJb_Gza{H#; zfy7hQs)Nfo)ufmgU1L$26-O4HtZA+D3<%Dim4KXoAG$Slq_7Apov!o^$tm-lo{0jSZ$4w?9EH52|Y` zs~aE2Gd=f}q?P;cidnG7rq#_~er6M5KxEgs`rQ%;Y&K|Rw%Xj7O5ZbSAtaPj>@Md& zi(RfgN)|PzMXEx#0JwLOR9*XRAUm9I4apNtb-^D{Xj6~_5Fw2mT8gHNoYVoP_ms=n?Ja*->^bGots2R%7xwVz5zX`HWeP}< zF|u&2x$_+5V&3M~A6_4GvQr&hroSFvig9I}TFh6~&~qwSA|+8(T?GelvUu*u6l(7% zS0Dor0cyq4EA1QG5#w}H?Ug$_)Qr`eVJgjd5c~T*N4P-B`lRBGKjoaKJ9{bg0nlb3^f|@B&b}P zxY5OEbzbS`#G1L~V^{-ExkyD2u~TZLyb4rQHBB93Vq$c;da7&OjL?rKlzBS)%ykT$ zt3-yKZ2M-dI~9Q#*t7GSU{`H7M*kH19Mn#?ca*?XlI?d~h! z)en^WNA_?dBKA5X`Z5+0bLe$mm@gV~xuJb93}Hy8PeaK+6+_VQCk`0#zo5Rk_y^E{ zey5Wc4yB`*t%~%nETegKqwI{U&S!IQAozO66li%}ss3Q&LMgT~s!__{&Qz&=Lmh4T z%1ERiFpl`IHD?lT5tC$T3E$U6JdoXE#E|P1kU=+?K^7OMJHBcTtH}{6rmh( zt>d_hcd-{{T2N#@9|1i~nj0|J_!49`LW!-o$gA{tb&L-@tc-p1UB39KKPe!Nzz3oPoJE@TgkMO0s)FIpo+~t7;tN<-o->iYG&oFfxB$Tg{rHd%RB~L4gPi zo1W)3(A}OSH+&?rs<>}X{aZ)MR`Wg`q1P(w({zcXdi(NiC0a*i_7@1VP~jK?wF)GB zZjAz(#^p`ss)1N-9ho=+i7O}yVHz?=h$s`-Pr36GppEzFYSJ#68s;q19M-4Unr|a+ zOmHvdbD?qyJ0^0|4pH>^>egNBWudGrj3R^LYO&{>c3S;Zz%xtZ-$n;!XTwy zsRp#NpEaM_9N^rZVo9AAsl(xSdMhe! znoV#Ave&s+E{5U(7aIN#yhw0+Snal3%#GTiU#S2M1EVjY6aO9zL`!*%1ZVVqdD7R4*g@^*%55?#>&Jy%JQbv zgRjLTm7{+a4hS^#?AF<5vP(Or5*cV9nrl5Ol` zw1h6*NV8fNN^M8%GsXT)|5w=&omNe=uX?9tYxNt@? zjuJHpSjsdI0oaQa?*`#&jYv`S-#BYxQR)6fXM_=?Byu0Am2|r&b(_F978d#c6MTrb z@@Jy+s?Am(X=DpTG=@OA3EO`oR2{fZZT|>B%)fW1f+`A%``FeCnP*B>O1ZNm^O~cV z7@|oQ={|>PkO0&hRTs(Vlj`9Kl%TNxD~X&I5RBuk%Y@kLt5w{T2o)+*HX-d8iM$m~ z7;85G=`<~Lkq!(01K_TI8;f~O;U-or2K7%l&|W=OsnLB`H~qv|dSYm1Uqd#@`nN7j zzpeC1I{M~(+uL&DJ0mOAd#9!(UEj&vw>H!(T`UdGj>*&vU2*ewgGR8J@|aD$Ojp}s zt00u8Vffw@r>~AD?)|~%&Ts*}1CFYZ{yO2;@G4LLF(X{h9Mlqq=M~|VKYvxm{r$wq zh`EzWSF#c-HMuv4AWiWlpr{VoEpqL=4??Tg+P{xmNuRHR^i$)u&y)sK$M3&l6720I zwv7Hg2ercrP3betQx=SWZ8=vbY-1wh9BGT9T)CBmAl;{pK$*$CM?(1Tc?#t;baSF? z-J#Zl-)W^P8}SAF)Z{y7Yjv0IIg^-14rn9FL{k>WOZ=L*?TY8v%^K5JXc)&VNtaYVH!!%7eX!2u&jlgBY1VCF)&$XQ8Dsr|(ir7bczhl$r>`d` zE*eU<(4k>d#aYLZ4x3gC$YBb(&-#7pJX9?GHtfV@g&TxdYPHl9XgU_B{^B%! zqW^I)puN=3=uxW}`>j{)pu_+EM2b;6S27_u9$V!!aquvQV!>DZyh){8=JlqvFWAS= z|L1t7ol?JGTf}7N!qRUA% z6J55++7MSzIi2mBSL5MzOy}0;x5_hB zjlf;kMIJQUeYNW>tRg=@>-M&UUyzPtjq0~5Mv{f@N}t;-N}aajd?uP() zS`}CX%R~*4otxz}l`=P_nQT2DwJuJR?IJej)X?P8Y_Nc|wHkN7W++FM2mK?PZ~s zVZ+50&ot!-%}cDwy-NFdnZ<~*hw#sT37up8V)oWSH@v;G`NCXUe=|I^PQ5Im3f4~k z4o$7>$i7LP2n`ClMeZcQsC@&&m)Y~oIBnG)!&fH~rV>8{MqIi#fbvhm&o2Ugeg|IH zDEg-?!ICvX1~;%%_j?TtmyGAW_U|K>c%DsJ#ie|J-ZhozI2WU+hOt;B>cAE_pueDb%z9k07yeMt!6rE&f@hxDNM~xqPFi0zY$)!`(pLmIYh%>+Pfu%>^SignrRy&TL-7lxX<~^qNoaDs zHTMN3ow_)$e&$eT2+7Of%cmI(7ELWuwoobEuBcsyIjatvbOX2v9{XpVSlMFRWC`?55x=x z26e#n92j~*@(1C(0)I~0sU-}fB$?G>vmxj(8;v;)!c(s&Utk@_5V3L#cHzEV-nnSY zncfnp1jZ%R+|bsAo;jkG96XS9OE|ONazO$U-5F3Fbg}cOgO@%rqm20t36iBtCmLd? zp@fH_w$23a^It@ASW9c^wUdKWp-%wGV_R}>vQZf>)b>^W-R%hBQJa0PL-Em-4y5Eyp+1hykxs}t?|7f~HRoTyX;wuv9VWQTgNUG8dPz=VwjMOD)_t~;vioLb**RzwL`UUF@9 zL;LJjHpV_lm#_>aTtbG1_Uu1ef|?{!zO?8+=IWl8m8<7v&k1N6g)8v9%{M%r+qhml zvwyEc>jeH8k6MHMciX%%nM-F=m`U-A;Q8t;&*4wC6#j9|xJr|9O<4uQVZwrE!Fkv5 z(AJie^Sv+V;Q*FgeUF-Ct=Ti4cI1W*g%QQil{sg{AzBHPFIf*e z1nGS?-I!JE=TsnI1en#?bZ8G{wWaJ9zID4-{#L)SE;525s!gu`qNIB<`6j6t={)fx zLWIkKla@d&p68ck!|)|KESw80g#&J4yjT=OI17t;yG8W>l2&WtmFB8B+ShvLDI2?gKn3PMhnB<7 zw+61SQ)}Kge@OpzODR&>3G*rRm7V1okL2n6PJTv7N3Xy$o!faeG$bY%wCRqLmv2*p zBMtW#_F4W3Lm6yaRj66>Y#z|1Z}V&Tn0mP`?psxd$RK*016aSYXb;&D!$dtRwhQ=% z<|-2uH}_o?d%iix3mSOrprS)8`eDWvjC%W2{e6ghl=n1PcF~~OwJ$lxs{M*Ltn~Oz z-;GXPa|fMABI|LD6j#=}oZvv7BOp-z>O)j@dr3;Aj$wx>d(#at<#B8I2MkAl@j_G( z7TkBB(UmjxvGKuuZjDRoSE6`da!}Cxo6uUIm(e*&LF@TXO@_@FU!l@t7bX2v(=vrzzDxob0) zzeP~#s+HhGvh4DcHpK{h*@|P$CYb{p@IXaSLX!fg7_QazkwV3AIYcz4kj=S)ITmv# zb&3g5HF_{+Fo!Z%AyYL*e|}>K1&yt5jrpr-?3;%>(3a zuWyas-}wA9l^C=7mM-KAWP52a2UgJKxx{lp_ACjD@PlVGu}?l0>#lHxVF-QsRBCNu zMjpl3Orx3GmrLrIv^liwk8y>yx=A7tmMSYeB&{~Gx%l(xwwTi2gpE4?yise~pY2Mq z9O@8rRAAk9wx3y~O_}Dry3Wl+C~9pGN#iuO`=r%+L^hOOrP~NG%y;m^w5ux$Hle`; z@YBDWLKTbSsb~?Qj3Wb%chP`!<12TthfQ8lxux zIHL3`FWoBeAHm($@5qiq|8i&aAx?1`cCPq^+Tx8~J6oSKLO8V2lR5nkWJuH1y}(l~ z^;fYebTB5foL+(sR|=8B4B>IP%|@^|KG|y7J8qY#=}_F-!m)zWQ9$$-@;kY`&OLf` zzC%9$_-c|pvvS2?73AEND>-BsFsBVmlUO(_ZT-D?T4UTqnjvGUr9H%TgX?*; z5tMPO3Aic^gACS6M{@UD)zDdYEo36%{cr5O1yEdFw=LRuf(LgtB zySoH}YaqD0yL;mvm1Pnx#ho*7D0cr29SJ$2eg$7L_h7jei(ZniR<6 zo8UvdnZz5V#5*L(xY5$IQYYkRyPa^>gGQYBgIo=l~9X0b7v$xy;MCul&L&7Q6};opHB%a+83l3b=bIBaf@;FA`KZzzrHStHO2t=iEwxJwi?xm$9o9AV zBwTFhTu0GD)q8q+$S5S$=e;HJ%#C0?u*t6armjA@Acc+3ndY9hq-lxjWFqe`cOQ&q zSlxi}nF~!@>Y~M0@X2Jqpit7#Dz!0C75|ks2Ov^R4Op*P{wM~Vrd*U+{0ZBJr;|NhhOY@AQQ8ci|FLSY)N zhlO)c_&BYuhH;J5S^dM6M%aqHK=wlY&DV%+%{%nla{|sOI(Vo#f8|$YK5UWp=5Hj&1dL~$bd1WF3+8M-XALFxz z7gxzV06eNo_pO|4V0kNrNle{^&N;Wxn#A_&j3n{I>sV~Q2^fN&tO-e6mi3Czweh;O z+NC&xoknf|-Yb7)fX7^+ zy{vWJdXyAhDyE5`E<@@SoLvZ-+3{G=4S8!f(M_BuoKEy_Y%XJZ7t&ly4DmERVyKuc z!==Z&Hf{w)^K*Lw!zd{IkO#IT$ierzjvf)64jUN*oey(qb~Zx6O)WRzZ{CE4hL=^6 zSIT;N!^P`zICdOj8k9UA*yEz|S_&a;r)epXLW0i?u9wT>McQaT{dqYm*MWZk`kmpO z5QnWZ`QBuG(&p&hqXnBzT3qaT@HwriB zIos;kB-MAiF|>cddTW5;{dwvSU<)vB+KEA9*x2}@RXy4a*NlJ6K45;N!JC|g*Y4wD z9Or?bbTgU8edK*g04@F&Hv0O&H$rx~xUFROqqwh)~Qp!$1`ELkhup4>d!)#K1D# zVQvEN##_cN&x7%J$k%mupj2yh^^EiIXpWv_Lje{ig@4ZA!q6nqAk)P_1;QzzFU4E~ zF?oZq;*!pnSef#WX(d@@WC5r2w0k0vSCDt>&$gjiTt79N5@-hU_)nz^ob~B0MS`}+ z1=dGZYsb_#jz3EqOYspY3!uY+8+1Gi+g9brCLk8ZTQS^?6@0wZ@dza_s&XXWtP`~2%!1NcYdOOjkg-ESnQ8sHMoLCb#qcO+? z2e8l*3lDUNLZcaI6VC=P51!s@)^@b*-Z(DFpM6J2eOCGFc zyeIejGq2f9bvsh7JEeLJZ1qrYVh!n+cqYdW3?&l%Fa#4C2sSS2@IfN%TSMp-!CzSX zRP9$k4&opt{ywy%5`uE_OXG((6GX27k|Ne&hhvcvt1IdD?Z(YDSBh<) zT^S1$abb7X#^tV~{rZ;qORdK&8I83pnOULKYy9&x^_n892gnZnuL*}Svqm<_W~rYW}D zNurYvZAV8r<9OjN#`urAuVRSQ0lS+xoY5Ipvrv`GlRka+B)WNP*=BvjsYWpMRNS1J z58GBusIv{l4RH31HdtTEC~XTQ;`ElExvlyPZ29Ae)I-xw!)dR=QC`r-O?CG6`)JVC zLpw2*33mP&-=Pdr%`Eqk0m;`{)^_~OA!k$d*-wrhm|gwNiolj*pObl;7hH(qMxUED z>XOVt3Mb3NIGxFAJ3MJ|_Yf3V;gL1)2Y~2jWKIDa)o2PIj~5t=e3~sHy&4^2`4D$^ zP9&yj_mvkkuFdebJE(gEq{Fz%{s7QVZdQ-al?r0)N1!eA0Qg{vCGMh~3JJ}5sQ8w= zk0T`yeQiKLE^s%$e8attvK2_oeM%zQGl`<3r+wVD6i?h+p82_!AWAdlJWJx&qSCe< z@}d6wIvpGRVcIv5((j;EluzXXK1`XBNu@&aeH0XP&KnWQizK=N`U!7XUQID^(K zx85ZeY_ELhH;E=P!8Gno?tZz!4fOqizhU_Yfb30Oal9yyams7Dzc)IhyjeVaOH=|C zyiDm`CtNU^cOI%tKBoFem|lDR!F_8tBJy3JiNzzvw1+%GJ$4ltumu#B@BorSoke+ME;?!jM;AO25Yf5$K`(6cK#LqsT=18a~VutJk_9;=MhVeyfQ^h z;dp)Lg&3mcO}|;jOfU96-#}nD=Awzlms}KeON0mF`Jg}V%vC#A9@UHd#$pk-l&d2B z{0kUe5q_s?p`)ruZoeJ4sfRGI&pTiK03<+kd&4jL1tA@oyiQJ`G40i14EAL*Om;^k zb5`pz_bBtN6=RNy#g9Do>tV#cDV%gO8VXF#ab<|{wjQiZ&OuPxBy0ZJ+3)d4}|Z#+}}{HU8GCsSEw6n zpz(oaEewiLw1`%Cb`4UZOB8wY*3Hyou=ca-F=DTLn(!n`h>Z6c*S_rP*?7HfzARNz zY~h5~*d(2X5*Nt1lQH4}WzcRB<^KKp$QbNx=HgM}?$yI-r7S~Uo}sOd^rN5lYsK*H z8Q^KIlXJG6dh3Ymdl#bBBB5vK=48VI9yO^ZHu7xd>Y~S0(Z}YtE5Fd3df1uT1m6NL zw%7o^_hhSUBwWNryZCy+A5gXc>_SVqAJa@`MhQah+06R17j}Xtr4)XSF6^`kL8xwY)$UL<1ng zY#H!6h!8@9Z7PZSzn5#hrinD9lW3GAoY}Q%Dd@Y2zrocJihY1zjk=@GDY=|rJ zx6v|+&BH$l^omT7SZ8&{d@*@J36f~I%&4deV-r~_?A0<(Y&KHdNsmSGzOTxpi;=&g z95~ZwBR@`9(gD+i6SLN16uoMJ(bGH4W({^GU0#H7b)g=~7asAvNVaD5o5YX%zoxmP zhp+Kgx*YAJU~AVIR~+t~L!M>9s`=rH5#5{v)p!Nv5%Iljs7wJ}sb6nMw(<%%>a4v2 z2_+lQwmm$~$Aora-F6Y1d)}?*IXOpxvSvAN~Q#8{xcyn*EV)eReG^S}Q*690H;>c-cviF<~(APB?Zrq62Ox-D{ zk8ZJrvzD7CirHn$M&Mo#_VoE6;EVaazl}fiMKTCgI(9gTZwf~ZTxbhLu#|8$iKZNV>BX-mJxC~G7yIt z_X*eq*SpMeHN@EjrNB$CI(puya#aD$J$WPEw@X-%ZND72{m@p{*yraXFKH7r}U+$Fba@;}Zg$262OKlrV4IoF-4Se{i><=jL*QU<7OC2FS&?1KB7kt*D?x*EKUpg5NT$9^_1|`%XWQui2jwivfgijq%j*GmfF2< zE4{mL*9ptbsoozJ^$DkKou4r1<(pYTxCs^#g-$);!$@_6t|B$&3DfkFa|#=?ylhhl zqH{;Vel z%;vB-u#DmVg~Rv%5W4bT&-;f;$NwAJ1|<>V1SV;7uhMRuuT0i?71F02MDl(Xmn^av z@hZBd;8G*H0l9IJ8w+?9+yx=s~Jen)v zFdMf(Zd+?#3PS6k zNI_JMUurWsu)bK-P*+R|saR~IG=~aWjS9ATdo(sx|8^`E#^jK~T4-6>Al?|*X&c99 zu|uj?2PG}6l8L;&Axe1Q>lW#Np!{hU-aLDaTRcdXW0YR-~23G)oQ&@kkU5 zq!tOmz>;4}l1?DEuQs&Cd{V*Cejcp#-Do7Nf-v%o5ei&L4B4`N0znNqG=yrJ3UmDJhNT5mLmqQF(!5ev7Vjl5i#QoU*N zx;eDJ`}em&hZqCP-R#Tr;?~JXTkW+^o=K(Bmfk%#AEFY2CduL!n%XIgbdN-EXUo}) z->iybOMmI9_RR|td;CE{7~aq!4|Yq|xQ{V3YLpk^skLSc?6)=IM}#H!38{05uw9`E zSA8>S|Nca=qrxM)w2efo+Bo3zEITwIprM||dI*VfDdUnj<*+!hrF0**?~jY_z|!8! zzBK!WqhkXv?~eK1u7vc;n~pS9!uEAtrK9Iw&)IgrtrDoh7Ub+qnV-HX z!%Wk}sO*DoU~qIQN`<5BrotzPj&8(CU!#ED_;!D+j)d!Vjhc(;`_mOKvablTE*M+_ zY!IzS*?9BN)C$sIaTa@@Zx@-IP;<@)hY1}!e50N1_c2JJNgm`K&%KskpEIa}ISI(c zbr1M=B4mL$ZaKuo~Y&}uef zvsDgcX-`kMDXUHqt19%LYj|s#YkkEMHVznbOtsR@*X2d_#P|(m?hlGHY`Zm2ba_79 z?MCu>AiI6m*0}VRAu!oh+Mb;d`L-}nVUDfRdh5H=95=I9ka8i63$fmEk+Ek9OzjDG^9fYq9WoHgq^*XXl0hREvBhIu_l{7Vz*~*;?i*>Ls)a1*C+iKixCEI|u29!DtE6jNm zLymKsh*zA<($_=XW@JyJ5^e&+$~|xEX&jXOom_xiQJ8}oJ(=^fAx;TG><$rCQYv_Nb^HJ?2t7e?+?IGp`=}-rk<0vZHbGh zSxYAue}coe`JwXT$=1MXCvgp9o+jG2_h}wn&F5s)c@FhMV~ShHuPEjn7kk{MAR>=z zLW21c+~d7+&iiQ^>XJJa34U0IeF(p%R|zF@!vSYYuf- zBaP{+tktFB${<0&tBk^ck`rtzRwrANX3dHPo|>;592&*^U_N0i@-qm^r-k~|s^u5n z+Uf+OWs3@TTghhK11ndRxPY_YNA5sL8`cJ`{n_Q^?en`mGp~@;J(cO&Ql98$m2{^` zE@t{`g2RRiUA@YJf@Phxj#AyWa1t)%Uur{xd$1I2np0Q|=H^(ViNbm)V&Lr%%?=q; zB*pVH6RQ&t#^$CnGj^pjYKJEHIRT#<$sQ9+-y$s}BzLI?`6qty*w1`F#2_oduisE`|M`clXkSkD#jClz{}OTvJ;DzjuSP zT{(3QiA4nT70f^3;4e*GpdHNScarTSn8DB)y&4IF!g{=wsZs1yh6RJJPiz%FUp zc-iVb%WUVW*Y$qJSLq_?>`M+|Gu>&F>B82M(J0>7V)lUw z2qgx)2eC=erm`S#DR5O+s#KYJl`IxaJiLs_%%mV*p95%_jZg0Tnvmxl~ly!a+ zq4l;sP*}NloevcKsP5c+lcWwy?sNY@??$!*IEYSMHM&Eqx0u|vBK49lOGRw6+1Jzr zyf$(OY`=cRUp8gQuB$(3MbhGqueE|;H^zbNw1_`Gnj1P7Pn(8Y)KFUh;N6F4-CJ>ag>%ZKTt}$ z*06%&EDK}l0$^=ZVT;`jaZB9*n>%N@wN|^?ssfqBXqBne2H|Xj@93KfRFzv>)u(Ja zY+4BK2cQ~@7OszpJom1n%;`FWMk_7WCZ2FjD%wc!DA4`H(cj&BSG=xFp-Eg6aIW2hq*S`Gl!uUUUZQo z!#^T}PidlGgZU~y?#MegclJx|oJDnLaU(}_v3%&66F5hhg9kP^4?v^XhU=Oev}S~B zF8IZ|-H0bdNhZF?8U0X0lhs@1uLmlbHgEBT)9u{7H#vq9qxF> z0)s+r`FjNHQZn#x_vuo_Q^iM^=97*G{*B|nn^2xm1U{jIw7zEE#~+P#ZPBm#HPBd6 z7@1B%Dsc^tnz&&7%m%l9rH1oF-##w7YdRe9Q~+rhxznM-=iKxZ`6%9ENV_aZNJ=JI zozm)w3V@z6sWiC;=D$6Y>7;2e)p!yH|5`Snwd1Ku)ZwEXXd~WnT{Y_fX&qtMRILo} zceCJNpCVL#e){iOCrd!*N_^Og_N*k~He z33-{A^X){saB&xWLuRBbF?4-W9})q0!? zTXCEK<89D1>oH!(AMa(--uSPE1e|2@A%C;>?XmMH1l!oT70Lu`h01oLWwocr`}7>0 zGwcN!EXL}s9BdXx z@Ea%i!*3o`$mU4hr|`09%X@L)8#ZPh%Bcnnqd3`6EI~^#fC}+{0AJ=(=>rJ7+|}vL zl+nrvddMASZwiP8WQ(fLm$`Bmw>gN;S3DkIyI}FZu}^Ibj1(bK?_%Nm7zbNu#s0qsPR(o+p?rxtL$oa%)M;!?HPwV zxp?3LjH}E0E}Bup5?mkce}`PZ8>C_6Xl3kp(jTXtKW5EFLvToOF#5_nUdn!hm!5KM zf)U*RhLTE0TTf4x27VKatv3{wV>5;f{Q8<6xw?Tgt6E|k&hl%EY*I$LV&1UP4-N#G$(3L0&?5{J0wR}&DK8G25@WTlkw5tBp1FMh!#(yq(66`|y~<(1Vn z?itR(cWOkaV%F!>h`8$s{n{B5sdknN)q?d+uSsoIy6NL?-VB#&+XU3jvCx<{YHl|s znOyI&;4dZBB-3O<=ew}qw$(N9jFZ($*eBrKd`w350K_Bv*0&fHR=|4Bc<+k5osIXA zd$tF`CUZ+o?$cV&0X_LhAdpLlLQOEzDr;TOYi?S}!YHk@8By?zy(v=U5XTMnSxpQ}g1} z)g4>@*?)h;oX?ZuM$0-dN~C5_O_dHEl_mPZf3 z>5KM25H91Dt4%{mExb7ETEdy6rtsv0oLv)7X_x$r}J!rhH6Y1rdP|M9p4 zzYwykb4sl|=*fk&q@+{VWje&}CD6fF;V)@kr>(BX*8{(KAu0zk_K)L~2L_%3Ow2Gy z;(xc2_kx=ROJ|-^WE<}4O%d`6TQG+uUq4MxE9qYR-3sO39_$2e_A!mQscJtgu5|nR z|HnWSy(e;a#Z}4ro!N2{jOxFc0y#Q78 zAFY7&4kq#q3nKqz!4hVngB$~Y0*0Xct0Jf{9s%zrh}tu+!7R7qcJDR+60+udams+g z0>WJ5C{JbA$OPX4a9u<4zF;bAuEh?KtQH)I0$f~%r=)8APl%yP^DK+)E{>j-jsg`C z28$e+BK2L~OYe;6At4AxhG}eiOnaL_n9~tzYjLO^yAtO%l}iXSfArA{R+~=9Jhp;t2r0G%9rsU?Mr$`YNL& z7_}XoB);c{++5XLoE1&o@i#Om(eI>uv%e&G;S-H0PHQks%Z&^TkjvGk;~NHI(8ACE zh2(|*2Ll|FIqN~|4+$IG@wbO@B}RU53dxLLlXB8{q9yr2Ag1o8AaUKw)rG0(HqlVg zYwlqf=B5gkUPYO&&S=&xPZmGJQG38Wdqr zt6W$B;kyG_r%hv$8DeBlfy>hjW2mKhE3K-(A)mz)NI?cKdpd-ZOJt11Q1}y~~q7*X82)Ve~l*+qc zgp`VNgm|eDsYL(2JD3UK-Gi15VI`+-t#l1KTW~*?m#IDT*oyq$W*;ZWd|0 zUUM!a?Z6wZp(_(dr;biNY}h@>d0XnjSlFVWC!66-W4)Q+rYCY!IuGT{x9ntB5Cz6% zyC^@H*Z3D_zlV>6OUUw!`Etn`w5;&_lY36%%jjNk8`W!Mv5`x@g9^|(G(}<-z`Z#lP_N?*a}bJPAlk~orY%TJ$6>X&i-465`m&^0OBKeL}mCDJj7-$r1%le#A9r?fGdz{>sKY$nuzg5wZf-{*BVi}>p50dbfhrg?z zckXgK9PL6$gzARht?GX&cYH(a8e^D5mkL|Ht7QKGvR3{8o{mHzXo@$n#fyI!6#W+q)j#}6>>222U=T4X(1+x^M_v^E0&6S9O$8Nnw#s6pzqUH&N zzHInyz^c|ieM(393zGFeS0SuZZ(z#02P^)G!M|98)eq(f{j+{vuft$fAH5JtYh(Ls zvhRBK?0N}KnuR%ZWix{78#~Xtuokk?5xca_*jg!hZw@-w$35krB@9%Mt%;MfBTPR0 zAD4DUmKdnq-2W@ei5&dw|5Q%ofQcwd*jd{-s@NMEgV;WSTrG@2%F-X%K3X_CeE~U2 z*xA_I*@A4HsrcBWEUcYDj%=SKR6l`??My&yvbL~$dpm1GXAqUMqYH@bGsxD=*_?`x zgP&7G1OxS-rTp{j4~*qQPD)k^00##LlTG{z_R$3n%_{{nyXG2q$vG6cqo% z@BTk4oCp|!qy8`9#Qzdb{9mI4XMEi~Ttx;6zY=hXSD@^>wx(xfUC#RP7<@~4=Bo;~ z21(dP34^k?3a(5&P&wFPjni)n`f?vMZIIa&n3AXm=ZJ<>k|q3Rqpa`{#2 z$Rwr{+iXyIo!R&f-Y4DZ0FSQ9BpKe4zzze`z%> zsU_Ev9`R(^bVI1Q&&HDZmw?m%jC3v>1B_27yFR?IIrUu>vexcb0LAWYd0vI?IxHQ7 z9vJU@=D_~}3^5@P4cvU3v0X_)bxa=I_pyvRuP`h+djjoZ+dk#W0$6WRs-<9s|O z3Q9kZc=lO61w(sad$bs=?6QS4sjMObFL}N^*QW_QTe}jWtp1s~N^OU=jj2tMcaVED zR=g!mp;7LkxB2dAA8>PesBjYorQFBebrTGi)|LumV_G{q0JoJ1+ak5gT~Y5>9N0e& za*+pCA>0i^t$x&+JgKH|I96jNdbZ>x-GRL1tjyVdwsGOJ-n!_dHp)6mscE@cKYIFj zaFey}=efiZ<=&Gvz692#nYN^!)A7^nIZbJJZ2E;RzOV!v=#DK2XjxN-jE)Bd)l&p* z{GJ|Zf4O|@Z>4>pjv-9h*~fJ{pM2qVq{Ol0IRFi6bN0On-MOkV6Gepb?w;vXNP@sP z9^E&$gUn;z7MnH_J+sUsuww!J=3Cd? z2ygZ+N--3J?0cT!UASmC{dLW)Td=o-NLP-etMh1hn&{fZB?lQFS$si|)1tpswgJwO zPT-`=y#_s;ZCnn`BgUrLWfcX4v*f3qNBOAl`5iH_>rrQViX|~`*PFEwLqSyx2(ab6DJxBoW~mHP1_jsP@wb6m1S&9 zz6u;HOjZN)HZ9h|>LNRdeI7}j8dpLe#I&IB%c^OpIyeU0AJ9Aw)QA$(C!KEC3@@%AbnnYB#81hY)YmLKlFTTms}8WtUC;BM2XTez@U zwJ|SpratUf|K!)!%)d*Jji&9)T5L&h@1KM-68Fse!QjNeSZi6_5QLOb%OnBM^scxY z6&cC7Urt#{16vpmn#E~4cFi|eM)i*~+Vx)HgJLEj% z8odGqsKI&$GM37s=W!fWHaa;y-lETfD0^(%XXcT{P3{c{^1*@@Lbh3 z_nokJ+vVVJmG4^%0QzrbuJKcR*%$Y!;(v&E!)NS;R6zCB=G&d<91)_!YAC{1)rH60 zIw?ZVzqSwxcw$T<&G`!%Kc`|D|2?r!jH$sFM^Tc9QME+HR@cJs_**EW zF580s0!fn+uu1rwzwoC{>=yBROFuUQ2;CYiU~KgFP}|?HHCkFiXO`uEhP_6&)cSP` za4G*OsMR1}KhL(qUP`vCUHu23F521mwCnnU?XXA}xA5Nw(CZK=zoyvevYHzE)F-)41$17IWxf<-5*)$V5<%U3k&2>v^vltVAv`!jSv zA$WUW&Z*V$Bss)V6xZEo|5x$TCpl1)?N;{@L}EyA$$lR_g~-rut?!aB0Nnogcp;e%R94FuBKQ+;6zwi*@R@-wVWZ z%^N*!S_8=24SkDRpo$6-9u-WAHZ1RW-SW!KeKntVZNJ~{W_C|4GqERThDOb=5~!a! zI5A1$px3o@qyPRp;-?w9Z`pC|4iuS&v3 zouL=yeKfF{Ons2a#WW>KG)!Y7M-i;pID6KwK8>iGEIi77EGrc6l`vdZue6+|u1(Cd zd)`}oOn_~vK91D8=hUKUuNFoXG>7ehsCGW5V!>_ty8b*;kLS_7QZcDG);3OF@&l1h zF!}Qj6BDmMJ|P>|@-yWgyNYg&>&kbAG`MgHq|srk6yoGP)E(~=yzNf==(x@={jQCXl0PrF| z7C)LqKAF}l7;RK#ZL_{#p()n~&H5sIMY)QNd%0*}IBEdA>IGD8kcaxl+I8$kp^A=ST)TR05wpF8rB)mO=7w%JS-qh|=!Ygs1- znl-5T3I6OgwYq}vf>Eo!iE!U%(;O1qFiUry3sCL~G;0xLR-{1) z3Chf+er1vrryS<+MhBa%Mc^ zbcSQEgkzx7++6iY!T?##2lL%8BYin2v6lLCGshr-s7ke5a!EWDs(*HZ1%5a$R30je zKH`9!jjkN?Xqm_4hmQ^$ULZ-E`~6xWxyWVw{^ElCrWJdC7djqHz~dQsM^1-i+V_EZ z>Ck#c6YgZHrf1Jr*Xo91i}6sK>Ptn(YDC4B%T2@Rq(3@};;On`ZIX*DHypon$Hswd?k6xKVWD{M@&1oP^xaE?&9eo}iTQ zWzK}&p|?tmP>3nITivBLKc$AH`FhlUiI1qnvDW66>CJw8jAM&+6Mfp}h3fug$sI0- zAW}fc7mNnIuaP$E+0xf%Hmfzl;`imo-xLHZQkO4rZM4?7eHR!@>v-@?X0L0WS2-W= zDRn!9) zNJoH@-;;ZJMt+Uy$5r`3O$NZ!2o)W>nO!d_dlU4y5F2kLkz7to=poqXHQ7r^0xmxK zzV$Q=Xm8LJ)4XBv%~CtM9q_F1_OPJ|w@=|wuIE(}*;`&wl>S)Q;nv$l9BiKKOBXV~ zxJRf|MZcEv7&B5nbZ^OwIyy)}Hb=by3zAn;^KpbwU-EH#xKyxC%*~xHbr87F|DHm6 zWGo$lg(0bKsdiQVv6R9wG5-i&n33`nND}=vAg|h>Q&2Z^UP-iN6tbEn1}8d`@T=I0 z)~G2I#0fE$OT6=v`SHSlVWoyau)1uX%ro|3TI`#24l0sCt{`U7;3*7X|Jna-iSrd;t;!$3=Ur0ZCQ%+Gh`ZjhVXt0e!AbjbON0r-$?>b6{P}6;-8|-6 z@2gnGwG;YJZOJ-rb*=pE^>$1u&o3NgULShy`N_+))7(BLdVmbA(E3G~XS~_14lFg~ zBKvm3J>p)zKYOZSkwAK}&@8Ms%k)|kh>UwSz2Gb zym|O9Z41%%H6*>4zQ(KoHX^*B6Yxw!9Z1vTM0nwR7nL%mg#;RYgak9xm$bk^`kkA^p0tK4J&vA9V zQ|Xnb%;-46n1c7k`*xq4ndGuMlvajqExSV6B{9Pd?B2- z*-NEuib7}G6C_Ts>?Ar{OSkQ<0ng4U%Wh?;lPfMNxKk5q$W_6=o;yc}({~DpmEzQ+ zs{n0#b+Bo*wPpdE>!P>TH~SG#oO|oT!Ab{kkH&lD&%h`3No5=D?d$0(XN%2xNtvIB z*}t(Air_qP6QYyUr3g*cz@ZI-IvOo$uNG*Na%et>y>Ao$0=FC0S9Hs5;?Akh>YO`D zH^25?)HACMq;Nnl%yW$;F{LWe|Cq8w^6NPx*D9=qY1#~j9bKoYnCNyNc4XM=R7H^5 z4sjB&{)rv1?|Wei>|E!(NP~aix8LZX!9B(>mnchBx4wZb+q7;3^2fWWJJ8qQ^n|3K#w7X=E#eyQjRI;GVSJ}TuLEGj8LK$6F zQT8ZLC(qJ*`X-NxKV94EN1x*{V<$YHNvm$kTOPoiI{MTs;cKli-VbVmtY%3AH2xQocSO|Kp>_# zMLl3^z{vgE@B8~@N5_Y{audJV4{owqHaTH)5i7#XXo{lCp6$yyuuZ$WgwC>8WDNUD zNFv;6%Y_Q7Gk0E|)7kG^Ua@ZYitr#ew}oB62(ru z@U>uCb8AM~QJ9jf#L90|vF}6&Mo&ji@Yt(CzOukgV=!gHqGS<4t zsJ~$5+)tTe7026)sY~R#wFjLm+pG-D@5=G>1Pe|y_x*eGF~>#Ao&NJ8c!w#=#^6_0 zUN^~vjQ(g|*}Xfz;Pf#nEO-U<^PCDd=tjR$8XFhYVHxq4?AiG>FD^@ECO%yJ0RYC^ z1B_#dR)^8E>uo*bRw*5nq*f9Tuj|34np*su=Lk>b43lqXgQA(nNNv{Qx3dsuR)A!e zL(`+Aw$dtrMRO|nLAP6N6+~xZ-)F6~)2}8H3|3`QusT6zG=dJpl$ZFhZMk%Tv_px3 zUjDFy3(I$|Tnn|IVfG)0Vsy#Wv>5<7KaCDrHc^R6?{*(igU?M@-G##n0P=`*9`URz zs?dpzHKn=vrwqybD3;U3kp`s@r5-y+D<;?}m$tN&Ane@8L~VsnU+R-2D@`OoX*Cm& z4bLn=U5aR7VhistOaBXXHl|I6>%!^IM!7rF*}Q6Y_~^;ee)Ss~89GPY#78L^2E zm|i1Bs{uij6|Q)2S9Ll`#8EXtv!VxhvW|<`uWV&a^^Ibm_e6ppy)+uVwJ!CtXMgq! zc%$J>e;}0k9ug<}^y}qP_-ViWlaNAOc5N7jSYesLD3Ky)N8`aSpI2C57BM=V?)7@RBqd~HiA_Oe;#&+`Vv?pP%PJqj%Uc9R^eQZHqCK0MKFv3Y3q2`Gyg@gs7EW!&5>NPLroFz-tmD2J z053@rB8dP1d_Yp4bv~>=v18&G8(Y~+n$T7k){1fT6ZiiB!1s*@79Y@ph)!W~5T?l9 z|KLS~Z!g+Ougs*J-BGy~IwkS}Q2*8_gS|{9v&%O(+V>&WX7H5x?`1+&{ax;As6Y6?W(4}n!NWusj!sk(|lafr9 z_56Sm3%=ME$(|JpMfYKDs%mSPuI2ujQPFY-5KoHD#TIFj0isV|@X!IsADH+3kWQ{4 zDjGrR(LC`ppzDKALL6B@DRSv5xkrRgSNbtk{aJ?X63pm7bkAZvFFp9~h$(J!7xjAU z&1l`1z@wmRUUq%Bs~+Q~E4}g&Jg!Dd1M(qPKvr)@m)e5!Gr{or9a{6*mUz5gi%7Q% z{V$Et4eWMl`tXLMJ9?>TIykDR)^e+Kv(CPYk%pO4ghX$u>b-dAbjEa0m5kf`I^>vEvtkc#@)GJ#SC;kfj^BT zBhW7w>6$4@dm?>8!mt$daG{lnN0v_5kKIK?2>|*xm8ga&|jNQDLG;c~@7s%yRiLX!2vtz8b;JW?MPYd1?C z41bATl`BFEL3B?WvlMU)C>3j7iS!r}^lKONRl8Ze6ym5?$ph0^OE6=U=ld}MW~QnZ zx;r}heO-R;;t$F_OFP_FrdrphF8Wj!>;+9M*gO2b(ft@YM7>X)qfg{Zxus}v>FWl= z4K=2}u_%%$~WRPp34a5`#-L&5 ztjB=+`XZ(|s6>v!{7)dTayUNZ*JYl)zX=LX2Qe6}J(lQT!wI z%fM|nR-db=oM`od!JYO*ZqTeJkLX1KUB-s8hebyLgj7|X?Vj}y!0yel#LE&4C81g3 zS7XP1WA*smuiKDvgk5rAm~tOFo_0kUNr8baCnQ}o@NM<1P4%#1>FQ$Y>Dg)?7#0t_ zdyFn%`)};M1yCJLw>G-55G;fM!7aGE1=&dO;O-tQxIAEZu*0wjxBzL|$;sj(3kK=2T+$NC$0pEVztZ#Rco z>uV<{hL!#lwzgezJo^fLjodN+6)lQp(F0xHQJ!x0yR+hUbH+&SYg|1GH3GM#b;kfz zgrtZIPH`A`bcTu!WRB?G@7O9PRm3dxyI@#1b2shw%W2t&8lE$or-$nfg8shO>XgS5 zKMTi@tzHir?4q*6BSwKPzy}cKQjc8FKR*-ys!WDg#@Ds->>Pqkn>4du_rpM^lh^hg zNf-2fuQJO=&}#Yt+bu=Xm1X|E6=8?6Vo~x+FZ+~pK-Rcuz9C#&yEoBUEv)AZ`(hK6 z4Z6rgkIV#;Q1roy8oRTn2UqZ6-l`jZ`9N{PRIob}#gt|vKRA)vDiG^ zuE9n=HnY2D$M7BlqAhP@pN4zA(!N zvxX7t|5bl!#Q%yH!Sx?`5ekN8rpyY44v&-uHsGI1%u1$?w$2X5rjGpl%;L5-PLCT_ z(nmst60*uzGDk^xhsz8J?lr#r4 zLsM?o==uExO5u~S4fE${wz$4H**m00EHsp%G*WD|g;6HeA7PG;*x0NLSlP}PCu)|_ zv+fcpGWxc!gwLPF-*S9zjX?76ZCapb+aGh?nV)97qg>{hc@jl~s_H;KMt`R%Y;ui} zyQt??Pqh;Tg*Ob%J4F!d=9D$$fs)>IaJb@r1);J%ga3x}BaCB(h4gDtHH-HL60>uS z6l<0#;8Oi{Ert`Is|gCue+%56wsP znRkb2?3H~`NC^7e6BOJhmbI2gt7VSJ!Hjs8w-4{#?0(DO&k;%R9xxtt<(}B-|Qs}2FYg&-v2c87T1xr+wWHmE5__zeWxeV zvm5#Yf2wmz7KS~cb!zo}D#mM#YE_N}0k*TLA{YHrgd$HM1&J^_?vGkRf4_N7BpG9F z*p))j;h6S=kOo}gkG@sm zJX+QK&xlA*?}bwEKipK7EHZ@fWi3wuS%hBor$9DzWSO9kGV2CEURGY>CDN(aA$f zx=b}?BIOdz+9WnB3yD}fx5~m5a_c(H`S6XyuDi1(aO#~T#P|^W0UH*I))2-b(Jq4? zwK_vQZ6Y#(S`x@v{VL>MWcDmhu)~zRn2|3fDd)zz@5FI3T!(_A^kFO( zRFD5PN$+tA_?1kU3+Q4?OUZ$SXi|GdWq<>=>vQT&4m4b@CRR}+~vyt-O+|D zX$`eBGA#5=SJ(c56|l{Q=wE47=axkEg;^_F<7Uj`w4Ggn@2?~-1FoPC@&MC(%96e= zULYdf?EW7B-u)A15dbq_qiijB82wuvMWtedAzwW;uS_!z z2##)`DGzrXf0O}0e$>VQamOE3;sB}y-(N`l8+hQSd z0R4SLXnXgkzkvs4OYxAu4jo1upd8)YWjx#gt@aVO1FeoI>o>MZJ9Bz(-y*wOo<)@K zls|WSSdg58RPOJ;o3s^%Squ6K!|(GHKK>Fm_jn~dbEbxtVWDW~YtQw|_VaY#+~Dcn2KB|ULTb1%2#@{miVYJJWp=7n5r z`-(p_{TchIe@wc7)mS<)Mp*XCbGa-2Mz^2_JtY~n5@#Udh-yvGT>j(%c}9NxS%GbD z>cruN3tLqCqtS_b&At>VH`3WfT@y&B+lRPWPMqnnmT}f&REJlEGMhfp*^6I;B z>t^tYyiD~(M-RrVoJOrHEIRef?UWb2>{w|lrOuo#gZXycC`*&}X0*!E@*8tE?n|5M#Nt(zynT`rm$dIw4xYF*hcz{iuh|GV zPGGN2vs!rM2RYB2sP+Ymp;vjvk@=FtrAq=XpBysoHS#bknz9AoEs%AywNncH#E<+m zVZgpU|?M*1dp9t9V-mA+@ln@#5<0 zbWIK@t^R0NIannBL)M)th2FfCd~8N#bU#E=(faUxF5Mu~Ji)qkY2Ow(b#i`QSMKiX zz|T}C0^h?=;1c~Cl-n3nQBTFOU>>j@vI&~%temEA7lggM)wJkaZ#AwcqA11O=5#eo zuz3+r3ent;+BM5^Mwz0Ox|exRFl^>PUy0|oPB^!z>Wh^xoe7yeiO%Wnv@Bg}I;kr8 z*&R8jBPr{na$<(#9G2{ed`Kd(=A2}LsNA146l5Ans?z%I3u<*9Plwrah2vUjeH0x@ zG(xP`fqtnx4s-^qLAwQ1f4w6Q!A%QjYBb?B=o$Ozy+v+^q9S`6@&&d3LbIWgj8qK-SkHWPE+!EXzec$zd%-J*i(>pS{&&8W5bV#nQv|&ck7A=Ym4Bjy-NxD^`3{7Es5V< zbk_fa0bnVA2$uiPN;8f>H3lq~XW75?+*p1s23S5b5+2n`4QLy1tooyF+h;%TGxRG4 z9zDD&PJ1;ycHqnr=YPYoX_GYoPlj#82wVzd@O! zhOyl@kkEz)to2cW)f^yrU2xnLpXV#9A2N?B7Y_roDO-b&*A9mRKPE63;WYut9 zFgU!B7-$NA8~KzZS921|n6-zY-Q{IePV|)?IlB@b5f9FS*#ES&h_0nnrc^J*lG#vR z-`Q&2coAr*ar)KNX3dJ%GcK7;mhRVf&-iXtxb-kb%WKFA7B%RX{0IisP5X$DKtA$t zscMJjg{qc}DF>$nx$;H2gN*X+_|K}Gm~5-pqm4jJCH-;&E>RiXC2fJ{f#YqJLm#Pn zok9rV_R<@!hOLx2ygBZ3_T!dQfNbn(3zajDaB|1`fz`#ija3th)OxB*bG?Gu_B?{; z2QuI1pOiaa7B6uUJBBt^k%4t{%uY&oX)bG+J)3SrW+&>H=_>U4`&Z;^WUM5WaE)i~8agl;(h+tBe!cSPZYcj zi)@r_n)X}dA1z}S^ui7T7LKyO9!qO1s*Cv$H_DDU&Zq)hmAFN{zOeYzc_spd8d^_a zx;y{(BHdyne6br}WH~ot|Kz3l)>u$}9t|AI9jq-&LpI(Tmp&AOoSn<-=$m-BPG?;c{Z|#kq?~CWIDdt3CU%7nhj!V3=)6h@!E#kJcrbNab} z%-?-&npgPlvM#cug6BKD(-mUHRZ%ln)dlr(%|KGgfrMt|IJCl<95F#qM>1fkVg@-g zM$;RoB^|qMoY~ak-Gs?}B{#zfMjZ9zT10!*0cV;(vqhoK07Y2kGR5b_#5lreP`9}J zmaMX?*Lx-}?C@M?`gapyckE&|SXCVT3I#;H?=e*OIS0HX97WOkJ24#w7#M;(CNUqd zN8GA(zcx&i8c4W=%gn-OcK^Uyidjha58ioyVMV`#kBgTS=lXoCGSvH3Ad2{b4YImR zy#@YMc+)$Z^PA>bxXPFF5`)j4ZdMnArPGHu2L`{2X>d3GR5oA4{j|1A*K+mw{r=%l za>*69p)0O+HdDWVHX-w>m^|ZBG%X)}?R&-Kfr*P^&zpHrOov@a4C)Eidymj#iSZQA zvUx3?5FyI?M*61SI*~)f!k#6lz4Y)U30uvOC7Q|XUi?e~)nv)^m9VQj4o0klfP|4? zQmLV+tPN8B*T7uEq17}l`XJKK5r-2<$sGqHspz&O?ns?hTe0?{?vDD)>6oX~amXb( zUW>=_Gk7@@PJ3Q#?F4)#NLbQX2F@4MGlJDqr7qN-b4Yo+(^}QyGPIq(&t~AUO$0;p z;YkXLZ_4xsRIWcoyazEGbsyQv;l5`)x~kZx&uVB026*N{OUA*R41+yB2rtwumqgem zlu+*FCS*0dC#WVk{CFm583sK;yu}Tx4PN;3vf4_5q-`QTe(}$_p_LJLoe=lNJLH}1 zXc-|TTV-;k6RNaa-Q3d2=%6|NZ&5<0L=D|?qxM5$wyR5YyPJG&VJk^w^9@yT;$D3^ z=-tH{vMASeS`G0kD99a_4WtGEOe?p~p~YF@k{b%frluE4Xk5{16|B}5lvve%@tLE4 zlV1PTmX4ZVr3NdF1~#li9nX2_V&vY*c1t&)@5c;@<1Y|N9m&O5Tbh&M1Nq<(7#IW# zvV!;cpO>PcJcz3O0v$7NXkI|XOm&Txg1!;7#fixWr2K3PBT=?hov1;&uy;1&j=S@P zp&VW+>9L;&e@4X?Y*u^!TCUClHpH+dODK4VYLh+yF=ncsKY34Z1`&FW zyesFT!MPO&@uF2VUt}@SlTc7`CK_U}yAF!Ai7iyp-lTIS=HYoDu0PU6p9i9JrTazH zeE$U!K*0C)wI=zD{nKK7*?a7+lRzZy$bW|DTbJNGnjH9rjx1#(fxDDGx3_cwoH<;p zN9wu2>*=jO5@HS%AS1%JV+Dli8TnRPUQVrD~g!bmd z?Nj%JM+IF}9F4D?+)6V~FJIFoQE}m&4$0Z5;$;E2m1YvTkt0SX_uE<0+fJfQRpid~ zOQIHmVo^@SKOhqh>;RBriJ0~h*#GExJ9r~A_Z<$^YB(LqA2pCz`9F9dGqkTSDs~B9 z)`MGVy{FKj=Oj^CgG_I&(xL@)a-r(a@5^4`k%~o9U4U5_Jp6n0I|TL|f6JHn(^z_@ z4Qhlp$Q_l|R zvvCxNqtU1TAQjIv$cu7C56rEG6MWA!f@P!nG1LLJ)(tNIrwYXcC%N-wl0jt8B`ZNz zFG1wT=d68vO-@JCjVu4$4Nf=$T}_-0=_)RF6fQE8g2Ms%LwqYx=D`j$#Ir^C?$e-0F!rW77!W%MmK2dlc%Bl`aPIrf0n zO5;g>AQj}oWCWMu+$b|*6d&-SVji#jAe@6{EO)9T|7w#;vvd>gUMFPqo{z*>&k0{% zH=-oj+T3b0c;f)7!btwy>T-Neml}1CfM5Qw1m?#rz9v)~yZ=Uuvide)0+or}L`<+` z@CgFgjDDYRx5FuB^ZHCD3bBvydG{7cSKouGAQ1J5yu0cbNQd-6yu{5_!kX&)P^X{v zLIS%^KO*YZ*GnP<;rwEB0xJfUk1WEU$o;<`sja8D?9+7Re1=*8k!C#1z-O4iB4jko z$>c-+j?Px7YWj%m)6h;!wRIn)Z;S3_vG)__1-46~GHS!N?cqkp&CxK?RXQwCqJm=` zdf>GIs$P1gqgN%*o?g-waZ0F?j`{tj8`_kg`n&@XVZOlpYOTY*Hlna3st2=f z&W0;Gnv55Y{0m+;noiB8OasRF%)WD}4~hXH@>(;ktNncvGL7_5q6T37a>c z75j!kAQ&YdgRs^b&LP^hLUYinOW zm%0s3%!|>Ljw?$NENcw^a>-pcz8=20{F6e8B1%YgnBdC@eL8(U5&cV&=krT?J+@|q zGn6Uluf#>f1!PFp>CIL8nkc4%)s|4^3wF7bR-9R?( z;2A{?lR&wz>MYGgs(DeL7Jb~2pLpj?&adNedDO($S)&YpEf#frf!Yw(RB$Ma*Y!SG zS@y`bbHr&z#GRixe*is+GJJ3WWL!4B?XxUDJCS_Y@B^E82LTQc0_-9lOm9O^oT zmi}?Ph8TUvhlH0(Kz8X9E?f^kwx=a`3xOxrU8QlUN@#4f@m4AMN?$HhKPtHM^7I|N zQHhx`GMTDg+?S|fB+V@e`&UHPf7olYV!jkw((;WyaUK!XaUOwsFG6O-ADm|w&pCS* zfgnH!J-??aT*&7$cJNPTB|6~2AAzUa1l|$JXdplI`wOR97}k^nPaae;uQgjqt_yYw zrFPB>I&=E_YU&@Dx@#Dtw3d(*N`_^N6q@JP>D%p1_B{$)-tp6m^k_8X?#hU40!POl?1GTK# zm-~p6kwDGB8WUcfbLw4FV*J*w_(nyUO%&Ib-bDiz%W1?T-S9NYM&JCj-#^{eLrG#2 zLC}hpcg_4j3wD>vpzU3x8DZ>BGVu<1w3w9{nf)1^8EAmQb`e|Y8_=~W?{1-2YwF?v{F*3TN*=Z;w6$6cGzA*G*eMg1&YpR3k8Bc+4 zccrsVXL;h#;eQ}APj(s?xQtDhZZ@zH3kT*p+R<9TybHNeN!NlnL+A>IST-sG9%AmV zM+79hEwo1%?0RwdVd-554P}fg4p7~IVHAC?VCies^_ODhTX9Bd>PV{nxP2YjrA%z*n+x)e*#6P z#`s!>XMXWfo=Ov#e_k72CqKS6r0E%6tRee<2Stdm3khX|V9v2_X<%5TA}pP;K#OJt zxw%=9XmD_Ow0NSZ*zg`0@>z+fKr$r|Vhw^QDn`_{#km3;Yyd7Y=yEkp49gN`K5;Dj zxh7WBV(4USEGQ9LXfGoM5+@hx@++`a;LSIRj}I((FRrNZp&xznT`C9{Wbmv#KK}pS z_de(rKR(O*lMj2D2mab2cz`2HCJ{)Q2k|5}D6OQ%TJ3{Ck(GPvH{wuY6}vqGYMs zw-k16Dw0PBrxzmrHJ;SDLgt>YYSmXc_) zC{gh*iI`*ZjyL!W-_nEgZ3{IQdyPLixxn!t7F=HB0KOQ$?NW}oMt7hQz82cK;O!oihFDh(8AigL$L zwj(0Ko8`}prvVB#&w(e7t^V}SG(>EyG&C7SsnNgY)*h(04luWtb^kSTFaaZnx;V)y z|KP;6%w#xlV^)1fuFC~tQb?0rg@jl%iaB_6L zpP#A$?qMe=B+cYzYxwlC4U$4=KV+qK_C9H={HzS=y_fV0m3lUR!r5*g7Rh-l!z`*lq@Lx-xd zKKx%@llmqFJnbt9=`pknJ>=@QZBSeFg+eT_rdY2vNILT`!`LZZFUfXRuB z?6>c$2>@6a^PF$z*>K}Nz?GSh#NCnc6+#ii&BlTWc^nVg?gpTH;heK>*$+Y^55TvG z*K2!af$eU187B#uz`Ql;p2H0}*`l6=yv`-DY;p(I`VL+aXGMzRyIQkfX)O?iW!}EM z;Jgbt#||uEshQ?z+Tp1aIqvPr!hy38^rsKKo1VY#xX(S6bGwoC=s4<65Nf2drw}fGgT0xI{0i zQ2%MVTZ&qD>^Rpxt?M%o&2qxpsmV&}ZACW6N9*N%&5E6v`TU03;KQXKX>i?ZsQE2a zQ={V^iQIWVozW8p9 zNt~6_IMF%_eT8^CP8pEonGHWGvL)sJ~MT01|w7 zC-Ez6CL<8wsb8VFXXy=CS;1;m+kEIFAh-r|A=V$sSbn&7=#>4WI?)+r^KfE->$?}~ z#jYijr9o>zCa4{)^{VHcynaHi;F@%GeK3|=? zc&XxvsK)V(V#T#If@sMr-r?c$XIOsNy+U{y_xXePa4T_(`M?vTKD{pv+}=qA&OD_! z?K8M4&vPrx`+a?c`s1#KS&y{eewi!=sf0e4&>E6`)+)E$DMQl=!u5!({^k^3sDIiLrY;it--G2#j5#PaOI z?#l=(=NAJgYYxPLh~yX86!9lAaKaRpW0ePV67U|%%Gxpgmu#C8M^y1k>kYpAg`!U+ zkL3$Uv?9d(SPLvq_Yvmz{Uk?V#Ps|ns_Wt?#9U17Qr6$0oWq}}kyU+K+Hi}pdV9CH zRd|uJ@kJ+(fZ`UrdRUFVR6sZP{mH9O@6$cFkOc4PbOmV^y6~$5uNJLNY%Zcy@ z2b#OPWNcaEDY-okcGQfkKhY+yrO%=aW_M|H3W=!;%iK#*$V^|A%jg?HE*4sK*Nnsp#Jod%#z}aBQ^AwF`UM}`N zz+KIS;oBOKte|PVLlxf}UsY#|C`Wp|K?iYp#88#joGt6OggnW!C7(Zxj=hi}JX{*l zxhTD6%&L9iPJ=TTm1FBd9ivJy#;~5WPP41CF-2HZaY3rIg+PEgD`NfLpw9hcXfO`s zszPlyW@ma_-ikK@-713`-lqYV-*vUjH#o4RUt~-S3-xcqBpvm(rB<*UOeX4H`ltH4 zh-+v)5bZJyy7#qV+bRNJbwn&g+-|Q7Hv*Y0FgD*o+Vyf&6vdp=pQ;WqKZw`?A)VXe zl#3gsLgi1l#YgZv7@`4K9 zt2~-gc>*6^^R!M+&{%2x7GFUU_j``oIvT3g^jk04S6!IT;gcv#fUo8ob-~7sk{yJ` z$?w%hMHaF@)5C;DNF$ig*6KK0CQ0=3W(!Wj3K1&sX;?`{t$7xVyiKpB2qNVp(k8NV zd>j9Cb751W$)bO&4mS(e$X7C$)aO))$Q)KVmDnOBWj@d|I4BpT_LLm=6s_Nf z)4k0&OfFa{VEv@gfz9+KkOFf&j#7%fVya@s0$2`zZ4Nzn&d>p$zLtNQF9vUSc$$rp zUVo%Q&fNJ6bovBxm2fjdrV9zW8Pktg)!)(ya2~yu&j7x+g|@(F1=DAqeYOR!4a*li zu7TrR&K~$Pb^%LXMDDBUF5gk5ap3JOF=^KdSP0Vs{*WQ(Si5KF<^S!kfB(VT;Xj=h zEEmTpMlC9YHzR;%KLb9wa{%vrDeGSQQYNDo(c6l79@Jv#aew9aY!`{gP_d3FhfetU~<7>5_xem={hzB=sdsbW#i@Pu+>^l)E( zI2qMAw`r{pUq;7xv4yKTn+#=hDRz4|H$f~9d`x7MSKxq8^+you#EhM&~v zg!8&q=`~QQwXDQ7{1&)E*YfOjT8GtYA|QKCw%*1?Sq*L1GMEn^HlST3$MMwAn}M6) z3{$}_FNcUTB1*(uN62wp{_msUpA7OhXE6`ASe~>l=w*rzMqQiv^pZw3uBhStoo@*2 z;du5YA_e3vXFgU0Y0g;1Ata2hof#J~e6285;wU%g(W=U05Nu}a+$B{uX zVIZcy5y_1sfCwAK?bFyMCRb{=LX&7@j_*!}v}J0{6eff2%aRCI`H~2*-BFaa&D=XhKLjQDmId%ysC15-hba*f-8YrEWCFf7p+q7+#TlRER z#dnZ9ctlklH9jkSHmf~AhOo?OB=jSiUCk;<9;*YNjxyF)=o`ofln-gw$T-`f-lWr| z)i*3LCHU~DBfyyU=Mw4vebI>fbprt%4c%z;VAhN5XGUh@8E*^LNO9sVttsc}lwAr* zEd(sGNo`%*`yWkkraUSQG7_7SnEmhqc(^iX!K@Nh2?niFEp7M+cUd7RjtEAM4MM<5AKjFY=ie^)pMXcA{rGxhDE6-68LsmMf}2 zuCzU%ndG>UQk$-=$^Er@7LHviC9DGYAL$vx`FC#slNNV*TAlacSsl+Xz$Qx-Em>|F z;;C-cehM5=L%$Kz#eQh%r_1j}4w}mYoYhwbc-aX?*z0r$2KuvgB+}BfhusU}<(F}Z zDSg~XZG2u;JLySZYUtlIuyAZs!o%iIo?}N>zS<#~+ng75!ZDO*I9*v@njkt$_R-P} z3CX%kUKy0<rWmih5+y621V^cr=R z>7wdr9Y`a~(TB5dR1B%fkY&POywP7_hzEOF!K{9U>eQNR9U|5|&rS-g3L1r2&jYud#9v~}#v>SR6$d-!I=8{K2ax&fJDY{E z$NB_lzoqy;?r%RV)cyk501Fz1@tb$y?}&$~h3^}zAC^cKT-pxQ1vWCIxjY|*yxE2T zLKdJhyEY^|b(*8C2Bj!R#9b?-< z8N@K#@Gsmp+K@V_-#T!7&}EJ{-!$A`1tzTD-H`!~Z%>W5J2eRu{63y$vp|SgbY^+* zD%RJrkTo8yZNTF!-T&iz9UC#2{I9DyeUL8Z0!|au?j3ljP&>yoZmLgzL z{r*?-S6E=d_dB@YqVjf@53(q*=c8Nzl>Vz5 zr)mx5*l+k3z|OW?V%Sctij^1(=K9-tYFyo8>1M5bD;2j16_;1 zLJXVCkiSA^Bf_>nm<9zg9__JG=2v&42nIzl7!zWJOK8Dq7nZ_H%mJ*{$Me5`RdP29 zrW5%_{@4CS@S_xA!)3{foZmG?r!kasjDl$>b2Q%+Bd4D$3WpHYEk_5_E~fmK!I&R? z`~KU09&B7(|M2tR8aJ=T^_z%ID{~Y`O zZ!qR`807!szVg3``^xRV8qNNPj=cyjb@sG2MoQjw`w{BwPDn!%RI+j{`6YEtzPh8uP=lMP0ren0R!mjZ-r;yvb35+h`Q zT3tv=CDsJIE>PBT=PI~P1ZRMd4vZud+vt0`%mE)*Uq;x};XEz>utpJ;!Qg=oQ5KPU zFB+S3L=Go)j5f4PJ!NL@?_jLlFfy5*QR+PH{c@}%*4>4*34JxzminwTnKoLeHomuB z+s)_vDT1(IM7}O8RYjehmoXl6_W}#1#}bTweiu1pbYw+sO6@@M?UX*@#ojV%v&M>R zbWZ@alM=eE=neiwa)SoHcKrjoq6`y6?&bO z8OQMhn}Z_*se?SJEc-Yo17WCTm)rQYzgF#}*2$_2U;N#6McfM#O+;~lN*0N$;)c-= z_J&1qwGCk7mWTDQIEb^&FOaCel+*>0G?ISQ@>T%w+jo(G^d@b47#uAti~Tvpl5s?gcveQ)N@iJ^aqfu=+UKew+0vXJ39b=Pm*;YI zIq3}Oc&TE_@3KBYTJ=MKL*L5Um1eQF!*0Yy09;tER7U-?5hhQMXLkbR?E|^ktNnM* zcc`USVzo2YFPDE*ezfZ!nDmI~RLtM@u!;83))o&%WMdg1xyC5uL{nRlQ+No3rmHtG1=& z_~bLjD51c<&yCE(_AgL;;I9F{+pL_6Re&gb-2;1$_ci%V#NKR%i^)_DAG3y zMNdF*xl;2VYnqT$CgNYeLJ5)atRHk`$ti#YOyaLP0}&~{>b@D@G3la%w!d05yN{WD zh+DqQN|7pNBd5lreRG=e?7Hz6NTlVp+Sf`B`?MUEn^BJlTg4mo0>F*`dCAwtW*Zna z1J-JQJV_rYU0iPm5#`5kIMKL_?0Pq$k#8pZQ~9J==LgiE)-9Z1c5Nzn@Tt zZ1Py3;%yiGsx2dsg@y31o2~3P~QGTb28A zQS&(^Z`)&D3#uuoS&_R%2BVw^+{Yj<`NDfymt=K6rzUu{G{GsbYvhN_J7)P2j#?Z+ zlH*Bz!|+pMnrItz0gROAY3pK@d6kn@;#X4|RvH4S$ zfBzk7wi0k9K#TLXaVlkKTJw#7T08qG`t3F(^v=z3;;S4> zmZEGBa-dvggxtiB>d&AG8vVjU2&URL+~&jHmqT5nyt){|jN_GfVoB;XoK2CTrC z*YVvbfoaAy@hk&p2TD(Sc?dJQi`}*!#3j}qG|Df^ioYvWT7c(HGty`2she%MX>U>E zfFQ!jr^Z8{%jq0#25JM_C<(cqVbh8^+8mV+5th~b6d!Y{aHgC|+!Sh3waez+{JL)% zO+Sk&xBa0DPmR33{LRS~)CcFGiTR?O=h58X3*O861!C3V=~v9v6~|{p7ll!CJ}y%w zTADjpBZ*U)tl`sTS{P}Je05Hoa|^RL^?ib~PugGoWF&@X2_ckh3SG8`5J=KSzg*0X zn^WgO!YH1cVxvvzq5*h?G?5SD0|eK-O?R&rdu@F0Y>OhTo6BfKxsR@hqv73U_B%Qe zvA4EiIiE(J*41uYru%l=*kEDkGmTx zj8{k0s0$!YCwY}G^>|Oe&R}7-IFq!}SbSHTeQ(5_2N=0=)xrKF~EzL1&q~Hm0VCdzxRm#H9ftPNpo`lEuiQvt+ zXvNmUgC!peLo!VBh#sX@^C>gSg({7fY(Ji8z9c_G-uuX)}#3SEP zbrs9pywaufef#0m^eAc8%Dt7_+1&~$f1Qiv3a{LqQGlT#TPU~5GtMSbF`{o6s;ZqP zqd)#i23X)!j#$+rsgbHiVut#8O!2=_m)Kf2Cgj+t(mL4Mc7DMV%;my^j06sF99bP< z;}Qj;0@DfN=O;MpheJtMK9nSwrr)Hh^O6OTjKn_ge%e{ewZ5L8%#eC z&%ZcLOiXyXvC+2e^m4{-Nh^1d2_HxIdrL8PvnpZq>)BadJqj}`sTa_OOX|{hD-ukB zKWGjN?JX^Ko(9Ucu?|1H}5C;4bQGuPm9rBQ_fPgYzhOL zR6l)^Ztf`;Q%&OgbNfOW)S{2Hr~N-qcgis4jD}x1I|`yaPKV|%jdO|`v>w?!?5>nVhLPb zPiI$l>7!3b0?+T&btbahT;yrvH7O!K(G}NG&uluE)dMDXxXS~N+p7`<7%QX;(oNBu zpWX#lMOt%&lWFBl181W@If=;Cjstd=7T$If&1y#4LFViq#9WE-f!qZLh0k8{)%P5c zBl>}x1=`}Qbr!l@ttp$giGfi6w1@P4OfS~j^=JjPS)YG30k4S8J`k$n5qF4*!S~tQ z5{{+j1mnpUkaIHrBSIGT+Qh?N4;vm2oI>A7-t8s+=?Kxzk!iVC1q_k{T=BDE9`NWW zV&Ryk){}zd?g+H;uPb4d5fVg@o|957(CG-F-v{X*s4*{opm#U=0yzMaa(Ac4_++hZ zUYe_X7=I(w3)ch-12-FfQSO*4#@W{t^if?~=*;B_4-pUfjZu}&`iVgNTYIgQPH-bK zcq3Ik1D`2OO2zfVQhZM_gLJ7}8qb_VvE>z=+xRAZ8>%S(vq~|5P4Vqm7~Wsc%L?$2 zh=k0DJs6emd35!L%cxaGFC$UBXr`tf@OJ2b`h##iLQg_DcHAjUHerj*Qmd z#wBZ~d}{nCf@AOS10>+6V_h=SN`1nS{3-rHHWuTC)@>)+amGMEo;v2+n*Vs2<~4q| zOhk>P+6gN)y#*!-9k`Ic;);jZM_n70b)mCDB=~bSjEVtrxO5!jh^bAFNTIl&_sFuf z)`5A`ZB^{?RH~Xc&2+ZXp*`^qh0$OSkreo$T)HNA?EzEQ>f!%l?H!;qTeh{~*tV07 zJGO1xX2)j79h)87wr$&1$F}vS_c`aDegC`fx4(PGH!`xaMzUsV)~q>atyyo?^E3~> zmfBN7n_BO;i207Qa1G#I%>0UqjS1G-$ry!b76;?MH&;|U;j-WI);fQB2?>KbHkQ<|8hajax3&SdC0qlc=m;OnInTy`a|JSkqkA zSkj1IN$;_g`Naj;B6!;TDh}LkdUP*ub}zTm12;5BPGv9q31~_7$>5;u*v@>GB3atZ zwffX`V}$R-cNoe^KKwvNd~KmvsBsjotC*4(-)#6sKYxIaL?k?CnTN44&5)VJ<;|!Q zr6a%NepTce+zUU}aQsEf#d7z(5I;aZWaCkK`IWY?7SE&23m}@(yyyt+Td!DcJ+B*i zyyi)yw?_NFS^B~08Qz_;`UDjvhyCU!u~*A2f<=gSyFkHmY% z(%v!w9yzKe6^3z1=f5U4?DO#+w2{c}(m`-qiU zM9}=|QEi{R*H0aR$ z$3jIVSNm1XF+G)whIHKz8MzC+k&O~jf)`&{ev6%QmHuj^K?j=^&JakuQyiF>_e-*k zoQ3&g>os#4HTMqL%CWmp_DcFs%*uR@E1Gm1(YHpn&;A6Va=c13>Bm<$65J9Bs^n!1 zFy^wEFQh-?#Rz|&e5ER_e7f{|f5+n8HrD<}UuGG(u|jV$AJtWLZ??hTUO)LcM?R~q zObFhK?W|9H*tUarH08zD$Is7=yG-2Em;xQprnO_DBi4 zKM~!gQz?KJ%>D}ls=s#^_$NEp4`j)d7c6%??Qds$a~Hew=geqyhcO|lY!-{)!?F+V zL!Lo+dCKFwlA*mG`5W%VaK=q>pqaSfH~^f0KD->i6n|0onWH+M*ya{a@Bw|E0s2G9 zKSt}b3?)@YZ8j*263uB_eka=GI13$EV?RnTd{#E2B}yFQ3B3T-PumXP#dxOh{- z$bpaiIDieIGbtNm)Jjn7#IDwfw_p8=x;;!|9jDSJ@I_=D!n%g_p~LTvIX}l0jM7;$ zLVm#voDlWJ=OyEArQ~-VLyiFe13(Tq_sL-W=dGf@@4o;BLV&nonZ%*LUdz7hD*coL z@siTL8Rq>2MEINEkwVC-#l1hFCej8neb>%I`w9{<9J`dH_NER&+F+V*G!njte%^}V zz2rild26M!r{pMZ7|H(>zvg{WVzcy#EQao8*;{47BKR16@T78v(k$<_qHXyug6A~< z0g&`9;q#Qy_2>sc-8eyJ&0QLYj z01ltu-vAr|DgX!oR6pCDUp1Fr@B;<1cv z??Ye3X2Z^UdU$|g))-~@YOb6G*Gy=_#yB}f6k;GKrx8%rCO=J{D=u6b-ble=te_Hu z!d{-EssfIhTf5OyvzUx%pq_ckGHA?5-gW) z$zM;MnuDt%o6}7P;RcBxc~bJUv^z>GkB+wX=Z}*WgBrU-VwO=#24RXUKRygYR7A<- zU^p%YEY-4ElApU=E_H;H8qxyb(+vLEd~|><M-hRCRBX28K?%4J4*l?Ks`)aG#Pf9niu$Y* zFil(=T}CM>5W4~gwX-YIXv5eyCER~%TXp`765Q# z4u?|7=@0>cEgwnh5SV8y(u=vkQ%%z!_Qev6Xf|LzorUSkZQ};PZGDFylNNX0ZR*a1 zPp}A>+6Liseu3y@ck$dU1McFXw=E*e#Khe#qh$~5fxt>i9W#!Tge&RlW(js8AV!Qu z#044d`aIz?U41BYPK=6^>t_t)A(9~w<&su4h0F$CjTgO3DH9MpXORCz#I$6AvshAS1*h%_KD=RShH@a4v?zk18&L)chVN50lU%>aa6VM-eutG$J z{5+!g0@?9m?b0u4dgtcS-n!EMbpKS<%ERs9>GJ$^Z`;brwB`*ifup}tO`+|zn^H1$Q6`Nc41 zdzC$f2fdQE z1v^7CW-?AI^a?d}!M^V|AYp3(dJuuJ(&OoM6f6ess=5>zvt{sFnsocwpa|t}TGD}; zyQ=$fm9-g_wRpTj0$xfP)ia^2RfCK@)Z(U) z-+q3uv$RIMTEVVJZI%j_GF)#pylNw#4H1fa2v!_2`lpYT@9-+w==Z4#w37vm%o3la zfm#-umolX^%7r?>5TuBGn!_wjA)KROEn;A;jCxg!4A=LUgjtwDIEM!6hXz<`>?muR^&QaF zXZ4Dfey~&oury>xXl64zg&sr99~BcB*#QHzTOyXDON$m|)Z=E03>5G@N3@CK-p*>= zDq_FYp?a1So~j=vro!vu!-dg$qEjz6;oq|&^iOQtSr9h0Wt$Xe1~V=fc3WK>h1iX9 zBv`+r4^P!dVMkR9(P)gT7>l*y;Z%GP3$u;ZL6iveGZHK)>9z9jwSlu*t6D*0Yxq1f zC6=_Y`isD4vHv+RgY!b3XLijUn8$*p#X$B1H7T&Y@|wFX-~v=fxrUvy zF0lk<(!NtNzE#N!jiQY@RHYM{ut>;$JyFM>QQGO&XrM(L7VrP z@g8C=E4*1?u1dp$)G{A88w=lW=eH+k!+IMBbFDi0mfx-%m)d`Mdv$n!Jnq2mcW}Qq zxAL@cp1prx>g45u@l3#c>D<`dSi`U2Jzo~;xa{R-I+bM|bborYZF#!DOTRwZ91N1R zi5irRG9Fd!@ZiFcvD`CzSbbmbwYz@7$G^JReUN2+S=|X54mep&eFz#})5n#1xvc6$ zra;ow^=yCfb+1Qi8gng39N1}dK5e=I{OZkvg*p+G^Q$>=qEO^|#|~6mrUBkxKC%H` z%WflT+weh!Vm#jAT(L^`>;g{r9m9*qkd8f+%M?F~5Z@yhnAFiQw0G%7UjWt zT{~1rVRJ+H0S-CI=&#cWJ}sy<|CrOX^0|G3CFSs57Hn^~KDBFY<@S?OE;`7NI9 z&pYA%Qb@|i#n_(T^0SEfvmE>1Hui^{f{m5F^`GVF>3&1>|Eos&A2dkM@aN*{ec5g={M#VZFCiUZsMR!n@fpOcFodubv*6-d&%_0Tl($&kr!xDF;~TEn?D7lOS0G-TqoI5#1NI zYGp^q?3N_1KC|0PhHk>@)|BV~&r1Bg7O8B8h}&bneDKVC3|=Y=S6W{4YF*tZ zlZ#XOy9Q{VnviU;YaqGdq2u%^+>76`b{;B*wZq*0^w)zc2CV(>ZCr+IH(@r8zZUcN zobphMJRt}(YJPdsZ~N)Pxj*!om)qdLL35(HBt|ou_*%WmyrQ3@gtWOPEvUjodWEi`tjizkxryBdo9|8V*60?fL!_|%9f|I zA6-I&&H@xu2^=_3P5d}!K0hMR1aMLqT0RM5i8u+pi zye-O{>sLr}me}-;u7y9<=5hDbh$@0N@W>3TR6L57;jl2Zn(Oxivs~}hz@$QL>JZNM z0l;wZ3|zaW?dmoDop(sRiF*KCU=+Q=I!(jtqP=VV2B@NvyVUMUVA9i&6j0HPqCj+X z1hrJ~xk%mZ?Pxi~+cuKTV&&0Me^jcho`qV{_hL?o` zJX$|Yp6i@wDiHOWamPHn{Dy3{!|W2f8`xh^s0_3SCx-}ox1;no{kNa3U2hcD%8gFl zkH_R71oU2FcQQjJ30SVs0wDpXVR_*msRxpMGx32MQ{>Hh3?uuFzCB{T(64Q>CnOAJ zLv;X>LOCo)JR|RCG2sn7eSOtr!;hNPTbi(-%jbr1hxH#fe9~{V z#kOSKIXt~QkX%_~gRL3Ao9&{|=yswSJSgCYe84O-$qW)-BV(KIAzpy&9os9^<5q;E z9J57F8a)ZnP@CkAnoT{7T1~zh9Xwm?b!9;Y%iVlvp7p6^@*SZDj!7-RKbCoxi^MGe zn|M!IMSdQ#*;|UYGTx@NMpIjv9n=RmSm!@NDkDpHaKa%+feH}FmisS1_F7>SAsto| zmw#OD>?{vnS&RODXv7=(0f6fGgzaAfZkhKk981u8ch&v}4P70EkU=k{JOgj*_EImL zV5txflnBjWOCzuW5*a_rPr2Ew`tMxqJgF>=C;IOo>B3ua4pe(pMmd&7Xa~f7B;el0 zJu$r8^;z5z2-h@iNgAPCM=7KG$H6ijfW4{g-aY_{%AYyle%j!B!NIT=l4bKrS)@LK z>nrFEmKJV3@ReU>Y{tAMW3k<=hQt56S7{K@)SkG&zx(OI={it==-(~OzAYqn9Mn1$ zf{!EMa*M$NFvd>b7q$%z-6S zSCKJN*nf8Tb#=*%o;mI%X!V>!+QIN+n%-)O!8vOtb^8mo$Vb22W6{a(u;om3z_$B- z{b|il`$eKNZye2(i5Ch7#R-9bW{2Ph>b!Xg3_}S}s27H%o@l+FATJ1Da4o7#s zHzdtvR*UqV5B@wf;2ZR1@^KK;96(-f0e|2-zF3gd9Dz~xH$E0~QvX}OXi)t*%OQT) zZh$ujh}0C|<|4!)L0kY}c?bRIcg)>xy}2KBAWjHEce46)Fv7S%z4E!ymEJ&t*#J%$ zC^>LJgmWH>(0cywHst;>MZ$DY^%x?#kv{a#gLFs|a-!YhMY-81PT|EQwxREcX&{F3 zvVjb93K)g>K8ku=$7x+9O!6=7hPPY0^GQ0IT?Uqw~g6=Xt^vsNOMmG zG(P!7(p6I4&}cpObHcW0#|COYR$wUayt|Ruu${i9`L)2PWM@EbE^hko+0q=V7gYG% z$f@>#7MbLB03LJ+7nMqB!=mSU;V&Fk_BPu>9)GVwyYrY^+yZ(8JPs((0XdPQ4ZJI| z*n;w;)dEt@(SgF2TkL_qQ{84T<$nXT6l%xu@^68wSkV1(S5(=9dTFTDfmFf24zQU0 zj=*yC;g8mYcSo@l-h$|r(+R`to0+2n%bN9$(U7I%_v~#;y%y|-Ybm@A!kViC&%3DW zJ6;8QM|R0u#2cU~=MC05rR)E>3H%uCk>ohSmhxETG5#HB4Wu!91Ey2tjr|qP3H$iw zlJ~N+sId#TsMCj2zEQCB9sV)DxUomOsMEJq-bT<9&I|S->mBS|_>KCMuO0VJ{ZV%* z#0~sZpdI-=ix(K{6|#PQu64_6u2rA)_+kt2&FqnzE7%SG74uHV6~YPpSg{KEJ+uWH zkN*vrE7T4DRoWZr9U?0GeR%FgAO6^Sj%*9i73hxN73prW;2kt$<^vACoAGncu!%f= z;DH?e5AQ?wEqPbGJDW?OW5Y|rxepk8M6^3Y#Sdw=Ia!EGUWiO2z&l3594{H-IlsOVUVx7;49Dm~ z{BN;Ka`1>j9oTTlsLa+SGHFFVPK>SwHB
      ^q(iU*&IVxzrS}faMW{!h}KiXfacP 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

      yW?rvBcoc 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

      ?ODc&9>aUpC-O>|cd_3O_W@X2#O6Ei#zF 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
      +
      +
      + Filter by ID:
      + +
      +
      + Filter by Task:
      + +
      +
      + Filter by Image:
      + +
      +
      + + + + + + + + + + + + + + + + + + + +
      Build ID Task Status Created Elapsed Time Platform
      +
      +
      + Loading     +
      +
      + +
      + + + `; + } + + private getLogTableItem(log: Build, logId: number): string { + const task: string = log.buildTask ? log.buildTask : ''; + const prettyDate: string = log.createTime ? this.getPrettyDate(log.createTime) : ''; + const timeElapsed: string = log.startTime && log.finishTime ? Math.ceil((log.finishTime.valueOf() - log.startTime.valueOf()) / 1000).toString() + 's' : ''; + const osType: string = log.platform.osType ? log.platform.osType : ''; + const name: string = log.name ? log.name : ''; + const imageOutput: string = this.getImageOutputTable(log); + const statusIcon: string = this.getLogStatusIcon(log.status); + + return ` + + +
      + ${name} + ${task} + ${statusIcon} ${log.status} + ${prettyDate} + ${timeElapsed} + ${osType} + + + +
      + + + + + + + + + ${imageOutput} +
       TagRepositoryDigest +

      Log

      +
      +
      + + + ` + } + + private getImageItem(islastTd: boolean, img?: ImageDescriptor): string { + if (img) { + const tag: string = img.tag ? img.tag : ''; + const repository: string = img.repository ? img.repository : ''; + const digest: string = img.digest ? img.digest : ''; + const truncatedDigest: string = digest ? digest.substr(0, 5) + '...' + digest.substr(digest.length - 5) : ''; + const lastTd: string = islastTd ? 'lastTd' : ''; + return ` +   + ${tag} + ${repository} + + + ${truncatedDigest} + ${digest} + + + + + ` + } else { + return ` +   + NA + NA + NA + + `; + } + + } + + private getLogStatusIcon(status?: string): string { + if (!status) { return ''; } + switch (status) { + case 'Error': + return ''; + case 'Failed': + return ''; + case 'Succeeded': + return ''; + case 'Queued': + return ''; + case 'Running': + return ''; + default: + return ''; + } + } + + private getPrettyDate(date: Date): string { + let currentDate = new Date(); + let secs = Math.floor((currentDate.getTime() - date.getTime()) / 1000); + if (secs === 1) { return "1 second ago"; } + if (secs < 60) { return secs + " seconds ago"; } + if (secs < 120) { return " 1 minute ago"; } + if (secs < 3600) { return Math.floor(secs / 60) + " minutes ago"; } + if (secs < 7200) { return Math.floor(secs / 60) + "1 hour ago"; } + if (secs < 86400) { return Math.floor(secs / 3600) + " hours ago"; } + if (secs < 172800) { return "1 day ago"; } + if (secs < 604800) { return Math.floor(secs / 86400) + " days ago"; } + if (secs < 1209600) { return "1 week ago"; } + if (secs < 2592000) { return Math.floor(secs / 604800) + " weeks ago"; } + if (secs < 5184000) { return "1 month ago"; } + if (secs < 31536000) { return Math.floor(secs / 2592000) + " months ago"; } + if (secs < 63072000) { return "1 year ago"; } + return Math.floor(secs / 31536000) + " years ago"; + } +} diff --git a/commands/azureCommands/acr-build-logs.ts b/commands/azureCommands/acr-build-logs.ts new file mode 100644 index 0000000000..dd9de92730 --- /dev/null +++ b/commands/azureCommands/acr-build-logs.ts @@ -0,0 +1,65 @@ +import { Build, Registry } from "azure-arm-containerregistry/lib/models"; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import * as vscode from "vscode"; +import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from '../../explorer/models/azureRegistryNodes'; +import { BuildTaskNode } from "../../explorer/models/taskNode"; +import { getResourceGroupName, getSubscriptionFromRegistry } from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { quickPickACRRegistry } from '../utils/quick-pick-azure' +import { accessLog } from "./acr-build-logs-utils/logFileManager"; +import { LogData } from "./acr-build-logs-utils/tableDataManager"; +import { LogTableWebview } from "./acr-build-logs-utils/tableViewManager"; + +/** This command is used through a right click on an azure registry, repository or image in the Docker Explorer. It is used to view build logs for a given item. */ +export async function viewBuildLogs(context: AzureRegistryNode | AzureImageTagNode | BuildTaskNode): Promise { + let registry: Registry; + let subscription: Subscription; + if (!context) { + registry = await quickPickACRRegistry(); + if (!registry) { return; } + subscription = getSubscriptionFromRegistry(registry); + } else { + registry = context.registry; + subscription = context.subscription; + } + let resourceGroup: string = getResourceGroupName(registry); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let logData: LogData = new LogData(client, registry, resourceGroup); + + // Fuiltering provided + if (context && context instanceof AzureImageTagNode) { + await logData.loadLogs(false, false, { image: context.tag }); + if (!hasValidLogContent(context, logData)) { return; } + logData.getLink(0).then((url) => { + accessLog(url, logData.logs[0].buildId, false); + }); + } else { + if (context && context instanceof BuildTaskNode) { + await logData.loadLogs(false, false, { buildTask: context.label }); + } else { + await logData.loadLogs(false); + } + if (!hasValidLogContent(context, logData)) { return; } + let webViewTitle: string = registry.name; + if (context instanceof BuildTaskNode) { + webViewTitle += '/' + context.label; + } + let webview = new LogTableWebview(webViewTitle, logData); + } +} + +function hasValidLogContent(context: any, logData: LogData): boolean { + if (logData.logs.length === 0) { + let itemType: string; + if (context && context instanceof BuildTaskNode) { + itemType = 'task'; + } else if (context && context instanceof AzureImageTagNode) { + itemType = 'image'; + } else { + itemType = 'registry'; + } + vscode.window.showInformationMessage(`This ${itemType} has no associated build logs`); + return false; + } + return true; +} diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 699871c94c..64755d7019 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -1,5 +1,5 @@ import vscode = require('vscode'); -import { AzureImageTagNode } from '../../explorer/models/AzureRegistryNodes'; +import { AzureImageTagNode } from '../../explorer/models/azureRegistryNodes'; import * as acrTools from '../../utils/Azure/acrTools'; /* Pulls an image from Azure. The context is the image node the user has right clicked on */ diff --git a/dockerExtension.ts b/dockerExtension.ts index 43e2dfeaca..e7d2ab960c 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -10,6 +10,8 @@ import * as vscode from 'vscode'; import { AzureUserInput, createTelemetryReporter, IActionContext, registerCommand as uiRegisterCommand, registerUIExtensionVariables, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui'; import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; import { queueBuild } from './commands/azureCommands/acr-build'; +import { viewBuildLogs } from './commands/azureCommands/acr-build-logs'; +import { LogContentProvider } from './commands/azureCommands/acr-build-logs-utils/logFileManager'; import { createRegistry } from './commands/azureCommands/create-registry'; import { deleteAzureImage } from './commands/azureCommands/delete-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; @@ -134,6 +136,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(YAML_MODE_ID, new DockerComposeCompletionItemProvider(), '.')); ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DOCKER_INSPECT_SCHEME, new DockerInspectDocumentContentProvider())); + ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(LogContentProvider.scheme, new LogContentProvider())); ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(TaskContentProvider.scheme, new TaskContentProvider())); if (azureAccount) { @@ -234,8 +237,8 @@ function registerDockerCommands(azureAccount: AzureAccount): void { registerAzureCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); registerAzureCommand('vscode-docker.delete-ACR-Repository', deleteRepository); registerAzureCommand('vscode-docker.create-ACR-Registry', createRegistry); - registerAzureCommand('vscode-docker.queueBuild', queueBuild); registerAzureCommand('vscode-docker.pullFromAzure', pullFromAzure); + registerAzureCommand('vscode-docker.acrBuildLogs', viewBuildLogs); registerAzureCommand('vscode-docker.run-ACR-BuildTask', runBuildTask); registerAzureCommand('vscode-docker.show-ACR-buildTask', showBuildTaskProperties); } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 47b0188997..a9714489b2 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -6,7 +6,6 @@ import { AzureAccount } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { NodeBase } from './nodeBase'; - /* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ export class TaskRootNode extends NodeBase { public static readonly contextValue: string = 'taskRootNode'; @@ -23,7 +22,6 @@ export class TaskRootNode extends NodeBase { } public name: string; - public getTreeItem(): vscode.TreeItem { return { label: this.label, @@ -37,7 +35,6 @@ export class TaskRootNode extends NodeBase { public async getChildren(element: TaskRootNode): Promise { const buildTaskNodes: BuildTaskNode[] = []; let buildTasks: ContainerModels.BuildTask[] = []; - const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); const resourceGroup: string = acrTools.getResourceGroupName(element.registry); buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); diff --git a/package.json b/package.json index 7d88062aaa..0eaf1d3272 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "activationEvents": [ "onLanguage:dockerfile", "onLanguage:yaml", + "onCommand:vscode-docker.acrBuildLogs", "onCommand:vscode-docker.api.configure", "onCommand:vscode-docker.image.build", "onCommand:vscode-docker.image.inspect", @@ -50,7 +51,6 @@ "onCommand:vscode-docker.create-ACR-Registry", "onCommand:vscode-docker.system.prune", "onCommand:vscode-docker.dockerHubLogout", - "onCommand:vscode-docker.queuebuild", "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", @@ -69,7 +69,8 @@ "main": "./out/dockerExtension", "contributes": { "menus": { - "commandPalette": [{ + "commandPalette": [ + { "command": "vscode-docker.browseDockerHub", "when": "false" }, @@ -82,14 +83,10 @@ "when": "never" } ], - "editor/context": [{ - "when": "editorLangId == dockerfile", - "command": "vscode-docker.image.build", - "group": "docker" - }, + "editor/context": [ { "when": "editorLangId == dockerfile", - "command": "vscode-docker.queueBuild", + "command": "vscode-docker.image.build", "group": "docker" }, { @@ -123,14 +120,10 @@ "group": "docker" } ], - "explorer/context": [{ - "when": "resourceFilename =~ /[dD]ocker[fF]ile/", - "command": "vscode-docker.image.build", - "group": "docker" - }, + "explorer/context": [ { "when": "resourceFilename =~ /[dD]ocker[fF]ile/", - "command": "vscode-docker.queueBuild", + "command": "vscode-docker.image.build", "group": "docker" }, { @@ -149,7 +142,8 @@ "group": "docker" } ], - "view/title": [{ + "view/title": [ + { "command": "vscode-docker.explorer.refresh", "when": "view == dockerExplorer", "group": "navigation" @@ -160,7 +154,8 @@ "group": "navigation" } ], - "view/item/context": [{ + "view/item/context": [ + { "command": "vscode-docker.container.start", "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, @@ -258,7 +253,11 @@ }, { "command": "vscode-docker.browseAzurePortal", - "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureRepositoryNode|azureImageTagNode)$/" + "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureRepositoryNode|azureImageNode)$/" + }, + { + "command": "vscode-docker.acrBuildLogs", + "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureImageTagNode|buildTaskNode)$/" }, { "command": "vscode-docker.connectCustomRegistry", @@ -274,34 +273,40 @@ } ] }, - "debuggers": [{ - "type": "docker", - "label": "Docker", - "configurationSnippets": [{ - "label": "Docker: Attach to Node", - "description": "Docker: Attach to Node", - "body": { - "type": "node", - "request": "attach", - "name": "Docker: Attach to Node", - "port": 9229, - "address": "localhost", - "localRoot": "^\"\\${workspaceFolder}\"", - "remoteRoot": "/usr/src/app", - "protocol": "inspector" - } - }] - }], - "languages": [{ - "id": "dockerfile", - "aliases": [ - "Dockerfile" - ], - "filenamePatterns": [ - "*.dockerfile", - "Dockerfile" - ] - }], + "debuggers": [ + { + "type": "docker", + "label": "Docker", + "configurationSnippets": [ + { + "label": "Docker: Attach to Node", + "description": "Docker: Attach to Node", + "body": { + "type": "node", + "request": "attach", + "name": "Docker: Attach to Node", + "port": 9229, + "address": "localhost", + "localRoot": "^\"\\${workspaceFolder}\"", + "remoteRoot": "/usr/src/app", + "protocol": "inspector" + } + } + ] + } + ], + "languages": [ + { + "id": "dockerfile", + "aliases": [ + "Dockerfile" + ], + "filenamePatterns": [ + "*.dockerfile", + "Dockerfile" + ] + } + ], "configuration": { "type": "object", "title": "Docker configuration options", @@ -467,7 +472,8 @@ } } }, - "commands": [{ + "commands": [ + { "command": "vscode-docker.configure", "title": "Add Docker files to workspace", "description": "Add Dockerfile, docker-compose.yml files", @@ -651,6 +657,11 @@ "title": "Delete Azure Image", "category": "Docker" }, + { + "command": "vscode-docker.acrBuildLogs", + "title": "ACR build logs", + "category": "Docker" + }, { "command": "vscode-docker.connectCustomRegistry", "title": "Connect to a Private Registry... (Preview)", @@ -673,18 +684,22 @@ } ], "views": { - "dockerView": [{ - "id": "dockerExplorer", - "name": "Explorer", - "when": "config.docker.showExplorer == true" - }] + "dockerView": [ + { + "id": "dockerExplorer", + "name": "Explorer", + "when": "config.docker.showExplorer == true" + } + ] }, "viewsContainers": { - "activitybar": [{ - "icon": "images/docker.svg", - "id": "dockerView", - "title": "Docker" - }] + "activitybar": [ + { + "icon": "images/docker.svg", + "id": "dockerView", + "title": "Docker" + } + ] } }, "engines": { @@ -723,15 +738,17 @@ "vscode": "^1.1.18" }, "dependencies": { - "azure-arm-containerregistry": "^2.3.0", + "azure-arm-containerregistry": "^2.4.0", "azure-arm-resource": "^2.0.0-preview", "azure-arm-website": "^1.0.0-preview", "dockerfile-language-server-nodejs": "^0.0.19", "azure-storage": "^2.8.1", + "clipboardy": "^1.2.3", "dockerode": "^2.5.1", "fs-extra": "^6.0.1", "glob": "7.1.2", "gradle-to-js": "^1.0.1", + "handlebars": "^4.0.11", "moment": "^2.19.3", "opn": "^5.1.0", "pom-parser": "^1.1.1", diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index bd341f66c7..f7224b05da 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -164,6 +164,7 @@ export async function acquireACRAccessToken(registryUrl: string, scope: string, return acrAccessTokenResponse.access_token; } +/** Parses blob url into a readable form */ export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); diff --git a/utils/Azure/models/repository.ts b/utils/Azure/models/repository.ts index a6b3e51f30..be87acd391 100644 --- a/utils/Azure/models/repository.ts +++ b/utils/Azure/models/repository.ts @@ -8,19 +8,19 @@ import * as acrTools from '../acrTools'; /** Class Azure Repository: Used locally, Organizes data for managing Repositories */ export class Repository { - public registry: Registry; - public name: string; - public subscription: SubscriptionModels.Subscription; - public resourceGroupName: string; - public password?: string; - public username?: string; + public registry: Registry; + public name: string; + public subscription: SubscriptionModels.Subscription; + public resourceGroupName: string; + public password?: string; + public username?: string; - constructor(registry: Registry, repository: string, password?: string, username?: string) { - this.registry = registry; - this.resourceGroupName = acrTools.getResourceGroupName(registry); - this.subscription = acrTools.getSubscriptionFromRegistry(registry); - this.name = repository; - if (password) { this.password = password; } - if (username) { this.username = username; } - } + constructor(registry: Registry, repository: string, password?: string, username?: string) { + this.registry = registry; + this.resourceGroupName = acrTools.getResourceGroupName(registry); + this.subscription = acrTools.getSubscriptionFromRegistry(registry); + this.name = repository; + if (password) { this.password = password; } + if (username) { this.username = username; } + } } From 64a7a95540d10a15eda6f9396796f00344c51a08 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Fri, 14 Sep 2018 17:23:08 -0500 Subject: [PATCH 59/77] Adds streaming and command standarization (ext.ui) (#73) * Adds streaming and command standarization (ext.ui) * removed unecessary append lines * small fixes * Fix merge issues --- .vscode/settings.json | 5 +- commands/azureCommands/acr-build.ts | 65 ++- commands/build-image.ts | 2 +- commands/utils/quick-pick-azure.ts | 22 +- constants.ts | 3 + dockerExtension.ts | 775 ++++++++++++++++++---------- explorer/models/taskNode.ts | 1 + package.json | 2 +- utils/Azure/acrTools.ts | 66 ++- 9 files changed, 631 insertions(+), 310 deletions(-) diff --git a/.vscode/settings.json b/.vscode/settings.json index 278ead5ea4..810a99cf1b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,8 @@ "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "editor.formatOnSave": true, - "tslint.autoFixOnSave": true + "tslint.autoFixOnSave": true, + "yaml.schemas": { + "http://json.schemastore.org/bukkit-plugin": "/*" + } } diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 0166795515..2873f111bf 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -8,65 +8,64 @@ import * as process from 'process'; import * as tar from 'tar'; import * as url from 'url'; import * as vscode from "vscode"; -import { getBlobInfo, getResourceGroupName } from "../../utils/Azure/acrTools"; +import { IAzureQuickPickItem } from 'vscode-azureextensionui'; +import { ext } from '../../extensionVariables'; +import { getBlobInfo, getResourceGroupName, streamLogs } from "../../utils/Azure/acrTools"; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; -import { quickPickACRRegistry, quickPickSubscription } from '../utils/quick-pick-azure'; +import { resolveDockerFileItem } from '../build-image'; +import { quickPickACRRegistry, quickPickNewImageName, quickPickSubscription } from '../utils/quick-pick-azure'; + const idPrecision = 6; -const status = vscode.window.createOutputChannel('status'); const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; +const status = vscode.window.createOutputChannel('ACR Build status'); // Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. // The user is then asked to name & tag the image. A build is queued for the image in the selected registry. // Selected source code must contain a path to the desired dockerfile. export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { - status.show(); - status.appendLine("Obtaining Subscription and initializing management client"); + //Acquire information from user const subscription = await quickPickSubscription(); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); const registry: Registry = await quickPickACRRegistry(true); - status.appendLine("Selected registry: " + registry.name); const resourceGroupName = getResourceGroupName(registry); + + const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); + const osType: string = (await ext.ui.showQuickPick(osPick, { 'canPickMany': false, 'placeHolder': 'Select image base OS' })).data; + + const imageName: string = await quickPickNewImageName(); + + //Begin readying build + status.show(); + let folder: vscode.WorkspaceFolder; if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { folder = vscode.workspace.workspaceFolders[0]; } else { folder = await (vscode).window.showWorkspaceFolderPick(); } - let sourceLocation: string = folder.uri.path; - let relativeDockerPath = 'Dockerfile'; - if (dockerFileUri.path.indexOf(sourceLocation) !== 0) { - //Currently, there is no support for selecting source location folders that don't contain a path to the triggered dockerfile. - throw new Error("Source code path must be a parent of the Dockerfile path"); - } else { - relativeDockerPath = dockerFileUri.path.toString().substring(sourceLocation.length + 1); - } - - let osType: string = await vscode.window.showQuickPick(['Linux', 'Windows'], { 'canPickMany': false, 'placeHolder': 'Linux' }); - - // Prompting for name so the image can then be pushed to a repository. - const opt: vscode.InputBoxOptions = { - prompt: 'Image name and tag in format :', - }; - const name: string = await vscode.window.showInputBox(opt); - - let tarFilePath = getTempSourceArchivePath(); + const dockerItem = await resolveDockerFileItem(folder, dockerFileUri); + const sourceLocation: string = folder.uri.path; + const tarFilePath = getTempSourceArchivePath(); - status.appendLine("Uploading Source Code to " + tarFilePath); - let uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); + const uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); + status.appendLine("Uploaded Source Code to " + tarFilePath); - status.appendLine("Setting up Build Request"); - let buildRequest: QuickBuildRequest = { + const buildRequest: QuickBuildRequest = { 'type': 'QuickBuild', - 'imageNames': [name], + 'imageNames': [imageName], 'isPushEnabled': true, 'sourceLocation': uploadedSourceLocation, 'platform': { 'osType': osType }, - 'dockerFilePath': relativeDockerPath + 'dockerFilePath': dockerItem.relativeFilePath }; - status.appendLine("Queueing Build"); - await client.registries.queueBuild(resourceGroupName, registry.name, buildRequest); - status.appendLine('Success'); + status.appendLine("Set up Build Request"); + + const build = await client.registries.queueBuild(resourceGroupName, registry.name, buildRequest); + status.appendLine("Queued Build " + build.buildId); + + streamLogs(registry, build, status, client); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { diff --git a/commands/build-image.ts b/commands/build-image.ts index 1f3e350deb..aea006e310 100644 --- a/commands/build-image.ts +++ b/commands/build-image.ts @@ -31,7 +31,7 @@ function createDockerfileItem(rootFolder: vscode.WorkspaceFolder, uri: vscode.Ur }; } -async function resolveDockerFileItem(rootFolder: vscode.WorkspaceFolder, dockerFileUri: vscode.Uri | undefined): Promise { +export async function resolveDockerFileItem(rootFolder: vscode.WorkspaceFolder, dockerFileUri: vscode.Uri | undefined): Promise { if (dockerFileUri) { return createDockerfileItem(rootFolder, dockerFileUri); } diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index 0b4d25164a..309f2c176f 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -9,7 +9,7 @@ import { Location, Subscription } from 'azure-arm-resource/lib/subscription/mode import * as opn from 'opn'; import * as vscode from "vscode"; import { IAzureQuickPickItem, UserCancelledError } from 'vscode-azureextensionui'; -import { skus } from '../../constants' +import { imageTagRegExp, skus } from '../../constants' import { ext } from '../../extensionVariables'; import { ResourceManagementClient } from '../../node_modules/azure-arm-resource'; import * as acrTools from '../../utils/Azure/acrTools'; @@ -182,3 +182,23 @@ async function checkForValidResourcegroupName(resourceGroupName: string, resourc return undefined; } + +/*Creates a new resource group within the current subscription */ +export async function quickPickNewImageName(): Promise { + let opt: vscode.InputBoxOptions = { + validateInput: checkForValidTag, + ignoreFocusOut: false, + prompt: 'Enter repository name and tag in format :' + }; + + let tag: string = await ext.ui.showInputBox(opt); + return tag; +} +function checkForValidTag(str: string): string { + if (!imageTagRegExp.test(str)) { + return 'Repository name must have 0-256 alpha-numeric characters, optionally separated by periods, dashes or underscores.' + + 'A tag name must have 0-128 alpha-numeric characters, digits, underscores, periods or dashes. A tag name may not start with a period or a dash.'; + } + return undefined; + +} diff --git a/constants.ts b/constants.ts index d4c4114828..2e96b71f6d 100644 --- a/constants.ts +++ b/constants.ts @@ -27,3 +27,6 @@ export const NULL_GUID = '00000000-0000-0000-0000-000000000000'; //Azure Container Registries export const skus = ["Standard", "Basic", "Premium"]; + +//Repository + Tag format +export const imageTagRegExp = new RegExp('^[a-zA-Z0-9.-_/]{1,256}:(?![.-])[a-zA-Z0-9.-_]{1,128}$'); diff --git a/dockerExtension.ts b/dockerExtension.ts index e7d2ab960c..ec5e9e78ea 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -3,323 +3,556 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as opn from 'opn'; -import * as path from 'path'; -import * as request from 'request-promise-native'; -import * as vscode from 'vscode'; -import { AzureUserInput, createTelemetryReporter, IActionContext, registerCommand as uiRegisterCommand, registerUIExtensionVariables, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui'; -import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; -import { queueBuild } from './commands/azureCommands/acr-build'; -import { viewBuildLogs } from './commands/azureCommands/acr-build-logs'; -import { LogContentProvider } from './commands/azureCommands/acr-build-logs-utils/logFileManager'; -import { createRegistry } from './commands/azureCommands/create-registry'; -import { deleteAzureImage } from './commands/azureCommands/delete-image'; -import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; -import { deleteRepository } from './commands/azureCommands/delete-repository'; -import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; -import { runBuildTask } from './commands/azureCommands/run-buildTask'; -import { showBuildTaskProperties } from './commands/azureCommands/show-buildTask'; -import { TaskContentProvider } from './commands/azureCommands/task-utils/showTaskManager'; -import { buildImage } from './commands/build-image'; -import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; -import inspectImage from './commands/inspect-image'; -import { openShellContainer } from './commands/open-shell-container'; -import { pushImage } from './commands/push-image'; -import { consolidateDefaultRegistrySettings, setRegistryAsDefault } from './commands/registrySettings'; -import { removeContainer } from './commands/remove-container'; -import { removeImage } from './commands/remove-image'; -import { restartContainer } from './commands/restart-container'; -import { showLogsContainer } from './commands/showlogs-container'; -import { startAzureCLI, startContainer, startContainerInteractive } from './commands/start-container'; -import { stopContainer } from './commands/stop-container'; -import { systemPrune } from './commands/system-prune'; -import { IHasImageDescriptorAndLabel, tagImage } from './commands/tag-image'; -import { docker } from './commands/utils/docker-endpoint'; -import { DefaultTerminalProvider } from './commands/utils/TerminalProvider'; -import { DockerDebugConfigProvider } from './configureWorkspace/configDebugProvider'; -import { configure, configureApi, ConfigureApiOptions } from './configureWorkspace/configure'; -import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider'; -import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider'; -import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo'; -import { DockerComposeParser } from './dockerCompose/dockerComposeParser'; -import { DockerfileCompletionItemProvider } from './dockerfile/dockerfileCompletionItemProvider'; -import DockerInspectDocumentContentProvider, { SCHEME as DOCKER_INSPECT_SCHEME } from './documentContentProviders/dockerInspect'; -import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper'; +import * as opn from "opn"; +import * as path from "path"; +import * as request from "request-promise-native"; +import * as vscode from "vscode"; +import { + AzureUserInput, + createTelemetryReporter, + IActionContext, + registerCommand as uiRegisterCommand, + registerUIExtensionVariables, + TelemetryProperties, + UserCancelledError +} from "vscode-azureextensionui"; +import { + ConfigurationParams, + DidChangeConfigurationNotification, + DocumentSelector, + LanguageClient, + LanguageClientOptions, + Middleware, + ServerOptions, + TransportKind +} from "vscode-languageclient/lib/main"; +import { queueBuild } from "./commands/azureCommands/acr-build"; +import { viewBuildLogs } from "./commands/azureCommands/acr-build-logs"; +import { LogContentProvider } from "./commands/azureCommands/acr-build-logs-utils/logFileManager"; +import { createRegistry } from "./commands/azureCommands/create-registry"; +import { deleteAzureImage } from "./commands/azureCommands/delete-image"; +import { deleteAzureRegistry } from "./commands/azureCommands/delete-registry"; +import { deleteRepository } from "./commands/azureCommands/delete-repository"; +import { pullFromAzure } from "./commands/azureCommands/pull-from-azure"; +import { runBuildTask } from "./commands/azureCommands/run-buildTask"; +import { showBuildTaskProperties } from "./commands/azureCommands/show-buildTask"; +import { TaskContentProvider } from "./commands/azureCommands/task-utils/showTaskManager"; +import { buildImage } from "./commands/build-image"; +import { + composeDown, + composeRestart, + composeUp +} from "./commands/docker-compose"; +import inspectImage from "./commands/inspect-image"; +import { openShellContainer } from "./commands/open-shell-container"; +import { pushImage } from "./commands/push-image"; +import { + consolidateDefaultRegistrySettings, + setRegistryAsDefault +} from "./commands/registrySettings"; +import { removeContainer } from "./commands/remove-container"; +import { removeImage } from "./commands/remove-image"; +import { restartContainer } from "./commands/restart-container"; +import { showLogsContainer } from "./commands/showlogs-container"; +import { + startAzureCLI, + startContainer, + startContainerInteractive +} from "./commands/start-container"; +import { stopContainer } from "./commands/stop-container"; +import { systemPrune } from "./commands/system-prune"; +import { IHasImageDescriptorAndLabel, tagImage } from "./commands/tag-image"; +import { docker } from "./commands/utils/docker-endpoint"; +import { DefaultTerminalProvider } from "./commands/utils/TerminalProvider"; +import { DockerDebugConfigProvider } from "./configureWorkspace/configDebugProvider"; +import { + configure, + configureApi, + ConfigureApiOptions +} from "./configureWorkspace/configure"; +import { DockerComposeCompletionItemProvider } from "./dockerCompose/dockerComposeCompletionItemProvider"; +import { DockerComposeHoverProvider } from "./dockerCompose/dockerComposeHoverProvider"; +import composeVersionKeys from "./dockerCompose/dockerComposeKeyInfo"; +import { DockerComposeParser } from "./dockerCompose/dockerComposeParser"; +import { DockerfileCompletionItemProvider } from "./dockerfile/dockerfileCompletionItemProvider"; +import DockerInspectDocumentContentProvider, { + SCHEME as DOCKER_INSPECT_SCHEME +} from "./documentContentProviders/dockerInspect"; +import { AzureAccountWrapper } from "./explorer/deploy/azureAccountWrapper"; import * as util from "./explorer/deploy/util"; -import { WebAppCreator } from './explorer/deploy/webAppCreator'; -import { DockerExplorerProvider } from './explorer/dockerExplorer'; -import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes'; -import { ContainerNode } from './explorer/models/containerNode'; -import { connectCustomRegistry, disconnectCustomRegistry } from './explorer/models/customRegistries'; -import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; -import { ImageNode } from './explorer/models/imageNode'; -import { NodeBase } from './explorer/models/nodeBase'; -import { RootNode } from './explorer/models/rootNode'; -import { BuildTaskNode } from './explorer/models/taskNode'; -import { browseAzurePortal } from './explorer/utils/browseAzurePortal'; -import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; +import { WebAppCreator } from "./explorer/deploy/webAppCreator"; +import { DockerExplorerProvider } from "./explorer/dockerExplorer"; +import { + AzureImageTagNode, + AzureRegistryNode, + AzureRepositoryNode +} from "./explorer/models/azureRegistryNodes"; +import { ContainerNode } from "./explorer/models/containerNode"; +import { + connectCustomRegistry, + disconnectCustomRegistry +} from "./explorer/models/customRegistries"; +import { + DockerHubImageTagNode, + DockerHubOrgNode, + DockerHubRepositoryNode +} from "./explorer/models/dockerHubNodes"; +import { ImageNode } from "./explorer/models/imageNode"; +import { NodeBase } from "./explorer/models/nodeBase"; +import { RootNode } from "./explorer/models/rootNode"; +import { BuildTaskNode } from "./explorer/models/taskNode"; +import { browseAzurePortal } from "./explorer/utils/browseAzurePortal"; +import { + browseDockerHub, + dockerHubLogout +} from "./explorer/utils/dockerHubUtils"; import { ext } from "./extensionVariables"; -import { initializeTelemetryReporter, reporter } from './telemetry/telemetry'; -import { AzureAccount } from './typings/azure-account.api'; -import { addUserAgent } from './utils/addUserAgent'; -import { registerAzureCommand } from './utils/Azure/common'; -import { AzureUtilityManager } from './utils/azureUtilityManager'; -import { Keytar } from './utils/keytar'; +import { initializeTelemetryReporter, reporter } from "./telemetry/telemetry"; +import { AzureAccount } from "./typings/azure-account.api"; +import { addUserAgent } from "./utils/addUserAgent"; +import { registerAzureCommand } from "./utils/Azure/common"; +import { AzureUtilityManager } from "./utils/azureUtilityManager"; +import { Keytar } from "./utils/keytar"; export const FROM_DIRECTIVE_PATTERN = /^\s*FROM\s*([\w-\/:]*)(\s*AS\s*[a-z][a-z0-9-_\\.]*)?$/i; -export const COMPOSE_FILE_GLOB_PATTERN = '**/[dD]ocker-[cC]ompose*.{yaml,yml}'; -export const DOCKERFILE_GLOB_PATTERN = '**/{*.dockerfile,[dD]ocker[fF]ile}'; +export const COMPOSE_FILE_GLOB_PATTERN = "**/[dD]ocker-[cC]ompose*.{yaml,yml}"; +export const DOCKERFILE_GLOB_PATTERN = "**/{*.dockerfile,[dD]ocker[fF]ile}"; export let dockerExplorerProvider: DockerExplorerProvider; -export type KeyInfo = { [keyName: string]: string; }; +export type KeyInfo = { [keyName: string]: string }; export interface ComposeVersionKeys { - All: KeyInfo, - v1: KeyInfo, - v2: KeyInfo + All: KeyInfo; + v1: KeyInfo; + v2: KeyInfo; } let client: LanguageClient; const DOCUMENT_SELECTOR: DocumentSelector = [ - { language: 'dockerfile', scheme: 'file' } + { language: "dockerfile", scheme: "file" } ]; function initializeExtensionVariables(ctx: vscode.ExtensionContext): void { - registerUIExtensionVariables(ext); - - if (!ext.ui) { - // This allows for standard interactions with the end user (as opposed to test input) - ext.ui = new AzureUserInput(ctx.globalState); - } - ext.context = ctx; - ext.outputChannel = util.getOutputChannel(); - if (!ext.terminalProvider) { - ext.terminalProvider = new DefaultTerminalProvider(); - } - initializeTelemetryReporter(createTelemetryReporter(ctx)); - ext.reporter = reporter; - if (!ext.keytar) { - ext.keytar = Keytar.tryCreate(); - } - - // Set up the user agent for all direct 'request' calls in the extension (must use ext.request) - let defaultRequestOptions = {}; - addUserAgent(defaultRequestOptions); - ext.request = request.defaults(defaultRequestOptions); + registerUIExtensionVariables(ext); + + if (!ext.ui) { + // This allows for standard interactions with the end user (as opposed to test input) + ext.ui = new AzureUserInput(ctx.globalState); + } + ext.context = ctx; + ext.outputChannel = util.getOutputChannel(); + if (!ext.terminalProvider) { + ext.terminalProvider = new DefaultTerminalProvider(); + } + initializeTelemetryReporter(createTelemetryReporter(ctx)); + ext.reporter = reporter; + if (!ext.keytar) { + ext.keytar = Keytar.tryCreate(); + } + + // Set up the user agent for all direct 'request' calls in the extension (must use ext.request) + let defaultRequestOptions = {}; + addUserAgent(defaultRequestOptions); + ext.request = request.defaults(defaultRequestOptions); } export async function activate(ctx: vscode.ExtensionContext): Promise { - const installedExtensions: any[] = vscode.extensions.all; - let azureAccount: AzureAccount | undefined; - - initializeExtensionVariables(ctx); - - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < installedExtensions.length; i++) { - const extension = installedExtensions[i]; - if (extension.id === 'ms-vscode.azure-account') { - try { - azureAccount = await extension.activate(); - } catch (error) { - console.log('Failed to activate the Azure Account Extension: ' + error); - } - break; - } + const installedExtensions: any[] = vscode.extensions.all; + let azureAccount: AzureAccount | undefined; + + initializeExtensionVariables(ctx); + + // tslint:disable-next-line:prefer-for-of // Grandfathered in + for (let i = 0; i < installedExtensions.length; i++) { + const extension = installedExtensions[i]; + if (extension.id === "ms-vscode.azure-account") { + try { + azureAccount = await extension.activate(); + } catch (error) { + console.log("Failed to activate the Azure Account Extension: " + error); + } + break; } - ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(DOCUMENT_SELECTOR, new DockerfileCompletionItemProvider(), '.')); - - const YAML_MODE_ID: vscode.DocumentFilter = { language: 'yaml', scheme: 'file', pattern: COMPOSE_FILE_GLOB_PATTERN }; - let yamlHoverProvider = new DockerComposeHoverProvider(new DockerComposeParser(), composeVersionKeys.All); - ctx.subscriptions.push(vscode.languages.registerHoverProvider(YAML_MODE_ID, yamlHoverProvider)); - ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(YAML_MODE_ID, new DockerComposeCompletionItemProvider(), '.')); - - ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DOCKER_INSPECT_SCHEME, new DockerInspectDocumentContentProvider())); - ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(LogContentProvider.scheme, new LogContentProvider())); - ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(TaskContentProvider.scheme, new TaskContentProvider())); + } + ctx.subscriptions.push( + vscode.languages.registerCompletionItemProvider( + DOCUMENT_SELECTOR, + new DockerfileCompletionItemProvider(), + "." + ) + ); + + const YAML_MODE_ID: vscode.DocumentFilter = { + language: "yaml", + scheme: "file", + pattern: COMPOSE_FILE_GLOB_PATTERN + }; + let yamlHoverProvider = new DockerComposeHoverProvider( + new DockerComposeParser(), + composeVersionKeys.All + ); + ctx.subscriptions.push( + vscode.languages.registerHoverProvider(YAML_MODE_ID, yamlHoverProvider) + ); + ctx.subscriptions.push( + vscode.languages.registerCompletionItemProvider( + YAML_MODE_ID, + new DockerComposeCompletionItemProvider(), + "." + ) + ); + + ctx.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + DOCKER_INSPECT_SCHEME, + new DockerInspectDocumentContentProvider() + ) + ); + ctx.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + LogContentProvider.scheme, + new LogContentProvider() + ) + ); + ctx.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + TaskContentProvider.scheme, + new TaskContentProvider() + ) + ); + + if (azureAccount) { + AzureUtilityManager.getInstance().setAccount(azureAccount); + } + + registerDockerCommands(azureAccount); + + ctx.subscriptions.push( + vscode.debug.registerDebugConfigurationProvider( + "docker", + new DockerDebugConfigProvider() + ) + ); + + await consolidateDefaultRegistrySettings(); + activateLanguageClient(ctx); +} +async function createWebApp( + context?: AzureImageTagNode | DockerHubImageTagNode, + azureAccount?: AzureAccount +): Promise { + if (context) { if (azureAccount) { - AzureUtilityManager.getInstance().setAccount(azureAccount); + const azureAccountWrapper = new AzureAccountWrapper( + ext.context, + azureAccount + ); + const wizard = new WebAppCreator( + ext.outputChannel, + azureAccountWrapper, + context + ); + const result = await wizard.run(); + if (result.status === "Faulted") { + throw result.error; + } else if (result.status === "Cancelled") { + throw new UserCancelledError(); + } + } else { + const open: vscode.MessageItem = { title: "View in Marketplace" }; + const response = await vscode.window.showErrorMessage( + "Please install the Azure Account extension to deploy to Azure.", + open + ); + if (response === open) { + opn( + "https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account" + ); + } } - - registerDockerCommands(azureAccount); - - ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider())); - - await consolidateDefaultRegistrySettings(); - activateLanguageClient(ctx); + } } -async function createWebApp(context?: AzureImageTagNode | DockerHubImageTagNode, azureAccount?: AzureAccount): Promise { - if (context) { - if (azureAccount) { - const azureAccountWrapper = new AzureAccountWrapper(ext.context, azureAccount); - const wizard = new WebAppCreator(ext.outputChannel, azureAccountWrapper, context); - const result = await wizard.run(); - if (result.status === 'Faulted') { - throw result.error; - } else if (result.status === 'Cancelled') { - throw new UserCancelledError(); - } - } else { - const open: vscode.MessageItem = { title: "View in Marketplace" }; - const response = await vscode.window.showErrorMessage('Please install the Azure Account extension to deploy to Azure.', open); - if (response === open) { - opn('https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account'); - } +// Remove this when https://github.com/Microsoft/vscode-docker/issues/445 fixed +function registerCommand( + commandId: string, + callback: (this: IActionContext, ...args: any[]) => any +): void { + return uiRegisterCommand( + commandId, + // tslint:disable-next-line:no-function-expression + async function(this: IActionContext, ...args: any[]): Promise { + if (args.length) { + let properties: { + contextValue?: string; + } & TelemetryProperties = this.properties; + const contextArg = args[0]; + + if (contextArg instanceof NodeBase) { + properties.contextValue = contextArg.contextValue; + } else if (contextArg instanceof vscode.Uri) { + properties.contextValue = "Uri"; } - } -} + } -// Remove this when https://github.com/Microsoft/vscode-docker/issues/445 fixed -function registerCommand(commandId: string, callback: (this: IActionContext, ...args: any[]) => any): void { - return uiRegisterCommand( - commandId, - // tslint:disable-next-line:no-function-expression - async function (this: IActionContext, ...args: any[]): Promise { - if (args.length) { - let properties: { - contextValue?: string; - } & TelemetryProperties = this.properties; - const contextArg = args[0]; - - if (contextArg instanceof NodeBase) { - properties.contextValue = contextArg.contextValue; - } else if (contextArg instanceof vscode.Uri) { - properties.contextValue = 'Uri'; - } - } - - return callback.call(this, ...args); - }); + return callback.call(this, ...args); + } + ); } +// tslint:disable-next-line:max-func-body-length function registerDockerCommands(azureAccount: AzureAccount): void { - dockerExplorerProvider = new DockerExplorerProvider(azureAccount); - vscode.window.registerTreeDataProvider('dockerExplorer', dockerExplorerProvider); - registerCommand('vscode-docker.explorer.refresh', () => dockerExplorerProvider.refresh()); - - registerCommand('vscode-docker.configure', async function (this: IActionContext): Promise { await configure(this, undefined); }); - registerCommand('vscode-docker.api.configure', async function (this: IActionContext, options: ConfigureApiOptions): Promise { - await configureApi(this, options); - }); - - registerCommand('vscode-docker.container.start', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainer(this, node); }); - registerCommand('vscode-docker.container.start.interactive', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainerInteractive(this, node); }); - registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); - registerCommand('vscode-docker.container.stop', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await stopContainer(this, node); }); - registerCommand('vscode-docker.container.restart', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await restartContainer(this, node); }); - registerCommand('vscode-docker.container.show-logs', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await showLogsContainer(this, node); }); - registerCommand('vscode-docker.container.open-shell', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await openShellContainer(this, node); }); - registerCommand('vscode-docker.container.remove', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await removeContainer(this, node); }); - registerCommand('vscode-docker.image.build', async function (this: IActionContext, item: vscode.Uri | undefined): Promise { await buildImage(this, item); }); - registerCommand('vscode-docker.image.inspect', async function (this: IActionContext, node: ImageNode | undefined): Promise { await inspectImage(this, node); }); - registerCommand('vscode-docker.image.remove', async function (this: IActionContext, node: ImageNode | RootNode | undefined): Promise { await removeImage(this, node); }); - registerCommand('vscode-docker.image.push', async function (this: IActionContext, node: ImageNode | undefined): Promise { await pushImage(this, node); }); - registerCommand('vscode-docker.image.tag', async function (this: IActionContext, node: ImageNode | undefined): Promise { await tagImage(this, node); }); - registerCommand('vscode-docker.compose.up', composeUp); - registerCommand('vscode-docker.compose.down', composeDown); - registerCommand('vscode-docker.compose.restart', composeRestart); - registerCommand('vscode-docker.system.prune', systemPrune); - registerCommand('vscode-docker.createWebApp', async (context?: AzureImageTagNode | DockerHubImageTagNode) => await createWebApp(context, azureAccount)); - registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout); - registerCommand('vscode-docker.browseDockerHub', (context?: DockerHubImageTagNode | DockerHubRepositoryNode | DockerHubOrgNode) => { - browseDockerHub(context); - }); - registerCommand('vscode-docker.browseAzurePortal', (context?: AzureRegistryNode | AzureRepositoryNode | AzureImageTagNode) => { - browseAzurePortal(context); - }); - registerCommand('vscode-docker.connectCustomRegistry', connectCustomRegistry); - registerCommand('vscode-docker.disconnectCustomRegistry', disconnectCustomRegistry); - registerCommand('vscode-docker.setRegistryAsDefault', setRegistryAsDefault); - registerAzureCommand('vscode-docker.delete-ACR-Registry', deleteAzureRegistry); - registerAzureCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); - registerAzureCommand('vscode-docker.delete-ACR-Repository', deleteRepository); - registerAzureCommand('vscode-docker.create-ACR-Registry', createRegistry); - registerAzureCommand('vscode-docker.pullFromAzure', pullFromAzure); - registerAzureCommand('vscode-docker.acrBuildLogs', viewBuildLogs); - registerAzureCommand('vscode-docker.run-ACR-BuildTask', runBuildTask); - registerAzureCommand('vscode-docker.show-ACR-buildTask', showBuildTaskProperties); + dockerExplorerProvider = new DockerExplorerProvider(azureAccount); + vscode.window.registerTreeDataProvider( + "dockerExplorer", + dockerExplorerProvider + ); + registerCommand("vscode-docker.explorer.refresh", () => + dockerExplorerProvider.refresh() + ); + + registerCommand("vscode-docker.configure", async function( + this: IActionContext + ): Promise { + await configure(this, undefined); + }); + registerCommand("vscode-docker.api.configure", async function( + this: IActionContext, + options: ConfigureApiOptions + ): Promise { + await configureApi(this, options); + }); + + registerCommand("vscode-docker.container.start", async function( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await startContainer(this, node); + }); + registerCommand("vscode-docker.container.start.interactive", async function( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await startContainerInteractive(this, node); + }); + registerCommand("vscode-docker.container.start.azurecli", startAzureCLI); + registerCommand("vscode-docker.container.stop", async function( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await stopContainer(this, node); + }); + registerCommand("vscode-docker.container.restart", async function( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await restartContainer(this, node); + }); + registerCommand("vscode-docker.container.show-logs", async function( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await showLogsContainer(this, node); + }); + registerCommand("vscode-docker.container.open-shell", async function( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await openShellContainer(this, node); + }); + registerCommand("vscode-docker.container.remove", async function( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await removeContainer(this, node); + }); + registerCommand("vscode-docker.image.build", async function( + this: IActionContext, + item: vscode.Uri | undefined + ): Promise { + await buildImage(this, item); + }); + registerCommand("vscode-docker.image.inspect", async function( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await inspectImage(this, node); + }); + registerCommand("vscode-docker.image.remove", async function( + this: IActionContext, + node: ImageNode | RootNode | undefined + ): Promise { + await removeImage(this, node); + }); + registerCommand("vscode-docker.image.push", async function( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await pushImage(this, node); + }); + registerCommand("vscode-docker.image.tag", async function( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await tagImage(this, node); + }); + registerCommand("vscode-docker.compose.up", composeUp); + registerCommand("vscode-docker.compose.down", composeDown); + registerCommand("vscode-docker.compose.restart", composeRestart); + registerCommand("vscode-docker.system.prune", systemPrune); + registerCommand( + "vscode-docker.createWebApp", + async (context?: AzureImageTagNode | DockerHubImageTagNode) => + await createWebApp(context, azureAccount) + ); + registerCommand("vscode-docker.dockerHubLogout", dockerHubLogout); + registerCommand( + "vscode-docker.browseDockerHub", + ( + context?: + | DockerHubImageTagNode + | DockerHubRepositoryNode + | DockerHubOrgNode + ) => { + browseDockerHub(context); + } + ); + registerCommand( + "vscode-docker.browseAzurePortal", + (context?: AzureRegistryNode | AzureRepositoryNode | AzureImageTagNode) => { + browseAzurePortal(context); + } + ); + registerCommand("vscode-docker.connectCustomRegistry", connectCustomRegistry); + registerCommand( + "vscode-docker.disconnectCustomRegistry", + disconnectCustomRegistry + ); + registerCommand("vscode-docker.setRegistryAsDefault", setRegistryAsDefault); + registerAzureCommand( + "vscode-docker.delete-ACR-Registry", + deleteAzureRegistry + ); + registerAzureCommand("vscode-docker.delete-ACR-Image", deleteAzureImage); + registerAzureCommand("vscode-docker.delete-ACR-Repository", deleteRepository); + registerAzureCommand("vscode-docker.create-ACR-Registry", createRegistry); + registerAzureCommand("vscode-docker.pullFromAzure", pullFromAzure); + registerAzureCommand("vscode-docker.acrBuildLogs", viewBuildLogs); + registerAzureCommand("vscode-docker.run-ACR-BuildTask", runBuildTask); + registerAzureCommand( + "vscode-docker.show-ACR-buildTask", + showBuildTaskProperties + ); } export async function deactivate(): Promise { - if (!client) { - return undefined; - } - // perform cleanup - Configuration.dispose(); - return await client.stop(); + if (!client) { + return undefined; + } + // perform cleanup + Configuration.dispose(); + return await client.stop(); } namespace Configuration { - - let configurationListener: vscode.Disposable; - - export function computeConfiguration(params: ConfigurationParams): vscode.WorkspaceConfiguration[] { - let result: vscode.WorkspaceConfiguration[] = []; - for (let item of params.items) { - let config: vscode.WorkspaceConfiguration; - - if (item.scopeUri) { - config = vscode.workspace.getConfiguration(item.section, client.protocol2CodeConverter.asUri(item.scopeUri)); - } else { - config = vscode.workspace.getConfiguration(item.section); - } - result.push(config); - } - return result; + let configurationListener: vscode.Disposable; + + export function computeConfiguration( + params: ConfigurationParams + ): vscode.WorkspaceConfiguration[] { + let result: vscode.WorkspaceConfiguration[] = []; + for (let item of params.items) { + let config: vscode.WorkspaceConfiguration; + + if (item.scopeUri) { + config = vscode.workspace.getConfiguration( + item.section, + client.protocol2CodeConverter.asUri(item.scopeUri) + ); + } else { + config = vscode.workspace.getConfiguration(item.section); + } + result.push(config); } - - export function initialize(): void { - configurationListener = vscode.workspace.onDidChangeConfiguration((e: vscode.ConfigurationChangeEvent) => { - // notify the language server that settings have change - client.sendNotification(DidChangeConfigurationNotification.type, { settings: null }); - - // Update endpoint and refresh explorer if needed - if (e.affectsConfiguration('docker')) { - docker.refreshEndpoint(); - vscode.commands.executeCommand("vscode-docker.explorer.refresh"); - } + return result; + } + + export function initialize(): void { + configurationListener = vscode.workspace.onDidChangeConfiguration( + (e: vscode.ConfigurationChangeEvent) => { + // notify the language server that settings have change + client.sendNotification(DidChangeConfigurationNotification.type, { + settings: null }); - } - export function dispose(): void { - if (configurationListener) { - // remove this listener when disposed - configurationListener.dispose(); + // Update endpoint and refresh explorer if needed + if (e.affectsConfiguration("docker")) { + docker.refreshEndpoint(); + vscode.commands.executeCommand("vscode-docker.explorer.refresh"); } + } + ); + } + + export function dispose(): void { + if (configurationListener) { + // remove this listener when disposed + configurationListener.dispose(); } + } } function activateLanguageClient(ctx: vscode.ExtensionContext): void { - let serverModule = ctx.asAbsolutePath(path.join("node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")); - let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; - - let serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc, args: ["--node-ipc"] }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + let serverModule = ctx.asAbsolutePath( + path.join( + "node_modules", + "dockerfile-language-server-nodejs", + "lib", + "server.js" + ) + ); + let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + + let serverOptions: ServerOptions = { + run: { + module: serverModule, + transport: TransportKind.ipc, + args: ["--node-ipc"] + }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions } + }; - let middleware: Middleware = { - workspace: { - configuration: Configuration.computeConfiguration - } - }; - - let clientOptions: LanguageClientOptions = { - documentSelector: DOCUMENT_SELECTOR, - synchronize: { - fileEvents: vscode.workspace.createFileSystemWatcher('**/.clientrc') - }, - middleware: middleware + let middleware: Middleware = { + workspace: { + configuration: Configuration.computeConfiguration } - - client = new LanguageClient("dockerfile-langserver", "Dockerfile Language Server", serverOptions, clientOptions); - // tslint:disable-next-line:no-floating-promises - client.onReady().then(() => { - // attach the VS Code settings listener - Configuration.initialize(); - }); - client.start(); + }; + + let clientOptions: LanguageClientOptions = { + documentSelector: DOCUMENT_SELECTOR, + synchronize: { + fileEvents: vscode.workspace.createFileSystemWatcher("**/.clientrc") + }, + middleware: middleware + }; + + client = new LanguageClient( + "dockerfile-langserver", + "Dockerfile Language Server", + serverOptions, + clientOptions + ); + // tslint:disable-next-line:no-floating-promises + client.onReady().then(() => { + // attach the VS Code settings listener + Configuration.initialize(); + }); + client.start(); } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index a9714489b2..9845108977 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -61,6 +61,7 @@ export class BuildTaskNode extends NodeBase { constructor( public task: ContainerModels.BuildTask, public registry: ContainerModels.Registry, + public subscription: SubscriptionModels.Subscription, public parent: NodeBase diff --git a/package.json b/package.json index 0eaf1d3272..318b720a3e 100644 --- a/package.json +++ b/package.json @@ -678,7 +678,7 @@ "category": "Docker" }, { - "command": "vscode-docker.queueBuild", + "command": "vscode-docker.ACR-Build", "title": "Cloud Build", "category": "Docker" } diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index f7224b05da..18ef712ac9 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -4,10 +4,13 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import { Registry } from "azure-arm-containerregistry/lib/models"; +import ContainerRegistryManagementClient from 'azure-arm-containerregistry'; +import { Build, BuildGetLogResult, Registry } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as vscode from "vscode"; import { NULL_GUID } from "../../constants"; import { getCatalog, getTags, TagInfo } from "../../explorer/models/commonRegistryUtils"; import { ext } from '../../extensionVariables'; @@ -164,7 +167,7 @@ export async function acquireACRAccessToken(registryUrl: string, scope: string, return acrAccessTokenResponse.access_token; } -/** Parses blob url into a readable form */ +/** Parses information into a readable format from a blob url */ export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); @@ -175,3 +178,62 @@ export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuf let host: string = accountName + '.blob.' + endpointSuffix; return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; } + +/** Stream logs from a blob into output channel. + * Note, since output streams don't actually deal with streams directly, text is not actually + * streamed in which prevents updating of already appended lines. Usure if this can be fixed. Nonetheless + * logs do load in chunks every 1 second. + */ +export async function streamLogs(registry: Registry, build: Build, outputChannel: vscode.OutputChannel, providedClient?: ContainerRegistryManagementClient): Promise { + //Prefer passed in client to avoid initialization but if not added obtains own + let client = providedClient ? providedClient : AzureUtilityManager.getInstance().getContainerRegistryManagementClient(getSubscriptionFromRegistry(registry)); + let temp: BuildGetLogResult = await client.builds.getLogLink(getResourceGroupName(registry), registry.name, build.buildId); + const link = temp.logLink; + let blobInfo = getBlobInfo(link); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); + let available = 0; + let start = 0; + + let obtainLogs = setInterval(async () => { + let props: BlobService.BlobResult; + let metadata: { [key: string]: string; }; + try { + props = await getBlobProperties(blobInfo, blob); + metadata = props.metadata; + } catch (error) { + //Not found happens when the properties havent yet been set, blob is not ready. Wait 1 second and try again + if (error.code === "NotFound") { return; } else { throw error; } + } + available = +props.contentLength; + let text: string; + //Makes sure that if item fails it does so due to network/azure errors not lack of new content + if (available > start) { + text = await getBlobToText(blobInfo, blob, start); + let utf8encoded = (new Buffer(text, 'ascii')).toString('utf8'); + start += text.length; + outputChannel.append(utf8encoded); + } + if (metadata.Complete) { + clearInterval(obtainLogs); + } + }, 1000); +} + +// Promisify getBlobToText for readability and error handling purposes +async function getBlobToText(blobInfo: any, blob: BlobService, rangeStart: number): Promise { + return new Promise((resolve, reject) => { + blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, { rangeStart: rangeStart }, + (error, result) => { + if (error) { reject() } else { resolve(result); } + }); + }); +} + +// Promisify getBlobProperties for readability and error handling purposes +async function getBlobProperties(blobInfo: any, blob: BlobService): Promise { + return new Promise((resolve, reject) => { + blob.getBlobProperties(blobInfo.containerName, blobInfo.blobName, (error, result) => { + if (error) { reject(error) } else { resolve(result); } + }); + }); +} From 039b80c9b1e6342da22c7b0c82e9c8030a7c30c6 Mon Sep 17 00:00:00 2001 From: Rodrigo Mendoza <43052640+rosanch@users.noreply.github.com> Date: Tue, 18 Sep 2018 12:54:54 -0700 Subject: [PATCH 60/77] changes for ACR 3.0.0 (#80) * This commit contains changes necessary for the azure-arm-containerregistry 3.0.0 * Fixed PR feedback --- .../acr-build-logs-utils/logScripts.js | 14 +++---- .../acr-build-logs-utils/tableDataManager.ts | 40 +++++++++---------- .../acr-build-logs-utils/tableViewManager.ts | 12 +++--- commands/azureCommands/acr-build-logs.ts | 16 ++++---- commands/azureCommands/acr-build.ts | 24 +++++------ commands/azureCommands/run-buildTask.ts | 24 +++++------ commands/azureCommands/show-buildTask.ts | 18 +++++---- commands/utils/quick-pick-azure.ts | 12 +++--- dockerExtension.ts | 38 +++++++++--------- explorer/models/taskNode.ts | 34 ++++++++-------- package.json | 33 +++++++++++---- utils/Azure/acrTools.ts | 6 +-- 12 files changed, 144 insertions(+), 127 deletions(-) diff --git a/commands/azureCommands/acr-build-logs-utils/logScripts.js b/commands/azureCommands/acr-build-logs-utils/logScripts.js index e8db8245e8..8d722d913b 100644 --- a/commands/azureCommands/acr-build-logs-utils/logScripts.js +++ b/commands/azureCommands/acr-build-logs-utils/logScripts.js @@ -96,7 +96,7 @@ function sortTable(n, dir = "asc", holdDir = false) { function acquireCompareFunction(n) { switch (n) { case 0: //Name - case 1: //Build Task + case 1: //Task return (x, y) => { return x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase() } @@ -285,15 +285,15 @@ function setInputListeners() { /*interface Filter image?: string; - buildId?: string; - buildTask?: string; + runId?: string; + runTask?: string; */ function getFilterString(inputFields) { let filter = {}; - if (inputFields[0].value.length > 0) { //Build Id - filter.buildId = inputFields[0].value; - } else if (inputFields[1].value.length > 0) { // Build Task id - filter.buildTask = inputFields[1].value; + if (inputFields[0].value.length > 0) { //Run Id + filter.runId = inputFields[0].value; + } else if (inputFields[1].value.length > 0) { //Task id + filter.task = inputFields[1].value; } else if (inputFields[2].value.length > 0) { //Image filter.image = inputFields[2].value; } diff --git a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts index c92cc052ff..a0cb75adf2 100644 --- a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts @@ -1,5 +1,5 @@ import ContainerRegistryManagementClient from "azure-arm-containerregistry"; -import { Build, BuildGetLogResult, BuildListResult, Registry } from "azure-arm-containerregistry/lib/models"; +import { Registry, Run, RunGetLogResult, RunListResult } from "azure-arm-containerregistry/lib/models"; import request = require('request-promise'); import { registryRequest } from "../../../explorer/models/commonRegistryUtils"; import { Manifest } from "../../../explorer/utils/dockerHubUtils"; @@ -9,7 +9,7 @@ export class LogData { public registry: Registry; public resourceGroup: string; public links: { requesting: boolean, url?: string }[]; - public logs: Build[]; + public logs: Run[]; public client: ContainerRegistryManagementClient; private nextLink: string; @@ -36,49 +36,49 @@ export class LogData { if (this.links[itemNumber].requesting) { return 'requesting' } this.links[itemNumber].requesting = true; - const temp: BuildGetLogResult = await this.client.builds.getLogLink(this.resourceGroup, this.registry.name, this.logs[itemNumber].buildId); + const temp: RunGetLogResult = await this.client.runs.getLogSasUrl(this.resourceGroup, this.registry.name, this.logs[itemNumber].runId); this.links[itemNumber].url = temp.logLink; this.links[itemNumber].requesting = false; return this.links[itemNumber].url } - //contains(BuildTaskName, 'testTask') - //`BuildTaskName eq 'testTask' + //contains(TaskName, 'testTask') + //`TaskName eq 'testTask' // /** Loads logs from azure * @param loadNext Determines if the next page of logs should be loaded, will throw an error if there are no more logs to load * @param removeOld Cleans preexisting information on links and logs imediately before new requests, if loadNext is specified * the next page of logs will be saved and all preexisting data will be deleted. - * @param filter Specifies a filter for log items, if build Id is specified this will take precedence + * @param filter Specifies a filter for log items, if run Id is specified this will take precedence */ public async loadLogs(loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { - let buildListResult: BuildListResult; + let runListResult: RunListResult; let options: any = {}; if (filter && Object.keys(filter).length) { - if (!filter.buildId) { + if (!filter.runId) { options.filter = await this.parseFilter(filter); - buildListResult = await this.client.builds.list(this.resourceGroup, this.registry.name, options); + runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); } else { - buildListResult = []; - buildListResult.push(await this.client.builds.get(this.resourceGroup, this.registry.name, filter.buildId)); + runListResult = []; + runListResult.push(await this.client.runs.get(this.resourceGroup, this.registry.name, filter.runId)); } } else { if (loadNext) { if (this.nextLink) { - buildListResult = await this.client.builds.listNext(this.nextLink); + runListResult = await this.client.runs.listNext(this.nextLink); } else { throw new Error('No more logs to show'); } } else { - buildListResult = await this.client.builds.list(this.resourceGroup, this.registry.name); + runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name); } } if (removeOld) { this.clearLogItems() } - this.nextLink = buildListResult.nextLink; - this.addLogs(buildListResult); + this.nextLink = runListResult.nextLink; + this.addLogs(runListResult); } - public addLogs(logs: Build[]): void { + public addLogs(logs: Run[]): void { this.logs = this.logs.concat(logs); const itemCount = logs.length; @@ -99,8 +99,8 @@ export class LogData { private async parseFilter(filter: Filter): Promise { let parsedFilter = ""; - if (filter.buildTask) { // Build Task id - parsedFilter = `BuildTaskName eq '${filter.buildTask}'`; + if (filter.task) { //Task id + parsedFilter = `TaskName eq '${filter.task}'`; } else if (filter.image) { //Image let items: string[] = filter.image.split(':') const { acrAccessToken } = await acquireACRAccessTokenFromRegistry(this.registry, 'repository:' + items[0] + ':pull'); @@ -126,6 +126,6 @@ export class LogData { export interface Filter { image?: string; - buildId?: string; - buildTask?: string; + runId?: string; + task?: string; } diff --git a/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts index 22a9e6a4da..5bb6e7e206 100644 --- a/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts @@ -1,5 +1,5 @@ -import { Build, ImageDescriptor } from "azure-arm-containerregistry/lib/models"; +import { ImageDescriptor, Run } from "azure-arm-containerregistry/lib/models"; import * as clipboardy from 'clipboardy' import * as path from 'path'; import * as vscode from "vscode"; @@ -32,7 +32,7 @@ export class LogTableWebview { const itemNumber: number = +message.logRequest.id; this.logData.getLink(itemNumber).then((url) => { if (url !== 'requesting') { - accessLog(url, this.logData.logs[itemNumber].buildId, message.logRequest.download); + accessLog(url, this.logData.logs[itemNumber].runId, message.logRequest.download); } }); @@ -70,7 +70,7 @@ export class LogTableWebview { } } - private getImageOutputTable(log: Build): string { + private getImageOutputTable(log: Run): string { let imageOutput: string = ''; if (log.outputImages) { //Adresses strange error where the image list can exist and contain only one null item. @@ -150,11 +150,11 @@ export class LogTableWebview { `; } - private getLogTableItem(log: Build, logId: number): string { - const task: string = log.buildTask ? log.buildTask : ''; + private getLogTableItem(log: Run, logId: number): string { + const task: string = log.task ? log.task : ''; const prettyDate: string = log.createTime ? this.getPrettyDate(log.createTime) : ''; const timeElapsed: string = log.startTime && log.finishTime ? Math.ceil((log.finishTime.valueOf() - log.startTime.valueOf()) / 1000).toString() + 's' : ''; - const osType: string = log.platform.osType ? log.platform.osType : ''; + const osType: string = log.platform.os ? log.platform.os : ''; const name: string = log.name ? log.name : ''; const imageOutput: string = this.getImageOutputTable(log); const statusIcon: string = this.getLogStatusIcon(log.status); diff --git a/commands/azureCommands/acr-build-logs.ts b/commands/azureCommands/acr-build-logs.ts index dd9de92730..2d0ab596ae 100644 --- a/commands/azureCommands/acr-build-logs.ts +++ b/commands/azureCommands/acr-build-logs.ts @@ -1,8 +1,8 @@ -import { Build, Registry } from "azure-arm-containerregistry/lib/models"; +import { Registry, Run } from "azure-arm-containerregistry/lib/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import * as vscode from "vscode"; import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from '../../explorer/models/azureRegistryNodes'; -import { BuildTaskNode } from "../../explorer/models/taskNode"; +import { TaskNode } from "../../explorer/models/taskNode"; import { getResourceGroupName, getSubscriptionFromRegistry } from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { quickPickACRRegistry } from '../utils/quick-pick-azure' @@ -11,7 +11,7 @@ import { LogData } from "./acr-build-logs-utils/tableDataManager"; import { LogTableWebview } from "./acr-build-logs-utils/tableViewManager"; /** This command is used through a right click on an azure registry, repository or image in the Docker Explorer. It is used to view build logs for a given item. */ -export async function viewBuildLogs(context: AzureRegistryNode | AzureImageTagNode | BuildTaskNode): Promise { +export async function viewBuildLogs(context: AzureRegistryNode | AzureImageTagNode | TaskNode): Promise { let registry: Registry; let subscription: Subscription; if (!context) { @@ -31,17 +31,17 @@ export async function viewBuildLogs(context: AzureRegistryNode | AzureImageTagNo await logData.loadLogs(false, false, { image: context.tag }); if (!hasValidLogContent(context, logData)) { return; } logData.getLink(0).then((url) => { - accessLog(url, logData.logs[0].buildId, false); + accessLog(url, logData.logs[0].runId, false); }); } else { - if (context && context instanceof BuildTaskNode) { - await logData.loadLogs(false, false, { buildTask: context.label }); + if (context && context instanceof TaskNode) { + await logData.loadLogs(false, false, { task: context.label }); } else { await logData.loadLogs(false); } if (!hasValidLogContent(context, logData)) { return; } let webViewTitle: string = registry.name; - if (context instanceof BuildTaskNode) { + if (context instanceof TaskNode) { webViewTitle += '/' + context.label; } let webview = new LogTableWebview(webViewTitle, logData); @@ -51,7 +51,7 @@ export async function viewBuildLogs(context: AzureRegistryNode | AzureImageTagNo function hasValidLogContent(context: any, logData: LogData): boolean { if (logData.logs.length === 0) { let itemType: string; - if (context && context instanceof BuildTaskNode) { + if (context && context instanceof TaskNode) { itemType = 'task'; } else if (context && context instanceof AzureImageTagNode) { itemType = 'image'; diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 2873f111bf..51f9b020be 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -1,5 +1,5 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry/lib/containerRegistryManagementClient'; -import { QuickBuildRequest } from "azure-arm-containerregistry/lib/models/quickBuildRequest"; +import { DockerBuildRequest } from "azure-arm-containerregistry/lib/models/dockerBuildRequest"; import { Registry } from 'azure-arm-containerregistry/lib/models/registry'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as fs from 'fs'; @@ -52,20 +52,20 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { const uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); status.appendLine("Uploaded Source Code to " + tarFilePath); - const buildRequest: QuickBuildRequest = { - 'type': 'QuickBuild', - 'imageNames': [imageName], - 'isPushEnabled': true, - 'sourceLocation': uploadedSourceLocation, - 'platform': { 'osType': osType }, - 'dockerFilePath': dockerItem.relativeFilePath + const runRequest: DockerBuildRequest = { + type: 'DockerBuildRequest', + imageNames: [imageName], + isPushEnabled: true, + sourceLocation: uploadedSourceLocation, + platform: { os: osType }, + dockerFilePath: dockerItem.relativeFilePath }; - status.appendLine("Set up Build Request"); + status.appendLine("Set up Run Request"); - const build = await client.registries.queueBuild(resourceGroupName, registry.name, buildRequest); - status.appendLine("Queued Build " + build.buildId); + const run = await client.registries.scheduleRun(resourceGroupName, registry.name, runRequest); + status.appendLine("Schedule Run " + run.runId); - streamLogs(registry, build, status, client); + streamLogs(registry, run, status, client); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { diff --git a/commands/azureCommands/run-buildTask.ts b/commands/azureCommands/run-buildTask.ts index bcc962ee6b..3f1009f1f6 100644 --- a/commands/azureCommands/run-buildTask.ts +++ b/commands/azureCommands/run-buildTask.ts @@ -1,16 +1,16 @@ -import { BuildTaskBuildRequest } from "azure-arm-containerregistry/lib/models"; +import { TaskRunRequest } from "azure-arm-containerregistry/lib/models"; import { Registry } from "azure-arm-containerregistry/lib/models"; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import vscode = require('vscode'); -import { BuildTaskNode } from "../../explorer/models/taskNode"; +import { TaskNode } from "../../explorer/models/taskNode"; import { ext } from '../../extensionVariables'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; -import { quickPickACRRegistry, quickPickBuildTask, quickPickSubscription } from '../utils/quick-pick-azure'; +import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; -export async function runBuildTask(context?: BuildTaskNode): Promise { - let buildTaskName: string; +export async function runBuildTask(context?: TaskNode): Promise { + let taskName: string; let subscription: Subscription; let resourceGroup: ResourceGroup; let registry: Registry; @@ -19,25 +19,25 @@ export async function runBuildTask(context?: BuildTaskNode): Promise { subscription = context.subscription; registry = context.registry; resourceGroup = await acrTools.getResourceGroup(registry, subscription); - buildTaskName = context.task.name; + taskName = context.task.name; } else { // Command Palette subscription = await quickPickSubscription(); registry = await quickPickACRRegistry(); resourceGroup = await acrTools.getResourceGroup(registry, subscription); - buildTaskName = (await quickPickBuildTask(registry, subscription, resourceGroup)).name; + taskName = (await quickPickTask(registry, subscription, resourceGroup)).name; } const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); - let buildRequest: BuildTaskBuildRequest = { - 'type': 'BuildTask', - 'buildTaskName': buildTaskName + let runRequest: TaskRunRequest = { + type: 'Task', + taskName: taskName }; try { - await client.registries.queueBuild(resourceGroup.name, registry.name, buildRequest); + await client.registries.scheduleRun(resourceGroup.name, registry.name, runRequest); } catch (err) { ext.outputChannel.append(err); } - vscode.window.showInformationMessage(`Successfully ran the Build Task, ${buildTaskName}`); + vscode.window.showInformationMessage(`Successfully ran the Task, ${taskName}`); } diff --git a/commands/azureCommands/show-buildTask.ts b/commands/azureCommands/show-buildTask.ts index 5ac52dc778..e1938cca24 100644 --- a/commands/azureCommands/show-buildTask.ts +++ b/commands/azureCommands/show-buildTask.ts @@ -1,34 +1,35 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; -import { BuildTaskNode } from "../../explorer/models/taskNode"; +import { TaskNode } from "../../explorer/models/taskNode"; import { ext } from '../../extensionVariables'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; -import { quickPickACRRegistry, quickPickBuildTask, quickPickSubscription } from '../utils/quick-pick-azure'; +import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; import { openTask } from "./task-utils/showTaskManager"; -export async function showBuildTaskProperties(context?: BuildTaskNode): Promise { +export async function showBuildTaskProperties(context?: TaskNode): Promise { let subscription: Subscription; let registry: Registry; let resourceGroup: ResourceGroup; - let buildTask: string; + let task: string; if (context) { // Right click subscription = context.subscription; registry = context.registry; resourceGroup = await acrTools.getResourceGroup(registry, subscription); - buildTask = context.task.name; + task = context.task.name; } else { // Command palette subscription = await quickPickSubscription(); registry = await quickPickACRRegistry(); resourceGroup = await acrTools.getResourceGroup(registry, subscription); - buildTask = (await quickPickBuildTask(registry, subscription, resourceGroup)).name; + task = (await quickPickTask(registry, subscription, resourceGroup)).name; } const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); - let item: any = await client.buildTasks.get(resourceGroup.name, registry.name, buildTask); + let item: any = await client.tasks.get(resourceGroup.name, registry.name, task); + /* try { const steps = await client.buildSteps.get(resourceGroup.name, registry.name, buildTask, `${buildTask}StepName`); item.properties = steps; @@ -36,8 +37,9 @@ export async function showBuildTaskProperties(context?: BuildTaskNode): Promise< ext.outputChannel.append(error); ext.outputChannel.append("Build Step not available for this image due to update in API"); } + */ let indentation = 1; let replacer; - openTask(JSON.stringify(item, replacer, indentation), buildTask); + openTask(JSON.stringify(item, replacer, indentation), task); } diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index 309f2c176f..61e37a482d 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -34,14 +34,14 @@ export async function quickPickACRRepository(registry: Registry, prompt?: string return desiredRepo.data; } -export async function quickPickBuildTask(registry: Registry, subscription: Subscription, resourceGroup: ResourceGroup, prompt?: string): Promise { - const placeHolder = prompt ? prompt : 'Choose a Build Task'; +export async function quickPickTask(registry: Registry, subscription: Subscription, resourceGroup: ResourceGroup, prompt?: string): Promise { + const placeHolder = prompt ? prompt : 'Choose a Task'; const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); - let buildTasks: ContainerModels.BuildTask[] = await client.buildTasks.list(resourceGroup.name, registry.name); - const quickpPickBTList = buildTasks.map(buildTask => >{ label: buildTask.name, data: buildTask }); - let desiredBuildTask = await ext.ui.showQuickPick(quickpPickBTList, { 'canPickMany': false, 'placeHolder': placeHolder }); - return desiredBuildTask.data; + let tasks: ContainerModels.Task[] = await client.tasks.list(resourceGroup.name, registry.name); + const quickpPickBTList = tasks.map(task => >{ label: task.name, data: task }); + let desiredTask = await ext.ui.showQuickPick(quickpPickBTList, { 'canPickMany': false, 'placeHolder': placeHolder }); + return desiredTask.data; } export async function quickPickACRRegistry(canCreateNew: boolean = false, prompt?: string): Promise { diff --git a/dockerExtension.ts b/dockerExtension.ts index ec5e9e78ea..ab240eeb54 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -100,7 +100,7 @@ import { import { ImageNode } from "./explorer/models/imageNode"; import { NodeBase } from "./explorer/models/nodeBase"; import { RootNode } from "./explorer/models/rootNode"; -import { BuildTaskNode } from "./explorer/models/taskNode"; +import { TaskNode } from "./explorer/models/taskNode"; import { browseAzurePortal } from "./explorer/utils/browseAzurePortal"; import { browseDockerHub, @@ -284,7 +284,7 @@ function registerCommand( return uiRegisterCommand( commandId, // tslint:disable-next-line:no-function-expression - async function(this: IActionContext, ...args: any[]): Promise { + async function (this: IActionContext, ...args: any[]): Promise { if (args.length) { let properties: { contextValue?: string; @@ -314,86 +314,86 @@ function registerDockerCommands(azureAccount: AzureAccount): void { dockerExplorerProvider.refresh() ); - registerCommand("vscode-docker.configure", async function( + registerCommand("vscode-docker.configure", async function ( this: IActionContext ): Promise { await configure(this, undefined); }); - registerCommand("vscode-docker.api.configure", async function( + registerCommand("vscode-docker.api.configure", async function ( this: IActionContext, options: ConfigureApiOptions ): Promise { await configureApi(this, options); }); - registerCommand("vscode-docker.container.start", async function( + registerCommand("vscode-docker.container.start", async function ( this: IActionContext, node: ImageNode | undefined ): Promise { await startContainer(this, node); }); - registerCommand("vscode-docker.container.start.interactive", async function( + registerCommand("vscode-docker.container.start.interactive", async function ( this: IActionContext, node: ImageNode | undefined ): Promise { await startContainerInteractive(this, node); }); registerCommand("vscode-docker.container.start.azurecli", startAzureCLI); - registerCommand("vscode-docker.container.stop", async function( + registerCommand("vscode-docker.container.stop", async function ( this: IActionContext, node: ContainerNode | RootNode | undefined ): Promise { await stopContainer(this, node); }); - registerCommand("vscode-docker.container.restart", async function( + registerCommand("vscode-docker.container.restart", async function ( this: IActionContext, node: ContainerNode | RootNode | undefined ): Promise { await restartContainer(this, node); }); - registerCommand("vscode-docker.container.show-logs", async function( + registerCommand("vscode-docker.container.show-logs", async function ( this: IActionContext, node: ContainerNode | RootNode | undefined ): Promise { await showLogsContainer(this, node); }); - registerCommand("vscode-docker.container.open-shell", async function( + registerCommand("vscode-docker.container.open-shell", async function ( this: IActionContext, node: ContainerNode | RootNode | undefined ): Promise { await openShellContainer(this, node); }); - registerCommand("vscode-docker.container.remove", async function( + registerCommand("vscode-docker.container.remove", async function ( this: IActionContext, node: ContainerNode | RootNode | undefined ): Promise { await removeContainer(this, node); }); - registerCommand("vscode-docker.image.build", async function( + registerCommand("vscode-docker.image.build", async function ( this: IActionContext, item: vscode.Uri | undefined ): Promise { await buildImage(this, item); }); - registerCommand("vscode-docker.image.inspect", async function( + registerCommand("vscode-docker.image.inspect", async function ( this: IActionContext, node: ImageNode | undefined ): Promise { await inspectImage(this, node); }); - registerCommand("vscode-docker.image.remove", async function( + registerCommand("vscode-docker.image.remove", async function ( this: IActionContext, node: ImageNode | RootNode | undefined ): Promise { await removeImage(this, node); }); - registerCommand("vscode-docker.image.push", async function( + registerCommand("vscode-docker.image.push", async function ( this: IActionContext, node: ImageNode | undefined ): Promise { await pushImage(this, node); }); - registerCommand("vscode-docker.image.tag", async function( + registerCommand("vscode-docker.image.tag", async function ( this: IActionContext, node: ImageNode | undefined ): Promise { @@ -442,10 +442,8 @@ function registerDockerCommands(azureAccount: AzureAccount): void { registerAzureCommand("vscode-docker.pullFromAzure", pullFromAzure); registerAzureCommand("vscode-docker.acrBuildLogs", viewBuildLogs); registerAzureCommand("vscode-docker.run-ACR-BuildTask", runBuildTask); - registerAzureCommand( - "vscode-docker.show-ACR-buildTask", - showBuildTaskProperties - ); + registerAzureCommand("vscode-docker.show-ACR-buildTask", showBuildTaskProperties); + registerCommand("vscode-docker.ACR-queueBuild", queueBuild); } export async function deactivate(): Promise { diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 9845108977..f90d1dc9a6 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -6,7 +6,7 @@ import { AzureAccount } from '../../typings/azure-account.api'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { NodeBase } from './nodeBase'; -/* Single TaskRootNode under each Repository. Labeled "Build Tasks" */ +/* Single TaskRootNode under each Repository. Labeled "Tasks" */ export class TaskRootNode extends NodeBase { public static readonly contextValue: string = 'taskRootNode'; private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); @@ -31,35 +31,35 @@ export class TaskRootNode extends NodeBase { } } - /* Making a list view of BuildTaskNodes, or the Build Tasks of the current registry */ - public async getChildren(element: TaskRootNode): Promise { - const buildTaskNodes: BuildTaskNode[] = []; - let buildTasks: ContainerModels.BuildTask[] = []; + /* Making a list view of TaskNodes, or the Tasks of the current registry */ + public async getChildren(element: TaskRootNode): Promise { + const taskNodes: TaskNode[] = []; + let tasks: ContainerModels.Task[] = []; const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); const resourceGroup: string = acrTools.getResourceGroupName(element.registry); - buildTasks = await client.buildTasks.list(resourceGroup, element.registry.name); - if (buildTasks.length === 0) { - vscode.window.showInformationMessage(`You do not have any Build Tasks in the registry, '${element.registry.name}'. You can create one with ACR Build. `, "Learn More").then(val => { + tasks = await client.tasks.list(resourceGroup, element.registry.name); + if (tasks.length === 0) { + vscode.window.showInformationMessage(`You do not have any Tasks in the registry, '${element.registry.name}'. You can create one with ACR Task. `, "Learn More").then(val => { if (val === "Learn More") { - opn('https://aka.ms/acr/buildtask'); + opn('https://aka.ms/acr/task'); } }) } - for (let buildTask of buildTasks) { - let node = new BuildTaskNode(buildTask, element.registry, element.subscription, element); - buildTaskNodes.push(node); + for (let task of tasks) { + let node = new TaskNode(task, element.registry, element.subscription, element); + taskNodes.push(node); } - return buildTaskNodes; + return taskNodes; } } -export class BuildTaskNode extends NodeBase { - public static readonly contextValue: string = 'buildTaskNode'; +export class TaskNode extends NodeBase { + public static readonly contextValue: string = 'taskNode'; public label: string; constructor( - public task: ContainerModels.BuildTask, + public task: ContainerModels.Task, public registry: ContainerModels.Registry, public subscription: SubscriptionModels.Subscription, @@ -73,7 +73,7 @@ export class BuildTaskNode extends NodeBase { return { label: this.label, collapsibleState: vscode.TreeItemCollapsibleState.None, - contextValue: BuildTaskNode.contextValue, + contextValue: TaskNode.contextValue, iconPath: null } } diff --git a/package.json b/package.json index 318b720a3e..d0913a915d 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,7 @@ "onLanguage:yaml", "onCommand:vscode-docker.acrBuildLogs", "onCommand:vscode-docker.api.configure", + "onCommand:vscode-docker.ACR-queueBuild", "onCommand:vscode-docker.image.build", "onCommand:vscode-docker.image.inspect", "onCommand:vscode-docker.image.remove", @@ -84,6 +85,11 @@ } ], "editor/context": [ + { + "when": "editorLangId == dockerfile", + "command": "vscode-docker.ACR-queueBuild", + "group": "docker" + }, { "when": "editorLangId == dockerfile", "command": "vscode-docker.image.build", @@ -121,6 +127,11 @@ } ], "explorer/context": [ + { + "when": "resourceFilename =~ /[dD]ocker[fF]ile/", + "command": "vscode-docker.ACR-queueBuild", + "group": "docker" + }, { "when": "resourceFilename =~ /[dD]ocker[fF]ile/", "command": "vscode-docker.image.build", @@ -237,11 +248,11 @@ }, { "command": "vscode-docker.run-ACR-BuildTask", - "when": "view == dockerExplorer && viewItem == buildTaskNode" + "when": "view == dockerExplorer && viewItem == taskNode" }, { "command": "vscode-docker.show-ACR-buildTask", - "when": "view == dockerExplorer && viewItem == buildTaskNode" + "when": "view == dockerExplorer && viewItem == taskNode" }, { "command": "vscode-docker.delete-ACR-Registry", @@ -257,7 +268,7 @@ }, { "command": "vscode-docker.acrBuildLogs", - "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureImageTagNode|buildTaskNode)$/" + "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureImageTagNode|taskNode)$/" }, { "command": "vscode-docker.connectCustomRegistry", @@ -483,6 +494,12 @@ "command": "vscode-docker.api.configure", "title": "Add Docker files to Workspace (API)" }, + { + "command": "vscode-docker.ACR-queueBuild", + "title": "Queue Build", + "description": "Queue a build from a Dockerfile", + "category": "Docker" + }, { "command": "vscode-docker.image.build", "title": "Build Image", @@ -639,12 +656,12 @@ }, { "command": "vscode-docker.run-ACR-BuildTask", - "title": "Run a Build Task", + "title": "Run a Task", "category": "Docker" }, { "command": "vscode-docker.show-ACR-buildTask", - "title": "Show Build Task Properties", + "title": "Show Task Properties", "category": "Docker" }, { @@ -659,7 +676,7 @@ }, { "command": "vscode-docker.acrBuildLogs", - "title": "ACR build logs", + "title": "ACR run logs", "category": "Docker" }, { @@ -679,7 +696,7 @@ }, { "command": "vscode-docker.ACR-Build", - "title": "Cloud Build", + "title": "Cloud run", "category": "Docker" } ], @@ -738,7 +755,7 @@ "vscode": "^1.1.18" }, "dependencies": { - "azure-arm-containerregistry": "^2.4.0", + "azure-arm-containerregistry": "^3.0.0", "azure-arm-resource": "^2.0.0-preview", "azure-arm-website": "^1.0.0-preview", "dockerfile-language-server-nodejs": "^0.0.19", diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 18ef712ac9..e4e475502b 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -5,7 +5,7 @@ import * as assert from 'assert'; import ContainerRegistryManagementClient from 'azure-arm-containerregistry'; -import { Build, BuildGetLogResult, Registry } from "azure-arm-containerregistry/lib/models"; +import { Registry, Run, RunGetLogResult } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; @@ -184,10 +184,10 @@ export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuf * streamed in which prevents updating of already appended lines. Usure if this can be fixed. Nonetheless * logs do load in chunks every 1 second. */ -export async function streamLogs(registry: Registry, build: Build, outputChannel: vscode.OutputChannel, providedClient?: ContainerRegistryManagementClient): Promise { +export async function streamLogs(registry: Registry, run: Run, outputChannel: vscode.OutputChannel, providedClient?: ContainerRegistryManagementClient): Promise { //Prefer passed in client to avoid initialization but if not added obtains own let client = providedClient ? providedClient : AzureUtilityManager.getInstance().getContainerRegistryManagementClient(getSubscriptionFromRegistry(registry)); - let temp: BuildGetLogResult = await client.builds.getLogLink(getResourceGroupName(registry), registry.name, build.buildId); + let temp: RunGetLogResult = await client.runs.getLogSasUrl(getResourceGroupName(registry), registry.name, run.runId); const link = temp.logLink; let blobInfo = getBlobInfo(link); let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); From d77295185dda4e51eb2388c91f6224f9ea18e076 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Tue, 18 Sep 2018 14:38:52 -0700 Subject: [PATCH 61/77] Run task fixed. Issue ID: 79 --- commands/azureCommands/run-buildTask.ts | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/commands/azureCommands/run-buildTask.ts b/commands/azureCommands/run-buildTask.ts index 3f1009f1f6..e909c1bab9 100644 --- a/commands/azureCommands/run-buildTask.ts +++ b/commands/azureCommands/run-buildTask.ts @@ -29,15 +29,15 @@ export async function runBuildTask(context?: TaskNode): Promise { const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); let runRequest: TaskRunRequest = { - type: 'Task', + type: 'TaskRunReques', taskName: taskName }; try { - await client.registries.scheduleRun(resourceGroup.name, registry.name, runRequest); + let taskRun = await client.registries.scheduleRun(resourceGroup.name, registry.name, runRequest); + vscode.window.showInformationMessage(`Successfully ran the Task: ${taskName} with ID: ${taskRun.runId}`); } catch (err) { ext.outputChannel.append(err); + vscode.window.showErrorMessage(`Failed to ran the Task: ${taskName}`); } - vscode.window.showInformationMessage(`Successfully ran the Task, ${taskName}`); - } diff --git a/package.json b/package.json index d0913a915d..13c6782ed0 100644 --- a/package.json +++ b/package.json @@ -656,7 +656,7 @@ }, { "command": "vscode-docker.run-ACR-BuildTask", - "title": "Run a Task", + "title": "Run Task", "category": "Docker" }, { From 3b723e37244230e2bfde4175aec1fed51f121746 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Tue, 18 Sep 2018 15:00:12 -0700 Subject: [PATCH 62/77] missing changes added --- commands/azureCommands/run-buildTask.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/azureCommands/run-buildTask.ts b/commands/azureCommands/run-buildTask.ts index e909c1bab9..52822f2f5b 100644 --- a/commands/azureCommands/run-buildTask.ts +++ b/commands/azureCommands/run-buildTask.ts @@ -29,7 +29,7 @@ export async function runBuildTask(context?: TaskNode): Promise { const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); let runRequest: TaskRunRequest = { - type: 'TaskRunReques', + type: 'TaskRunRequest', taskName: taskName }; From 5a9c8924f34659676009fa019df02b9fbe27a857 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Tue, 18 Sep 2018 17:35:31 -0700 Subject: [PATCH 63/77] Fixing ACR run logs for Images --- .../azureCommands/acr-build-logs-utils/tableDataManager.ts | 4 ++-- commands/azureCommands/acr-build-logs.ts | 5 ++++- explorer/models/azureRegistryNodes.ts | 2 +- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts index a0cb75adf2..384f58598a 100644 --- a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts @@ -109,8 +109,8 @@ export class LogData { auth: { bearer: acrAccessToken }, - accept: { - application: 'vnd.docker.distribution.manifest.v2+json' + headers: { + accept: 'application/vnd.docker.distribution.manifest.v2+json; 0.5, application/vnd.docker.distribution.manifest.list.v2+json; 0.6' } }, (err, httpResponse, body) => { digest = httpResponse.headers['docker-content-digest']; diff --git a/commands/azureCommands/acr-build-logs.ts b/commands/azureCommands/acr-build-logs.ts index 2d0ab596ae..e450311d96 100644 --- a/commands/azureCommands/acr-build-logs.ts +++ b/commands/azureCommands/acr-build-logs.ts @@ -28,15 +28,18 @@ export async function viewBuildLogs(context: AzureRegistryNode | AzureImageTagNo // Fuiltering provided if (context && context instanceof AzureImageTagNode) { - await logData.loadLogs(false, false, { image: context.tag }); + //ACR Image Logs + let imageRun = await logData.loadLogs(false, false, { image: context.label }); if (!hasValidLogContent(context, logData)) { return; } logData.getLink(0).then((url) => { accessLog(url, logData.logs[0].runId, false); }); } else { if (context && context instanceof TaskNode) { + //ACR Task Logs await logData.loadLogs(false, false, { task: context.label }); } else { + //ACR Registry Logs await logData.loadLogs(false); } if (!hasValidLogContent(context, logData)) { return; } diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 9ee700476d..c5e6f26f0e 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -49,7 +49,7 @@ export class AzureRegistryNode extends NodeBase { }; //Pushing single TaskRootNode under the current registry. All the following nodes added to registryNodes are type AzureRepositoryNode - let taskNode = new TaskRootNode("Build Tasks", element.subscription, element.azureAccount, element.registry, iconPath); + let taskNode = new TaskRootNode("Tasks", element.subscription, element.azureAccount, element.registry, iconPath); registryChildNodes.push(taskNode); if (!this.azureAccount) { From 5e5416982441c8463b3eb8eab0f8a33c41f990fd Mon Sep 17 00:00:00 2001 From: Sajay Antony <1821104+sajayantony@users.noreply.github.com> Date: Tue, 18 Sep 2018 22:26:41 -0700 Subject: [PATCH 64/77] Sajaya/top1 (#83) * Query only 1 record for runs * View Azure logs --- commands/azureCommands/acr-build-logs-utils/tableDataManager.ts | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts index 384f58598a..cb3b235558 100644 --- a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts @@ -57,6 +57,7 @@ export class LogData { if (filter && Object.keys(filter).length) { if (!filter.runId) { options.filter = await this.parseFilter(filter); + options.top = 1; runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); } else { runListResult = []; diff --git a/package.json b/package.json index 13c6782ed0..817c458faf 100644 --- a/package.json +++ b/package.json @@ -676,7 +676,7 @@ }, { "command": "vscode-docker.acrBuildLogs", - "title": "ACR run logs", + "title": "View Azure logs", "category": "Docker" }, { From e12b963119a5c4c1582eb2f43b445afd76e7a0ad Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Wed, 19 Sep 2018 14:12:23 -0700 Subject: [PATCH 65/77] Refactoring build to run and buildTask to task --- .../logFileManager.ts | 0 .../logScripts.js | 0 .../resizable.js | 0 .../fabric-components/css/vscmdl2-icons.css | 0 .../fonts/vscmdl2-icons-d3699964.woff | Bin .../fabric-components/fonts/vscmdl2-icons.ttf | Bin .../microsoft-ui-fabric-assets-license.pdf | Bin .../style/stylesheet.css | 0 .../tableDataManager.ts | 0 .../tableViewManager.ts | 12 ++++++------ .../{acr-build-logs.ts => acr-logs.ts} | 12 ++++++------ .../{run-buildTask.ts => run-task.ts} | 2 +- .../{show-buildTask.ts => show-task.ts} | 13 +------------ dockerExtension.ts | 14 +++++++------- explorer/models/azureRegistryNodes.ts | 4 ++-- .../{buildTasks_dark.svg => tasks_dark.svg} | 0 .../{buildTasks_light.svg => tasks_light.svg} | 0 package.json | 18 +++++++++--------- 18 files changed, 32 insertions(+), 43 deletions(-) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/logFileManager.ts (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/logScripts.js (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/resizable.js (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/style/fabric-components/css/vscmdl2-icons.css (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/style/fabric-components/fonts/vscmdl2-icons.ttf (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/style/fabric-components/microsoft-ui-fabric-assets-license.pdf (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/style/stylesheet.css (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/tableDataManager.ts (100%) rename commands/azureCommands/{acr-build-logs-utils => acr-logs-utils}/tableViewManager.ts (95%) rename commands/azureCommands/{acr-build-logs.ts => acr-logs.ts} (87%) rename commands/azureCommands/{run-buildTask.ts => run-task.ts} (96%) rename commands/azureCommands/{show-buildTask.ts => show-task.ts} (78%) rename images/dark/{buildTasks_dark.svg => tasks_dark.svg} (100%) rename images/light/{buildTasks_light.svg => tasks_light.svg} (100%) diff --git a/commands/azureCommands/acr-build-logs-utils/logFileManager.ts b/commands/azureCommands/acr-logs-utils/logFileManager.ts similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/logFileManager.ts rename to commands/azureCommands/acr-logs-utils/logFileManager.ts diff --git a/commands/azureCommands/acr-build-logs-utils/logScripts.js b/commands/azureCommands/acr-logs-utils/logScripts.js similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/logScripts.js rename to commands/azureCommands/acr-logs-utils/logScripts.js diff --git a/commands/azureCommands/acr-build-logs-utils/resizable.js b/commands/azureCommands/acr-logs-utils/resizable.js similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/resizable.js rename to commands/azureCommands/acr-logs-utils/resizable.js diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/css/vscmdl2-icons.css b/commands/azureCommands/acr-logs-utils/style/fabric-components/css/vscmdl2-icons.css similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/style/fabric-components/css/vscmdl2-icons.css rename to commands/azureCommands/acr-logs-utils/style/fabric-components/css/vscmdl2-icons.css diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff b/commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff rename to commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf b/commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf rename to commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf diff --git a/commands/azureCommands/acr-build-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf b/commands/azureCommands/acr-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf rename to commands/azureCommands/acr-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf diff --git a/commands/azureCommands/acr-build-logs-utils/style/stylesheet.css b/commands/azureCommands/acr-logs-utils/style/stylesheet.css similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/style/stylesheet.css rename to commands/azureCommands/acr-logs-utils/style/stylesheet.css diff --git a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts similarity index 100% rename from commands/azureCommands/acr-build-logs-utils/tableDataManager.ts rename to commands/azureCommands/acr-logs-utils/tableDataManager.ts diff --git a/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts similarity index 95% rename from commands/azureCommands/acr-build-logs-utils/tableViewManager.ts rename to commands/azureCommands/acr-logs-utils/tableViewManager.ts index 5bb6e7e206..1743d511e7 100644 --- a/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -15,10 +15,10 @@ export class LogTableWebview { //Get path to resource on disk let extensionPath = vscode.extensions.getExtension("PeterJausovec.vscode-docker").extensionPath; - const scriptFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); - const resizingScript = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'resizable.js')).with({ scheme: 'vscode-resource' }); - const styleFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); - const iconStyle = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); + const scriptFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); + const resizingScript = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'resizable.js')).with({ scheme: 'vscode-resource' }); + const styleFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); + const iconStyle = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); //Populate Webview this.panel.webview.html = this.getBaseHtml(scriptFile, resizingScript, styleFile, iconStyle); this.setupIncomingListeners(); @@ -89,7 +89,7 @@ export class LogTableWebview { } //HTML Content Loaders - /** Create the table in which to push the build logs */ + /** Create the table in which to push the logs */ private getBaseHtml(scriptFile: vscode.Uri, resizingScript: vscode.Uri, stylesheet: vscode.Uri, iconStyles: vscode.Uri): string { return ` @@ -129,7 +129,7 @@ export class LogTableWebview { - Build ID + ID Task Status Created  diff --git a/commands/azureCommands/acr-build-logs.ts b/commands/azureCommands/acr-logs.ts similarity index 87% rename from commands/azureCommands/acr-build-logs.ts rename to commands/azureCommands/acr-logs.ts index e450311d96..05ebd416eb 100644 --- a/commands/azureCommands/acr-build-logs.ts +++ b/commands/azureCommands/acr-logs.ts @@ -6,12 +6,12 @@ import { TaskNode } from "../../explorer/models/taskNode"; import { getResourceGroupName, getSubscriptionFromRegistry } from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; import { quickPickACRRegistry } from '../utils/quick-pick-azure' -import { accessLog } from "./acr-build-logs-utils/logFileManager"; -import { LogData } from "./acr-build-logs-utils/tableDataManager"; -import { LogTableWebview } from "./acr-build-logs-utils/tableViewManager"; +import { accessLog } from "./acr-logs-utils/logFileManager"; +import { LogData } from "./acr-logs-utils/tableDataManager"; +import { LogTableWebview } from "./acr-logs-utils/tableViewManager"; -/** This command is used through a right click on an azure registry, repository or image in the Docker Explorer. It is used to view build logs for a given item. */ -export async function viewBuildLogs(context: AzureRegistryNode | AzureImageTagNode | TaskNode): Promise { +/** This command is used through a right click on an azure registry, repository or image in the Docker Explorer. It is used to view ACR logs for a given item. */ +export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode | TaskNode): Promise { let registry: Registry; let subscription: Subscription; if (!context) { @@ -61,7 +61,7 @@ function hasValidLogContent(context: any, logData: LogData): boolean { } else { itemType = 'registry'; } - vscode.window.showInformationMessage(`This ${itemType} has no associated build logs`); + vscode.window.showInformationMessage(`This ${itemType} has no associated logs`); return false; } return true; diff --git a/commands/azureCommands/run-buildTask.ts b/commands/azureCommands/run-task.ts similarity index 96% rename from commands/azureCommands/run-buildTask.ts rename to commands/azureCommands/run-task.ts index 52822f2f5b..6b6ab7ccc4 100644 --- a/commands/azureCommands/run-buildTask.ts +++ b/commands/azureCommands/run-task.ts @@ -9,7 +9,7 @@ import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; -export async function runBuildTask(context?: TaskNode): Promise { +export async function runTask(context?: TaskNode): Promise { let taskName: string; let subscription: Subscription; let resourceGroup: ResourceGroup; diff --git a/commands/azureCommands/show-buildTask.ts b/commands/azureCommands/show-task.ts similarity index 78% rename from commands/azureCommands/show-buildTask.ts rename to commands/azureCommands/show-task.ts index e1938cca24..1ab9808cb3 100644 --- a/commands/azureCommands/show-buildTask.ts +++ b/commands/azureCommands/show-task.ts @@ -8,7 +8,7 @@ import { AzureUtilityManager } from "../../utils/azureUtilityManager"; import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; import { openTask } from "./task-utils/showTaskManager"; -export async function showBuildTaskProperties(context?: TaskNode): Promise { +export async function showTaskProperties(context?: TaskNode): Promise { let subscription: Subscription; let registry: Registry; let resourceGroup: ResourceGroup; @@ -28,17 +28,6 @@ export async function showBuildTaskProperties(context?: TaskNode): Promise const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); let item: any = await client.tasks.get(resourceGroup.name, registry.name, task); - - /* - try { - const steps = await client.buildSteps.get(resourceGroup.name, registry.name, buildTask, `${buildTask}StepName`); - item.properties = steps; - } catch (error) { - ext.outputChannel.append(error); - ext.outputChannel.append("Build Step not available for this image due to update in API"); - } - */ - let indentation = 1; let replacer; openTask(JSON.stringify(item, replacer, indentation), task); diff --git a/dockerExtension.ts b/dockerExtension.ts index ab240eeb54..cd14cffa63 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -27,15 +27,15 @@ import { TransportKind } from "vscode-languageclient/lib/main"; import { queueBuild } from "./commands/azureCommands/acr-build"; -import { viewBuildLogs } from "./commands/azureCommands/acr-build-logs"; -import { LogContentProvider } from "./commands/azureCommands/acr-build-logs-utils/logFileManager"; +import { viewACRLogs } from "./commands/azureCommands/acr-logs"; +import { LogContentProvider } from "./commands/azureCommands/acr-logs-utils/logFileManager"; import { createRegistry } from "./commands/azureCommands/create-registry"; import { deleteAzureImage } from "./commands/azureCommands/delete-image"; import { deleteAzureRegistry } from "./commands/azureCommands/delete-registry"; import { deleteRepository } from "./commands/azureCommands/delete-repository"; import { pullFromAzure } from "./commands/azureCommands/pull-from-azure"; -import { runBuildTask } from "./commands/azureCommands/run-buildTask"; -import { showBuildTaskProperties } from "./commands/azureCommands/show-buildTask"; +import { runTask } from "./commands/azureCommands/run-task"; +import { showTaskProperties } from "./commands/azureCommands/show-task"; import { TaskContentProvider } from "./commands/azureCommands/task-utils/showTaskManager"; import { buildImage } from "./commands/build-image"; import { @@ -440,9 +440,9 @@ function registerDockerCommands(azureAccount: AzureAccount): void { registerAzureCommand("vscode-docker.delete-ACR-Repository", deleteRepository); registerAzureCommand("vscode-docker.create-ACR-Registry", createRegistry); registerAzureCommand("vscode-docker.pullFromAzure", pullFromAzure); - registerAzureCommand("vscode-docker.acrBuildLogs", viewBuildLogs); - registerAzureCommand("vscode-docker.run-ACR-BuildTask", runBuildTask); - registerAzureCommand("vscode-docker.show-ACR-buildTask", showBuildTaskProperties); + registerAzureCommand("vscode-docker.acrLogs", viewACRLogs); + registerAzureCommand("vscode-docker.run-ACR-Task", runTask); + registerAzureCommand("vscode-docker.show-ACR-Task", showTaskProperties); registerCommand("vscode-docker.ACR-queueBuild", queueBuild); } diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index c5e6f26f0e..49742cf16b 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -44,8 +44,8 @@ export class AzureRegistryNode extends NodeBase { const registryChildNodes: NodeBase[] = []; let iconPath = { - light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'buildTasks_light.svg'), - dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'buildTasks_dark.svg') + light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'tasks_light.svg'), + dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'tasks_dark.svg') }; //Pushing single TaskRootNode under the current registry. All the following nodes added to registryNodes are type AzureRepositoryNode diff --git a/images/dark/buildTasks_dark.svg b/images/dark/tasks_dark.svg similarity index 100% rename from images/dark/buildTasks_dark.svg rename to images/dark/tasks_dark.svg diff --git a/images/light/buildTasks_light.svg b/images/light/tasks_light.svg similarity index 100% rename from images/light/buildTasks_light.svg rename to images/light/tasks_light.svg diff --git a/package.json b/package.json index 817c458faf..776287e512 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "activationEvents": [ "onLanguage:dockerfile", "onLanguage:yaml", - "onCommand:vscode-docker.acrBuildLogs", + "onCommand:vscode-docker.acrLogs", "onCommand:vscode-docker.api.configure", "onCommand:vscode-docker.ACR-queueBuild", "onCommand:vscode-docker.image.build", @@ -55,9 +55,9 @@ "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", - "onCommand:vscode-docker.show-ACR-buildTask", + "onCommand:vscode-docker.show-ACR-Task", "onCommand:vscode-docker.delete-ACR-Registry", - "onCommand:vscode-docker.run-ACR-BuildTask", + "onCommand:vscode-docker.run-ACR-Task", "onCommand:vscode-docker.delete-ACR-Repository", "onCommand:vscode-docker.delete-ACR-Image", "onCommand:vscode-docker.connectCustomRegistry", @@ -247,11 +247,11 @@ "when": "view == dockerExplorer && viewItem == azureImageNode" }, { - "command": "vscode-docker.run-ACR-BuildTask", + "command": "vscode-docker.run-ACR-Task", "when": "view == dockerExplorer && viewItem == taskNode" }, { - "command": "vscode-docker.show-ACR-buildTask", + "command": "vscode-docker.show-ACR-Task", "when": "view == dockerExplorer && viewItem == taskNode" }, { @@ -267,7 +267,7 @@ "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureRepositoryNode|azureImageNode)$/" }, { - "command": "vscode-docker.acrBuildLogs", + "command": "vscode-docker.acrLogs", "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureImageTagNode|taskNode)$/" }, { @@ -655,12 +655,12 @@ "category": "Docker" }, { - "command": "vscode-docker.run-ACR-BuildTask", + "command": "vscode-docker.run-ACR-Task", "title": "Run Task", "category": "Docker" }, { - "command": "vscode-docker.show-ACR-buildTask", + "command": "vscode-docker.show-ACR-Task", "title": "Show Task Properties", "category": "Docker" }, @@ -675,7 +675,7 @@ "category": "Docker" }, { - "command": "vscode-docker.acrBuildLogs", + "command": "vscode-docker.acrLogs", "title": "View Azure logs", "category": "Docker" }, From 71bcaf4731a3536897e967d98b3313ae262d9c12 Mon Sep 17 00:00:00 2001 From: Esteban Rey Date: Wed, 25 Jul 2018 10:22:54 -0700 Subject: [PATCH 66/77] Update Credentail Management Sorted Existing Create Registry ready for code review Jackson esteban/unified client nit Fix (#24) * Added Azure Credentials Manager Singleton * Small Style Fixes * Further Style fixes, added getResourceManagementClient * Lazy Initialization Patches Enabled location selection Location request fixes -Changed order of questions asked to user for better UX (location of new res group & location of new registry) -Placeholder of location is display name view Added SKU selection Quick fix- initializing array syntax Julia/delete image (#29) * first fully functional version of delete through input bar AND right click * refactored code to make it prettier! * comments * comments, added subscription function * fixed to style guide * style fixes, refactoring * delete image after reviews put my functions from azureCredentialsManager into two new files: utils/azure/acrTools.ts, and commands/utils/quick-pick-azure.ts Edited code based on Esteban's and Bin's reviews * One last little change to delete image * moved repository, azureimage, and getsubscriptions to the correct places within deleteImage * changes from PR reviews on delete image * fixed authentication issue, got rid of azureAccount property for repository and image **on constructor for repository, azurecredentialsmanager was being recreated and thus couldn't find the azureAccount. For this reason, I got rid of the azureAccount property of the classes Repository and AzureImage. This bug may lead to future problems (Esteban and I couldn't see why it was happening) * minor fixes deleteImage * delete a parentheses copied previous push to acr into new pull-from-azure.ts file Estebanreyl/dev merge fixes (#43) * Merge fixes to acquire latest telemetry items * Updated to master AzureUtilityManager added acrbuild stuff Rutusamai/list build tasks for each registry (#37) * tslint updates, transfered from old branch * updated icon * Addressed PR comments- mostly styling/code efficiency * Changed url to aka.ms link * changed Error window to Info message for no build tasks in your registry * Changed default sku and unified parsing resource group into a new method, getResourceGroup in acrTools.ts * Changed build task icon utility bug fix Julia/delete repository final (#49) * deleteRepo moved over to branch off dev * Got rid of unnecessary code, fully functioning! * deleteRepo moved over to branch off dev * Got rid of unnecessary code, fully functioning! * spacing * final commit * Cleaned code * Added Telemetry Julia/delete registry final (#47) Delete azure registry functionality added Delete azure registry moved to branch off dev Reorganized stye Made loginCredentials method in acrTools more efficient, added Pull Image from Azure option, temporary fix for no registries Split the loginCredentials function, added docker login and docker pull and telemetry actions copied previous push to acr into new pull-from-azure.ts file Made loginCredentials method in acrTools more efficient, added Pull Image from Azure option, temporary fix for no registries Split the loginCredentials function, added docker login and docker pull and telemetry actions Finished pull from azure right click feature Working again after rebasing and resolving merge conflicts Refactoring, prod. Estebanreyl/ready for production (#55) * began updating * Reorganized create registry and delete azure image * continued improvements * Began updating login * being credentials update * further updates * Finished updating, need to test functionality now * Updated requests, things all work now * Applied some nit fixes * Updates to naming * maintain UtilityManager standards * Updated Prompts * Updated imports and naming / standarized telemetry * Added explorer refresh capabilities on delete/add Jackson/quick build dev (#46) * added acrbuild stuff * added acrbuild stuff * Update to match utility manager class * Added quick pick for selecting resource group and registry * clean * Added subscription support * utility bug fix * added folder select * Updates and fixes * Refactoring, prod. * Flexible OSType added ID Move build to azure commands cleanup Relative dockerfile support Removed await, updating list in enumeration fixed chdir flexible ostype Rutusamai/Show Build Task properties (#70) * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show is working- final * removed run build task stuff * cleanup * addressed esteban's comments * buildTask = context.task.name rather than label * fixes from Bin's comments Merge branch 'dev' of https://github.com/AzureCR/vscode-docker into master4 merge missed small changes Rutusamai/Run a Build Task (#71) * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show build task works through input bar * added run build task * Right click not working on BuildTaskNode. Works through command palette * quick fixes * quick fix to enable context menu on buildTaskNode * comamnd not sending via right click * working from right click now. trying to do show in json * Acquire properties in JSON format internally, works * Now shows organized task jsons on right click * import * to do * issues with getImagesByRepository * try catch build step * acrTools * small refactor * Show is working- final * run is working for right click * run build task works through command pallette. added success notification * removed show build task * cleanup- matched quickpick buils task and tasknode withshow build task branch * removed showTaskManager * spacing * outputChannel and null icon * merge to update with dev Estebanreyl/build logs final (#72) * "Merge branch 'estebanreyl/buildlogsWithAccesibility' of https://github.com/AzureCR/vscode-docker into estebanreyl/buildlogsWithAccesibility" This reverts commit e645cbf5058c9bc838ee41a67cd6c5aee9945702, reversing changes made to fc4a477220c7819c9249557ba0d7570f5d0c1e0c. * Refactored and organized log view into readable components. * moved storage * Began incorporating icons * Added icons * Further standarization * added logs * Added download log capabilities * Updated Copy * Updated inner div size alignment * Added resizing script * header sort by is only clickable on text now * fixed header table * Fix minor display issues * Identified filtering strategy * Begin adding sorting * Merge with dev * Added proper filtering * Nit loading improvements * Added accesibility, key only behaviour * accesibility retouches and enable right click from tasknode * merges * fix module name Adds streaming and command standarization (ext.ui) (#73) * Adds streaming and command standarization (ext.ui) * removed unecessary append lines * small fixes * Fix merge issues changes for ACR 3.0.0 (#80) * This commit contains changes necessary for the azure-arm-containerregistry 3.0.0 * Fixed PR feedback Run task fixed. Issue ID: 79 missing changes added Fixing ACR run logs for Images Sajaya/top1 (#83) * Query only 1 record for runs * View Azure logs bug fix --- .vscode/settings.json | 5 +- commands/azureCommands/acr-build.ts | 116 +++ .../acr-logs-utils/logFileManager.ts | 69 ++ .../acr-logs-utils/logScripts.js | 319 ++++++++ .../azureCommands/acr-logs-utils/resizable.js | 166 ++++ .../fabric-components/css/vscmdl2-icons.css | 71 ++ .../fonts/vscmdl2-icons-d3699964.woff | Bin 0 -> 2792 bytes .../fabric-components/fonts/vscmdl2-icons.ttf | Bin 0 -> 5308 bytes .../microsoft-ui-fabric-assets-license.pdf | Bin 0 -> 467888 bytes .../acr-logs-utils/style/stylesheet.css | 387 +++++++++ .../acr-logs-utils/tableDataManager.ts | 132 +++ .../acr-logs-utils/tableViewManager.ts | 262 ++++++ commands/azureCommands/acr-logs.ts | 68 ++ commands/azureCommands/create-registry.ts | 8 + commands/azureCommands/delete-repository.ts | 2 +- commands/azureCommands/pull-from-azure.ts | 22 + commands/azureCommands/run-task.ts | 43 + commands/azureCommands/show-task.ts | 34 + .../task-utils/showTaskManager.ts | 37 + commands/build-image.ts | 2 +- commands/utils/quick-pick-azure.ts | 34 +- constants.ts | 3 + dockerExtension.ts | 764 ++++++++++++------ explorer/deploy/webAppCreator.ts | 2 + explorer/models/azureRegistryNodes.ts | 10 +- explorer/models/imageNode.ts | 2 +- explorer/models/registryRootNode.ts | 2 + explorer/models/rootNode.ts | 38 +- explorer/models/taskNode.ts | 86 ++ images/dark/tasks_dark.svg | 1 + images/light/tasks_light.svg | 1 + package.json | 94 ++- utils/Azure/acrTools.ts | 91 ++- utils/azureUtilityManager.ts | 13 +- 34 files changed, 2581 insertions(+), 303 deletions(-) create mode 100644 commands/azureCommands/acr-build.ts create mode 100644 commands/azureCommands/acr-logs-utils/logFileManager.ts create mode 100644 commands/azureCommands/acr-logs-utils/logScripts.js create mode 100644 commands/azureCommands/acr-logs-utils/resizable.js create mode 100644 commands/azureCommands/acr-logs-utils/style/fabric-components/css/vscmdl2-icons.css create mode 100644 commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff create mode 100644 commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf create mode 100644 commands/azureCommands/acr-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf create mode 100644 commands/azureCommands/acr-logs-utils/style/stylesheet.css create mode 100644 commands/azureCommands/acr-logs-utils/tableDataManager.ts create mode 100644 commands/azureCommands/acr-logs-utils/tableViewManager.ts create mode 100644 commands/azureCommands/acr-logs.ts create mode 100644 commands/azureCommands/pull-from-azure.ts create mode 100644 commands/azureCommands/run-task.ts create mode 100644 commands/azureCommands/show-task.ts create mode 100644 commands/azureCommands/task-utils/showTaskManager.ts create mode 100644 explorer/models/taskNode.ts create mode 100644 images/dark/tasks_dark.svg create mode 100644 images/light/tasks_light.svg diff --git a/.vscode/settings.json b/.vscode/settings.json index 278ead5ea4..810a99cf1b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,5 +9,8 @@ "files.trimTrailingWhitespace": true, "files.insertFinalNewline": true, "editor.formatOnSave": true, - "tslint.autoFixOnSave": true + "tslint.autoFixOnSave": true, + "yaml.schemas": { + "http://json.schemastore.org/bukkit-plugin": "/*" + } } diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts new file mode 100644 index 0000000000..51f9b020be --- /dev/null +++ b/commands/azureCommands/acr-build.ts @@ -0,0 +1,116 @@ +import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry/lib/containerRegistryManagementClient'; +import { DockerBuildRequest } from "azure-arm-containerregistry/lib/models/dockerBuildRequest"; +import { Registry } from 'azure-arm-containerregistry/lib/models/registry'; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; +import * as fs from 'fs'; +import * as os from 'os'; +import * as process from 'process'; +import * as tar from 'tar'; +import * as url from 'url'; +import * as vscode from "vscode"; +import { IAzureQuickPickItem } from 'vscode-azureextensionui'; +import { ext } from '../../extensionVariables'; +import { getBlobInfo, getResourceGroupName, streamLogs } from "../../utils/Azure/acrTools"; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { resolveDockerFileItem } from '../build-image'; +import { quickPickACRRegistry, quickPickNewImageName, quickPickSubscription } from '../utils/quick-pick-azure'; + +const idPrecision = 6; +const vcsIgnoreList = ['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']; +const status = vscode.window.createOutputChannel('ACR Build status'); + +// Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. +// The user is then asked to name & tag the image. A build is queued for the image in the selected registry. +// Selected source code must contain a path to the desired dockerfile. +export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { + //Acquire information from user + const subscription = await quickPickSubscription(); + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + const registry: Registry = await quickPickACRRegistry(true); + + const resourceGroupName = getResourceGroupName(registry); + + const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); + const osType: string = (await ext.ui.showQuickPick(osPick, { 'canPickMany': false, 'placeHolder': 'Select image base OS' })).data; + + const imageName: string = await quickPickNewImageName(); + + //Begin readying build + status.show(); + + let folder: vscode.WorkspaceFolder; + if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { + folder = vscode.workspace.workspaceFolders[0]; + } else { + folder = await (vscode).window.showWorkspaceFolderPick(); + } + const dockerItem = await resolveDockerFileItem(folder, dockerFileUri); + const sourceLocation: string = folder.uri.path; + const tarFilePath = getTempSourceArchivePath(); + + const uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); + status.appendLine("Uploaded Source Code to " + tarFilePath); + + const runRequest: DockerBuildRequest = { + type: 'DockerBuildRequest', + imageNames: [imageName], + isPushEnabled: true, + sourceLocation: uploadedSourceLocation, + platform: { os: osType }, + dockerFilePath: dockerItem.relativeFilePath + }; + status.appendLine("Set up Run Request"); + + const run = await client.registries.scheduleRun(resourceGroupName, registry.name, runRequest); + status.appendLine("Schedule Run " + run.runId); + + streamLogs(registry, run, status, client); +} + +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { + status.appendLine(" Sending source code to temp file"); + let source = sourceLocation.substring(1); + let current = process.cwd(); + process.chdir(source); + fs.readdir(source, (err, items) => { + items = filter(items); + tar.c( + {}, + items + ).pipe(fs.createWriteStream(tarFilePath)); + process.chdir(current); + }); + + status.appendLine(" Getting Build Source Upload Url "); + let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + let upload_url = sourceUploadLocation.uploadUrl; + let relative_path = sourceUploadLocation.relativePath; + + status.appendLine(" Getting blob info from Upload Url "); + // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. + let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); + status.appendLine(" Creating Blob Service "); + let blob: BlobService = createBlobServiceWithSas(host, sasToken); + status.appendLine(" Creating Block Blob "); + blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + return relative_path; +} + +function getTempSourceArchivePath(): string { + /* tslint:disable-next-line:insecure-random */ + let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); + status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); + let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + return tarFilePath; +} + +function filter(list: string[]): string[] { + let result = []; + for (let file of list) { + if (vcsIgnoreList.indexOf(file) === -1) { + result.push(file); + } + } + return result; +} diff --git a/commands/azureCommands/acr-logs-utils/logFileManager.ts b/commands/azureCommands/acr-logs-utils/logFileManager.ts new file mode 100644 index 0000000000..f377d78538 --- /dev/null +++ b/commands/azureCommands/acr-logs-utils/logFileManager.ts @@ -0,0 +1,69 @@ +import { BlobService, createBlobServiceWithSas } from 'azure-storage'; +import * as fs from 'fs'; +import * as vscode from 'vscode'; +import { getBlobInfo } from '../../../utils/Azure/acrTools'; + +export class LogContentProvider implements vscode.TextDocumentContentProvider { + public static scheme: string = 'purejs'; + private onDidChangeEvent: vscode.EventEmitter = new vscode.EventEmitter(); + + constructor() { } + + public provideTextDocumentContent(uri: vscode.Uri): string { + return decodeBase64(JSON.parse(uri.query).log); + } + + get onDidChange(): vscode.Event { + return this.onDidChangeEvent.event; + } + + public update(uri: vscode.Uri, message: string): void { + this.onDidChangeEvent.fire(uri); + } + +} + +export function decodeBase64(str: string): string { + return Buffer.from(str, 'base64').toString('ascii'); +} + +export function encodeBase64(str: string): string { + return Buffer.from(str, 'ascii').toString('base64'); +} + +/** Loads log text from remote url using azure blobservices */ +export function accessLog(url: string, title: string, download: boolean): void { + let blobInfo = getBlobInfo(url); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); + blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, async (error, text, result, response) => { + if (response) { + if (download) { + downloadLog(text, title); + } else { + openLogInNewWindow(text, title); + } + } else if (error) { + throw error; + } + }); +} + +function openLogInNewWindow(content: string, title: string): void { + const scheme = 'purejs'; + let query = JSON.stringify({ 'log': encodeBase64(content) }); + let uri: vscode.Uri = vscode.Uri.parse(`${scheme}://authority/${title}.log?${query}#idk`); + vscode.workspace.openTextDocument(uri).then((doc) => { + return vscode.window.showTextDocument(doc, vscode.ViewColumn.Active + 1, true); + }); +} + +export async function downloadLog(content: string, title: string): Promise { + let uri = await vscode.window.showSaveDialog({ + filters: { 'Log': ['.log', '.txt'] }, + defaultUri: vscode.Uri.file(`${title}.log`) + }); + fs.writeFile(uri.fsPath, content, + (err) => { + if (err) { throw err; } + }); +} diff --git a/commands/azureCommands/acr-logs-utils/logScripts.js b/commands/azureCommands/acr-logs-utils/logScripts.js new file mode 100644 index 0000000000..8d722d913b --- /dev/null +++ b/commands/azureCommands/acr-logs-utils/logScripts.js @@ -0,0 +1,319 @@ +// Global Variables +const status = { + 'Succeeded': 4, + 'Queued': 3, + 'Error': 2, + 'Failed': 1 +} + +var currentN = 4; +var currentDir = "asc" +var triangles = { + 'down': ' ', + 'up': ' ' +} + +document.addEventListener("scroll", function () { + var translate = "translate(0," + this.lastChild.scrollTop + "px)"; + let fixedItems = this.querySelectorAll(".fixed"); + for (item of fixedItems) { + item.style.transform = translate; + } +}); + +// Main +let content = document.querySelector('#core'); +const vscode = acquireVsCodeApi(); +setLoadMoreListener(); +setInputListeners(); +loading(); + +document.onkeydown = function (event) { + if (event.key === "Enter") { // The Enter/Return key + document.activeElement.onclick(event); + } +}; + +/* Sorting + * PR note, while this does not use a particularly quick algorithm + * it allows a low stuttering experience that allowed rapid testing. + * I will improve it soon.*/ +function sortTable(n, dir = "asc", holdDir = false) { + currentN = n; + let table, rows, switching, i, x, y, shouldSwitch, switchcount = 0; + let cmpFunc = acquireCompareFunction(n); + table = document.getElementById("core"); + switching = true; + //Set the sorting direction to ascending: + + while (switching) { + switching = false; + rows = table.querySelectorAll(".holder"); + for (i = 0; i < rows.length - 1; i++) { + shouldSwitch = false; + x = rows[i].getElementsByTagName("TD")[n + 1]; + y = rows[i + 1].getElementsByTagName("TD")[n + 1]; + if (dir == "asc") { + if (cmpFunc(x, y)) { + shouldSwitch = true; + break; + } + } else if (dir == "desc") { + if (cmpFunc(y, x)) { + shouldSwitch = true; + break; + } + } + } + if (shouldSwitch) { + rows[i].parentNode.insertBefore(rows[i + 1], rows[i]); + switching = true; + switchcount++; + } else { + /*If no switching has been done AND the direction is "asc", set the direction to "desc" and run the while loop again.*/ + if (switchcount == 0 && dir == "asc" && !holdDir) { + dir = "desc"; + switching = true; + } + } + } + if (!holdDir) { + let sortColumns = document.querySelectorAll(".sort"); + if (sortColumns[n].innerHTML === triangles['down']) { + sortColumns[n].innerHTML = triangles['up']; + } else if (sortColumns[n].innerHTML === triangles['up']) { + sortColumns[n].innerHTML = triangles['down']; + } else { + for (cell of sortColumns) { + cell.innerHTML = ' '; + } + sortColumns[n].innerHTML = triangles['down']; + } + } + currentDir = dir; +} + +function acquireCompareFunction(n) { + switch (n) { + case 0: //Name + case 1: //Task + return (x, y) => { + return x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase() + } + case 2: //Status + return (x, y) => { + return status[x.dataset.status] > status[y.dataset.status];; + } + case 3: //Created time + return (x, y) => { + if (x.dataset.createdtime === '') return true; + if (y.dataset.createdtime === '') return false; + let dateX = new Date(x.dataset.createdtime); + let dateY = new Date(y.dataset.createdtime); + return dateX > dateY; + } + case 4: //Elapsed time + return (x, y) => { + if (x.innerHTML === '') return true; + if (y.innerHTML === '') return false; + return Number(x.innerHTML.substring(0, x.innerHTML.length - 1)) > Number(y.innerHTML.substring(0, y.innerHTML.length - 1)); + } + case 5: //OS Type + return (x, y) => { + return x.innerHTML.toLowerCase() > y.innerHTML.toLowerCase() + } + default: + throw 'Could not acquire Compare function, invalid n'; + } +} + +// Event Listener Setup +window.addEventListener('message', event => { + const message = event.data; // The JSON data our extension sent + if (message.type === 'populate') { + content.insertAdjacentHTML('beforeend', message.logComponent); + + let item = content.querySelector(`#btn${message.id}`); + setSingleAccordion(item); + + let panel = item.nextElementSibling; + + const logButton = panel.querySelector('.openLog'); + setLogBtnListener(logButton, false); + const downloadlogButton = panel.querySelector('.downloadlog'); + setLogBtnListener(downloadlogButton, true); + + const digestClickables = panel.querySelectorAll('.copy'); + setDigestListener(digestClickables); + + } else if (message.type === 'endContinued') { + sortTable(currentN, currentDir, true); + loading(); + } else if (message.type === 'end') { + window.addEventListener("resize", manageWidth); + manageWidth(); + setTableSorter(); + loading(); + } + + if (message.canLoadMore) { + const loadBtn = document.querySelector('.loadMoreBtn'); + loadBtn.style.display = 'flex'; + } + +}); + +function setSingleAccordion(item) { + item.onclick = function (event) { + this.classList.toggle('active'); + this.querySelector('.arrow').classList.toggle('activeArrow'); + let panel = this.nextElementSibling; + if (panel.style.maxHeight) { + panel.style.display = 'none'; + panel.style.maxHeight = null; + let index = openAccordions.indexOf(panel); + if (index > -1) { + openAccordions.splice(index, 1); + } + } else { + openAccordions.push(panel); + setAccordionTableWidth(); + panel.style.display = 'table-row'; + let paddingTop = +panel.style.paddingTop.split('px')[0]; + let paddingBottom = +panel.style.paddingBottom.split('px')[0]; + panel.style.maxHeight = (panel.scrollHeight + paddingTop + paddingBottom) + 'px'; + } + }; +} + +function setTableSorter() { + let tableHeader = document.querySelector("#tableHead"); + let items = tableHeader.querySelectorAll(".colTitle"); + for (let i = 0; i < items.length; i++) { + items[i].onclick = () => { + sortTable(i); + }; + } +} + +function setLogBtnListener(item, download) { + item.onclick = (event) => { + vscode.postMessage({ + logRequest: { + 'id': event.target.dataset.id, + 'download': download + } + }); + }; +} + +function setLoadMoreListener() { + let item = document.querySelector("#loadBtn"); + item.onclick = function () { + const loadBtn = document.querySelector('.loadMoreBtn'); + loadBtn.style.display = 'none'; + loading(); + vscode.postMessage({ + loadMore: true + }); + }; +} + +function setDigestListener(digestClickables) { + for (digest of digestClickables) { + digest.onclick = function (event) { + vscode.postMessage({ + copyRequest: { + 'text': event.target.parentNode.dataset.digest, + } + }); + }; + } +} + +function manageWidth() { + // let headerCells = document.querySelectorAll("#headerTable th"); + // let topRow = document.querySelector("#core tr"); + // let topRowCells = topRow.querySelectorAll("td"); + // for (let i = 0; i < topRowCells.length; i++) { + // let width = parseInt(getComputedStyle(topRowCells[i]).width); + // headerCells[i].style.width = width + "px"; + // } + setAccordionTableWidth(); +} + +let openAccordions = []; + +function setAccordionTableWidth() { + let headerCells = document.querySelectorAll("#core thead tr th"); + let topWidths = []; + for (let cell of headerCells) { + topWidths.push(parseInt(getComputedStyle(cell).width)); + } + for (acc of openAccordions) { + let cells = acc.querySelectorAll(".innerTable th, .innerTable td"); // 4 items + const cols = acc.querySelectorAll(".innerTable th").length + 1; //Account for arrowHolder + const rows = cells.length / cols; + //cells[0].style.width = topWidths[0]; + for (let row = 0; row < rows; row++) { + for (let col = 1; col < cols - 1; col++) { + let cell = cells[row * cols + col]; + cell.style.width = topWidths[col - 1] + "px" + } + } + } +} + +function setInputListeners() { + const inputFields = document.querySelectorAll("input"); + const loadBtn = document.querySelector('.loadMoreBtn'); + for (let inputField of inputFields) { + inputField.addEventListener("keyup", function (event) { + if (event.key === "Enter") { + clearLogs(); + loading(); + loadBtn.style.display = 'none'; + vscode.postMessage({ + loadFiltered: { + filterString: getFilterString(inputFields) + } + }); + } + }); + } +} + +/*interface Filter + image?: string; + runId?: string; + runTask?: string; +*/ +function getFilterString(inputFields) { + let filter = {}; + if (inputFields[0].value.length > 0) { //Run Id + filter.runId = inputFields[0].value; + } else if (inputFields[1].value.length > 0) { //Task id + filter.task = inputFields[1].value; + } else if (inputFields[2].value.length > 0) { //Image + filter.image = inputFields[2].value; + } + return filter; +} + +function clearLogs() { + let items = document.querySelectorAll("#core tbody"); + for (let item of items) { + item.remove(); + } +} +var shouldLoad = false; + +function loading() { + const loader = document.querySelector('#loadingDiv'); + if (shouldLoad) { + loader.style.display = 'flex'; + } else { + loader.style.display = 'none'; + } + shouldLoad = !shouldLoad; +} diff --git a/commands/azureCommands/acr-logs-utils/resizable.js b/commands/azureCommands/acr-logs-utils/resizable.js new file mode 100644 index 0000000000..909f35c0e2 --- /dev/null +++ b/commands/azureCommands/acr-logs-utils/resizable.js @@ -0,0 +1,166 @@ +// **** ADAPTED FROM HTML DEMO AT **** +// DEMO = http://bz.var.ru/comp/web/resizable.html +// JS = http://bz.var.ru/comp/web/resizable-tables.js +// ******* ORIGINAL SCRIPT HEADER ******* +// Resizable Table Columns. +// version: 1.0 +// +// (c) 2006, bz +// +// 25.12.2006: first working prototype +// 26.12.2006: now works in IE as well but not in Opera (Opera is @#$%!) +// 27.12.2006: changed initialization, now just make class='resizable' in table and load script +//===================================================== +//Changelog +//-Removed cookies +//-limited functionality to manually selected table + +function preventEvent(e) { + let ev = e || window.event; + if (ev.preventDefault) ev.preventDefault(); + else ev.returnValue = false; + if (ev.stopPropagation) + ev.stopPropagation(); + return false; +} + +function getStyle(x, styleProp) { + let y; + if (x.currentStyle) + y = x.currentStyle[styleProp]; + else if (window.getComputedStyle) + y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp); + return y; +} + +function getWidth(x) { + return document.defaultView.getComputedStyle(x, null).getPropertyValue("width"); +} + +// main class prototype +function ColumnResize(table) { + this.id = table.id; + // private data + let self = this; + + let dragColumns = table.rows[0].cells; // first row columns, used for changing of width + if (!dragColumns) return; // return if no table exists or no one row exists + + let dragColumnNo; // current dragging column + let dragX; // last event X mouse coordinate + + let saveOnmouseup; // save document onmouseup event handler + let saveOnmousemove; // save document onmousemove event handler + let saveBodyCursor; // save body cursor property + + // do changes columns widths + // returns true if success and false otherwise + this.changeColumnWidth = function (no, w) { + if (no === 0) return false; // Ignores first item + if (no === dragColumns.length - 1) return false; // Ignores last item + + if (parseInt(dragColumns[no].style.width) <= -w) return false; + if (dragColumns[no + 1] && parseInt(dragColumns[no + 1].style.width) <= w) return false; + + dragColumns[no].style.width = parseInt(dragColumns[no].style.width) + w + 'px'; + if (dragColumns[no + 1]) + dragColumns[no + 1].style.width = parseInt(dragColumns[no + 1].style.width) - w + 'px'; + + return true; + } + + // do drag column width + this.columnDrag = function (e) { + var e = e || window.event; + var X = e.clientX || e.pageX; + if (!self.changeColumnWidth(dragColumnNo, X - dragX)) { + // stop drag! + self.stopColumnDrag(e); + } + + dragX = X; + // prevent other event handling + preventEvent(e); + return false; + } + + // stops column dragging + this.stopColumnDrag = function (e) { + var e = e || window.event; + if (!dragColumns) return; + + // restore handlers & cursor + document.onmouseup = saveOnmouseup; + document.onmousemove = saveOnmousemove; + document.body.style.cursor = saveBodyCursor; + preventEvent(e); + } + + // init data and start dragging + this.startColumnDrag = function (e) { + var e = e || window.event; + + // if not first button was clicked + //if (e.button != 0) return; + + // remember dragging object + dragColumnNo = (e.target || e.srcElement).parentNode.parentNode.cellIndex; + dragX = e.clientX || e.pageX; + + // set up current columns widths in their particular attributes + // do it in two steps to avoid jumps on page! + let colWidth = []; + for (let i = 0; i < dragColumns.length; i++) { + colWidth[i] = parseInt(getWidth(dragColumns[i])); + } + for (let i = 0; i < dragColumns.length; i++) { + dragColumns[i].width = ""; // for sure + dragColumns[i].style.width = colWidth[i] + "px"; + } + + saveOnmouseup = document.onmouseup; + document.onmouseup = self.stopColumnDrag; + + saveBodyCursor = document.body.style.cursor; + document.body.style.cursor = 'w-resize'; + + // fire! + saveOnmousemove = document.onmousemove; + document.onmousemove = self.columnDrag; + + preventEvent(e); + } + + // prepare table header to be draggable + // it runs during class creation + for (let i = 1; i < dragColumns.length - 1; i++) { + dragColumns[i].innerHTML = "
      " + + "
      " + + "
      " + + dragColumns[i].innerHTML + + "
      "; + dragColumns[i].firstChild.firstChild.onmousedown = this.startColumnDrag; + } +} + +// select all tables and make resizable those that have 'resizable' class +let resizableTables = []; + +function ResizableColumns() { + + var tables = document.getElementsByTagName('table'); + for (var i = 0; tables.item(i); i++) { + if (tables[i].className.match(/resizable/)) { + // generate id + if (!tables[i].id) tables[i].id = 'table' + (i + 1); + // make table resizable + resizableTables[resizableTables.length] = new ColumnResize(tables[i]); + } + } +} + +try { + window.addEventListener('load', ResizableColumns, false); +} catch (e) { + window.onload = ResizableColumns; +} diff --git a/commands/azureCommands/acr-logs-utils/style/fabric-components/css/vscmdl2-icons.css b/commands/azureCommands/acr-logs-utils/style/fabric-components/css/vscmdl2-icons.css new file mode 100644 index 0000000000..cd0e84c017 --- /dev/null +++ b/commands/azureCommands/acr-logs-utils/style/fabric-components/css/vscmdl2-icons.css @@ -0,0 +1,71 @@ +/* + Your use of the content in the files referenced here is subject to the terms of the license at https://aka.ms/fabric-assets-license +*/ + +@font-face { + font-family: 'VSC MDL2 Assets'; + src: url('../fonts/vscmdl2-icons-d3699964.woff') format('woff'); +} + +.ms-Icon { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-family: 'VSC MDL2 Assets'; + font-style: normal; + font-weight: normal; + speak: none; +} + +.ms-Icon--ChevronDown:before { + cursor: pointer; + content: "\E70D"; +} + +.ms-Icon--ChevronRight:before { + cursor: pointer; + content: "\E76C"; +} + +.ms-Icon--Clear:before { + content: "\E894"; +} + +.ms-Icon--OpenInNewWindow:before { + cursor: pointer; + content: "\E8A7"; +} + +.ms-Icon--Copy:before { + cursor: pointer; + content: "\E8C8"; +} + +.ms-Icon--StatusErrorFull:before { + color: var(--vscode-list-errorForeground); + content: "\EB90"; +} + +.ms-Icon--CompletedSolid:before { + color: var(--vscode-list-warningForeground); + content: "\EC61"; +} + +.ms-Icon--SkypeCircleClock:before { + color: #CCCCCC; + content: "\EF7E"; +} + +.ms-Icon--CaretSolidDown:before { + content: "\F08E"; +} + +.ms-Icon--MSNVideosSolid:before { + color: var(--vscode-activityBarBadge-foreground); + content: "\F2DA"; +} + +.ms-Icon--CriticalErrorSolid:before { + color: var(--vscode-list-invalidItemForeground); + content: "\F5C9"; +} diff --git a/commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff b/commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons-d3699964.woff new file mode 100644 index 0000000000000000000000000000000000000000..6c579dbdbb31cd67233f298992444bea615de776 GIT binary patch literal 2792 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 literal 0 HcmV?d00001 diff --git a/commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf b/commands/azureCommands/acr-logs-utils/style/fabric-components/fonts/vscmdl2-icons.ttf new file mode 100644 index 0000000000000000000000000000000000000000..03770761ccffad6d5f2b7f840c157945d6b57e31 GIT binary patch literal 5308 zcmds5eQX=$8Gqip^H-WCb=@STN#iqVNXds&l9qO5pp>T73N0%MRBu+ z(-08aUpwpGd!FZgK7P-~dpU&|5!I1NA|3kbaBpAdt@r*Kk`HkoiY67~G56s~BDR-E zSQnkl%Dy*z&k%`m?01eE6UpLt-#ZTe1au-3O2!~BH4{nauwOfom>z#4=f4kp5%=b} zs>I57|K)2$zD?M_Iu5}q<`uzne;eY->^=28JeKc4@GS{FsiVW6(F) zL*QQqFQ=5G`ox_6F!*b!W?j&xReW)-eZ#U$1BLqYuX-(sy%Mh-Nk|!&%okMAzFX`) zwgwWe)dIDDtH?Z4JO+N3w#)^zQf$-t>qGxc9zxvA!QI~Sn;LKky?A+!HCqfWY& z44kv488K5FV6!x^gv82vpvLF*xLuMcFzUWB@7q7}EMvzbFR%;u=Lgq6>l5#|y{B82 zcWHxpcBrR2&pJckcjv|S-9sa75qUac~GBd+H4Ue>i^5)D;^9*oT_}Pp37Z@!T3@j8N z?_Do2b3a_+(H3gvg0@gw2-qTn2s=Eo3(!K59$sBW0uF2qPEtLnQ#ijzT$4DZ9-^)O zkiR|T51kVKT6|u3zH|e*Cg)~ONH^Jb0rsBfth-2+NI zJ6cQ<6GRf-2R$XO*A6uwB;pT>w~vbDqHvq|r1<1$Q7rNo1+WAt;5q9#ODpjHVjdss zund}{gW|(*R!okH4;NDiAU-lG(M4=A1r_)Z;KuZrfbXT%phNz)Ev$pJ`FS=W4{kRU z%<{H_?3I(|e@qvdMRGyEY@W1)CDSaCWST`2Zzhw-W8!`01tLE+!5{bH53&A0e@A-= z-mM#s%}=OI;z77>KY6lf3hfuD1Wq!`X33ODdh?TZX1c*4&YO(pOu=O4oXPQW_lVI< z9q`{LwF;{n)(CZi)OPuXupn&Ny!tv}Lx7h81>uF3=ooXq^Wp=0_B`<7JLS1!$I5dR zj=9V1z}@Ai%TM3U4p<9VFD<2b%yCjA%iJ=-BK4{mPQ-`dgGBeRfD+qiMPpPQC`({N>dc~-{UVx+v7BAgovgnSI`EaKwpHAFS|K;ZDWH4d4wDz=pYS6LceW(g2Oo zQTi!!v)$}*_G9+0uns%iQ!QO>)!ycaY9NcT2v&ZrWmi~QoW_@rLo@)r z2P`q+9}dk(7H@KBf$GGRLyM5#>(CPD#~s>5YsH^Aw3}MQ*BshIY4Myxd#O_zbLbUx zi}avFuN03;1&6MqTfFgKJ|*{Ux@xPuPm8AYj6R-~cj{?FPb*nXPle?jiG)0)O~kVq zc}UHu=}9#f-kpPR-{9Upc}FIrW-~+TL@uGE7mFA32h?;1KIOjfwtl|HJ=hM0)d^jd zwT!ID*|ZWPz5v-2f@-N~A{Rq@s_6ApVp`s)T`8-{F<_Zb1Rj<{ zYpKVy)P$T?GugBj<)bhk0FmZ>U1L>XqlPG1HOc2Dts!bmpGqZkWnoZMPq9m-rg=I# zx{EcJHF8-wrt&m-h2v_%xVWMi!ql{*&KrVupgwVJOhbjkpTFDm6v@;_o9HUq3Thu| z6s0ujl)*htS;%&RO5=N#uL>j@s1${0MSyGOIMoLH4E$Q)QjJg z^$Ww_;!cIpIUc7M+S69QI1&X^9DFZu^S<$V_bwSdV_`PXDULA)^c4%<>xh+;Y*k_! zd=7qiwRk1$idD3^OvetNBNg$pSi8Z;EIsaD0Uj>l_26*~XlfQVZe^oX9bU1zVZcY) z%9+=~MmGj8^PS+Rqu5DRaoHBUmwa-$=0XYYqMZXr^4VSG7HRPxe0+ F=)V_d(H8&! literal 0 HcmV?d00001 diff --git a/commands/azureCommands/acr-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf b/commands/azureCommands/acr-logs-utils/style/fabric-components/microsoft-ui-fabric-assets-license.pdf new file mode 100644 index 0000000000000000000000000000000000000000..47153a3b8090f343b8f3064c1523769d8d4d2992 GIT binary patch literal 467888 zcmdSB1z1$y_CGvyHz-IAjWom1A>Cb~2uchkISiemq=bNUcY|~YNJuLU(vs33UB)}0 ze!qJ4-ut`n@4o-%{o`|eIs+H0@#`K*yaU0Rl%n}Z(*lc5^^hl9xtaf=tBitGduV&mzNMzAiNnwOpH?n0y8p&j|l_`3S70+wS>4ppz2VF z83YQkHMx9GM*-pjE#WkJ9z zF*#*@^CHNaxw7dO={Z+ATJH3$w!1m4N_;N-ceXQjUr))q>3uF`g-wJ z*%wd2*R+0q-69S`AdMI-E!M0&zi2Ycv3e%eC+1mqI#F}hZo+Nib8kjR?Q3J5hZ_rr zci;6skX@XP+uqq(b4S^iqHc2;FSnjj+tSHbs%mY(*Iz*#dfmpS{5{A;Pe-D^USP4R zsg>@is&Uu~K1Opn7$L$9lk7~0Vv7XHGrZK7daRa4RAZ6OQvJ{d zllOT{x1?_S+!7U3Cc06c=F?<$J^ai-$40+oR;#h3aJ-K=3#_iG7HM{zMPz*a)q=Kn z)0dS1f4OiquKvgBw%C#8EJ2#FK>{tUb~p#`R@${yH`d+21&NkI1s}9{EhlZnMB}0_ zZmyn_RfRlIgzgi3wMxhNLd;%cl`E2K?vtY{BPhxrG1=7t9-$;2bSdYUA0Yf{9kCMb z;T_fWsQ2E%K?U}wZb%SY`|G#(h2ouo0d-Oa6bc4Y1zk6-J19}T(A$|Yq}-gyTIJj* z16WWohjQ9(F?{lTQbznq^(X@Ki0)JO0#p5S>W?U%38`x*3u-wpl?w>GwW&Fm@Ul_` zyq?;S43osIjDvLjS~!ZW!79+<=TKi}Ek*JnfF36KjXEf{+eet;p(w&{{M6a|Yr> zww|!j?By6?h!&mb-NVJk^*4#f)!DLhb3+_8;g5dcHXu{mTwB8{YmxDnVJB^bwFx|%={P66*7DO;-Mia<-)7B+=BSn$4P`FLf6$DE z55`!d9ED&afDMDtGgJdPFZj(@ zsQWOEE`JD3_>|>-))ew$7qb($aajY)Y{EL*Q7#C;-u}GQZ z9(oc~OcE4!p`W}=<4n{S=?@`@57YSB?q6TQ=0C)~!&62el0Tkp>`*omYsP-Aj7?dz za%0*%0hJ2vh766Hd)Xp7yO6(7vYfyxo8YNCRj=`I@dGja-8@`xGmCQ8xlI%wuqo|x z4{m?{tETv<6>cKkg%!hOSkw@O%0t z){oPDO3UQctuAzMy+0s-e4oqK4OONtX`lwZc%enNr!NNCkX;$H=yX zRi~7t;=b5AJ%oYfSl#4@a%8RwQD5V0^H+oYTXN+*X};>NnJF~6AS#nq>4Uu|iSG?-3*H-wNYWJz!so4Fucl_Hx<^e)R9ylfIR^FY^0W4(9 zX+I~jT{h19&$!L$Aup`1>%6rGl7tu|H)CRBJ)u67wRQy{A>$lme->J{^?ucs`{k%iPdNK2ER$T{B6prA5I4~f!jCl@ebG)Em?myC zj(@Ikp8EvP4%xEUFum7Ludb7j>-jm(W}r8MUJG&9Em;@0f)pZ~ep?ISczlwR0WFQR zNDR|DvcAGYva-%ci=`Q99`xDbpyyB3_n4INROUCx-qtu5_7BqKKeZzi1*1>>`EmIb#{DUbHr<@UZNw za6XoY1Rxv4h5En$)X7=FmW6$k?&Xj765M6BS;9h3ED;MzkhV~+xtHj;#VB$R4sC ze4=QZYUcyM5W)5td(eJCzrS-KJ|FeR@eC#H_?7(ohcxFf0D>JVZM@Vfj{E&wtu5NJ z_leyzwKMagve&v%rQDZ5AJ~nlblgyh6c{_a9w%G%P)qXSzDh$UD=-_tOSwHMadR1Z z%}wb;M>+;qC1PGcJ*v&EkUiEh3a^pO&fV^y^sgBW*qqHl&v5ZeST}s9T)_;KCfTnG zpBiXMCe*klk8f(H-W*y#cqn_)`|z_)n&phVcw%X1Z1?EW8X_5_>X=zgUT{2%Rmgp5 zXnng_jW+y4Ighu&qSShi{8!lc!*tI&-N&fLpq4D}(W3KKOmxNfhWE|%Y!4j;dU2BW za2yYQeEq(%Lp<-?32hh&R5W7 zwKqv4Pb+t$ps`*{zZ-4%`~f!fFuCU`l~jT3E@-qCqQCYa#hYK>tdIrkT!xp%TBV_e zv^cs4QA`7DQUzui4p>>)+MaTJ?+5jd{Cpgm6oyN->b58f@o&`++B)nCo^Nr&~3#>a=A%?UI_N)jAd2aF)23Nh#e*9$HR=ZMt!x$q}D zkqy?r40QNuI@}1!SL~ggrM0S*ovm;etQzxNeo7aKqFIl;e9P-Yf2+M9hMUT8pXifj zWn@OvX65@DsO?_#v-lNZqeaMVvC}e>r{tf24ZMNo<=3uT>BPLHA=rzoi)QH@>}8-0 z9bUu~Aa89k?9g+tw(Y*FnwlfAf1{bIket7|lWwLMsX`f8#hvE;IGQuXuX?UzYwca~ z)JJucN!u*jPouIjsY>BObkQHF%%H&r`l~#!&T+TTB-w?6CO9jv-<_Zn-!78PQ&EKV zGM~8KABlQ(7t=oii372(Z>;l4&9KxLdoz2K?C8;J8L300?Y_O~gr6Sgh_#`G2j;au zuros&Tjw}4k!tWVOeC(OVKclD7(S@v@iFN}m?YZJiF}w4x?q3x>=@~8`T(*pW-Y$X zd<&h`+1T=lk8yfu*;)Ipxtgjy}=VLI3-|!AT?i``(rES3o4VSy~Bm&bb8kKmyf?oIe3gw8eZf2>Q zT_>)g;xumqKUGJ$1X;X0TW^wvjJ4t8Pu`GQkx@;whr`MBr-z}#O7)b5ip;g_^E*@L z7@G|4ePt#G5d@0I_~(992n&_V7$gk)q45%5I%H!`n`yn#L6HT9n`I=5#-9|g9U;;o zP7sSI-Ctd~0N^WsKz@2(vMA|uY621K*hzP?0Q$D}nuQP1=JbAyiBE=U7;;2E`m+^o z5SsoG$*QbgvsZ3QVOS%8Eg3Vhlg-|ePPsb{vd2{6N3BjjRL=aA@)H3m@~c-A@*>Hb z@cd^nS~KK`2v_l>BczZ|eAe?so=2rzWRUk|ft(^4UUKw-YqEry#>cz$YbYWZ+OI!8 zqwKBe1=-(9?&aBsv4^M=4keoy6tC$$F~gIl&9uIr)4I6Fb?tn<{f!T8pW>k?kPt27 zmDf9hTkfg`su-BB8SZ1)m(jGJ!=8cC6rNq~uT5bo{kLqw@5F;W2CY$b*ilo_W(n~Ru z^GTg~bG>!UeD&JOpFs!LVmj=|NGi-$s|035d@XV)p(B%uI&e1sgNi{%qq#vWq677d z6Som5nkKMetzS}?2l=7Jy`su6lZ%Gt)>~<=7LV=jWd!2JIy|sdU2Nk&Pd7~( z8B&$)I4&8 z)ZoiI6i+U4@dwjE+Fg=_kKeGe-px06L_wx}?J3i77Syg?q(;J3H3d8zc`KC)GtFWW zpt~JAmq(Y>keF+x^E8j=X(DpN`a|9Wr{<4tNy=TET%wWNelc`KpJeBsgDl7lG3bRT zSZO@D*M&wq=01!+D>1e^QDLQ}n^Aig%8E2&(-L}G`s{}4Q#qMHg@`Vv+74JrU8&*w z<}8^|uDZu2Y5|4byyD3p7_;}rI3|OF7f@4ASpbnAL~azzV{mCR&zxvclfm@Jvv{QU9fE(Di22R*a7C+(%A?|syp|V-N!+~gS zNFRQ4Q~s!!70f@9bTZb%BeZ|in9JjNKG<|p?YqVWqiE+??Q>V5IXl%v5D7p*y89gL zDpWgVtM6Dce~n~npC z%nen9De1i4)`6o0RFtlTy+)=e$Ko-YbuDuVOvs7u(@p5-8;D+U$o=S{Hus+vl&3Km zyphfW_HZo%BG;*S&_!ggzvfmI^@dU z79@S+wW=msmJ2oSW(sq_lUWMUCnh7(9)KronA&xs$jJE8nR}1hzOFm6zCCHdqj=&S zxTdp+lAV73QBe~Sa|$x{-syoVmnG>V`^WJQ9!p=>o6$f)McI20P*?D<`{vmE!E%{A zsFAU?UOFQbMix3CFQ1J$!ht(JS=wKC6azcrH*iT?Drc@J%vN>cns`CjjtY8Z3i|3P zd7uq`TPQpstpTE{=I@I4-26<>@X^XcHUh=qmXw60I2z*O&$7`mWZc#@^teRk1{igbX>goQz?YK&mzz|Gc`ZYGea}!-$-cMvjoH7TT6J5Jy#ri>958 zk*&IxiZ-W=EgS%|v^D3vZD}iE>uCAc=gaoe5JwZJr9I3J3cP~%IAtxNjxZ?;BPfsu z1jlCozP@c~3bTOYsRCCB-p|O}Wyjw{{d?dmu=7gJ-(`a%bmoqiPf~yb!)W2LZobvxB(#;P|ny{36U=w{ zm|t<;OYrj-UH^h%IVDv%RqdcQM%J7rMnLYLC+HF?{kNT@B!2C539SCo>0dbj=x+y* zl#-T_kzv(>nA<^g6xb#0tWE#NF2H>Sm) z{%b$r=HeIp*%7#Yas+;mz~7F*E6DY;Bf$OYa?-v#0{=fa!Z+*l3jSh!ex6I~bHT08 z#|4Cs4Tr8Thkygh5D?t=uhz)Vro95|e^dLPty7*~%q07_nf~V%`bL`kd6ja5xc{<{ z;8nw4ME#BT_%#4rDSEX|;THN?w*QuKcm#hk4ln;N#`(up`rSA@e>4uaAe_|s%{YQr z&ijkTzp*mEt|53N<@&`Ua(}Xjf+;)}S;E}d<-hx&sS(^E;7;KPMK{4m`Fm*sxxaBox8Y>j70>jG z_&-N?HK-{B3J;#lKOO_8>@*?fmhjEXjadTDu0bw2JSTg5YlzKd0RM%cxq8ZPOXY7d ze(}Gb)&IZO|9%eqKa8ksY5Nd9X(ljzIG4x60R{p2cmz1$1Rt1(mxG@h$j1xf5a5N! z9Z4-XL3aCc4mo9kU`~l2beV(+%+k&lPKN1dDqJ4S7BHB-qYx*jjiae8hmEBP)Xve) z48~z%XTy2Lt8$u{@NjYQ2ngQ1r22lQ(0<)PfYP`d-(Ha{j({q+WjKuUz^5{Reu@j|EVc{2=-sH2Rz&QDV>HVSeFm~Ct1{w zboy_5@Lx6mVGnqn!1sjekM{U^`};%40rBv2z>_W>0eGMk1oH53bMS(IJc0ro+?PcK z|8aygg*Zd3?d&1Y-@_z)C7T%;LoH3{I2~bb)(}Td_=}dEIn>DB!tM7!1pdWXKW~wL zP=W_6@IwPIuK)-9wc`Jf2JoE3*70}Yd4Cc9=Qo-^2oDzIyA1Q+E$}_){~way7+w%! z%KlHZ;QK|3f3wqm+Y5O31TMV^#LvqCPlUdy^#8dV{YxeIe^KJ+SH3@(f*Zb-!FM!} z05=C0Km4Vcj{{yH1QNW=8vau);MszUi_0HZfi=V&{t9aeFW0fQhG!{GM%MPw%OwMI zzR}LNs;px-2;w(=O7U*%dq3bKAK(t_LOYH#KIsf6n?0$irv@C58H!~h=J|6I7Gr)F#gpL+QBR|Q*O z9(Zo}z4L!i)Aeslv3R~^3jdv2Ebd=&{i~sWSo@z>V}bbj|DhVIT7TAVT7b|8@7dQ! z5&IdDGQru|FI&W;`I>fY9_=u?crhPK)L6>BtXa-k&OO_;Gh?1qWt(*9<#-V;#@vr) z?tLKPd^$dQ#+Ed?wGTb5DXr{jO06hse+fP6`ncne6PQ-%W@zeUc&&Z-eO0 z(om2=(@Qg-v@{fN->*3$rU+Ue$@6G<&OdGS-O_XLc6M^{=4)KQHk^6eKOlgONoub7 zLJ$2G!ImuslGseAwPCBNwW^DPe8$y(YS*bqabCgB}y{ z=9YQ_u89>&C&y-6lukt_&XhbwMJZanjg&V*<-HBsy%?ZJpyHPR6}KMPMCOt1c?trr4ycNHNGIrg4GP3ja z)H*c3?Z-4Y*dTk)ep*A9Jw6!w6C+_vtBv}y9k zWHK!1!R@yT?>P@j5vesO2%#+MR?a>VTOpa>kmkI0tzup_?xFPUz!$wVg5e%_1ZwJr zjjHf8)6(AfLa%Q@SUfX?*pyMC*w`FNH=|qB9#bXWq8mTKKlgtVEXhwc4Nem;mV2Jd zEY{fE^(pRIgN!4LjH9%G~8kX~3qKPC{t+6vKqt;fxp^jf{VfzHG~c;s}T=1Vq(dBRwC z0N*sEolu}Gx0}E!=T0rZu)97aw?Aq5U44z|V9fbvHl%waA}Vc72`A1r7YKroAZ%R( zCp*^9jH_GsnMTh`qZji0+n=i_?&K+?WrlZDO&GMx$nKLUm@IPT2h8AQm0}9Dd_a^Q zEAPwi{VaWi@8zT&hL>L(JqQ8p%3de<6vO@HRs+5(Ww?N7^jl%E5KavodpFUBmWKth zgkSEpUZ?qllS$ystVzMU*hpF8=X((Ol9($%F2kh6iHHyl-Rrd(#mHH15Rdjqm)?l* z8y@K`w8jXurxe$u0d@%2=8UKv@=!3!C-C#xZyyHX=NMnhveB;?j35!`_PhrQbK1hD zb+W{tM?ahTkS(G7@M%}*#zJ8S>uNluI6-{`@{0R)E+vzTY%D8V+KQDYEwKN%WpZDh? z&A7H7c}294Z-A4m%;p+v?mrFac9y(>Q@a?#7!_iMSZ+{n zZs~;EsO`7Ed0{=g6ZV{ABmEgOZTtP{;E;wX;C~0U9AK4I!JK5Fsrt zk&z_WVcIOnk1F$4*4sg;wa@*j0A6x%AK>jOD!Yaha8L<{dpI0ADWoEK|4DeqE+ai2 zer7XDtmnNW7zY&7eEQ=gvgF9IaDL5QTUz>R?tG&!fbgQ5J!?0=aHS3u7;w#^9#Uhb z1%AR}@vPrn7`vf(cT#8g99<*+lvlW_2*tKdnXl3Osm%1Q1#6SFn#UNqY(2u=cZ4xj z98*mwNgeYabhI>Y*hCX48?#)TIV;EM&Ta-Vc~?Y8s0j(b9vT{xB2cfy^{{WbJJn_M zZeK7vjB=%aepo2ocJFXR)GepDyazGU`+3sPq+Taa}9FVi}@);hp|S7!560F3L@rKn6BS(UW%crTvFxUaoH z#LFJI&+-Lcj)05bAc6e0GaxZ!%V#w29uX#;vX7q0^F&RC(3l5%o-RJ`ISj1v{Apg> zdna0My_h$CjBX%>Qr2To|J0Bw>N@OnLGWzg`K?JJ1s#8dPSO*ed$HsXxPmvbc85WN zx5OxNFHJj#|Uf(8t;S87s|G~VZnnO5Ab29 zdD~4yw_i(fDJC_)i-D1jNbMX@;U*)RGoP=A^4>K&^ZjU1Vs>WAV!ak&b%?qAi~PV=-s?8l|ThFou01Ry+iIB7`AH$dx4{mz*^{f z;*gO(bnu~Q^;G>@!}d_JQLw>;XFM`e|#9_nVTBESB$E{P*+cBR`{>+6c99y4MTRVJrE+|DAk zrZnPyDJe_QC|*P=`LubfM$(g4Ys_nSGS%SLlEpZJsW``4YNTGjMhyZtDwJkzVTvcT zM>#n?MEA4VR_q6iGDe=Le7ukC_1fn?E$wls>8lL!a#Ot1+Y;4?uej~JedPLc&x@chM42wYwL zqQlSd%m0d@xO&PDegCq{pHMeALG#-j{A=*-|HmBW)tl*WYX8k){^pJfKe^*K;{I>` z@?SaR6*tEDlS2yr?vQ_BAo}n(`pcKzpV_jjJ3l1-*&+WR(SOe+!B?c%6{`8SFTm>u zE~SGLG$6s>T=J^N4>|u=E_wBoA0z!cm;A#l{Lv}dxj{clEdJ+i3Fi456y_EX_*OLW zPeA(b?CmeeJRJJ`h6Hni;Y7pl?CsUM|9M?sVIIGz{v8+j%QLUgk#B?skXKNE^GB73 z02uyzGiy7jmc5Y)1WrMG{}~ylq$SKz9RiiIv$40cy(%z)mpWYiQcPM(`}^<26yU%Y z)ZXs$mt$AIGW_wwF>XFC&=sNcqfqDT#MgNMzM`yxEC2xk0q_9+5AbyXFfQY2X$AnO zr~p_10024w2|*Zu2)`nRAD}=>05bfH06%1tQ?Aa;@atUo0Z_Jd2Pzvu9U-Kcg~3;;w${e68o5{QeNUx-Uk2+lTh^9X^tgm^%I zya#_Kz!KmN0K(5k04Tr_00G#-KQRL&0537);G1C@KIF;ot#- zE(bxtz`(%9!Y0MRAqCM;(18BqzppI-0(1llL|r5VS^y#e0ulkj*Vh1Q_?)7^t#E0G z?|%r0NXRItXy_Q2Snvkb_y9x%BqT&+Boq{6xCs!v;NJs~2~Y@WxFt}DG>p(_9f(1X zA~VqGB+FY#G>1OY^B6<@FfgxOCndYVz{tdWlLgGn$1fl#1TU|Vm6KOc)Y8_`y$#16 zO-vzX<`$M#jxZ-@7gsm;$4~qN0)v7>qN1P2#Ky%ZBxYu1=j7()7Zg@hR#n&3*3~z( zwZHD@?CS369Ud7S8-F)3Ikm93w7l|Rb!~lPZ~x%%==kLH?EF$M1i1hF`1_{VKj=jO z*9#FD83`HfQZEEV7x;lhfQ&-JjY=q?fo9}DL<@R^PAnOjQQnF{$D{d?#27k+d5s>t zz_53z+LdPiGsXP=UupK2Vt?y31;9o^fEy2q0B{R%a?YITi}`=)L0B##v8ZH&K@-s% z`(b~jW_nr@v`@Vzy7{olejI4{>CR}XCy}}N4M{wknJAX^4!-G0BF3?(jeA~p9kGL- zbM0{TtALQKkWE;+7q_3!jhP{*BvB6)rD=XfK88DbMv;L!MiU`J=kHS1p<*eEc;r$R z(NM{uoQ|L$aTb48J9u4r@q#Iu+9P(QCHjE8t1hW>JB1We=IT<7jeX>yT55 z>{4su_RKq|Xh-7-7I7e%`YRZ&u=WMN^GjipH^q(pJCVVd@?-+@w}}?ek7jSHj_`WS znmtH@+Qjlsv&@IqcS6-fV5Q@d(#mq`xHlC=DynzDJEi6pweDdnNj(Jf7*DY`q#Ac(Ekm%X9hvQ67*!Jp*9y%yj9;pj%g{fKyZ^DN^rz74A|eqLrxo(s6g4mrBpJ~G2h%ojWHvC?SCkryBNg)BRA%X9h% z&oC%&VJ_Qn+BIs-W8@~D>nr6W7YOV7e_j`tKm*QGTd5&TE9IRkPV1y;HAIc-b6?OQ zPsAHT>&ONu2iz%HWo9>09;xo@PIbA-xE&{nl`rbk71jp5suEov6D2J^k8(LIZyED!<*xVB`W>!17osGElCt>>Zl!CNgERptD7FUGL#ljmat+ zKPF7FQz?cu@V-5oOQM*HiLb1;-gzRn-MIJG;xH_1)9h6ZMab z1jymc)abfFVvSoW-@`Obnl(}VvZe;t zY4Grd;_4^LVwF-?tEuB<=DI#PjyeD5^J&ACUudiv0;03@)$)}0-n`Sun2v}iI&nK% z*DRC^fJ{bKQvwczaRc$c}-J)|kuL?0X+lM8owj;Qwo`lex?6^N_Y z@;&q}Mc2hZiMJ3ZtjeVW&M&OFZ{A{l7KI_!;%DU*y|!Bt*x!&+O|sgMocKQQu(BAB zu;lQC(GI(Tx@=!sJcl5%`FQT0?#lvpwuby3*sC9N^J@;T3n^(e zX4cubFX??OeZ;n1q~ylX@!G;Xk)~!+4aL{+{oT>Q!;6l(@I6lzz8tG=7rH_cV|Nl3 z)LVDsRC`Zuq|6q>Mm1z4jAV$_5&j#;J@X*&TPf8&3M$57AU_-3zU{X5Lbhs$SBJB< zQy!T>h~nC^>0V0|d#pQ03GXDS-rcch=Ccu`S>hjOXM|)67&F&+MfE+DwPGUdX|%l% zN?#J|I2L&nzTV$-9uWQ5U+G6x$zKC9wS^;LF?7dqqlMk)B0nlUS)C) z57}HmN>I;Aa>UmW3#2_%@ny1^MN_-EJB!N)f?om19rR*2!%K<$;WECca+<}sYU*wz zO-$VUgCqIHGi9u?F)AIEau_~2FtnbssQn%7N*k)sN(`^u`oQZYm1J&AHUz)(4s zY2V%roky$rA|Y$}yLT0fqP%Mvxy##5Svpt0;BRY=%bSMWv9*;9Rxc0^1{_S!B`sj6 z4ETQqERZ!+G}SbO%r3>`Jc>b8XvG;k+FmzUAr`7_PTIh{CX%H*a%jLfYB>k|m_0n# zqUe>0C7WPQnFKwd8?P;G6unk5uT*5iqwu9K(d0uH5+J)GmEYYIbH}QBcdEIIbHcN% zYWws1MBJH+r9|7RB(?k)a7?)7bmw&<%RZ|q#_WcCl`tZ4NwiL^>lZIQ#^nO&V%7W` z3a!l(QkQLHnAlV8(F`Bj&YzB-PnMvhCYYLPmcS}2W6%n)!toX#I!X-955gXL38Au; zb8^hy3GQLquu6VTmwQsr!rtUTEOHa)wKwt$e~rxgYe_l=iZRv{rc}xA$kW+#!k+5i zceUEzywBDq7dunYyPV7)@2y_FQKijN)0`B$P#d}Wg=D0R{?yXV^mv!#O{%NjEaq~Q zgRckGm|fd>wAzBsuwmOVR*^6shnmne9k%PX#A!{bjkh1l;Fj_3)VdJ**O(_+8d5im zc@xX!S-Wzyf@CXcr0Vs7_-Xig^F>7o!gvsZD3X*vle zHK3r@m?PL^7_NT-jH`??ZJ#VEEC+Dc71)Mwk3Q1>LJ}h1NRlT7PGq92L%uCYQ?XG+=9y?% zI|Iz`#QHpJa&rS0dbYQhn*4L(C*vnP+kLs~BD97ZL}llkPxG?-UnmZiJP#R0?@@=k zGNtoQX~PU5KaLx}g-+(J)Is1LpsYu+7J zr93q+J38wG8kH68@EnhjWw5Z09oh7(t9$2uyYl|~?IN%Zp|B6Sj_uhF>(E+3n37EC z{E~}q?A@kUuq_BCK87h?dxtYx`w2A)mhS>B{$87Dho>m zvzu;4va%Fueu-{61W^A=tkn(QzsZ?fWiV&WQhY{XR2lArSt*V}f<0VJKt?Sr0a#EF zNrqA1P5hp^=jSFE1$8g`llBuw_jF0T1t`gA){U{-WHv-DcUJ@65Sy0D%IIU0N#Q;* zRY%It%uF<`_5Tp#*BNpr{urqV-kh7#R0bxf3-A+utoVjNiO3gKK{Ov>REOL^73<}- zM$EnGeD(&J=@=y8z-mD!EawKlS-rSKIuO193wOT{f%tXi%p$`%86%4mo@Lqi{-Nj| zfOoDh+mJl2r3D}}gc*=o#IUS+U&-uc<3yxE-4af+aX8J9%<`cS7h}eRPg$OhU5oDx9U-NyrlPI1vL%aHt$}bmgzasW6bpw6z~+bgf> zj@a^mx}f9+fH@ynAMoK8hzu#+xSlF~KB7PUMoz}XO$Gjvk(l7;ri2Jg>w|+R8^63< z6*^iG$iV+khe;|+;${LU2^3ZDm<)^n{n@d~A$}&M?-k3NJ6}laA_Y9?x5q1^J18k+ zMhb~)lZUB@O<10BVu5!)ioE?`13=XQ?ue#Dke=k?fHoF;vg@i-c8C-M``GgA6I@_l z0oD$?22I2WeO?LSbx~dkrOF$GK0=dSQ6d!>3V6K|Yqz5mM8?QfxY@0@#!r+Il}xPL zd&938GtRg{kbp5Q*zO*;?PLJZ8h4u!L+SG;YHTmo%k!Q45SYz(2VWpNzDsj2u*@(M z=1uH3D9MZFdhi~cwX<_%6dk(HTAk@D*jMgA{iJx89+#7yCd5Uz+qgH&RczVRQG0x- zMsLRI+Q_|WRr*AW$QvpZHJGs)QR4UVxA1bDw5B_pY(J_#T5_m3T3nmexf25D*SQl2@MnET$*n(S6Z~cRRYNN3j7SDjodESqgqqbn z5ej$RZDRDL>Lfajf&yW^86~zCvNh05ZYyv#2m}eZ8)`S2Sw!xFVFJ4A{!1z?JIp1nRXS*U$D$l*r**3`ZA^U(D%3=u>%N_Y4U1~A&Rgt2Yy+LWY}K5%HDr%x&6d$Cjs z!uy2!VtIrY;UPnL$O8NqxRMi0Z`f&h2sZuikG^x10XV!nH{U(lqYO=TdTlk>9Z6JY z@&UnjeifcrH>f1tv5Fh&z>={ZlI#Vv#kHf-z4w1>t07LIJmvLf2&^&H-%uW9C$FVcbz42Harpae|GYq{Qv{ra5l$C_p1a%+JJn?1iMOiE1?(xuu zcJm7Xq)L8!DQsIlYPO{R*ema}7J%z8rlK365VCB!`8jzJxjd4Ricy^Y%-s)75DjgQ zWMW91Lge`NK~6yvA<9EyY3C}pshff`_QjaBv%*1wT^b?dW81uBp0}Kc6l)=oi1b__ z`PZQ3D7}NO^bVM|IvT|>l)!mh*M-|MXa2yZ4Ft|mIN-}@ZAFpza*TJk%rVcfu`-2Y z{KFh={}4{Dh)fllI{KLs|12poLBq>LW+XiA+K?qFKVW4$>4YY??eIK8TLUM(GhIXp zr0HdE&{S*;tL(UuO@<#I#J^QI0Bwe88JHU1=~~(r0$4H}(Fk!}O(k27VyLoA)DcYFMK{{v=R(XO)>>M(lyL|Rrj&Jw>-|Z1+xsf23vex%&JNY0SHH(tT~yF zC?bww&O?@&8=aQcZ?MgR>CEj!3Kvr3=;yh5`tkDaKC!&b(?K|=uLM*PQ-8GYT(hW7 zeeAb1bK6`7;Fu^AT9X*_@(fhi_u$6DG!tz4`KQ-k0oU1gb*P4&o9qtt9~XMn8*tR5 z_)$(?%<^sZZf`~pH=M&`T`KFaSa(evWO#p3bCGYl`f&{KP;dqnO>u7c@HAsE^HE7# z)w2jeJ?I;4q?e*<=Q!Jk?!y7x=DI-acrQy|LS zS4>qR&mCJfW5*z(n|i}$`mFc?HuD4f$HI>^oAw_jlDOxddY0RhSum_=d3YQP zd3C@rOXk9T7MQ*vizhMlQjrk$=a88!Z`o#>>WA6^E!w5@WyKx=+|MLQC#9;~UxKAs1@^j*MX z7y^4=hd#?|7P?Un4>;YtSd@ar8A^4#notROqz-$^gZlUa zq!lKSP~)epSY_K`@05F>wHyPqlP}%!9sNP8wzJvT4@^iqWgotPM}e;Z+zF|N<}TL9 zvP__cp7@~Knq8y5q!spr8_kzHRChHO=~xdFg7HTUDJ&s{Sdrl#!iTrgo)0Ebxi=- z6!-8#g}KkeI4WzvjK0oH%Iaeml@4ENC(}wdy?N;2x`972LU2i&X+yc&_Htdhtep_^ z;H$S0&toWFP@I2e<&heu61Cy5Wt-HnnP?m4xXb;4vQX{B?}5pSMff85mRVh9SO4ZH z#*x!%3jgrg=I$K0{1L>2?h zMDV?`k4+3+i!6&=CXH$Br__Envkz6vF&yUD<@A634hKEV+%=K_9mq-$rLYQwb?DU% zgD(m`Z9bLHjs`&D@+ohzjm6t6*2?OcXlr(KYlM~yGVOs826D^u$r5$>awnaT%uLXy zyJxE?R>{aHOwyTXD^14pMqqAYZ-y${%n}A$_6bEgJLQ2uf%)Xm0;K_6QZ?1}i5t=i z98`-6u_|?}b*Vx}fF)k~2C6vFc2w`Rdjw&P_zFy7jZqe419NZl8@*{EO&WxV3&K-#_Xy6cia9dOv4*?le?;jk`6sa5qR&yH_z#*nUF!@uPOrgKYz(b!SZl`Uo3x za1b3-Zcbz3d;~JQWmc!KTpgApo}m}svh3v`PImone-?#%@!scnIkSz(&hF{>7gDjp zb)8FtJ{)(%%8TwMEpD4`8k?BYGNH;mz#ZhL%yt|QTzaHGkT)eCoi&}swsk{Y?mZnT z(>`?!q266ZMndsWCQOFzi_`VA9Dw?`(;Zj=ttXunJrv=Y;|GKovH>YSRv`oEzJb{0 zP5g%*vJb;@;s^s-eWehyRvh1n3YLa==y42PTO^LrcpXA&jx9u;pX+0G$ENOnrCiPO z2cw9iz&T$*rMy?sisRVtZnEUeCLN`1~ zoHC2rpY5VA$&ZnP*~KAB9C&uQyjU6$-PnBiE?WYieIb!(F{fSYb{$<#8ck4;x}#Hg ze9F?9yLqK1l$yVvl#)6v-knNqRk}eZbcl3r9F!}?!Trg+$jbsrXi29zuzk?55d&ws z=LSd_U5BA9I=o4c`feFVd4bH$s#&Xdh{wV{{3a+WeNyD4=v^sL{ND(_QR9O`;C1z1lU%bBy=t|QYrPY7wJKjky7>hy2{lZQ4 zx`S3)oNyhfZHiU(?8?|yEVNQQwq08tpNc&gNfYc8xkZ{STOKz!;=BCU?YJNbjgJ}XJ z>$=Iq(do_U$G!r#Ju)(UES?0p#i7$8Qy;0&<};Ia332nu^p%Fmyg+QLuQsk#sqZLk2#@6ow3s@h`qIsT$a7}gf&)J3h_0CJ$m8r zX~52Hj69YZ>l%fp43+%JZ&Ojpm#J?quSRe8cMri<{4=ga7V@Ay0D-&|b}aYTZ+Nx= zkIU0;Iy0j3mE}>^rDHZU=ZU5?C4|sc-s{G<4Sf~2AA5`CnciNgsrzy9?y0=pwsuk< zM0}mLJ$@29>L$4>lW)u6<}WDTru2qUoU-@d`f(yW_@9~f>* zwOQOC;+u-z=k*CLH@V}k((C~{s(oY4wEGf@-K%Bzq2>utMIXLB|2UFiN{?IDM;^hZ zpu5YWwTq+j0@l}x%j%UKYcIX+o@=I&LWyiTVFu78k;);eoYZ0j6TIfal+ zrReUidQa*LoZxJy?}Dnvklglp4zZ`D9E?3gY2r?GR_Tjy(}fveDGI!l3Bd`bUssL- ztfFNSM)pB^rYXO;UGUa2Lf4DM>X$-s<6m#y(#)$JeqZ%ZpRUhcl(E-GN#w(Jk{=4~ zX>$dH-2yd7pU=zW^&43e+KAH^l7D`G)DC;KC)@)PL!R7Vvn;_&w={t(X|C zG`YAihCFpdRz{ln1=VTCl$vAtOJt1;hTFvsNracK!NQfJ^Bf?c{xq=j>ph$do9L-i zZ27W`Wxmu|wRHXGFF-Qx4gSaYAMLBB+U1>^e2s@17nU4|^f?l57C)6{q1zyRyg*MGd9(qKA7<|GL7Kt!)sg#ouVThE45*EOw|H2J#qP!QUz;gToz>`_<_e4#r>t+HO9(#m;|q z_0;hKg6(sS&jm90Rw{N^q%=;yHHj8?k~&UNQVHOJVXpBH`MK6l3%RSCq51p-RY)8k zn!{x*P&I6~mau|`O=l7r3vbdqDS(Oc)jo$YTjYXatI8ExoxEE#3cYvqsJ3VQbl=Y7 z)|X-_vqESvF%?7s{}X?gtL`$6eWu&XC%{~|fs3Z_X4#x>b<+4AX2|?zZg9H?o8o=98q~B6ImRhP&I==3?k-t+Pt?gJ81Ulf!QrBwX^)< zIqi9URCk1j{8#8_6=f5R&JKeZ+?VBeWtR98v#kc-LcdQ7Sc9B-A-)}TBdh;pZ2(0# zbnk960WyR0Qp?6A-cFs(Q~i9sBjPxADiPMhOb+Cs6)#`Yo^x!jZH}blw%(g}7H1Mw zyTe+UYni-s^@zOL1#cFrzHe05Q(sLWkDq>9dD>s@oGvh$sGN7p8jt0q&G=nMtJ4}K zwzRRbiuP8Sc}{!ur8HJkS@5)9%roK%2AvIuA}Y`erT8RVUKSOlsJ7LRNg2GrD5fLW z)qw^5cacsTdvbd2KpOVDzFf7Ons`Ioc*T5%^Fp9LT~5!=RESJzpL){ggb!U844Up!B43TilCZnF)%V-OpuE&Vh#K*9{WmXCxE9G3BP7%%q z@)f5Cty#Z(0+>fuF0ENb8KSq@K5|T7mZMiTxYK>JG7w_7z(v=YMDa5ukU!Xvit)eD9V^5~tqf{zH_h4GfTB{$gE zCx`64|DIJgh|@wbQ9{ll%9v6(`dySPXGugVSMt`R)ULi;P_=%lV9I7&a-qIT6xh(>Hw!Ix z6DbTQnKko>oTb8f69goKtGexG^fnnP|CTPPaXD5qpL|ph@Oe$RUobwt;5tZd()fi8 z8GDzBp}CsxaY(S^gOFl~DMQ{0neZss3>WwKSer3kRtLm~+Yqgxt0X&fhWrh7W}+lI zMp%FHbv_Z#zE*1>6T<;NH5f+`VO`v!;&yt032Qg;iVoIJlAv|*hkSu`9_X2Zy7t~M zwMedpRF&FbaRc%i=8=74Vf2fti} zaxsrfI@C{UL5j(47vfkY=2s0%ciJn%TZ3TJ4Jg$ys4-RTlcJHCE43-by=9s)VP*>9 zHjm|tNY_;ojJfZ%31CZh;v-=Ia@hBAw;L#dbB5fiS1GdpJ-{Rh3`Q zJfFL10(Z82Q4xy|94!NrbnOKFCbRh`Tmt50*X@^yYBS@o$D7A@f*-H$>H3M1=92o2KS*P^QZ|$Ho>c9e^s#-~ zeAY@#H@v?z_=Uu12yAgP*=%l($*E1e` z0ViRDtZDLnzq}SR(JMh@NdJhmQBazWCz#LL)9;|EcA%Kl+m;L=8%*C7<5Xp01~IyM1}t)t7zS=V@@(QTF-NiJ;~$b0UI-K?H8doj zt>xEwo^dIKc>$cTP=$&qYrI-#hvaZ48#EZg7eJA9!bI!k3rh zbK$;VsH>;AS`5_|^i!pgF={pfxb;9$St*^O7nI!n;(4`S5AclJbhNKw7H4a|b$?fD zN8gJs5b`PKKXLM^8T8&A>3j(3hdq8pTpZchWu+U&M{P;RZyMBD zO_TF~=(4wUuW!WBlS0(pa1=B1hFYG7tl7b{;C6a4C7*+0EU8;kJxK*WEdiWwq(!t} zEAhiGpD<+8<86*<0_Xbjh`{VbR7HVZm;F@8j@3Oy-r~q4X$&InW|&MhNk`sf6IcFj z%*}X@VaB7I#KLD!x}r1<kXRqel z=}6pA$3z)Z0M4@8$GNLoNDs=KW(NuAP-x~*L~Rnu;&}xY?JnbdojeNY`$75Hrku@+ zY*$BXjfS(aaq-e@ou)U&vawrZ3HMGWU$u^HW2kK*L(W`txOM)rMHV*cregP4)vDxG z!>i9PHnL&-Mawc!i?Ow;(o1?N>jVo`ubotyk7wOKyQ|&ns8o!&sEN!67R*A)T&2FX z+QX?8D8kVMp2mNxnHL&c7($+O5=z65H3e%-jV}k6Fe>GIsSOq{$0GJMF%$a4Nd8ro zlcnv0nOep&%bp!*Pp8P43iCm&o>@stk`Wl zdk+VEnQ;0|+{m;-qnkeD0|hcSIW6ti`E2;u^k(i%wDgc|9F-~j<@FB?^-b=44)cr5 zQ3WP^4SIH&C>sT%&tny*N$-p*BJhq#nZG7b>zay_V;C%|#n1ObRl4j76FtKk5iinR zL3|d_9eeX4m{>KSqi?to3CBy7K!QBRz6TD~ctys!U^#T>dmsnCWP`8a$HsSRc0e!+ z*PXD#1}4<}c*y9a*Tn66IJ5CBQ{NvDJ*zmZUoX{cx@U1(hGT7bp{6d!{2prlh+4nR zgs33SmLcq#Z7;4Oj00IR;Gm$)jY#%rZVTMx&VgviNET%Ud>g}APyXG6gt^Ccy@;~4 zpx>@m;S;`bL&Pikgh~5QYb^?c0xsC5xQ&I7?q4S*4`nkAvruf2xQWrZ?eTc#Kmh`E0X83-u+M|fZTQf>c1f3+?H+txyOk(U5Bn24ln%%y^gTiE3=12E1?&%%p5INkeyY~ zR!E&cbO_p^qg#Zq>8Or>Izl0?B*oGo6fleI=}`=Yp@`%vCo9X?HcY8{mJfA%uDT{e zWv3OoW6Li8RR7p0@wbz>d5n=JgsftIjz84c>gr>MKET20I;{f^Q&qw-q2g}Q zQg`uj4S~uuacNVP75yl0p0$%_r0~b?nGc%Hgp=|3+UcJ?ry|z9vYo`T3jvf`nxN(u z)COzzEBI9M2@xlKkQFFO-r;r2`D741BcbtfOZNEFH@%_+gK82B2jci*QGh~Hv1nVXy>hsyU1Hku0B+Ni4=D7{s_ zS4OsVkaSIIorF(R8k#+64>6(A(4q~j z=~>^t6dR*?M@I)$9V0=;tsf)DQ;Pq`-afEJS1KyA6*tZ=dTHHQw@N1DQ)G8!Xz18r zUBusESl{6ac1H-MffnZ5&0a(-lZV3_;TG-+Ar&g8#^)@s1>id@ZD0l|h+L2VHL!#bX6jkAsd{m@ZkWlkch_Dyc@-Q1rm{pOzIzrF-4(8e^${%7a9E93cDXx6E zwPQH_i*)(sH%Fb{9d`a3w^*2y;Rxv$X1A)C|J67r!5RK2j)ND{F zv?Q2b9{5P(oJQo816^~B9K%@>`uh*nR8{k)=g(=;MlM-jhc(~ISX-U+6iI(K5NuP* zoIElPRL57si;L+~tD54rA_L}h%pLd2BkQ)g z<@U|`aN36n@!{LokT;tdx3qn+4A`?DRs+Wl1*@&TOn*lHqlAUckf=GhI5!|$#$BB+ zr=)!Rl~l>*Q7H#iZox<2kVDc!;--n40!>r>#z~GHn(hmhD)O8>ylkz_wqE#T8`3cU zWOKL35s3X*+9Z0Ze{MeLUN>sMCm{s=84E-*jJ>_*?tI`TO- zwak_=Y{pr=Xz$28FNixhh@TsmL(6?`#zS4&EW5y7UnX!*RYYqQ)Rov`y6K!6b~gju z{^Uq`Qk!dSOD0;VHc)UTW?T2N9kY(@GlIgwC*Aj#J%87O6+4Jlk7a70HYkTS&9`e zI%FNv-aX)`BpkXC)$h(k@Xk_|@41D~)L{GP43I|04yD_%DtI>U;&iBfM_neLVmp|s zU$CTbN7c{DN|Vg>RGc*^=@XLDvey#&^tx%xfsZ?T$|^Vh_yvE-wmr0)r$BLS*cQiV zF89Duk0bS&s0?4ecbm7HT&{gO!>r->L6)?3hIzd=BkKdsR7S3TcYb50dj_5ag_W8( zDS+h7FpY9mr7B16M|;0QD#Oe1#)sZaAC zGmn&=>H5^)_LPk@+PU^ajN`PDoFr6(3#nlSlg-iyq%S_8M_Sk`B8;;Fg)iDl$V6-0 zcb)*y^(6x;+ZIvZeb2zUrQ{#^oz>d$uq^rOvz4l`_ z5KyXO;GFdymG6YEFXX-y1}$5$jBX19Pyj3%N?BRc4Z5e4iggfeTm#}CuG=6Uf-Fta zxq=m6v^2!pB-&VCPLh6Cc@`Pg(ruB@P?qA1H6?{rD_+OR-B-4ds;EcYDI^vA2JCP! z65_aCc*h&v&^%LH(JvHf(Ny()PFIr3=*%`&4d>SA`lPnBaJfry>rq)BBP>qd1+JOR z^gOrwU12?wYl>2jNUUrsLs9e6u~iU7@wJtVfjBr}VqMdHDnR7W4y%rk`LQNt4H<;M zWaNHr^%4jc!Chq01MDf!dPmT}A@0yLNL`7j?cPyI9$X$ZHKU<*%-nS)*UZ$y$9?rD z@*8M^7Wxztq&Cj4Uf4^p$dQ<2wQOjK{T5mPjc%;M*LQNlH#S)y1mvV_k0@f+Lpvzy zY2n(%b@c?W^zZsQ*UyIVIO+}1VvSyWE_+eO@0qu1JaSFE(Z72;t0qc0y<4q-D`^4w z#3muR3S};qHEGp{740th$}%RMGqX-Jv(VgSv#&PVNgYYuR6<;?WPu8onn&>Wrx1P6 zta`PZ?XKAL<4$&rYJ;~=ONC^+h`f``g3c3w%y;(rJta1seZKn0r~#<~VSlv*pYNUv zmKa?I(nsk1&RLfC+(u`??YCRaT)PvoFk@cIX>^S%x^E>6;tcv?B`r;?ukDjgnvWvM zV>Nd*JTBW%M|1=-FA29ABI107cYD{QHN+U`2px@!Wcrz!e-RZ6hW?24((=R(2W$x7 zNfc6NK4*8ur_teDiw?qtL#Ha z3cd^tUA|=>E_x2}ibnOsEto)E1?}O$FV+R^TMI41YrcE=W3%xk5eEf6RTW4@yfJP_ z=ff}M9)~Ai2YfXWV*lvv@&pKyf;Aie*IG6}yrNCd7HMhq>W{GLh7dJuk({HxUH_1t zYQhbpB`oZyu$+#!t=3I&Eb?vd+lPXpI5+Z%Tf?Oo#%N&c;S16$Z;YA+1Wy23DI zU2+eo%DvWxLP`QGv1PjI=I!K~O`QtnFt6^c-<+cStC@U?qKXr2V)F`iMwBNmCHV0!*?!+$`og#LNW zKdh+#o+i(q=Kc_Hq!6^%-_R2O6M%=mC$WV9tp17{@gGP=`z!v$e^WMEmcPmQ!2V0szmxo*BOtPH zG5-o=`a7xsBpj>2&h) z!PXU?xU}5b&s{`mfSG_ShZcJYukDWa%TtytqxlT34{m39mOpmGQ=$j^MQiTa>+3Wd z`g`visDIQJ4D{dZ6?Ny4Xbak3oU-t^x=TIcqbVk@XBegCko#xXrcPOLUvFU11o_8? zuIbtJAn@0Heh!bqoBI)+A~+!iUy>1fY;CEYau(GiOzV!Xs>Pj)P%A>>ELiKFo$wxz zaqGzKqCx5KTF8T&BG$)?3b>#W`ZdG^I4!22fLmmkpEY~%MEj1CL?Qlp{_#qTgPeyS zvzXEi*1KV|0ex~8G4y*?W<(F5#*R=kp=|@xx@_KFw3#C9gwK#Qvy2)~N??2=VYA#+ z0eLVk!@hZ~-w#ZDjhMdZJ9so{Rh+Ta0kaQcW$l+t{b~_bT8EizNjP9hPP=c*%M=D^ zOfPnH^{$F6By{dfia(wzY#3ilAzxvU>UPvSh0~pCAH*fH9dH(!+odqq>Bb{$!Hqva zk8b}kq;HVBi_Eg9hkJJ11;&-8;o`&I_m5#pggyM|!dJ8LMW0#eJ+{bX5{}H^R8OsG zLVg(hrKDg=;=fN4^E8;3jDD*6iv3e%$CA=qhiYuMN6s5u{| zcAC-qT;9_LL-p zXE#_@DW0g;fWnmZ;F)ot-WmEE;Zekw+4GH`Djj#Ow|F9+$->I@iq@xNgX!u%-$cyr zNG=c@$c~)R*>o)-e@7iE&$DG1sd3?@?kQwu(xdNImVbe_fxnSN6GKxTNj&O#;yo^xW!hcgYLI<>)6z$o7ye{MaV%sA(rmH1zj1i^~3vsUfh z_&E`#g{s-~TbNO^j|*9i@C0b^lo!9+#OOK?|5DH_VbmR&cH!k+|daz3gsR zYu@3lxnS1~;|K0+jzG52s(}%HuIR1$kFXZr2^FUuW?_5RoP+!*`ReyJ4XRtO_>Efa z1ks`fKv(Wv)IqtaA};7-BSHGJ=(#N6=~pMUhWwuI#@G^=DXCpu6T{Yaal%AaSwn+g zs74#U`gj;#eqBxc{%yk<`a6jWB9#=0(CKhm7SP@u&WnTa8cbn~KBGdP!=US{v zv(AVirWDI_*SBY)@6%bAl|4K7vDi?md_%N(?5jT6<>n53j0@|J3=w*58c|iNaZSsK zG1jqdq|JiGQ>mtxP8^N=3T~43BjpEmY{|rD&ji#H!qs98XUa31U%jK3Vzn%AT{}$E zNecRubsNuTgNZeEB&;yCW?3JR1?`tk+a1lXk0PsTMPZNB0LbJi6{i8+Uwi|Y;A-{b?acl5MXK4SeH6lM%Ipt-bJc#K-X(<2!3-$ZR=WE z!spT|o-;?%)>xUUyFDJ3&Eq>$Z?|)OXG5zR+H~TMe~R@21%$DH6sK2;rW{}sLI`5) zy8Eyw$!Pc7xz$fNiOq}Bu`@Lnc%`<^`r=U5*G!0k(FzEjLrcJ(ac_87 z(WPGbxCIlWdB+(Rq++Bzn2&RFM;;LxLY!(sUg??I0(XPpx>FE8%E&Mb?rWIxtR}-D zvbfg?Z}4BSiPn%S-M403h2GCg$}FpNZNJutW^x%p$snIJ@93Rv6d1I329JW1sm>_<~%VcR}9kWi`x32G?tIW3hL(V zEeOv3aRhHH6F;=&fL>?;~xtee~rcUI|9>RWxQZx=U{|L5Ui|>?4%qVT#OLtDOO1K3l^S##CZPq z`7Zu7g7q&zKEESaLxhOm5Ue37=6^@92L6g*E&eYNtXcjh;l!^J%kNYAuUJg~3Xq74 zhvRoZB6i5`e>)%%BpV0&uL#?}iw^&1021+V{RNQdm;GOUe+DFi2z5NX99$4&qCYJA zzZa0`7>3{{AknWtOaJ!+5}^trXv%>NFpa`MG0OJc8h7j@KYE1jR+TlZ@BJ-X_miIh zv^B!$p2^Q|Hb@>$sQ-LkH8Ek{vv^5IOVrvL_t|EAN70W6$yGa1BkvCDHQif0_KbTJ zxzkM4@~Ssa0DglfK+7`{WB*5&-Y0+uQlEeKBSC###PW*nBxSM+LUgY~*ihFh0P1_} zJeV4q>5uM72myp1jCqgZJ^_w>#pg1g0O>4GfEDQ6qD0>K__Xfk=HbN`!!}MG5v%M$ zV=yy5l)txC;nGxSJIwp4t%7J1N~w1%_8>kaqO^^F(kPG?qe`sf8TBBh*x@u@tV{0s!y?_`G;dFx;+V402mTb zRc){tdLS`Wv7YZ;wO0ve)nmaM=mRA>WMRCkXZd7Xg!aNOAO2xD)hHYvOx)5@HTYU> ziJZ0>${%GHBBw_2MGL?Dy`%nR?6I!Qzj1vQ2euJ+w83qMk$@S%Gzux?u|SjM1vdPX zQSyf``Exw{%IyAsWe{a4PXN*d>)Wl?fl{D0*W;Vom5X8Tc#TYy2OV81>EyTh{RIIq z57-=v6L#ECpLBT7+*N5F3R=%)E}q|=T0Q}~XAL|Ot&M2ahB3KBZ3$y~v@vg^5Jo3C zM!b!$w;uy#sGb1N!k+-W)X>cdIpk9eV=i#FQ`JR+VdAWp zqVG(O}GG1t}sO@k$^ID+GIAR*b8+jH1@W^jh zosM-m1X_bV46QG=B#*rYzHEIabTuf$bVsw{h0;~fT_9NE-I`)nVwb_SFd^{8djN%> zIQx|GGp&^>NhLh;1vR5!i-r|w73#c!DAnPtJA}~p=eH91-F&4(HC51a2f2=&^@vJ@ zKIuIiwQ8KtZ#nY2o8_LvP3{=35Y-TgMQARvFDgoY zVsG$ZBR)>EpCo*0uYjxPB}eW@4XdhfKmrcFdoJT%|fOVOd#cP2;HPyT73Z>Q~X=`3t^H4djb!6NX&3HhA*LHW@*S`Ht9-g3DiercU-O1MLY$||8{H*eexmw8&Z>|&-Ex^HZ>&@=U99dUyHPUDTb4*9 zTv*uq;aDHKuZivR>|>feyKWjw_}tstB5NiHz4U?dcXM{|18NcQgi2)|Bw1SQq6|jW zXKUE|m&k>d_PBRN%Sw!!c--*Z{XMLZc(PHIDSp*L&qSG17!8fi+=ZpmrQ9O8hL{hl z%& zj95ZVX}@$497eCMi&^~S@-W9znIH>4U?)%vq(6-5Qx``PH?O#%!vxg;0d4Yd%z#y> z2_J6AKwa9my%(I&0MX$xkd0@VQiYu6M1Kl)N(j#aYDLHm*|=<%+J~=$7H}E(rD;p2 zZ%!D5Jw2Sz?tzyKIGV&LG|CYl9jWooJ>^d}D27mQGltqC>19fmQE)l*UVhQ%fq z!UwX09D}htSfkZYc`(FGbgRO=4vH~wZCGU`k4$0UJFgyJ1$D`*?STq7YPw5eu&3s` z$uY>NSQr{Hl!N%dFx3Y)^*lqTQ>>!vgO*lH$V;ZRBv!)NTc$JeRn!n;+3OABy0TuW zldYtzfl2jFuFnSOaOqp>b1&@B--mUp3U$;GqH!0;?AF@r)GS89C1r9Rbfx0<_iLFD zc~%J%+uQPM1a&tbYUCLJ9z7>&FMA#h&o+$WPjMt9Y{$%os-*rDX|3PH zz+$MYo}iM%gjSI;*ZhP-Gy;?jtC#6l7a(jb-w0Lq1S3b2gb3cx^k8AFGqf zu#b-5(|12w+PsS-R7I=n>__?2K6P+aPVb6EAqXlgaTAMSh z(@`B^h)E*Te8`D`o5-=^PX}bE&R#?!N){7@_DpLC5M3Zls3~Cc059X8iDWQsDFxEE z(cFYPstS@!9IZ~jN}u&`%Up12F?h}ni}>xnl^SNVcUws==bN?;$5MBtG~Gf?v30*n z@FxnSj5t4W+e2YOqx9l+X@dIK&Ah&W+Jdehlane`Bn^<>1nAkRqixhNTX~a#lj)}2Un*IKCfLTi(cK7&5hJY2Pof@7DT-hv~)jqQZr6|NK%jY$t2Cbg znMOfdPk;^azqtii{7tY?y8g&PHPE}`VCD(1gQtJfdOiMk_e`5#J};X%a!3ZM!+$y8FkEle@J2f4H*Y1lz=*{>PzcFpsmuOl3$SFQu~pHVJT4pSw=}Zn*E?ju zeBVa$-gh>cT{E2Wl6GA62~go^efvz;dzB3`5tDpt06##Nx1K=e)s94V_xM4^ZY0_# zz$GJOh88Y9zK8QZ$-cu;flRo-t4A22YlUIrwV&7gq|eX3@pD}K4|NPq)K*>|?bj|Ptc9l3!wo5)iTlp@LU%x0-lACoAV1nvtW*W4m;M4)^?r^Gf}XM@3%KEQ+i7#21XA;>LoX+!O=){N{$Z?%}!!M{z!&P zplUn1CLV=zB0$}C4hi#N{<;0%qQi{|1jh7?Z7<{E!c;U#}FzDXxbBlY&JVsk;Fb?&a-~H!qJ`_aB}B=dk6--R}920!&CIjNeMi-dmrH z9JF4qkmaAc1)eMpw7oElB)z(sX}aqRdUO?s*r`L#%U{fm7MPwbEYMb$^tHY8v1v>9 z@~Z=%L^M(nD+AmR#jjO6jla1~(^1JX6gvoxLTRMZ)X-2A29egt!m?Dk!D7u{1N`+} z1AxS)Yhv(H9gvhYbP#Pwy1e(w_#@WzwX|f@1Op>kBLd!v-J!*4$S7UsZ2l+3R1FpO zUSKvY!<)Qw`s>EWcMk@S&Eyarj11=}u<4E-<_<=&ryFm@I>JBWqP|#=v?@{lO&4na-&5cpJ{Z=WcJN zy2q`pWP{d9DF^gnXF2oKDcX!Brce&y!@IuX3GfslE&O_?c4UTVcmEJP7`|CtK%q| za|zOV3KCp`tFg`K4n&#is-DM1*|r@$m)a?&p*JR`O1KZWbs8a=wKX9USpX;jxrF2 zrEDZI;+7uotu$_J?WirKM)9I_B)N8c;5qGD>N(*C3Qv)u1W%WcGrl< zYI|)%1p=n~%Y^#^%}h_}x+zBzj6Ej<#f15hrB_BTULP;ESN&k-x80RD^PX?0YYp*~ zmB8=WJj{-P6*g>xWeZf~WnMM-a#89|hqpRgBi)Pis$T7#As3aYo*kbf7ICP)K-g`S zEDjD=DZjfWk4O=mC)%4#6sl9@-nZjn;1}wPx_#q=cAcAHl+Km@iyHvqq|0LLo)O*<0}f8<4vB@ z)q6e9`;0`G)DuyV=D_4aFI~?mTY&+AZ@nEap>v*`0m$C=S=Z&MBu685z|tGjeq0JU zE86M*vYPf^sc`=*=RCmcwod>o!TcwHE~+|5>ALj|-USn63LhwUgG}4cKNsJN?{Q&% zNDE=nz(eiE&HoT45B;Hea&`1R`0DprI#3ntL9_(P8WFxs6z5rg4e+)V5VhU#4sG0Q_ z18@`D)%i&4Dfi3zqC#UC!cC%66rTWPhw5tOUcd0Igun1D731IdmoQ=o#L(#9dPcqE zJ^KS-5^32i@=ip@+$(mHbCDiDMnzCTg!;(wJGL0*z69#0fp!Rhrnu zZBgt+*0Q7DW}~f!`KxKD*G=fpl+~yhhSwk(2Dw`;;UQO3U-bYU=)~WGVM zM$+dO#Ypz9rOLwv7%hQjoSY{#_?_j*B`PKK!6wQoBgo9SBo2?jLal=gtIAVIn5~BP z@RT#>SdR6}O!)!bS72sRkIpDmfOR{&Gc)-~LqBE+p0?~;n6Z#p_=%=lCX_v=ghBI} zp7m{rv+5y{h{pXwj*f&Lj{wvn@1%I({hL+;qs*0ubk$+Pxud16i@Pq5x_!9V6UM^;jmbPUU=pxKm#$DkSJXMQ6P}#$X!iaBy52<(B_;P6+hP z**oz#*$tro zPO=bon3neQoHDrCPPc0O5)9e13X=$H0d(B1xuw@fCePk{5J#FLRP}rhU0{x1(~`o_ zq(spbvxt2*fuJE~nN1b28`7$^{prx!XzIK;c+kVs_O-v zJ4lU@3ZpBQx2I_QtL2allulNfnfJ3j806Xw{62-aVrq*+`uylr;+FVc?OptUrK>c? zc>YAVGn(wi_>CbOS|8=LI@0e^+3-BYUO3}alaB1^$1)3ayC-6^JSwz%IlQr{-%tDc zxc6lk00$6vE@6}Y5{+K38MUNME!RM9^$GqkpCLHR=ur;yfjzoAYOBM~)oi2=A967A zqua~ul_!r$v~hSXOP88E8xYG>9PjwYefu03(!TYETom_h>OqLvc@RyJ@Na*1% zH=w|E^=&y-T;+k8+90}y4*1G8_SF;h74E){xBgn1cJTr5CA&5L@R@#2t| z03@Jj$@wnL$V`7kUCb^Y)@}QwKrVU<2=soLH z(xlodx2h8~F#Sk&K_JS9`JrUU@;CE|yL0>T4Q5&EK13*mtny!WyQ_9!)2&Ho3T;Ew zLg%o*uJZ32A*=jx+MHygu!cB|u!N|Z18PN{IVeY^DVRt1+o@#(cQ0kv@WUI`DEIHx z$~wOv%EMOAibmrRy`_UL$*&U-WgD`#9*AUVZlwiCOG~>WOIR|zmatrtkM7Bag!Drx z{nec?SxSV|K{3VtAx6Wc&EHX<4N1~lPOF)mUJ9PK{JH*T(w;Bnz)6(So@Z2xW<(j* zleXNq7o|v{e|bvrwy4$q^XUO>!7o_N8isR;gYMe6b96e zmnq7jOBPD9i4!XDiC=}53;SSz3ap#FssV9^3TnjU-CXpp1d0j_H$7y)nd}JVFDZ_+HjQA(0=PNKniW0W~ zxizioahJTFV`T-@!I2dcKe~NKhArBl&UnC&fk2|*e(oH$@q%)GvD1Jq5p~Kb{pk3s z*YCb4e*z`~%3WDoKbLC5>0-!~vF*i36H>?>2@W`6%{NMTta92%&z1sfDg+oEr&B2F zp{oP=-0QythxD^6Sc#IA^Yr(6z-Z{L`m3o}y65Z;=7;lwl1R0q8dwkFyb+DbErpi* zE_%#R<|5uDlgDtWhEK7wrUt@)!Mq(hxud$|XEypch8jzT!YXK|N}PJS$D&%+GJdJ< z)xVh%9yExtt#38g%bocw#Kpu6mbLd)@Bp_eYE3VYd&iZKL?fn77j|*4a_@FsI=3c= zsc=xBbI2712zb}KyeY{riF*WU__r7ge~-!V?~mzHPbspcT}|wmed~Xz6dOjIU zi}|qY0k3S?%w=ZoVdFv-D{i#1AA>TD5X~qAW%h{nyWMp*mmE15s>|R-X*NmX=1p|V zu7}k|nY+@SV6Sl{c}guW=M3$F8c~iT{Bnktfm;1oj{3pTk-nwoh++C-Q;cZiF{@?& zt-(p2+(W)`mAB!X?i)+?wNm|XBw9cbf!*PUBH4A!077DM%!Ev*DOGqB{D&-YYfD>C zB+tCHwDh@Lim;sWC7AED_UTDf(b}uK!%$QI%$fc7@|(`TaG{yLuU5#$3M26)0MY1eJ9uw;x8<#@kNckho(lD?tyB*eI-*JEgndbRzPPM! z@xSf>0k3n48dL7hyjPTeztEVluSOKOxUv%ac$-b{#-DLDjjcZv3iZMOAsgQ2MjU>2 zU$*F5?2PWvgi(+x7%v+*Gv}M25 zN$}eXsR#Yp}|Eznbe1|75Ov-m!9n zxeXCZW+7q;SvhI9JA4O3wEuHS*?W_d(YK6`Tz!Wim@Z=4+LQaSZiecre%iziX8G)V zCC!WNM;=b;bq1lkHxwWh-}oV!K&^}>0oz%k{XUZxFq4MIyUO~;Kp1^n-1rV_#uCg} z-)<)pe;onJY-_>ne6w3a3i?;|A_0k4v4U%f-aC9qm)Li``yvD?81)%M%Zqa^_Kqc^ z$ju#OyqpBEEM#PhNV~=H*S^T3Yj-YL1UWHh91439HgvRCmsU#=P4%Md`olFNNCZoY zGQ<$y=R1u9T%!ombdY`B%hc^!A&vbOqJ6i3TWz(K;q6ezAFtzs8LDeJD~`iXK!``x zhwyT-@9KlgDWVb^&(vsx9$%2e#uNMFQP$i;&%1+Ew`DbRLk~LXY?PxGf<&5&N52de zQ4H@z=JXbfA}iFHH8^RS{osSZ;58d;1WMD}yql2PCp2dr8urOvOg4fnRu5i-BsbDu z+YnQsw0Vb*-Y&YLUFv3aTrmo1g=-*Qd~%kkP{SjjH+_=$sf4$xp78= z$3mI6Du`ZsV`jKD-v1i03^V+oJCt%p5hpdCzS$)c?X#B1tc(Rk*dnd9sXywPr|m4} zg@co$s)`}E4VqMX)2%ATGTt;2d+FBA$p2#REr8h4vMr+C3s)b2;L;ITrT6#2Km-iKcAFTCN&Fk_eCkZT-SWv>jg4V#Cfq@jGW(UGy#Y%b6y$FGkddZ7Z z`_B||_$S&>p%%m0Zx)urQp%NDn}D=-qjJRuhbvwd8)Rk7pD2Tiwd8ECzn$2vta>PN znX9CRbr6nnWUy)T>$e@{ha9zEivy_}<2J&!BC|ENUf`smFJtHg{7m9WvD?CDWzV14 zkTnIVTdEPen+J9Zk{F$`(!yqCTu+XFZ)^&sEHX2u3h9CkTDl(dV*1EbnSD{tOqn6L z>7qdBycegJz8hL6BRs_kVp?9RkNLR60Cy+B+)MaV8ZLg{FQFxSVQKNPYB&1bt6@?+ z>vtein;`u_igVtrHIq+NI?uLiao^|@O>ouBKn{TX5}mfxeIlXdy!UuAIc6%$>{9+V z2#A_f@LA3LA%zPOhuC*R-x~^?Bd0<8cn)|M6{eB&$b`uON_lJ8%Y0lT275$N+ubXv zGF0dfkZy-C$_>iYU`u;s!N_yA%fhjoRgsWmqm)60ba6AS39L93*3vpHJPNN&<(9<& zef0FgFpFrsM6N_pZOrdW9j+Aay_zu^Q`^a}<>pZ18M_hOL?ieXGT5X z;_riaG}Wh8R^&dl*ISv`RZ=9#7GKivA{1J0Xy*pzN13}a7HQcV@y7}T#_8qn#Q3`w zEF(;g56;HvN@ApZve#d29pHscLw}laW1>+NrVLg~)FR?uZ6adHpr!|icJ>SI#`0f` z0PF^Qt4f$aXTI-M0WxP`#B6sRYnfuMA zo{=#e4+p1o5GMv>bX87#fYyEH`xpK%)2tUm5$x?cPri)Z&e+z)V~$|sTUltk!3K4h zVG5nf8$2!6{$iJXAJFnuYuM6HO1YsDmwB532Xe&S;MnQB-8@We78k@SMUCk%p{l03 z^asGo08*fux@340AuA$HhF)#{mfgkI)l6`<7dx;Q@eyR_G%x=95jE~Us##_#uRbn_G?IRMlso3<+3nBe8peYa;6rN zWXp<26wpULc+NdAAp8GhBY%R0x^v>{KH;Cnoo?N5aQ$qwj-@~$RLUaR1^F^K>~mAB zt>^~-0npccu+j(i&X*YI*1&*D=|LhoeY`eGAdpV_OpTtpjeXj-%R>aG3IhYJxzV5( zrV|>I3z^k(5jmB~=U@VD)^C<0CnE`9dh_2}iYvkVYZm1HT6^`i>Tr}WOZ^e*Lh2ez zt7H6e%akffSg8MP;NNcGWH8XNB)IwulRb}R%%7Mn;biI-KSx-l;f%cKt8zG-h8Yg@ zrzpG^RAK%H;12igUziNLHMGrp-Txsx{tv*PjP5zbmJzD^;2*cBDYPMi+r7S*9a3)A z2vTP`Z&b?VJqXrEb0Fj6F5Vy&xN3`^lb*=t${1UNO4c%L2Umwn*w4iecs_%-DX z?L5Q88b=j*A*EuO^V;Pr4j)dL)5jtzc)7y&zO%+@ez@gomt(i*B3Fxsj|4MLwUH{} za@qCWZ&L(3bhPudHkA`Cd0!-3yMOe>NVlbzCv$940NNDPSaKb;H8mhj>Ml`l@O)rQ zRa~ZN(2H?kU{Rl{lUFcHTcqTR%%N){Z1C*}^H`5YVCCCfnnB%j%apiNIr=DZr%8$t z`Vpl5?eHQY`13`~>cv8YuTM4No_blWuZr#~N-f|I6N0Vi4b|l6bBTohZ7BdYM+%~8 zU4~18nl)^|aZ!s>!>|IPLZ(5&+o_-a^jmhV0!43fYI}7K6_Ks+oQQ|il{(0lf|X)0 zY<@^9)^wGW3?ZJJ&K^orT2O`Bx3NK10!fSEToIu5;kNFMjCeO#LyR=&B@6Vv2Pg{T zEamobz0--?o3VOwD5V@tvFr}}-N(nIyrcAVmr+VmuJCm^z94bOk3xNyjKf;Z=r5yx zIU&dcTuL2L^tnp<%N@Ru<|6K>3nAlN#gXQ{bG3*JSR-$i9>rMKQ@xda%w^|AV<7mn zqV|PIskk<>x%zZoFnWfwnRF)|etRU3>3ZaJ;G^*42^IXy!8HsgnLfS|nEAu+QZ}KB z8v;XG$3`X-yo2169HRoW@H-KHyNYr~cmj$byD5^TEUInZJrIxCy=mLqh+Koc`%l<1 zINo?KK+=;4orjpN$_E)}FZiMM!vX(Sw$%U9-_53|F$Jr+=O^UY`W)!4NH#2j#Ox19 zdi{XrOE5P$s98Q@1!WA>@q)&My1v!#_SYcZd=C;@oBE5+(b+xZ6i%7S~@5i z3G2u$&V(GX3j`elef(#WNurGYT@nSvs!k246JOpPYVep8DZo&m26>|wlrOAy`c-oZ z8?pTW%b1$Eet-=sgCW86C)%Yrjl535S+uLY{>5x}tx#58myZ}qdMX9y%U~)wMJ^w` z#otYkB7Xn}A1-ws7@+mS38+ZA7g!8^mQv(dr~%!LigFo;9ROEQq*j-@&o8;0W{7LJr?L5?|=rHJSmBZudqD2?QDYfivBXP>75 z&Fgxda5dG#zMel2OSDVuY70F##>S_a^p}Ul{)(Ixvnz6(sS>F&mbzOpT(m%%f8CKN z^Jm#PR;C`eBTie+5^XA`3szETvW1ef1AdnpY6egIl)L$m?;}A#2fxn;TJA(|s~15w ztV^9c)y)YWPaf(?R!WfX;#(QPGzTg+M3T!r#m2a4wgq`&8tMrW93k{&o5d&@fPE`$ zZhWmNqFK2(|BqYmAO`Yl^cfu~-=Uv8>^8z_D;Ta1{bcs&JPffoy|$I8qNTkC<1z}4 zwlcS>-d*N#P4^RaDN+won=O9f96>RJ;y1&LQSHM~Id_{XS#YT2aIhH;LY`S@VXnly z%$v1|JJgNw$M42MKPu3jFY9))OE6C4Rh0Ed&Re`>@4*mTsjjmnh78T)33xnwO?Y$R zvt(&~moRnPxUOj8+^gb`u~MIpJMTe+T@m#`skATT<(HZ6O+uPjM7;~Stou~k(!CMW zH|Hx|c1r6z%jd=SPn78hg~o0Z`E5(=ttG?Z705<;2o&CCmIqoj)&s4h(b;CECka^Xj^ zf1TF z;AG1gE1+IOu#U&R0dP;aM%GF5gl!Q*XNcsZCCk*`LbZ~7Vh5`~jq;dMMBcBZG;=bbf?oQ*w~pornNLn^5rO4}Rhbyb-m2K6MzZ4Fby}{Jg8t_H6;BcB8A|7$?ybjVhra z&aStsY&AJH9{VBIf4!&amU)~xm<8Hjp`m-zBfVKt6CkPB{3$D^+&Ga{2EIt9-ZNww zV*CcU!_?5$_%v2U#(shU2J;AR z$3r1RAGJ#*)|_x!%)iPK!>k#+CO=cQA-`HK(Iim88n@OpCq;04}Pb*E2%5>ZlNO0=YhTbbH@eGts%$wkVwqA0R66@+(yso z<*CFqu+dYg>Fzp&5S%^Y;@aV{1_5uN3CkU5)6`mN>%FE2Dyj?TR-kzd-*YvYjBQXZ zEqCLjsDeqV87(AJ-Jtl4Sn8r;?bH^$s>IWrm~LDMkwH&~?;^;7B)KiIAA47k$8NOw zJZ@fPqm{xttzt62tv?-Sl|AEFqO8)OmtBu$7wUK@Ew0eqfRlI3((Ouhm+>U3*bD@b zIC*PmMf~Ei2V3ZWELYuG+|B5{4&#(7-Sl;%4z=1;$L2;dlA+18S)S@`(R%j>pv6R! zc_c{AojRqGJGbyBcTCO~gxmq1_tX&Lral0L{hjDq@N~+|sjRi;eeYhw)S!6%ewRQf z_K{t80`F${8BOT*WF$`6ea5Kc=S0egl6Yl>16!_~s|)@U^r#=?HJgBJsz?VJs>^a& zKu5=#vcUt@5Ih-pmlUTfAET8r0dxUU1yRx=&Lv(rDRO7X?MTYqSv{pBAL zij24#r($%kNoTiip?iLzvv9}ca)Ul7{ImrycYI)q9}up4aqF!rV)^SGUQO>u?02(A zd~^;t?Mw-qC$K>nvT&~Qo`A3K2-6?N=z?C`l{dJL?17z+G+2GnoL=2p>{V14%rfW# zQyf{+U#$n6YrCz{3bI`td#NIZ*ju=<>*`1#CayDShz}4a!03_Yg1Y&@q@JA3sE6OJ zLA;?fapzb#Espc3DLeSG(~RpgB)UC2I|~W(GR>O4d1jf!HT%5+p~{F53+M3{MY(@f7sF#7S6XSMga8y0?XX7@|F5lhn(m|3nO7V6Y#We|@; z_q*mau}i)f;mBsf+zxnWpn!OWAQELa<@WcT@=pY)aQ@piYCM{33y|8$&-8g{MIp&y zya1T#or03)NcVUx>BLu3@I$E7plowP)xcF!T0&(XsY15*g00ZKB+ERXZ z>~h(Uy-Qp?#=hvAovmbmlw@Nhjk;%iCgkk}$O_67)aB|^g9L4n(!=|DN?rxV&1ZC% z>sGgiKAN#H7EK5bV5`yZ&m+Q-n_z6}JqTlZ>tpM4FYdrcZ>*Z_Pozgt2=Wph^HR0r zR_0pU6-YT?#0FXOe6foyQ+~)Y7Q5**h zNZ;;{=uycNQ&y3@@bxVsB%I@{vSIdvkbgBa`oQgO$_t6S+$~hFzSzI_EvXrEC2=gX zeeEW~Jj-j~`|D+A+jt$y-paX_3ARAg?AbEE5z-(6Hhu6Ch^>EZ8f29OwT6jY)~VLj zTOAD&>aC|NshFYK7TzUzqJknR@j16WSJImI5rQS7aq)lRnUqqphJqpXcg&mE!fvH6 zID(`Mf_ZtbNEygB#bz><^u>wiKaG;`SHCbpZ4obZM*&H}W|qqpS=}V5N|_{%GbfRV zDJMeL6*{Hi7SEUbs8>4yz;AP$xwdb{AnRIGMiqx{0g4K3&Ja?YDSb%(v|BklN||{YzHEN(4_EstZEggdx?p(A{X+_n*^)Mcce*_>ZQ6p}WkSDyoe!9Q z##5h9&pkW#<)NXj4;58~smLt7gAr;JoMn-KQMPHbD^fbUD_fLe({Pl=6&VS3n%Cc$ zc^o`9sV>UGT;%R9QA$alltJ%Ufpcg)pprpp^p5!PSftH-rnykkO3=>aHfUXW*?wCf zbJRjHIRJnO#ZyKu8<|Q!?K@pRx|n0W2i+-{`W6c4xvHLw$ZPB1#XrWNO;Z#{%LP-C zY=6U5lJl>XrhbHGVE$fg37fA=AEWYyyuS++zYJG^F`W{g>GJRLFZ~1H1bMm@+0<_j zb7|u8#n#o5#y-}R2LocgG!x&(`7`Ig%_Oa6H&K4nD3!2>Mzp5Gx1A@cUMD>lN97ghU1WW4V+&m;hw$Z z{pnjG+kN^J?_MzEGWQ0_A=~Z8;aU~c=V@Y0qVZSCQWk#*R5K6*cLtToghwd-&RXSZ zWPTCZGMN^Ern>Fo5r*DuDgsxZD`13ev-WumAw?hF-3;~Ed0f$FWdg3H zhCzEycjcbNptcT6nshXR+xPyxxVRwi*?cP2ckcGMYBeM-FA9{Q(S?XY6}6p0M2mV} zmRk)hjJpVE);ypadg|q?XdWso=tF{~9yi$Uvppjz03`QmX|p-|vys_fhoSayy6qyj zM=~FaboB+O6?*52%*>yRG?cn0Bb##S1Q;wln!mg3GW4G0KoP|Vj0S3(1rBG3S4Z}b!X z@TJq(=0o!Q9M^_Fo`08+BOJ2z_2&dR&{!?)s1TY)YZwL4#9U|r+HMNa+S}9tA z(&IjRZ`q?uD!#wI9=!r~Kx(o^-|hNkStwk`F~3{!2r=kwhp}CJoUKkVD9X^U_3~j@k`(mFSpO#hSX5a2*ZR(0fli@tm zUuBy2>M8m8{@|F$dgy`x@T+T^2o;!KySHr3c z#nmJU9(U^{q7s=S4lM@{$Pp~Er2lMpvWkVh~@Xe4j0nh{hJTXYwhXxC94i!9ta-H_;2cMoZe zo$sZFAFNC{j`~W^aUO}9k&!;a9K_?qyX8EU@_9_c$q4dR{NrlFR-a?k%8!|W&UM|N z$taShY5l~uu~@`>YDxigyC=+(LTL0J29G}V9+%hATj5T2(gyx)e#_pp?Ho(OvSO4`V%Arg> z=W(SK%qA@mfy#i0TYy4dX^%R0H7&}G)>eGn^cImo&@5Q{`itZs#4}=-9UnPEHaN(i zzo>cV+nfr-w^&0ug5emb4iD5qpt7cL5`A0qs+$h?DDrzRoUnRvsEkpUba|}%>qX}b zu~Bs70*Hs-{ge{uh}ksBwP%vrnaxbR`{f!y*gh-z)2kp_Sf??ix@oMGMe%h41_TZ9 zyvWE!YisuxT+6Rbfr`emAZ&i=Q+BXBxFCrwTqOKsNG) zZwUWCR>;3s%75uq!qXi$DldrxL>1=FrW}9!GNr`EO-gCXq88iJLbC7B%yks$7(-l$ zmQbcu&ZO!i+p@j>lbH!)tC(ZcH_NR4pYqzln)MkshlBA9tR~TCM>5=-Zd0p-q@U}c zG_s2ZH8Jd?fOzqBFhdzutyPiZw{e6!rC4M{imm&Z19|>yAp`HB)Ku$Sb2{uY#`2eL zO72+43hW-2SqLm)wY$~cjl+}gV#AioJn8dJF$CET>5rnb@v=bQCbd@Z7bs;@wRHiT zK~m!UL|qYG2oTA5h5F_w(~Q-jbRCl1MnU1FQ|Yzn2aWo;{B$;E7*>{aXd;4sg@u{+ zNr^QgK5~;LLnaH2{u#?kIy&O!mnCsDO6Q0lgTIs7=#&>N6yz4_X}92N$7Y-FH=T$M zvTO+OZY-VYGqzk4po&Th>n~98_Dy88m-FxTaC67Do_m(R=m90YW9s&u2po0bIHZr7 z3$<*s$k^GKv`uo*H?L=c1++-@>k5R*APU(4Pag+w-8SW;?})qutL4HrnGzti4g^jB z()-=n0DUx`=_6Iu2u0?;9W{3PkbZTN2XMV7xB^Yh5L??WYD?}*j*p3jVyvZr)9MjS zT;vsDiSw=gPNQzZv}Ls0ip5JAAY+TdxTkV-Fb#=%#4`5A`4y}lzL-x9er>BrL#qi(cdGQX}}fE64ybye#16@++Oh?6n< zaW!A7d1drmll4{8oIYM&<12GrHD%bk)MFF7Eml?de|Bedp5whP#k}<&oH3r3KYX&X z^o-@{KODpla1z}tkO$~B&y*h?=QbqM5HDzsU_FG7jN{#aKWzS>t3GhUT$Z%AL_P1B zVxU;c#(4$mjuz#;>MW`wrQf)}NA{b-?ncS`LPXLZSx960f|WQIl_Z+J<@TfNy`gWl zB5zG=Rl~@u8s;TC!4T+m(nQm_kgGs!9i;MpFKtU<8QFXb-Z|Bu5p0O&D_}{OS$?DuQn1mj zL|s&*yn8>}Usat8lyP+$gmkk}Hw;BPr%mmXqTKrx+7_1`S#8fEryzbX`e@L2MRw&> zWUZrPP3K6$C|LYvB^)1W;;85SxKii$kK30pgJMP@DtE}Eh)nXevTng96ySFHcX^h3 zK}T%0{i)5}k7_bgd&0anEWwPiA|x7y;*x7S?q|xU#k3R`x891J(JKuC!{R+LS+L!@ zA)bNFV!XBiwOKX3lq4k56`_TGL-6&w_oINS?ZK8-Q1&~~^@vxi(LpWYaQzf54NcZ~ zD;sn=Zx<+{x^X^AM1P*2o=M7^m$&4aVwb{E5mq7C@dWTu*K48W?H)(N+x*TgQgv}b zvwH+AP(Oqk4LfV7MDv{05E?dcNpBi@=8R`{OTP~NF7)lt^8cFl(PkRqP zf02kRi==zZNll*Du+j&|W|14q=@^=R{#AI9d+6{7z~#wBACZB>7Qe@j7;31VN&U)i z-v^dtKdU+}4a0F@qj{{V`*G@SiaKbKc46x^7En9+JKMfIjb&p#{2L3$MV`;2A(V>d zrbZ10(jhzS{qE81j^o%sk8-uAaY?Z)UDBvF|H*s;;YTN-7~Jf~>}>yW*)eD_OU(zH z-u8Ls>AKo8h1^mp3L_CJU_chA8mRvXTX&IOzGisk7$U=P!Di2KRmzor&FkOn&o2zd zxKZ3y_~}zNl2iNR_kq-(m)VT2VKMPvT$fcj%kT4_`12ny2PY)g{`;=M7qj@k{m#MR zMFzRQdeJwU{5W$oB5T{i4)@=kuH?8=4R`qDp94YuyXSL$@@c0^TL1Z$m_JX@h2($f zMg7mI{l_W(&rF5?qSvy||MTeS{jVOXAQS;x&%zXf1heE|>F!dpgqnXl;r}1ZAaVv_ z_c`!`8vY-EtA$bl-26)ae!pK&EnJqG6O+&41LpQK^XJVgOt{&x(7h|Q&%Oll>mm;k z_=Z=vt5EZxXw9kz8YnL_Ie2O(et!I{L-{n~j+dw6FL3{fE&tBO$x+bCh|vAMKGMo_NHzz_4~N*cyO2kX0W%K zMViST)%OsE+jwhVcgHPAZ^6S*!`lzFnQxK6LVQ>GOp$D8=Q`JKP8HgOt6YUMy>k~K z<#joNAI3>X#$Sl-V;C^#Dzxg_N{2;jeS`yXFuFN0erWl&b`}Vh(E5SX7OZqO+vINN zdj0K94NzQ6KRD)V;1ntEWkvJbJ0^$jN4;;|azFjsz>inADvIJ(1Lyn4A9O~U?Ovy$ zA}i~`La4ZeR43cFFifH4YWJX)fZZQm14!AJWE`I!w)P7o*BowJ3hF?!*_4}=FtRQv zW&cK^{doPeh@#FXLvec|w)7yO9eZxqK8}pKe#ou(J$6W9Z*pXYDI%4Pu+x{40A}Z# zBx|zEty3r?Ki=~H{yj{MsklEZLHi5vH4sQFP`^uIMr6uAG$Ot@h{br0Stgz(X=zDQ zT@s}A3t&BNAmNDfk(l)Yh8{#kLcE`9?+f;r34nFM)K-zM$7d` zcvH!icE?}H%<9QXU#&VaL4zLm3(49mZR>Tl=<`15+#s&-@lJ-W_bLo#r2T^&18`(g za94ZkNnQAq`#XvagT;auY%jOI&2+#_IZz0*W58j8+W;-pdr*@hk5L5!1Ke>`)i41xG$d)R zh5woa>p#l7O@7(#wWw6RLstA6O;8g(ZVu|;4JESlAAX) zrxzw{N~){TSOXeHhtAL0mUi>CW3-n`qqyZz8uQHDp=mjwI>oE=CJJmIJG_O+CzyKB z!bjL%#)|C7HZ^S)ayk`G+Uw60jWV1AQy$ZovgK)lGkc`kkZWiId z#u2}ru-vVNr*SxpV5*u=^3S~Z0PvKEoW3p`PtDTQa_ai>iQ)+xW~imgI?j2p87geudELj`>oTaY_Jl7E|jUI zetG|?U_8r+{Tx8DTcN;LnB~TqsZ|@J`C0zW;)a^FuzJ4|-(8G}XU8>{+!J<$I_7{- z-L%HJDL!iO5aH-j+SV58DH0(eWvQNrm}R=Xe=W0V`cUmJ(uW2M{W5hq(pDEj-p$Fv zwsGc{Y>GiVZbiF-$tN)APVh^IFu`xO$6|CpA+ zn?LR=CvqD5E`@5pT6;NneyX~yb8?i`sX3ty&2v*X9N6pzTcuG=yMMUWkMfbb+S zeJaYdbv^7`LRWouqO%MquWaRw?F}Z9K`U;7^N}xx=E!n0>TC zCg=DtDHSakVRwK%ENP zNY=H*eX0J{vO!7GdR9-))72mK0zzzMj8>;Tle`cr@|gl4bf6MI$6Q?4>fGNjJDj-t zVfc+6(3fC0rmw=1vWcM`&2ZoCrr23fqms&R3&QF`53?9q_6_hVEfVCp8L0oBe&7Wa zpBvys4?yewtd|s!2$iQ|q;Z|xVXXQ?DcyrcbgRgQuP;>fno@ISC@;5W@Z~lfT01DI z2nx4Mpsslcw7t*aZ5xQaLM--XX6V#Pu6&OIOcM|UN#(cvTG29$WALdI1 z4%%Mink6Zpp!b~Ecdz!xOKT9p2DE5bNgzlL6CSULJsecSr%&7!mDPVfudM~C#B#XO9n4V#x11H@8%!>{zv&q*%w<%;^vA3-nNqH9 zbCafVbKOUBeKJzEg)=57rB*aY(bdxJ*uo2cBQEtm0A9Pm)ASdV1o{&Feb(?8Imz2G zCnArRR_EZG%%^)!z&%|m;_u)7ZVT|m8DsQ^hTuuwZYKu-AVvZDtajFuvM%L}2Yop$ zDK(0V1yjY8dsxn3Eh(00b=fn&sY%48C!`Ad8Y@kL-Q#{)9^Ny?y|}yr z78-WP!*@sH>hLJkh{Rok=@6b7_HggvzMqS*Gp-NZREE2FPhihPr(X!TA{MRao5W_$ z5|%*azqnS@bKb|m)k?4~XUgUz-E15EEZ5}SCd`*7A1%kgQa}*1r)et!`~whc0}aY) z`(K)BggB?-oK&t&WxY}u#rdudg_px#@Jh%u^D5vXUAbChbu;rNs`zaOwhXm%-s>W+ zh_}<6JApcRR<05mNEgKm0>ynQ7t| z$Jjs+?qC+cj2p%@7(B9=XoxVx7}0V#Mzr*P9Z%-emN~ z97Dg<$SjUheLB-uzKt2$gv+Z;lGmMljj1ZKev&v3Vi#}*=R1GyERP+cej$oZZoHf+ zD2?v5YsHZ1)l}>uyQ%TRr7L#-ti0lOKL4fdagpV9&dWNY#!rFg;E7dWy^{ zM-H@NQ;MiHpS64=!Ofa?4Hx|05PQ!Kr7`h6oqq0s?_?;iN-&{=BA~*SUIa-Vx2fva zdpk=GNv2Nvcd6sS=hvMSu6rkdwHd9Q_r(cFv?K*&SNHI)x5eSlXISOX%OewE zsj5!IVJC2di_9sC3E@bB<=%g>3j>aSQ{s*eUd$9l`H$S~UpZD*WQm6zs+L;JJBo4w z&!g|;7crZ9mghzDW#F4rNM~Ue&SlWTlKLor81LDc-IH665KqOuZlZXhtyQu5%?Wuo zIxD;muL5M_Z|tr6O*@!iD^86%TN91C0Rx`!Yb4fqrb1n82D&t7 z($rBUsx?zX9M5r}HUSw)x7zY1Au@ES>sI|>GN+m8!Ydj*ay8Qc#uKE@0hJLLiUSfBrrSpXc`e8Dn zh6vSQ<%?tC$+9t8?JcbS!>m`|s{oFZP`X#;T?RJwg)ghWfqUQiu7i0ToYna{^_lLe zhMPT6eGfVo@SPOC)p7a>Wqa^HOy^_K5$lLTW0Kf557t=PF_hPP(H7iA`S%QdXj7p%<+5piJGr+i5sL6 zaz0V|@DoJjOiL5?_CE>I%;vs9b5|W_MMSHytn?= zsH7C@gPq>m+T%*Qi(^_wK|SqHQt@Y6m(I0%hhg{^t!w!M@cQ87{%|JUQ1Lgb`yd4k zr9-KlvJL^1(>>d;Kp|R);W-o37!%?TwP6u6Xg@7|k}R#f|2JsNDdr~Or~6ms_w>Xq zesJNZF{RIN)eu@o=10u!#CBz=|Eko370gV;h9df+N2(k*PpG8Q`phN;`}bpxL+NsV zCSTpBdy&oe3E4p$)YYC={55>AcT*A8%l2>Mb>CgbU~%*qyzOvhaqtewmR~ ze{2g}6>SRaeyo^CmZtaXL72MbQb>79pht+_u>}4cz{w-eev#lKmdkO-vqy~1#!%~X z?=!!<7$)Cl)&tVT|G<n((}y~&$ILE=w-;C{nMx`uOW7w1gd*&V9qxTd5F@o^}dGdi7G}a>W*4;JTnF+G;m@+9%2_bN^HnAL4007wUT}P~D^``PG z$|gq|fv51OIN$qC<2r6Ck?yMnlI!G%ot?RQ^Qv+I{fUQ>ViXQ?X|9)CRazA`dFC&3xJf(rfru~af5I?~{-AGLp81iKrvo(e9?-5Y?tBC2fY#2gxqW%5bCDUw z0a-jSAZ~8<1R+r;&v)N6p5Ky05~<90V?A(N+#;%;!Pl4PTQ!t!?Ty$R;d}@u47%j| z)tz=#k+?By+h57_@q_*GyE*Ud55Z%g~SPQP4PWy3Zew&C(mC3&UtbU58C_V zy802=oeJXuM|d=<>?9Oz&7&8^hmm!QhO)uqK^P8F5_-K2pH3EcHW(tlj5LCHv=`)L zs2FuU1fp@Ur3BB^$Gu3&uaDDre%K%Nyv&a3Oy{mHRe9O zKqwgHBMrn^jZM5yXPx|Ub%Jtj@&Vj zU~l}?3Q8Qs^?`#p8j07-xLrp~^Z}3`$LyYCyjLaHi`Pl6uVnDD?bY5J4I}{du(_i> z1sxBxG@kIAbv5WHC0v-3%eTJTTfHV!S6_7Spb3to!S|(vWt90ql6~8`smDdjP>^rLk>t^{>$o)koP^C2nr6;U_`=_%z2%n>78l3{bBj*7-8J=Oe=W7>hcTc z^R+G0OdI&(eDW`h7U?Q1eB)hjJrg;qxhUeATFAeiDYJxKUVnDb&16d!m)L4ZrZAFC zFv!C!b3U{cyyer=?HTzHMj}bJ!|S#r6WjT<-fxdlfo^Dh=np`hL{Z&#iw)j>T#ru< z6>Z7E1?n!1Cm-VX0uHnN`o2Dt#v$n>^kM?#0TahavTwV>-#B|`%U0F+y^;mTdwC88 zP~ex;cy5ByHgc*abjG!E$9AM_NUIu%^G#$v8u75gu9QfwH^lVF7bhF-XvFqYK3=?N z@u~l?mUYNBud+{y#$ac#a(vhc=iL0Fdkl-5HUZjy>_}A5xYULnx1CU>v9)ES-s3k$ zM>dnYHJBwbBq1bbJ-B3VA3yw@9YkgVt1FNYTJkLeB z#>tF#1Z$kT=DXaAyEzv)0{B4cH{h~xBK?gK663S{p3I)xXN-6~j_vW$+Rf70lQfWza5a zT~{Z13eLO?0EqGNb&e5Kcx`s!CpEkpb1p7SrlK#724n#V8<>SAlD+StpM<$0E_pp| z7yV04G7TsTXVBhNgy20XvF}n@x>p58jVW4WboMWCOBaoQoXYTjnX+=c@{_M2gD!{< z$ku2FH8~{3sB=vEK{8D_M*%`HZ>qD-kin-f;gKb=AA&vK6rttgn)vDU;oc0pmvN%j z`~pXr4h4HNi%~|nHABErd~xljs_I{%?aytM<~9)xu;J?jLww=cg>%$}l;Y%gCl@MRjb;kV5O3D7R`_q%ba4E*$%f;XKI@ z12=eJ8c`pYNbWc5-V}Z|5aN~>zP?=U)D(F_JW8p&&LYSChLf%Vq*}N4R zEz?l%RN^mo=6y78WTO@s`rJv120D1h>ZBak4&=f#+!~wYg0zMQI6lQzADPy;&Gc^u zsxJS;*55aAZxb=YBG8x{fLE99w&UwJ;T<#X$U}-Y#O;QUym_T%KqW~uO!=~`k9(9j zw1b3XD~Lg??h8T#St7Pn^2E1Ba(bf|Fv58xn|}bxwc|Yuv}=DwxJ_?eScKUP_u;-V z(OVbY%kbo~z(8G;DDEr@qxmJvAsAxm0BWIyueV?1cY(#Z#LJsooYbuN~fR)S6MNC+4MyMTUi>sU_VDtgcEitn}Ca z+@p!B#cD%W3!Zk4jJ_>dJ&n|P{K0UQvTi@KC}o~}-BNwDq*Om~*~j}#&FODJ9P0~Q zG1X0x>?PFB!0j7W{avE2kWq}G!j zHUj1B=cD^;gLBbQ9ACPEYbyW--@4ZBr;`kbrYdGk&mX?vA%vI&Aa!jgROkEc!Fzj6 zOyl)NQEAuD<}`t(h5Ko&cvw4}8t;-G<1^4~H2hNYy9-c40-lbwc|tm15N4EeAuk!k z|KW;3Yoj`Z%0`Wft(~uxaJ_5qBm?$?d9arJ^o_8+g=Ws_55O5`b4bmyXof2N2u=jG zc_>iI^iYiIti*ADO4{ekk@|`Hg28K+!2oxrXqclu#Wi_=Y|h4%a?smG&+K$9yI8cr z4$C0G+C9;9S$}qGRsCc|fO>Y_ps8LbfdabcyB>}26P==nOUYBmpRzKSz z=*uzf9Zi2^Vznr^XdA=r5@!-b%qXki4uc$y#JO>i;>im(Ne57lp zZ?&I~2VRluU$$cloaRCA+8lI!ka1%!bGL|8!yOJD(5oIVXHTp6G}tc~u5G-E!b}6M zs6VjB()w6~R~;gKtDx&fxqm$_b01=bP2Qdn>w%9u{P@-K|3t%o7yP?S|C=BG8~9OT zRu@i6=bfkFqwn^LL+2tLmeR5fo0E?FYkc17!~c1S|2a^hdt6U%8f#R_F6OQx*kPMx zn%LUoYByv@GGEM~;JB2b5UvD2$x9_L3&L-9Td)Q?FBGl=WBy1m19_)^(zX2s%fVk}U4D$#+7qia#-4O;SBzSvNE_yMv^uvQ(;@}yYe16M zpoEG*1x_x|lUfy-F25B2@(?jd;5WqkMxvSQ)7qlO$w}jn@SS(r79kjqJUOm+gg>Lv zq9)aHbii<4e?}O;5_c$D*JIr{7xdF>k|KW)Zqo2&XqIBF!sEpcyHk6S!sTt&*gtIaji0spMqblb1w?Zie zHoGrpY-P+CUI9(_^J^D$-9y(S2M#(Oz7c3FyDx5_m!Gc)#%p{&-zg zNi~Zx^AQxROP7!~HPN0|WGJOi`V`vI zvA(?-;kR=m`d}! zsL-m-q3b~%rdQbIl+POF{^%P+FJ&uh;*_HPCv=Y`7Wi;89JzB;e-ztwN406IR#}gm zc_wF0bBlErY7O-SJ|p=0OI-B1T4k6Mk&R=9x_0Ap#wdT3e6{ipY~pDRR2syI!8^9U zxIK3@>#9RYxyD`(-j@}#N?L-l^RExOMc&h@p22qOt*yIkkIHSbGE{lYe^o=l>9D zeo=s$dPPUZk1bthF(t`~<@|g>T0h9|j-rg|q6oW62!?rcF!nEtSb>LP{h=BzENNZw zL2pR57=?zz!UElQ%T-j8iok&frb+LS@idczjbvH~Wxd&Os|{owR}dHFu!D#$W6>}b zD9brx+Dqu}0&P)eQ|s}`2SZ7|pZMw+KB6l6G>F{e6wy@Ygw$7bL|7os6l*^x3!?A< z-P^bQon6Cnm#uFWOF_7&2<6XJisG~6-Daznv%Tn8VFC~eq(Q-@vGn}5d+k)L#{{}W z{ELz12y^&FR~+}l4I;Xf(I`UG#XQE<#abbmuLQG?Hva9VHT<^qfAYs9@KDywmd?2( zD1~khvSBA*;b)Cy($pZY2T z`rKiE=8~s?NQCdV5s$RvGCBGuni!1HNlCKNVrWc#?RQ+j?yC1*a=EX)Zo7vIe$D&3 zS1|?kXiObZVAL^w$W?W}G()qn)qX!yT??%20CV_J4DkiZ*K3(wouGZU%<4nD<=o7aPv)7n>W1%5J)OwKC=KW}2cMG6!JjT2^hjgO@&w*?JMBlu?JTJ% zcqtNZA4vFnCN)onUGR_ zvbA-lfo1dumgg7>Y~LTcRa^KXd@pX?J8K%h89yEKp;CRJx1|+jN_mvyapf`wJM-Psc zy`r4(OkvJy*lmR?V4T8FBifzH1F{Sy8FUuvbssyL79MRsL|nBuP6xd z(R)Jo;RExdxm4%PSJQqWOg{bsID=VUa7T&T-oFledvZUIa_|M$k*El|sTrQRDK%k{ zOMaLZ8-8JiKqzkcET*0&B)^bS9RJtkIN7u|*HnJF^E3}aTEKPvc|_F^VX21uwIC(G zfe$GLPqx)GHhb?|NcS^_vhly2Iv@luZW}q=YX|O7%m-OYo<|4i9C!O3gf9Mu;NdHu zRCJ3*qxhk{aJTqTZCg&J%uS`}^0HS?zqGbcUiNEdko&O5}@$ad5DBbgc*u6$6WcD0TIALM<05 z`6rBNN$Sv0$nBZ$%-=k!zJfOq_j0kdbR3&eq~K)Nj(?A~Xj5}UkYG1to;`X+*2v?G zB@|_ufXrF2I`Fh!v?4QoBu=*RDXdVhmfCl<2dCnCA$)W9XiiA z?r_*@g|1zCU~Z>FA&WNf?r|k*B5;zQ(xw_Y@}M)UOn35yo;WS46&o=rOJ}3S#kWT5 zl+QV(K#f!$A6B_w%!cC~#~J`tzcm!BEZ(p=_%U`2H;Mi4s{dbdqa=l4$%-Mee2ql| zB6loWy$ct<417?)Eu3$K?_(M5~KyA-7A>-8qy{eXuTzn z@QJ=)EB{e|dfsKIjcpH%r+($SZ_vGrGL3=s&U;gzHw@(ZsOWuv1lxf@2EI&^_~^!9M>#YMEBV`09?xWmKDeS@j# z=a;kkK0e(ogtgRXF9z#c`>7G_jcCGeen{oGd*bQ$k+G*V)jik&P9z`SDJJ|jWiZ># zJMBdlaQ!uXSq`V|6Q=fxCIuR0x6*O%nO_Iq=4Y1bNOV$el#au2m8l{N%qj8tcZA-@ z1D>9><#nFRlU$2Z0)qYuu>q160a98I_5AcDY=pY~vQS^Up8EQ`$v0U5P3Vml?cw6P z?}0JDAvE8B-R=hve5b1dbB&LiH%r5o2x`k2(f1Z-phhDx`lHFVvA+h!qN0edX|^QA z8A4l`AT!}KS{kwKLm!Gm?OoRFo#~ELt|?AvAkFh@j^5VL9p;?sy=NRl{C=e|@$NPi zUkn^IrW#_W*92&g>WT^6slsy3fOpUX*X<=0lJ$8vhP_1SC4E zEZ8DSb<@WXe(sYbF4U}Oeidmk><0FsnfY<%1!m+Fvjo{vX69rpD$P*T9*3p|+BbB* zW|bT1@*6!>3T}e>zaf3HDvu^m-M%u(UX-E+Gao;2x@((}izOs9h}Tusw$o37&#y`6 zT#)nBEwU6h>ZtblSJy**vqf1=5Vt^x9Od&a)`lt`}8g0t;dB|94fU%SB^+~`~e>>@W}XmI!zG8wKJyW74$4HKRU zLZ=NkI(QiX{ls~6wJ|-(`l(%IhAw}5ql|5oAL6hSr))4X)=&KU&g*wDTRUQ{KFkNL zok~_zR?-5D(P08T0s@4_H~4Z!J`QGMg_n9WUic z_ItgWz+(|+z;L(BoQ6HO+s?N3LEU&ui?r{g1dGTXKyM;(ANpsJJ?W=Qy;e=xBe52}x@&umi%oFjS zyL_R|M&DE~4tu7Omp@nwET2-QT$7R$ldXCN-9o=W<}K9N!yH1`FEl5-B`V6p5$$DIcEwDKHnh^FI1UzqEYI9g6PiVhDCesFXsL~_K`a6837p1-@>iSJ!09Xqq? z_fq1G(73+5p_s~4WY{#ZEt)51==ai%Y?AcVUMvOu%9*mSEENKz)|SrtD@KJm*4;+t@Yw(NPgK7kiFW$EdmIo$Fg zxUfUKD~YFrkbue+n6v@;f8`lE+{=63E0&x2?J^Awk?3n-jc+&|P@B30YA)H+Qu6#~ z9H(gCgE;Q*%lX-?@Ue8vR%E1EX+NPWnQ4lSuH`^ek$Lv*SB=kLx6uPKRWOkrU^Vb_D~W0zT>3y z#!JW`;3!EZNC+Z<3h^ZEeiXD}`DIDRv06VyYjz}?+~#rr(_G|0s8^<|m;illg(0pE z*+BJ2^d(~^by7}wI+=NjG7}58*w9yYIad#~6iiR}iyLEb>Ly+%r?Ta)$n`F{p78I^ zR#$KHJ|kP`KQvGlib%1o-7zCzSP(;kAhZ_elJTfY(O{OA7|Rsf9?m``>Bb~B$){U- ze_H0DcQQ(T$yhq@-CwkmesnSV&|u6QHkp@k4^nn|$-F;Z6VDM)PF>v=j*Vnp7BV$pCtdjd48_>Ybp zXf#N3yeyw7f~$3pO5W|5M4I`3y=Ur4(lz}dJ_tg|9V7r$t8_(2hU2vA-w@Up7S<`x z7R+cICj*oOPDqASH7xd7PDZAtXV$kVGk-o1rrPy;(&iOK_*-<7` z5Hjz`^|XLlUdJ>F!kHif*;JqL4iu_T-cB%@d3JD>^U;i(s*OXeH-&UZ5=j=*_3W*_ zJyz7q>IVOxd2$_`y%lL0{AS%Gv}m;1KL>32-senJ*_L88F!UcH9neD1ja|iu>Zoeu zjj^OypPy8e8m-!VCrUlF>`=(0={2Xwc1he+?_9{@6OqOdd!o2(nN>JXN*a#E0m0t? z362-@Ybb4xuBE@vRCr0b&`2c?AVg8`R6Y#a@b$CGZ`h~fFqT2oAEM#-lpt4K5*@}u z5W-y_uF&*`tWls&OD*ixj@B|hI=?X99aJ|JYOM7+hw$GJbJneA z{f_q#vL>8$gGhz~)ADmKUcuUwU<;(Fx(BS&%uFv98t>fEmFb_79)0Y};qtU*-(B6# z^W@$XNkMXfcxhtqp?x2q8TWe z6b6u#o7)s|cb=dk;QPNN_)sg&A&w%=VY5MrGJIT8)AOz<BaYVjp4=)Sf2)whyoobM-n-$y!$6=0ma>s*%B|`=4@E8!po>0j z<;nTEpRG7^0^3~aJ}!0ICfgyzc=XlWh3=WOnCTd8{RWgQpQzMBsHU24Uu4;_j{!f2 z74l2LVNlsL&S)HgN#5ruTHcLpE!z>&}H+? z+UZMK#I~mh3&jXnd6^Z~En{b0rsUGC2Tj=4OSZjZUd^$sGBVd2yVY}2>LT+Bv+=2z6|BL1n@ zn_#sU$yi7jD<5LtD%$EnFvJLLWH?B?%mb=a`fQl#BBk(z-Cg1+XA_NuMJ?OHv}=v1!+83NAe6f?Ay?4w6xjrVG5II8DRr3$_fcz)0gkL1ShJ&_+J6p`fH z$~-C?vBA9;F7qC{fY8(|x0DjSU1NWvjKyw$-K$dv75RZHuH@-keW6RgQ4MiA)&#`< z+=5Wv-8HJH2V3jL`R{KN=e z<^w8km1a;z1u9k0g6*LPWWZGWpV!0@ye4qH$h{?k`bl8GOR(*5>!H2lkB4^cGvKo0 z!FKm`z~h^cnibxSIc(UnVLfmoS_IsPYSnYI&tDB0gKaBcqX;M6)JGcul3RO*=wh$yD+K!*QWO+rr^#(sWuDV;yjw)4JK)|m77YkSnIGr5v}(K=@d@Et#ZQn>*g-rIOq#aRC;G|*_B7ZRSl2+}zM~^?$MsAz#ub1rbiJ8*`p7N@ZvUgMuK zU^k|lFGxJkCoRiAPxblRR43n24OQUw$k+IjWQS?0R{yzhPI-0-2B?Sbu~o(M0nsdb z?(6gW(q?F`2)tIM>&6FYRe*2qfNxR;Zv~?VtThCsvrgjClXf-E)84WvJ?4gVYIyPU zYWgdikea?Q2|^0SLddM1JZW3#SHo6K|wsXW5}3iZHWGcfWK*egtyTqcSmmH18&O?xnZr# zA+099l9eN>fl?@;dmBW z=SAmE7h8|`&dyifE=Y#AV-rp8zC_bQ(m`^X_+vF3L~Ukj{Pm1njTAX5=WyOwA~P>U zF<>IYNaxwjs|6)px5w}s;v2rs1Nu))ut`jHol+L}dZt?Ef{kdqi#^>aO?{*NOW{nD z5O3o&d38pjpuaFW5#p2wQ*aEnEwO6J^a4rMevG+7Q< zm{nyjJ`K8J8gJyS2C?Yj zp%-My!~>fzn8>7VWW&Ns4Z}j6m`_)2nV%mvaCe%yFB?ibCrDy6oMUoW^@+}+qfa0x z^M4VsTi9F4r^Ea7u{o0oQLoA{s%ekF*FiP zw)6{qAmEeV?wWPU>R78Wt{HWxAe-vNiL#lX#h$qAX6gPGD|ne6B7!=@%U+9N-I`pa zgN4g#VH$4QwH2R@(JjL0aVJE_Zp?UfRY-PB@nKL_aeG!l$Q0i^Bh6ux=r;*mLIn@g zHOb<|m3Il~3+WC)WzqQw1g|hNkA#;!%Zl4y`8+1tpPlWr<~MPOAAhhzSEXL(Jg7`w zH6$N&Cizr)#*A7SdjB^>3NzmoUt=LDL|S&)od>e+FG)v4Tz(?Y{%lz`8<&6U;mLf& zR93xXliQcYOyMUfr>H}!bv90q8FaQ!pMG!z7bywuweh*;lav~_pB&i!;Hz|T9W`kD z%KFvvkTjJ{qMFyL_{i4hZmiBt!<<5Qd)qI`FK zcv=p5mxrB%#lOc;T%~*}m^MDlQKnEUqf^gU4ymv$!&73*zu1ZTDRR;j9-dmX#p2ZvAxwpS zrvMDPeWO$|>CSgLO%6=8JN*%!{TxWlhZJslWS+%NZ}qS9e)MxXGQHdX5Z>()-*Z|{ zK8MkN@vZqfc~lx-CjQg7mWggC-*o9w>lZZi*#KhAK`9W`8EDl2m{1r zJ8?RlsP&YmP(s{5x;Q2B(J{mvu({D{8cguI!iVW3`Cs&oZ#M)RKCyAqJwuo=esm>KdP?`0q zKk&{Bv|+^w^AtWt9ozyTxwwavj z3aG}mx<|iu4c9O@pL%4LR3$0l}rK~r8*u!C@P_{>Sx8rO7+djx(Nhq z636^oqBX8%vwD&Nw!9T>JK>fLOCsVVxp%-7?k@cG67$CQFWK^+2?pdQ!4K~+5@dV2 zhPecn(t(~CuCHLI$@7JZ#fNbult4soMAB3!?v2z}5R&|$mn>{cm zm9c$?(I&0w<#5WseBrXGQMmtbpn&g`*wK>VAK$n#{sIhkUP-hEaJj zD#{+~<;v(Gk4j-{q)*#`n*xpGK*Ksu1-!nXz=iSS4lqIdtEn zDvuG^uu7_Zw;HPf=br{no8J zt|Dbdr4v6TO@5%_v?|i4^(sTQv4VdACG#K0RY1w4i^Ar7vO?yQ!n*lQ{41T`y!n11 znd18!=8%A=yEnWKw<{;#XlfrU%ixkP3F){m)o-7D_b1Dm>4>w(RskxemU+W-8#D=T zpkW?ERWMSBK8!nzGFsYLV^h+({-m+MQ>JaU`~Hlt&ULnMV6#KG@B#lK8mWjJ{ZzEp zOHz_tOd`RGq5=z|{w)He6p5y!Bg>xir1=B>3e4_Ncl9E<`svppPu@#lnp>DGGp;2L z8HpxWiNW0kLm}7*r*_^)+A+FH6(#ki zaaqGl>jzpD1Skm)>Vh3A@`B}<@ln)#=ds1Yk$b8~WXKNc0m!TcM|-#>rPL-6FK<)k492nY~w@E`Jf3L*(XM@2uMo;sAg_eSX;V~l%2PY2?4>i4j z2tSuF8#fOZ90&pi1_m}3HVF<63Da8{O*8I zLm&t!09!c1fBi#1L_$VEMMKBH!~zX!@F0i?NJxmtNGK@C$e?uqsD~imMZv$%DUNzi z)fDZ43jtSf{2O#yiK;JzY9o7e+-9yJ7??!FB&1{yAJH=~GV$>8J?0k>lzbv3Eh8%@ zudbn~rLCi@XKrC>Wo=_?=jQI=>E-R?8~QvfJmSU6$b`hCkB(1H&(1I4bRj^H z{*d(#W&Z^rsHWC659@1S16v7t=>x~JWJ1C>J zhRzWLBscMjk81ry`we+pt}|z7`w;|vfo93eCa|q|9MO!0HO0wGhpT?&&t*~eD4#LZ zy^aA64IH8zy(?&cQk)FY^GagNOfOAVbUQJ7sK=>mp*-)dHs93Bf26n=-9~=n!;3US zeo&$OUY>u3dQlw)k?duZbdHn^r}q#QdIeTD~VF%axlqH!1Ipx zDc4J7^>a1+m!ENiiS9Bt`r_wt z`}wDaMG=S8OEMo*P1atJxJ?}wWw`C0kA0-RI*B^WcZVHLvR?vE(UpH14BfxhQGcog zjlR)jJKA=kzCM;Ugsp8r2jeVh)?QNmpj>Y%FkTpt_5UEy7O&2Nwyp&q{>P*vbL>`#-|M!)8gAK{(yY>(ic`@umFMcf4D zU3F6RiVxjIWG^lJ*)q%1^yW7tr=*2bk<}^N@y%5yI~@9U=;dlE(&hNfNz36e?kd9} zYs=aO_2n2LuzJVAXroIlYcQXuCEOw$!U4#)N8 z9&W?dPH-=OsRiVX-M|*8_c)a&WCa$Vnqmu3S8#8&58t@YRZrrczmNi^?2)2BY=1+< zNnpEGG9O;~$;Mtnn{d10=+Sn`tfw5%t~!9u^0||Ks{C>TY=h33#aZ6>dmwj1paFIB(oq-DkRH93oC{VOXFT{sVknO_-^1) zpONJM1UM^*`0_(z$PtbPG%k(6MFHp@Y|iYJ(K_vo8D89_4a12Vt}Xs<3UC1@$Vc@Y zL@~sXl&3zt8*y`k>2;Ew23>uKdws%qNs3nea-{fWrKAi?O6@&^E{MWwhc8Ly#Wb-w z^1ZK@8?x52r4ww)-I_cel>i3X<{aTi+*}g0Og36yBLhKv2C|V@i)7uVDhkTK{HA_s z{1iBzO`<;iS#a}x4EpP~7e7Ya$tU&d*ZJq8J}~3Kw=jEK*dI6`m2-3t4$+ShU}A52 zdIe(F`j%Z+6v2j-s4urv|4FIeQuO`1$k4jn%Gc|qb4}xI2kx5?XTUXknUi;~fg~6J zFBK+a0e6hlv&&&j*oxPO=bBF2hG1Lm4|lf>h5kHZ7H3X#5>NlLb&a}E$ljM%6QB#b zMVA4jTynPIux_QAis;g_wysbO^8%MuL0t>H)e*zZ7)a3qCc;^56J>$BL*1JQR{kCl z3z{v}M<5~}dOLiKC~qqQ+5RPeAlsTMSmSuuy?SV}*3Gvua99su95KSGx1j%m_Hr*f z=9~=b@oO&r?Qh7W7WEMj+$8~yTKC_O0P-&~&jW(5VyM|qm6z?Pw-u=Wbcm_EEyVs; zQY1Qb@=2xxWyS$d3wz6o=5I*#)-BTT4$Ch<%$vkjblk$KS)p(di#~muGpPkTx*G-F(16c9zajn7P#~(U0(M*cx#Z2O zUk4HAyb9a3#z%MK0NMey0Bz2{2X{Q%x#VR_1>*hKoCHdG1MfQ3hwG*(RE9mLZd_eKxH5wQjCu-Ef3K;>i1(PB& zYnp*mV0RY^&ac6Avt1id=>TrHzX3z5VNtj30!+toySRh7mkyOzH&Zueakb!{%+L{- znhLiHDE8@lfhB&RdnQ#h6K>7q-!;TqbBCAkK?v_iuIiT*Imu~B;5Psm`W|2>k-AOv z8*+F~eB0uVcNQdASqd6lcz|}QSA6IBv2kxs-DN=_;iE$1k89C<%JQYJ5BLaNkIT!^9d-I&E;jo*!1U8srqed@#OEWT8qf>ovW6W zT}geb%t{#vD-eN*9|n31D!kiB`nVwYn0vwhBr{uJgSB5%_=z>9?xNuTKf%tuG}uZvw$G{&S49qosM` z{YQCusoXn6Pg-nJFIVHL`>qdF)kgZeEfh{(!#>-JjV6Q8?IMhWd!}QJD9QJII6IUdCy( z^GYyDj;yu-G@j-qx5CnYLuR2_RKTDL-hiPV_t4^I|LWK6?|PE@KY8QJ?!zKg`FUtR zZ1?e+4DggQ>ooXUiWuR>{I-KQ0CtQaZ;ARAw9Fr{T#;UP>ETJI!NdU=VEWU%sG#2v zUT}kL?zT_BU`v8`$k~6o|LhKDgOrp|Fgth%B>$l0LC8vss0@DyCMf&^V7I{p$I}?d zDjz@vyakMd-lkG-dr|!_oTvW*e^;8l{Ul!;0ji2aKS&R>(zIi*-1B8dZ2SakR~lc}77j4wo$xwu71u7t~3kwQ!k@M5_Zll+>GIA@( z$+EE9fml2*BL(tOg23fQwUMXdK&2$ckK~lP|Hc-yfV){!$G?{@??Dg@4=B@j}mum@*+(H?q)7%q$eooEBoP6e@!&f6!|MpcL~8ErYm zbQplpK+6IIAeS!~Qml`FJLHr|h&;R!<W1OGyMSf`M$sFaQ`SB>YIdrz$k? zQUuSz>k8*`EPB&*?ZWTh^4nw%{eqZ@BH1`C+~}gWT;;wNjgH77!<%*}I0;QXrBj-b zsd(1uVaNiC1re#3^-bMqgK{kzlTrvE3e6JsSBwKPHz7oGi9nIUbOJxQVRg&n6>R;WgWiN@nHz$E6M^xH zmPer4_)PtA%hA0$K!;wG!bcpTY(r@r-_$F{jJXSpDKKB+z;UoTy$N{*n?ubioQRv* z@v$V_fbA7IL4Pr#UGLdYqq6^%s%}Y++-z7$<1uU2z9DE{yGCIJE1OU_1ijsxkYNDs zqg3Hlxv!USQRH#lnOYt75m4x^)1eFU)39MZ_RAxi6+nfTa5k_CK2sa}QwHk-P5(*9 z>cW@-nu=1sKdu;cVZT_Zug{@N@IJJZFR_?$3j2YLfVOc{Pn5?uVJ6&K^UY~t?7l19&qMU!x5DgP$7p1y;^275OQ!-nRW6?`^^@*j}M20M_tN*Ff74_xzI+0Kfo- zLZQ;kKYv3Op&9|CBSS2im-2k$@mWwT59L9ZAUj4!Z2NYucHGq_yUe%yo{z~s0S}58^yxo&-|5g=Kts1ceW^+Lj~Y)SGMEeK>Qwy-$VB0RcHT1cjB?C@X*ZfJ?+4 z5bzSZ3r02B-vpRNYgfbC65%+1$~pZ7i=o~t*oOULr@y`cn=0Ufe{$H;^uE7p z2e<>ovM`JJsf}l z|2=D~2kUL0+41gTsI-FDpk1R({WFTd1oT&K{Qz|7*E~d;*9?Y3CiM}C)1N{3Z<)W| zmRW{QKl&?1e&{cbxzjcHe%5V0W0}_*i{^I&Gef-U_&8E-B#}4P3ST70Q8&G{tcD|l= zyoynKEo*w&{^NU?d3o{vGgv2bjz(n$FI)T#?@?;!QXFEJh`jaynCw4Z+3RJnD~~V5 z8LNv%GBV?D0pmOI`|!m9!uRHn3&m7K)W;Joh2H7c8z*z&IS0&@*B(ymn#xN+%ggdu zIU@7OH<;$s`B93*n`*vn8uzTTL_lIkQTKl+FgTtf6wgAU( z&mK&K0hzGjDlw1Px*Dv;e)uTD#F+AQdx+{An_Xf2x$-cKsZNDY^YdqY!ssM63+EuD z>G5obLGBYUGs2kZJGle0M<-Pt6J+UARnMKtvIh>(wLg8t-`(G)+G)(zE?47jzkb>W zLz1mZVpu_^uP^g>xnL{2%_Zj&bI5HzTF1o3$Sic%z(C3#Ns${xij?jf>to$e6={zS z`zEaBF8#eS?eD6_lOms5lT4>d%Jrc*`uh69US-%p_i9y>pZR@zr}e3)scFmIy(Vs9 z!orWy+!{o~E2MQBueAGF!VtT-$gD$Vn_g1Pz_YkG@LHktZ~ zU#X^iJImGx()HwYsU=P~PwqQs^hYUWU!Lt7DBMqLY?)C*>ywpAXUL0+mNyuYLaa}! zR(*=H2wznqa7qa#yh0;z0ZX~+r0S$ruxe#P9FZajV#sf1<-lZQbOXyS1JxS>wR#;+ zDK?v8XRu1=mg)lQ7gZMwDZxn8M>csW@SucJ9pI0cG9=Pq)G9biK`z~36oFF?EGomE z=S7dCOAvv_IA1DsF~yeaZ}E$DyjC$)!a#{2Ew6297}GVS!MD&l___R++XUh^KQbpZ%jgrglN2K<*%KLTN@wA@a3F6h}j;mjWL_R9KMriJQ zByCS5B5`BPf*d9%NAl7AzHGN-mzyf<@k~e6SMu)W2Z`QHF7b7-pRR3IB_5yU7n)_M z8TQ4ovGYAK9LmJ*ZaUvr2fI-{Jk|{sIBfDXMzLr&(OMy6-ii4E0MAE*%U|^vFoc#p~cy%oNzBC9b`C`?ZU+ zPK5i+qm)|x^SNv+-B1$d`>%a_FeZ6ft67_#atz+GYAmw07#c3bpRHyU(H={@CL6?7 z<_BF>ub+tB?de^EW(mC^JI{|C9Hq}zd?esVp~xmMDR2>BrzpN6_$;BbnJyXwvioHa zN@12j>h&O{LA7N1Q-dy@#V1o1ntN@G%eW;-hh^Pbjgw7JxOgI?o7}?sP`-fAqP%|( z&L^O%GGp3Ly=2>tFL;6S>0m-yk^*UH`1>|7A~mNn3OZE2l({$!t=T4_HDEHI?`hOQ zT(gm@2%YL#EBAa!xUa)a@}Xn#cj%kSLQ!^bewsnOaLv5htXvM0)E}SlrEuBYP$?)Z zD3`BS9bhHE++xT}2qN&PbU3r)TyQ^)2UEzaO4U8>$Ss}<6o+D6|4vt>Z#_32g$#A7 zIM$;E-^=-&A1&YwH|sS&JFE2A`w2Go425t9`KI`7Cob zww!idlp>>QYKofWu&f`Bk$K>zRYokiPxoH$1WwIIW4a|li_LMhQ$YGZee@g zXjayyB4%~)@sILo#7Ml2hA2I-F-7jPb_yUvCl7WTp11KlATj15lW=OZac z)L9V0Nac*-K=i>7AdR*NmjuI z;3`QP3Ta;CQ^ZI~uulWDX!zB9RAi)amvnm>^7kx}vcF`DRizhPKI`Ejs0O+6=XO0e zH|gT+*;d+>A`;_~4l!-x7E?ypgfbBugz z{F9stRnU4N&!b{r)!8UWS0o>a&G=!qjIUu-<~F6(hso99;^n*`r zDl7Hy_hUYz2nyeeBt}MCX8Z)Agh+U7AU))tTHmvNL#TkCV6)hox+35d^mD+bKeS6x z`FSJcsPw_hLg)B5R`d{r1lwdkx#auf^9d4&%&u~szQopo5A2ysrtHOOM8j+%M;AS; zBZ)nX3T5P{-#6D;!!mmNe~BeLy*NE82)TbJ!^Lq_?^Dd9J>e^t&@qBzoI8G~(bRfI z%c@Uha<6zVPv>?jk1Hj2jePdTc8vpwFMhoF!90^*g~jv=1Ckh=ebkjrfUT#2pmaxB zQpH%DZU7=}(eaw4zGwa$a!q|*qium4y;NFuC>`F3n+ah)2C(@`>8fHpbG2F@04wO_7J>zAPO@EQ&G* zCAL~Oms3nTdr2u%^J}Oz=^+>HRW*cqv|JPN?Q-C_*mq2Om&^n4fqojeGA>)#GfgbK zn7ExXUvj9;I!2g!og;9)@opkN?$jc?m_GD)fREfh?Mp5Y70v zOOLvt%aH@;+3~Sb-4f*fdU~WvWBFWGYa6x)Qyf;V;{21M&z9B0k~a9bQca!TSuCyU zIXvOzO$t|$k>Fx*YP22yaT#7hRT!IWu&FkCxBs*L4$xJQNQj6b{S@TRfCNba0{ zp^Dj-U*gF=nB|=wjdq;srss*Doswgc{!r>^_{A??1Epg8v8d0B9xPeRQc}a8_rn8| z@z+liZ!V13RH5!`6nshu-Bn)-`@_ub1n_m*yS^kHJ;>5HDk+Qr&Oa3sqIV3A=vLp2 zk>kfg=#G$h)Hqn8(bUl2N-)JJ^%umg1y#K}p}QNrsV3*+gwMPmnz2mf!n?i=_&+*6|j7aoloKN`mJ7~shD&T-o&nLb(t+RlR4KH)4LM`(9 z&g=r4RK3}%#7LP5uxYj4@)IcwCaZ$+T>Z=^q zD}K`D@#eQh`Oi*Ox?Xg;FjZOd(;$g;+>F-T=WF7-nrBMz<`+0B6ij}9C}-87JMBq< zt!1Y(-Olqc&+FhcWdCb*tt?GWUwVq7pt+@QQvo|Sr^YN~`6lTd%2!@yn_=#cK@+sg z0<9aq>RoRfEe^iDenHx!CXGI%H<+PR-#Ag-hoCd#YV7j0i+j71g(ww6`1zFJu9OPC zc#U)Yx>UQm1oNeBUbxN*$|Jh_$MZSAY+EL^nsiTVJ4spJ&&Qd^+Ozzs!{B|}^~H43 ze;9WrAk0;?p^Qt?>rv8ohi8P(O}&_-@i|9RIf(D%JHBx>X5lxC;6LuvFSo5&^Umkc zmrq5%^A)=_`Qjq-hrzbtoFBzv)((7aIsD?C%Y%pe8x&||`sM#a(T*5wF&TbL{N6$!MGCXgWGJ=vU7?VO-bzDGT(`1PdM zg`UdC3DL2aapH~9fhhbrC zrm52Tc6|c)(PC2;zabW*HAh&Cac)Ft$Zr!6PI*)P3Jiqtc|VWvB;d_wQ(_FQGD+98 zywoER|B?f)ss$&rCXfUsxp`rcaTvBccRbOpnzQ|KW!GI}tt-x>?fArs*-HKL!{z5^ z$5f#eHgUeT(-MH})-sn8{V9c&-VQvZd)2QH_?O2(oOHEwt2WB=DJ6u9Z;FzN*{iF% z=B@}YAy4;~@qy*|sJPjVV}3d@hR|bCD-ERWP5F!9+|ySw9AR^JT)ISjVN9_i@{{UR z-%aQoN_1tBuVI+^4#kG&j1LFLoUPQBDX!jfK)d%#Qk5IYo(t@;rFc1Ztz-W-J(W!Jdh*G1JhS8l;V}6(a_w$F_-tE2H z-rf7$^E~&QbEDmnKRrG1obD59eJ`%j{MmoyJ6=jR=x1vC^tIadOuQJ4c2z*|NtQ%~ zkRM!QtR>E?h}R#T07Yrog3H0kO5=P?%pAnPBHxD+`)`U^7rz2KLdnO2$k3%=Sr>!e{r8%^pmiwETRarPp)-M#Xn%9c(WT~mxXG^u?G~M0J?BiD!*HNc zFmVz0JLS_O!%AegD9%Y%nDgS6Y8}(@wl>Bn6Mv3thP#W*K^BgG|AccQO}V#}D@W^Y z`NO>bAF+R4 z@QdnB?B;D;#yYU`yFSO_-nni;CjFharzu>Rmv^$w_9n?fyFrW0y&|_8Y55 z2;M3mthhLXleFu6nfo)8Wx8g!VLVUCo@rzU-JXpEZ*=|GOlMdiB{U0Dzy}_9Qgw0o zBUTi^Nm}AMt5G*ItP~bi&qmY;uRme0)wymRvYUmr5xjx(UuZlJDC9H7Q+;Q^QNssU zYOQ=l%l~`K`$R6%*+0~;7+-jE_vPZ+{zk3+MxjO-d)B5RW<2t>Wo{4pG53d$7Ifp% zW|XT4P6lfNkI?IHo;&oCOnU5s3n!)W_sI2!PPIGE#d}67j@7e`49!Q%^L4bftHdY` zC{tw{odyN^+9W=4?))wn9o-51PV)r+CbfG)`O(6!kP0+>v>2Xjf=lb?G)GNtyr0-c z>Yt%OAsX(*dW^Mfc{|oRk$+izjOcj}>yT{SMXd6!?r2^)ko|RZNO}A$Qp#UND6T3# zDEmKVs|i!kI%>4I-mUbSo`JC0?t1OReT0`oZMjE^(=9JFc{RP{Jeg?`S4%38{v} z%0+s!@90MQk;Yp}d`97TOB6=LQOm#d*%-8M;0sSMQS4li_t& zR@|aE@`ES&A0?YejI#=69K6oU@_9{S;wYLJpfNe!!y6+`25dXLz9nwr-lua5~wO#e^r74{|p^{X6q~XJWrGD&-;_2_I^;en+ zxPTU7_4x!^C%R9d{i(kS8j}|EZ+JP1qjl|E@Np%}-m04`8fXC*5p{v*gN)Itk)hPo zR8yxf;9p*Vb&y2F8PN$cWw-pY^L$vW4t-gd?b6V?N4BQ-mx5DBOG!&vl&UM5omOE$ zf_R1ZN_OXgo*oe!dvX=CPf%sV40=Cbg;iA0=e@@)TuwjzelA7q=!W)SV0Dngr1i53oj4NdfkNJiP<#UVhNLkSnjxgwWq+j#}O29rTo05vj2&SU+`SV8& zKTPQsSS!lO^t`KG8KXVM!0f6s=Ef`#e=pOiT6H37(7TJ<-c_oJ>>q@g#JBwQX~Dw& zu~%$vla;CUL(=U%Xh+CU3TH2^o<{sO5G;aZ8~rz$1O-j*}Z%G z#-A0_ys4F^pK*#v`&lzVuW{pIBC951$!>+dU2F{woW001KY7LTBCIA-G&8B_jXaI< zWbg|X<(voI>e29W>1ubrSao~nOoE70rTMvDMqU$BdSmxhnNW77v^EgOE~zVmvdHrj z+PKYJgHVWfzO+vqV)82B%H)I;O1No_VOtqiL1$wXn@`>6jSD_e-VBaCI_i-s*4(aH zy8H2Yi?LHYa0K#CAnCnC{&G@rJ#|;e{`P>efEg7>8b>-V!|BoyGhSa6QH93~!5!Skf_^s+8X0BRj>~Xiw40R54ODPSfe$$B0iK`C^tdHN zykAvNJN72?J{#ZQWqUTp*}VVD5QPx2Q4qzWudv@JUM!6tHy@13vm#n<8xkwcVILpl zmhaJcoK+~Vjh(C_HvUqyGj~(>hy=}cl+RJb6>n(rrK3osCnD{pXwcbRW*z|}gzO^mB8@H?K z6ug2$k@|L@4NvjB)WKBV%eYDAX2bzjUr9mD*DJGfil|}6((<*$&>oQ}l}JiKlCk^* z!;DW*ZTcOkS53q4yVbnT;h&LnoAh5-VqT0$lD+lJJZQU31eo9daPRyk(btju{eT&Bs^eaMLYx$&BD3mF6DHpXjwdok9#Q7t7{3`7=m-_?z^aU{eOWsZMF&ip2H zW$oRbi6;G<%le|q!b>M8hqNnZD`YM6HOA4ufyBbQ?aFbDQI*L7)@ZKq-br+L@uK{w zJoSY2a_k?(tG4*tu_*fc3AYX)h!BQ>A|9}b$<5;>Wptct!t4E5(h_=}ysy8>J1R~AA0&0#^MtT)p=4p4dT6HGk9ZBMR~P+l}crnGt)}8L}!6R zLMf_mpB6gaRd-V|>wVC$$Lh87e$C6jxmcrrVb-Lilr}u|L*B>nwn`=G17tVn0W(r7 zY^67iNntTqnFH0jiO`-(Klm>Gtp+9aU1_AhlgR}up@1e!WZpz9bF)oX1Z-pVc4A;d z<`}OahCnNT)qCw7aGxQbm?tvxWAz{VIw^2N*?=X+<}fZH1VVMxVE#p)-zyG@c! z-Vi57HGD#9QQbK%Vz!7K#_HQ=zjou*r<_%!U(WyKwFV!*yj|nELN-{?Ywt=@$nJ2{ zY`=3NMY>q3>Y!um?wXVB=+*#d!9-ZKaFdq0^9V=JlRzb@3Ws0IKng@iZLG7oWRxyT z*AvmYG9(E**&O)whTTukdNsD4WF2W4_>VFO|=-yUDF$rQ} zbdALnwyvA7fTu#5p9YnCO`fwdCP`Yb`HxG<<~v)bsSUFAqv`!n9p&6kL_t4?Kzc{SE&b&L?U0Q4YV=6_uV|9k z)5M8123uDjG4~5ya=GxRqME0qdAU1^Ef!0uOm8NuEZJ1ov}mFzGr z&!g~@<$cRrP^g~7^IY(^nUDX>q2=2%gRS18GJN2~!1MEKv&M|{E0d2tR+Z6Y@sun`z+VFlCM{h3Hwry`sv33QlJFS% zw6MYsaJq)?6hBShDN0ksDz&5x)x}8Vx$H)t3LG!h2uX<@5Ae|YtR9s?exWkZE+!*X z)jZs{Yd}-_Hyp-vMHS`TgHji4tJS#6>3(#QU`6wisPE`kd45Wed)`%IYkwZLrRG>y zR(q}!SZV8}s3W0QjQms7b(b9fp$Y%ZbLCBW*6*sGF5fd{miO>t+~>lYWw+&e?|u$J z@I1Q{d^X&w(EV_xfq2*wCz@v zO)8N|B%0XG<^?o;=kZ-3qEwjh4CrLcvo(p5iwo~%y zkGdTBq5R>l8l8=gNENB}dg}fB>Puh8yqL4yU6y$Msh(^+XrmYTLOgfLh4WY7hkpD8 z#$c9ICf|dtxtJ(6+h#uZA4O*V4f8yZD5}SU?V)VYNYO%ioz~k?EK>#sw7NY#z2&|P zF}Kxc+?Bg6mCIBfEh>$xNK*$f6mmCCUg+L$ZFnI!qe2dL%}t>FUC)UqcQ7P!hgDOx zv|f|%bWplD5;|aIpD54X9_N;;M?H2CYnf5+3aB|HVZEzbuAAH~daxPu{y-M%D6#}g z7+pD0#^19i%1jo60oAWM_#XuI)>0FESG>xC5QOd#nV0&kk&y3&jMk_LgL7yq6&pRRqP`Bbv^G*D zW)mlGL(1q|{T9+%JovD8sjG=vA{WDYz9d(?X`x#6HKTz(FO{VSiSKHtuHoxV8YYL! z<{I?V*7(mi)D8Ojjy6=#=S)S$BW|zEPxHmt^m)=!L}Cv8TjJgI$o}5_O%w66iStE0 z?G-l%K}CCLzie+7awaoS3IAf^&f)_fq4r7@0?XnaZxYw7@X=hy(iLkCh0n{x-M$g^ zRJ$L+t|ZjaYvx3ASuxX zNbWPvOHxI4*DL#A?Xlb0Kk=Hnvq=_ndOD*=5o)N_XW}ZnH=Xy!`Ti`=l(OQpEQg1C zU0fLKul~krFr*wI3^7cq9uJ zb{KS?*f)2Aak^#e3CxKB4EX$QG}FA9(ZckV@g*Li!mqw%ea?JY-0x zA#IJ$Nnptx48Bx`)xpG}Rf(6OMitUKicpop`MM7ST3(y_}4S%j#-N z*;NeI(__;|R=jCj;b^fvm2a%GAw>4=Ye;u;<5^kWXW!(4Zck{k^y!5MSSNp{Z82?( z7-{H0bLsoGw735)sc%eXB$QqDK$iJCJ5PB(kFmPnWT9|prCrNZ(%(L6s3NQJr(AHkEiXono2WF#O$Tf;dUdPuuNP_{<^DWP4sincO*egjXr=RsHiPi3< zJ7zO*;zH+P7!XbctxZ%cA++GszrF=icHml&y9GF19H`bDiY^-kXFcd+^}+v%1;umv zy6lQa)?-;#l#&rNUjw5MDfD`gfd?^uUZuq@g4-iL`A^=^hcRk!@6uB~)>Y>5iT-9w zpGZ}V(feVH>C$7dW>Ol!rO39Ku8-RhqkS2Y9;U~T8sp|bYi-w~2KT|EIW07JZF?&O zKRg_+^afthleOBrY-dJrBiU1eg?1%Rp!zML09@pe^!ncP zWT|+#w)H*w<=(XEd){SSze7J{9y1_RJXu~l#asLq6&3ohg!-F{9MHO{JfrhB`y(z> zoFLC5qd>@}T@hV&>|bwddltoCMLCid=t#6^F2PDPC>rAX25a1}+_io4yY?tMFpJ0P zOYvHsfcBbh%yYhn(Yx^!@p=~AYO&`d9)z6?xYQ~B1!p+l9{&)-J=Tyf)=(cv{umQWbeCO=;pO4dt_Dlw1 zj7QVd_rD-?&k_6i)H6w^g$a#M#rl_{UyCjC-#(vEnR{W>@nC8}DX>Uw`kTxTsduGg zW`smh8ulh!%Ub1h-y#D8?HvSJ9Cg0s!Yd)hR6Xu>yLkC)22}$pjvE?h9X*CD=&Ye< zIxT4owcKn^`#)04WLkm!LJZm9LYCUp%9}Dh_K#lKz2fT{*+j576#%-DipD!M?{B^N zsmWp??If-5_v;~^L=|h*j$xLhLzT~%@?AJGRusCG zh$s_eZ~Wp5Hj!vHq+Cs9>9kph|8`vuy7sWJkR5kq4lXf@D(dgDbe!|sPQ%BrLv(^e55Cvm~IIL(a|h6_KZ z(WjyMnPOMAc(|%#SWZOEQ=m`W3~9@exwoGU=c*{P+I-1UJ?b3(sO)foGEAK8rGs5{ zBlj`-{WnW;rSlL{MTd}okWMe}(*bW@CHB|EGHf)|mOmA%zg37Rm9ndKW$-Vjo)k8& z9x}_;zPUY-9Qi#v%i`Hn9)Co&K~1xlofSTNy;LX z4h3Vp9=7%`kcD8Z??mc-dyk}#Peqe5yGrWUX7rZ`H76382R{Z{JN%>zee;X(mKAN0 zZ|Fsw^a-qmrz!g3G)04(L-zI$hqc?~_D?5I1%w;1+awf?G}6^3kBJ3mzMJ(+wurtw znY&6L6aSKN;EChSxl+=SnXa293Hzil?pO%^0EkOJNgzsKQ66GVii4A8qWs;QaIE z%I@+(Yw07qPvK1lWhq!>OI3z-Fb1B?9?AOgeE`)8Z&tTadSbJD#)SinNBtkv(phcT901s&EOdP_CO&@{b+xg?$4!l1?dlgh|nG9L)KRl&J@&r4(>6{^M&1)$Ey@Fbk#6@#m>!=>KgUd4!%j7u5a)U zqIXG?VTN}lZ=;M!6XTk4fhQAGBvVBcmTgNvZpXBsO4J<=RneCEOU zwvDIda-Gu0)t|58-_pB~9kD8ZZL#YJioOV_u9w64g3)LAcq{3sZ3>1|X*P+EHZLGz z%Fd9bO`Pzu6yr2nqKqL$pX})$8zU2LbVYv5c@k0Dh*zWTP+f6!iyM!{wTezJ*a^OJ zRGT?Ppk6)fqI>^OwyO8T&`TK?_HT^S^?MuY37Z!z`uQ$G3Z-;mbM(z(%^Xr;TtAl^|J_bYi|A zl)A<^fQ#1Q|EdNjab?@l>vK~cf~pyuQh6<<=kVLKHX7M_y07u>9M<@c&qn;Y0RBqW zu>+1RUItF76f<4}X1!uLcop4@&#YP3?Ufc#PiKYDb3r#FtE^g^x%N(SrstAIUetWs z?}IIf7Q8XUa(JEbp7zQ)$gV+P-r6RTty2@5BNjfv(SX&+HUzWN#ir#U{@#A)TJC(6 zMe`40zX7xrZzsbH#%o`Op^^JQG-Z?w6iYzS2G*d6IOobUn`i_U)kR^WteCB}vCHve z=HS+4gX?)J3hSxY`sMHybTN(YI3I3NNZ`e=H2O&-r#?QbDnWk}uC!Dj57#zUElLW! z!?80E*k*+=)15Ya-p=1b^IAi8NVKqlF+$^bQIBu0>*!&CZr1DBBUi&-6^JKQTvZo{ z^QubX$rj>p>l8S7X?10Lk0TGCHcIqkmxMA##>&OaRb|0RXM$(*u*6{Xc_%-K63Z?P zG~NiIKVOmJrS%J9`EK<~W+hVQa!h+J@Gel!-Gv}?shSHQ-9VxQD1uy&oLV#x@0k!- z&(97~ZHHaT#rHC0wYKSW7FB+b>}^nsmpoy0mmlwOK&@5|7K=)J)QMS<6Z56F7 zQp%I~*-BGDFUrj|N=B-Ay^fxFQL!BLt*2n2FI(6$<;&|!5|5=Q2NZdiDGB*B|4drm zmrP-VFAo>T(awY{A_=csHC=O24Idklhzg$HQal@GVJrYOf7$6RjpGX~2n3}s{6AR7*veM_!iTvTQoV@^HI>cQ5@W?PpvkvCBJbl>dSHN9L z^*k-wcQn|u<4iplvrJJQR60JPcGRg*J%Ni!FS`-3F}hBD&*(h(rtn3OAgnflc$q*- zwqRnx4rVvyll@psm7G5E-If}5vXaZpj8&^0b_bQEPR-_ntJXoQ_3(3*dr%~q1DuRZ zk@)_3?shZ{W9p(WvA2cB#dv1SEcMUxZG9!CIODCmM^g{M73ublKc?+*5Xi=ld8!Pr za<9r+!JcaC!z$-@477OaLrBhJ$oaQYgU-ihgNmirLk=hAo!?*0Tv*s-)=s=x(Ownl zdGdtkgp-I`_#~*|owYI@?JGFu#<4tftL&$}Nm-|Ln`K zQ|r0u>}yr}q*q6Et1&5uR;eaxuAW-^{Zmp-gJ^gkJ%d|4z%))Ct5pd;Cc13>RFrxo z7M7p?Vz)d$5bow8Dk#hRJN{Yb>HXHoIunO5%;AO^8hRs*Xv z&8f@!H>#9-;#PjrCeTuH?GvK9upTdF9y&#swB>9?4io1`@VI&Ak|M~nV_^& zP_;SAx8lv_en8YuHkRQl>0iiS9v zpEtFZlP?m=Lof42$KW|#y`ZW5cWK8rVq$E7R6+cEN6tdX)4@b`yigsNY8WtPap4j` zkEp80Q7hYC(Fx!nPY4+`@R0>%j!Iein0(fY3$ zm46ToU(k)_^5-IRs`*LB;+nak9f1@rTmIa0Rg+Ne3%z?sk|HJRZ{2c4H)6xFR3A-p zrC@Y}2|K0x(S023_a5%_5Q@@VjIkMh{r1zj(e27;ZG|=){jhqYM40*lbD8G_sZ!M;I2UMEXzPGcub4CBPlGmI>hESJ4^h=fGpEj zn)tcr2KW|SXol5tViWcIWeN!gx$rIKUxYXV8Z?n(AL3l)KR#ML&UXgw+A-^@s^Pg} z_qG%?e8~m@DVA1r9V%YtRQe~rWr)cz>$sSz%2*&CHF%@v?wr6}oN*fj_ePs;loXGz ziNUSV0r2AgXwX$vZE7{*WP{I#VH{?BFQ0M*x?WfH}k;b(> z!Oo{WG93m?pF5nxI+!^ij>T6PVXNe$yR#`TxLtX7c3D3C>lBZ#`2q4(lz%#Fm5%_X zi7XMxZ1d$oQ_{CTGhQbbWAmVba2J>P`*RsWnlOeyLnQYH5i=p}=yiiUsy8R-0!;|^yuOpM6V}yjEsc~weqHffTXUWD`mVUK{ zx1IBo$o13N;&rayEpdREG@kjF+Q6|m$eW$NE0RrX&D|n(4a2>z>~43QV|QGnS~S(r zF5XLUc#URk#gjt5k1ba5B;CX)hOGVK7PVyu-ek8 z$s%!TpO3oqD)_N}6YIedSY73-UFE9}-EoxUrlh=K(5Ktx{1G(=knGYFW7DtYyDSTk zCw91V<6CeVOOXqG%^gRn@E$ox`-)N@Cj{|Q-Q*z?W2Y$F{iKfwuXc@}Rt!3d*ix`f z!ZZ4zYJ5umw*lE{@(}(Wq-*63NhWT|u)&$0uZ&Ncc{IlAFGeit@5Q79JE?bO^{T+6 zo&c#kJ}pr}4(FK8fvQZ%O0(jM?Cd7D-JX08hv^g|Wh$|m@sj{W71vIEG{ES*sw> zB8h{92&YaU^WNlbgxp#Tc32V1Ie~}$N_XJ?^5>;Y&HHE8(2eMFI$@;CXJ59+jE)D_ zh=Yj4N-{F9E~NH!c4*kwSWSdCyC12>I&l<3C8ZA>!RX2-11}EM`daE9)I)-iDBA z%0h;VNeMr1Qe@e!_=q?G89YxkQ~Xc@s^H*G$zp%TQfAHu%2X|M2&`1>udsW6(A#ux>t zfI^_;M);d4vMK??hvqz_+vx%EjNQQ<@26qH$cIY2k)LGRhlch3KBXLT_4roSNe{Uv z*4s~J(n89mMAG=;fliw|FXIj+TlCJ*N%8lo+&y|RD91a-!yC}S{7m#*5-nOqjmsz3 zKaj_oEDo3Km>O75jn|^!%vYxVTzBYFnyP#AkrKBzp*%{hvx=U`)UuBHebq|hDRR?p5kBz zCtMIf1lQ2flc;cL0j6+FmwX1?7O;XgHFjjTl!WPzE)f0yJvWb*7ZOMU?Q373EDpzi zx(aUbpMuk73%PLk&o4Ucz%Kf=LGamB#)ujI zMz6x>@BLsn#!`TF{XPI?vVmJ9667%R%qADiKxuyXXakK+dVE-(erFF+>QAwlaefb+ zd9l>mUzLL~j~153bJF{eO_*!4#m}s&4!E`l8hJLUw;|$*lDh7y$@rq4`FLsrqFQ)I z$v8Xe_&OgXv;@YgX21l>_sE_r(-M^O1a8TL5xyy?ERT0LAqM`CRR-&UBLf|bqJwyH zo@p!QqXO^ME-GcXMb3Y^4=ZL?A%ubeixo;6cnr`#nAyx#WUuUw^Kdqfb7~hsl^&zc zLbjiI3MM^b-MOGI;JSHY?4<&#n!#%cUMXtJtV_oQ7hDWWXyN{as*u4lb9esB5ma#7 zXRaJX00pfzLK)m+!!`$m1Tjp0#<0AX_b2F`!XS8_B;86qN?w~^G!+>7n}h)ud27B9w}ad)wZqiIxK@YwaX6m zObOXVE4JC=|1Z81dW1B;XZNt(P$w)#1~1uuT&45ry&!}PkH`nkG!ARR=lHx#f3|e+ z=3*)x77VdN)#SM^m2QL^zjgiK&mGY1%*J)!?8%O#7bB?N3&qtd$P-kFOgABWc8xB^ zt_rIb)gev3I@k`Gcdkqhrqa%IX{xkUfky^hP=?4= z!JaGs?cHEy)varDj|T#frNrt7RDrr@BXdzBGY2xitAc5%v}bhd0_il4y{No1@h*V1 z$3z@Rwjh9btpdyrBADVx%#L#~+(ZuM`qAG;g$(SdQ`9>H#_!=b1^QeJzBe&5lVTz0Q2W z9}u!d9z(n__V!?5NDFslL!K!P;)UVXy)wGc_0!3}0K$8(C{n5QlZ#>B04j#%Q5+cH zpyymm%IPdPk>C<-$m}RVjxpStXF7nu3qwL4mro?NqSd9&k$Y#uI5YnC3Zl8Y(p98m zfiH9RGc#6{LBGz#a0fPT0=}eK>oc7(#jAH0x)iIhJDM2v?bfZJ>nS5X<6~xBWyq3g zh|W3; zC%(1BfoNq*h^QqF-k70%1U%YdCCiwOkPFwRglv{Cay~*O`SWY#_aE;2;Ib{d$A&NE z`Q5UKQ?jHF5nOj2h1>pplVzZLufNw7Ck(PC#*{kRh}(1;)4E4q<+c98+LM+gcyePR z<0|j{1f1*FP+i~?A{<%H6Y9r*-;^g*ZcE5=(npf|u735*FNt2dfxex(@|i}o*C9>e zdBend*B7U55w`jnX{|r`oA-2|x!M&*(+kqlFty#$mCbLzUDTl`!lq~G7lg`xXB8cv z_Kb>?mhlO{eOTsHJ&;!=-P5;gvj7_sV94%QDZ8)%ziJ<*_!mN3N=ISM;;}uX3Q)Rl zn4WnROZqfl{u2 zkUPujnt_v42WnJ{FKqBXq^VOaD#@Wx+G<3^+DhD=NdCKtLQ^IX^NxfRjZ7k2q53uC zle9A(oIlyILYu()NgnquT!lNZ3Jgx%zu7T>Y6k6zNdo~K4+BW%*P;F(liVv8aRt03 z8dZN^19Is&$flINe?VNuqRcH|PZ2@XOms1fb8?b{iu`{7f_5PG0Z|`hQ!FPKZojp; zWIrqw`pOQ{t67RyI}zJ@4ejiJRxtPqd<9|&EEOa#$K~vtStaTis`9xNy#T<16e+=U85EL`d}<^>f4MeU7?NI8?kkHUz#(Q z%;4TB=w3rbI?hPWYnGY!x#VhT-3oUjNW`6QY4^LOuW7Y<_!C%dn%h0qw7=C*-DjY{ z+*##*#cOTxNQ#S%6Dgt`OmGZpZ_$>fp=|F17v&-|blf%R(iS6?1?Y+t@^{mf*=;xG zYT(>2kb322R&bFr>Gie9IOCuUWFJcNor!)TY37q~yhAuDFR+L|lPnWp^CUA96(Hsh ztlQQ#51ZaljaFKAazWx!K*!5bY3hjG$EZ{;X4yYNz9=9B9-swy9T+=t(deM)ULHf@ zv}LkB6HwV=mSUys!(aA9u15BRUk5uo#zH~NI&iRJ{6rY^S<(09m=;8oZA)fHS@D&U ztSmO}IzqnWn)Z2huIsVPrWY0^eqkA(OYb|9FZxDDlHsK6S<7zkM~s))K{lXCv%b`y zYSu&-0u8|!3_LQHHxFAw?q>mb^Q!`MC>dZ>AVc=$%JRx_y%gW-StavvH#GkJ7TLx4 z#pH-DCnl(*bqfw-HPOOy_UTLGI}pFiBYuai9+{Z zXcC`$Js|!Lthem~_DOl`xL_L0Bd|{pA%cWsG-L|=7Z=x#)3 zL|<c(WOAC|$@PP%4?As7IN5x zXTW#DD?6$a1()K=!%^`=Y56A0J&2}vex_rGwHQYa3IwaNy{{jTP-NL0L6o|t2mjt5 zicCHGef?vbPBlM6BQOXeY(4Sekw$HgyY875KnylU+RCa*xYwcV3^q|d(GoraTc!ZQ zBQwKu;`2e~lj4)|93k^_eBf3yNsZME$q2cRn-CYg_ffgVLgF$z6Q>vE1dD{F&rzm z!zMTrzWFGbgS_A5;qDG14!udXrnBMZLmxgJVi`^n{6`F{Um?%w zV1MIzv^9QK9}y$>7_o_!rOt>va4!6wg}d(f$a<-bDWsUCO-`Rf3gJLV}`nGt3Fm1IWyaQ13Qu~DybGz+3#FbEx3 z9q+FJdsDQSJ?4fASN6AuAdcXJx(6SgbrfRmzn=6DvP^9U-H5qHpP(!K1YOAkaZU*A zoF*S&^-Gx}E{OBnA}BPey7S>%{0!W`S5uNbSxJ$3a4@L?PeUxnM^@lBxM@jj<|@yJ zN13KP7ib@5@-}buyOQPFhJyM7+DroEu060`0hJKOb#l-vuI(2pJ9XROFW zh?;p|#pX!XJyqrz^UiZEyif-z={RpH(%^&4SAcp8MMZXQfOy&_bq!saj<1s;8Kw*q zDbS_RTdFewLLr=IIu}>m;H>6WVoTDsEzmh4KLQ=9PBm(Kxv}Z1Wr(z@FvPU4ujF#p zPkHoR(-_~P==z}GIVd|6FM0kg+Of~QS5w1DKY4X+NMyF(+DV`G;x5Ril8w-?B>9ZA zmfYi#G<`osoiU+CiHWm_#n*26db&MND8jJ%HkveN{%`2Du%khlT6olAY*M0I+NNi> zI`0mDqSNCGI?Y|4{CqZl$pNLDF`+{pCD3Oz)&X&62A zXHMvQ-`j%wP8p~MWb z7IuZ3-}YrolQ4<9qoK?_a!WH0?#TjDDPj|gNA`BvNyOap55TazuHE4^gV~|Y#6)AP zZb$`UJ{T7ulsYoN!Z-(4kqU_GSQ<&U+mXk;hp_|YN@!2emyPSG5kxd|lWVw^Y2k;; zpE2)-f#*8zDKh=U)3L6#4q z!0Dwz}PXSe)sja{m3YFf7XfL21}vj|GZ(w_wX?BIV_#P(J89=n`$dwZms0+RV1YrHzf0M4;Osih992@FSSq&=N&u^W2s)wdUFIdd*o9r{q*3@ zS!|b%IxWW(+lpLE+X=>TT$=vO(}S6eehYci)uY8Kwr6YyD;z#wmBI6ePH`sU&y=Yw zby}0bet}XBj=sz~cOG`c`f%VE>rq6WZ1N#9%{w4Fc`;5HkttE~;IB_~s!-{+KX2OP zG5r-DjdR0WR_veiJc;H0uqt1=9H=v_w%3;TNl4?R}_oPb+#|AK#?s z|4}CX({L%ywcvHQKZi*pNy zs)X_D#s=cMKE}28qWxlH6VzC}yf2n5cfu!}g85Zumg` z=uq%YM^w}`lno{`607jppcgIt5Zg!!+d zo4o5+Pa~w~vGwJL<*9tF=tb%57Q|>Py5qk?rFQRKhI!uKXp+$VB;)~7Oa;7d@nmsg zPh1ddKrIo40yP~!-9}yeu{Mx;fItK|5s>DB?8vbjCLsT`!|~HrbdJ-(@g0$;$-@OD zL~WVQX09z|tWfhhIdC;2+facb@hSjhxGrQ+g=SsoChn4DWP@2jP#2zEtZ)kN?}>)7>P(XY_lQCGRx#kAxwT%f`W;%iXNV{Ghfd*evZeDO47 zx{HZ5yzgsuP1iSux30s6+3&Rucs5L!|LRxu*BY{L6Z;w9rp;8F<7qBqTBkAm94tVjSeAZ4SLW5aN1I~%2-j4JK2ehcxMWzu8okOwv7WrVSQCQ2sZh*j~~rM3?>3B%a-R zj_~hy^BpEba2LMPotJa-nl;+Be#~DRQ*q?Rj~>*t{aq17gie8-Dw@5^H`%M3;Z?gp zhV-LEV(a|9IU0xkFl=J!ZIRK!eXlFt8KK8FP3>P&zhRPN7z}^JC_1CGMjiBeKUBV^ z{>ZjIeNnbAJSyk7@UPoS#d-WM*xj=z`NgjuE~ITJBh#Krvlj*DkU@SjH^-ziTW{p= zcS%DCO~7bcKIbYPnH51`_CH zag1_2QGPNuCv5ry`(rUpYHw}*^>odHQ;r13d1`x+t%6M*BXy^eY@_=+w0f!5Pu&DH zV|xN9_fJ~?lyZKChd&f4Y~x707+&P4-Pk-Y7O3h;nn+4l-N~wcF4$TUe@a zl^7j%WZBaCHWuE?p!cW;*;PzYkr&F<9=8B@XMhKp@&)Upd>Q^KD{K|esbw6R$l8_G zyd_sJ5O49bow_-H?kZcVB*2ql@(oYZlwfc7tE-fT-%WNoP*5$Z743+Dn*ghEZQvTM z4Z<5YHy)!huv_5*tBS0C|6}j1qoQoTM&Y3i!T_Z~frnC&l4d}qg&`!QQ%bsX2oa@* zlx9#;kdl^E8l_WG7;xzBf#F=k^ZUK$dEax+x7N4T`Qv=wI%{#Sxo5cVEB3YbzII)E zt7YJv4ujr))>a!F_`3xmt10f$nKpj#*Tdl0OmA+^zzdaZN0*(H`LbES!bDFkn2!!t z&jt)V3`4iWv2v?TnI#f}ZcT4{ZQ%GIUG?Yii`)YoAER$f0{^<7?b4Yz$VmDP`x~pd z8&Y!Wn>X4R1)?fY-AsohJ4IIT>o|2Fyd^U)_!;HO%0pI?Fn1eX70OGV0u>W#Qu^*d z6{u(x1y(W+>JwmCz_Yd%&I*IL%dk^Atx9E*kg5Y4bv`h5QeF|e0?`HXKM=G5r|V&? zaL*bIxMGA$IQ=&YQ;Ba#C|E5_?0{ukY8dUZs9MZuJ|HbZ=AFqqUD+GW_ujVJ z_F%o!M^Bgg*-I+BuDit+$4%00lIM{5_B=nj$0v4yuOld9t|HJcG8m5h3XeixeUPav zg#WN6G0H4?Hqc0ANQcJ73^E+(o<9qFP6%c5S{3r^sx0& z(?)H}wO*ZjYgQA-&V1Dbv1r(5o3%Wu6s||j0=xka$qWe`AjUpgVQK5N4{Xck);+fQ z?8AL;pCx_oe?NPZby4@!&m6y@1;nbgX_qJNUQKrwHTq=4!2@pB|37fda z9lN5Gpn3K3z=^cTK9v68)B%U@rCL#9gyhH4KScUrzh3awDSV!6MGNwYE+>QvAQ`J+&T1;HSN$H3&s$4LmOx9rpR$;pI$J^a^3#~KYt}jt z;rvvQ4+?p%&thZCMQ!y$Y6Hy;K>{!T{hsBq+)>9!Y!O0oWPp<-#xO)=mpg;a#;{il z=N>F41X2nwh`E$11~+%W&9i&B8zRo^{hVApIVZWiQH0_XCYA+PDl=(gHs7w`xsUdXt|!+0|)T!RSHZn(2yjBmbjYm?}s_& z?yjp8XW49Lg)Ygak$RnLh?bWvWSCs7$9z zkUXH^aKa@kE|D0uCo*t?R1}*t0eBsYKq~pm8vZ?ie7H7yeKzNT_ZbHSN5BWGvVd@D zArg`m-s(8{qY`ol6S@pDs*wyzK4k|tU!kBr_`E32%y^hZ4=9~PZ9ki#iPjKxwgOr3 z7l44kBaJhTHOcsN1aPy~o`n6)71X3xagmjcxd_;Ytf3Vv@BpcpKQKxG;pal!5lcH6 z(L3Yo(+rUIv5%9bb0p;t;+hc=_sDc((xt6=(;iN5^Y(+9EQH=PeP}0V@6=kjV@|4t zlCh!!9x3+r8K_L6Vm%E84+93zJCu0f`0U5%)^B~fV(R?>=<`mtVw$3<5YufitUVq@4dLdyIsp_eY-;}WD(av+B1YeRQ%ebnS0JMuk@5D zo#uLl3s_$p)@%7K&Ja@$cm29<`ImB`%@v)3w1dw^593nI%Eiogth?XT=5Uuk{~UU& zM|R|nI@crm;VI(My&^lVqiE)YS_W?RRmIEdG%A-ibqDi*|Fj*{WHBo`F%8SfGc5Wk zrt&oWTc7t=QSFa8ZZ2){`V2`xnr#IMaW$wwnAj6}6QJ2@Kt2Y`Fqe7{4y?*R2=cQA zqunSg@L0}fxO#&@K*(RvHQId4!SB3Ptvv|5!iX6WjT51vAxPm<10X_U4{BHp&}~^r zCL=%FU|+sngKCOOs!1h&lq89Wm?MIb24JiHZuMS3t%00Kn+ z{yWw(e_2~mS2TsLD#!@j83ssNvH3k3a^kTM;y7uaVbrh!kczBIHjec+$OCu+)11PQ zJNVBPW1b?ClR@{KYzVZ7ppprN6O0Mc8+9P*miL=LRZktk^EjHkq0ad0FPF&9u#%%qcJJMGE-tyzDAsp}t%2 zEEPr-Ut&5Wb3f_T@M?sHd=CRRH|0K6nVRsX`mOm7_pBl9xxp|N!Rx&?_Jq+Yqr2~} zp-7NU1~T@$^anSU>nEvC^K#t!3yYFl!aunEj7kZ^BjpoSGeExwY7i&H z9jH4VnbM$y3Mh^t)(>3Pz{CQqpLjC(Ko6?CYwpN@GjOsEO4*5Ckw@575t zyj^$Y^q*zxy1EUqjzjx8d$H<>UWD?V&DJ+?W$R7E7LA!p+VxMItRM#o?Z zULHkzz^(UTx)4rlPV0b*3N@)9kW=g+lOX3lWd#oL0Jq{e{bZH-7>8gUI8=i`@uc?V z14jc#aS9rgNZ;D%eu%cAYldB9BN=p!*N3Pqai#B*!OIp$6?nW?wyiJ05=t; zBEVgu&U=u4_XzBZK+O3Lf}Lf=sx}7NZ3T>0 zWj2KC04FV};;r_XD9x65tKje)PJ8V$jv!>==?ozdk`p!H>ah;+KC{XkkT;Q|j5pT+ z%LZcGo(nl97xhZ+>~PsQev19dbz?F1r9|C@IDl?cz$eG@c-Yb1 zt=}#b?hcx&9vTIyuZ@)2L9(EGzhAuAYt@pa>Fk;!!RX{PTyN-j4KFtclA{k}R8=sF z#;e=TLaL2#>Yj|?^XosQ>ZMGiPuu&jS!DKvW$I}@`CwZ+Ua?S{3~SjSQ7K)VdPMEj z800+k*=WL9PH|qgB+^kWJ7&Prf|{;#MFKPMlO^l@$_$n4d#iqt@9&NI_j1n~`{g_B zS!U8or}&z5cYP|GQlAdoR1Ils%gCW8j7je<0^u$y37-Q)5&PDBLE|kHNji?re!t1+ z7hf=ZG|;P;%7^{;qx}$ct^)ro43%KT_erNTIJ5LorzpEd*qM4 z&X)eLmvw&(e8uWg^q!DSm0mL=V7>9HISc9RD&|om=KZJ0iZ7WbyU+Zy^@AwhstR%5 zzc9x&*ei>2uGm|?=xw8mo#{<6YQ7qXQM0;pD~0Ef>uGyfN+3qPxP=p=fa6bt#;X$0hkIWlnPH-iXu z@gZREVZg-?FaT@4oFYV5m(xk=3I%1wgsv5Syep{FbbB`^`y+_tQO?Qc^~&iRjT3Os zy)#`m5Vxri6SZeq#LR$1D`g;c?Y)2v*{D6>_F!TqV?0oyIM(G#1R%8wgZ>6qo8UOf zZPM-Ip`i^POeGDVjjjbx-7zi0t%h6$` z>p3I<>_jM8VJ1Zg*idkrlsLg2B~Y@|KseQYrWv9=@UZFkbMk4ie!M7A_pGs~mIj8< z!H6wSCRbHviK9(L(Q`<1zQnTF9+nFNcC#o)$$0Wv4Zs}m#sLrVezpag9ZhNj;X%Qf zAo_HWJSdvvNG%2WJ!-g>=HO$*E-1;wgH}+;uqFj&#M(^!G|r05{^pO^gK^qr&j9|`B)f7ZZpCt*f>#QJWLM7^fhwJXXbyzjS% zPwYB=6+dp8cF^@H5!8LYsd8h{<8C%3>ZO|*$sarwZ+dlng%B86+Ze<~NA4Rok*I`XHHo(vB34}JM--q=!@CVQeWcqb3k?zPsJF#lVy4i zarJ+`!WlOIO5Zqtby;?#*B#NFt|0S<8iJ;zB9d?J$l*Cs8y_%+{;!Hki~PSSD$OU% z|L>yGy!;RU_ZO9ZBq$>C|4>uf7-9@+O3y(?Wjw84LLkb@5H3))`4XsgEC#^`ca-4b z3$=!TLd_67aLJ~nUwm?a`&@8Ao>_Y{KQnQ5v9M?6khU}NbT;9HKnSA={^M=mXu|)v zkB*JGxF--0fk0wo|NA|zCBBEeLZS~vM0o|6dHF;I_(cVT|EIr!J|Wf+ZwNE^G=Vrn zTp$(@d+?0|A`P(vzj%V*Odwzs|EJZ;h6Lf!{oh*I7d62DKVK{R@Z=(>85h`$yRuoy z_aJI7JZQ0r*4$6n=EYY>sikA3fzF@`TULd?GMWlLU|@7N9Mu2%J#S0I+tOzP_Cju^SzQt9w<**yvKDBYG^ z)XT8|wL{tPyOM3BDe?41$tj!YWnlxup1zOIsIgKhMn?)2pMA?aLE4et89A(?t^VD> z?_ma6{c6@8R|nUy=a3^}p;Oig2QyFq_xwx?@$m1k+!I5fQh}JmQ<+zGIL>Lc$CSW$ z=wz5^3DWj7^Fim>hr}8>Pkwbn4|FNQ1S~Lz%S>l(M9@8EZX<$z|L4Zu6y4!UHJeJu zMziM-$dAz06t)(-P^~X94Y%CbcY3U{2fFKGMglj;9@*-!8+`djA3QM3gnqF;mrge6 zHCRLiRM658XcA;#lVk(wb_7(~&tz<7@b5xap^wIHkM>OdsEf`AyJs2qiP~9mVKa1K zf7E5%V3GxK>SA(cRYxu_kpqVM>=%hZW%xn5H^u%}zoKr$e9-SKrlYH*aMTEC#=X+= ziLYrMx;|yiH(TZVFh3GNT^A)kJk|WYs7B<1D`lnb=)ugvb-{{lWtye!8e{l%Ev|?Z z>lIu?7}0;ZE>DDUrLUh=z9tj=`B8v`hCLQ01zCt2Cs!uR7D#oM>CS=|{95it+N5zq zF9qE4kPORWLzEJiAFfO^if5i!Bx8jjy{K{e*G&3I3qj`&`SPLUj7u*3h#6&;+cDVB zzu1@4u4p&5r^O)J_t(Nc9_lnvfBB*$k5JeYG239#K>qx6kOw!u8eFGdU)uMUtBRR4>>p3wC+6 zTdzyaOQ>IVJ!Hqrg|yGg(HLd2GTHpnBE{2)_Tyx@CBwBsBmWJkK3e^{KaW!cJI4Do z@Q5-U?2?cD3*>49DxaK9)gyn~qr((Csz#dzZ;Yu2{4(I32#hiVA?&56_m?Z_!p1-7(ff`K$82UTPO@`!J|9Sq)w6&&BBXT`L{m2tL&&iY>)NC3mhI-zPtO@Ra2kz zP;&oeLh6+NfSf!JQY}B6mQdzOrZMCpByT`QE&3lgjij9f z!sqn&uDD)3T8EtmZ5X{`+S|N?w!{?mN>DIdBKXkjt(xAZe&xaHOHtq`mm0La@sQ_K zNg#4pCXXYE_Z*_EV}TgvRHeWu>sfv{(Gm*v_p$9l%)}n}&2HW1g33@P7fG;i(rNa3 zcCE!ma9AS=8%I*j#_L z=NyKM`|*8f(6bex&JSa$xF#Cm27OnYLh(k_Juz!qf4Xz*_GJP-bSCr77f4c39gtF) zeZNF?6vU+w!9nQN4t(jave(bEdhWkiLGj-F@IozTqi|1BpS(>@qHx-ufxZpo8DZZm zxy0n^$35Lhe-0@?U>k0pXz9Qs_I8nnE{&UpJ`@Q1p?Qmr(Bk=OJv`<&oC)%div%2I zu_7(xkafe?GM;}veC+zX+9>v@7UPy(RsHB^!;_D-sHq4w$mPP3YD6ixZhL!Po>#*WVtW!*StdIM(^ElSqmOw72AYurtqi) zzguy+FC_CmtJEr4_4bFKp&jO!&|%9tvBKB7xC`!*#&$ri>{1XZd3`zxZ=Fa_c|6qT zX?k0|YK~G-rJs_%jD_BuoJ8W&^EhQ0`yby`)gua&4P<Gd=dRn9kAp{|e&7_AtE;IK=+&C-G_*+E5kpuIZ2D1BYqy6n1iN*asX>cC>ke3OQ{vmm^ zgKDqUZPpv?99oyxD(aL79sUvrqQ)wl`@Idn7YOzf_reCzl-+cPmXooNz1qqmp4P89 z!=r_0Wum>tSI&2Lnn}s(&9O|8>|yiT%ed1pB1!h+^ScbX_R3d3ZoaZxX;Z=%HUA{;J*>+fDp+#u$N@GiNO9BVeYt`I z-Q)Kzc6ja){IQqa?ekdO+Mw1R7846+8t)%y#A@034lGSsrtr7RI5iTD3et7%OR#Gt z!QeUiUiU>Kyuw^6-|qj$rtiKG%?S1&s~FQoBJ9^I>G|YuHCH0RMUly?+K!rc#U ze4ogAogJsOIreE;Bzx1)f$8l&na1y*C*{4{vzHijJtxiVwc4fijbozXQ{>+4_rSkb z(T4QB+ZVIVtCS72Tbu}c6qyT~cNf1IH=JTPh_TJ-_7tL=R$#MqNReP2*el_!9IixA zo^sZvh}F)G-lrMvakvwiZPn|P&@rPC;o2uS_-($XY0i-69rdTSyrh=uB;qTeaM}~f zk*CcqIRO?z-qK2?#ayevd0s zF?*xfWf;Ml=HNrcv(7U7>SaaZ9aepN!z&q?!Q;U}6#4lAVzoR1N#;l>*z@Z7D89% zDCr!wo?D6PTl-YB+iCBA?ihvA=0q0i%1I16eW}~fdSxck=3P}J;IeePk1}k*63#)8Ky79&zzbG+tU%iZPme`B39*ddh6OII*p1N45S2bU9}X8&ElIY`*=X z`fEc�v-kPquQLS$%k5&0&zO}1B*gv$3e5zNo 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
          ^q(iU*&IVxzrS}faMW{!h}KiXfacP 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

          ?ODc&9>aUpC-O>|cd_3O_W@X2#O6Ei#zF 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
          +
          +
          + Filter by ID:
          + +
          +
          + Filter by Task:
          + +
          +
          + Filter by Image:
          + +
          +
          + + + + + + + + + + + + + + + + + + + +
          ID Task Status Created Elapsed Time Platform
          +
          +
          + Loading     +
          +
          + +
          + + + `; + } + + private getLogTableItem(log: Run, logId: number): string { + const task: string = log.task ? log.task : ''; + const prettyDate: string = log.createTime ? this.getPrettyDate(log.createTime) : ''; + const timeElapsed: string = log.startTime && log.finishTime ? Math.ceil((log.finishTime.valueOf() - log.startTime.valueOf()) / 1000).toString() + 's' : ''; + const osType: string = log.platform.os ? log.platform.os : ''; + const name: string = log.name ? log.name : ''; + const imageOutput: string = this.getImageOutputTable(log); + const statusIcon: string = this.getLogStatusIcon(log.status); + + return ` + + +
          + ${name} + ${task} + ${statusIcon} ${log.status} + ${prettyDate} + ${timeElapsed} + ${osType} + + + +
          + + + + + + + + + ${imageOutput} +
           TagRepositoryDigest +

          Log

          +
          +
          + + + ` + } + + private getImageItem(islastTd: boolean, img?: ImageDescriptor): string { + if (img) { + const tag: string = img.tag ? img.tag : ''; + const repository: string = img.repository ? img.repository : ''; + const digest: string = img.digest ? img.digest : ''; + const truncatedDigest: string = digest ? digest.substr(0, 5) + '...' + digest.substr(digest.length - 5) : ''; + const lastTd: string = islastTd ? 'lastTd' : ''; + return ` +   + ${tag} + ${repository} + + + ${truncatedDigest} + ${digest} + + + + + ` + } else { + return ` +   + NA + NA + NA + + `; + } + + } + + private getLogStatusIcon(status?: string): string { + if (!status) { return ''; } + switch (status) { + case 'Error': + return ''; + case 'Failed': + return ''; + case 'Succeeded': + return ''; + case 'Queued': + return ''; + case 'Running': + return ''; + default: + return ''; + } + } + + private getPrettyDate(date: Date): string { + let currentDate = new Date(); + let secs = Math.floor((currentDate.getTime() - date.getTime()) / 1000); + if (secs === 1) { return "1 second ago"; } + if (secs < 60) { return secs + " seconds ago"; } + if (secs < 120) { return " 1 minute ago"; } + if (secs < 3600) { return Math.floor(secs / 60) + " minutes ago"; } + if (secs < 7200) { return Math.floor(secs / 60) + "1 hour ago"; } + if (secs < 86400) { return Math.floor(secs / 3600) + " hours ago"; } + if (secs < 172800) { return "1 day ago"; } + if (secs < 604800) { return Math.floor(secs / 86400) + " days ago"; } + if (secs < 1209600) { return "1 week ago"; } + if (secs < 2592000) { return Math.floor(secs / 604800) + " weeks ago"; } + if (secs < 5184000) { return "1 month ago"; } + if (secs < 31536000) { return Math.floor(secs / 2592000) + " months ago"; } + if (secs < 63072000) { return "1 year ago"; } + return Math.floor(secs / 31536000) + " years ago"; + } +} diff --git a/commands/azureCommands/acr-logs.ts b/commands/azureCommands/acr-logs.ts new file mode 100644 index 0000000000..05ebd416eb --- /dev/null +++ b/commands/azureCommands/acr-logs.ts @@ -0,0 +1,68 @@ +import { Registry, Run } from "azure-arm-containerregistry/lib/models"; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import * as vscode from "vscode"; +import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from '../../explorer/models/azureRegistryNodes'; +import { TaskNode } from "../../explorer/models/taskNode"; +import { getResourceGroupName, getSubscriptionFromRegistry } from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { quickPickACRRegistry } from '../utils/quick-pick-azure' +import { accessLog } from "./acr-logs-utils/logFileManager"; +import { LogData } from "./acr-logs-utils/tableDataManager"; +import { LogTableWebview } from "./acr-logs-utils/tableViewManager"; + +/** This command is used through a right click on an azure registry, repository or image in the Docker Explorer. It is used to view ACR logs for a given item. */ +export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode | TaskNode): Promise { + let registry: Registry; + let subscription: Subscription; + if (!context) { + registry = await quickPickACRRegistry(); + if (!registry) { return; } + subscription = getSubscriptionFromRegistry(registry); + } else { + registry = context.registry; + subscription = context.subscription; + } + let resourceGroup: string = getResourceGroupName(registry); + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let logData: LogData = new LogData(client, registry, resourceGroup); + + // Fuiltering provided + if (context && context instanceof AzureImageTagNode) { + //ACR Image Logs + let imageRun = await logData.loadLogs(false, false, { image: context.label }); + if (!hasValidLogContent(context, logData)) { return; } + logData.getLink(0).then((url) => { + accessLog(url, logData.logs[0].runId, false); + }); + } else { + if (context && context instanceof TaskNode) { + //ACR Task Logs + await logData.loadLogs(false, false, { task: context.label }); + } else { + //ACR Registry Logs + await logData.loadLogs(false); + } + if (!hasValidLogContent(context, logData)) { return; } + let webViewTitle: string = registry.name; + if (context instanceof TaskNode) { + webViewTitle += '/' + context.label; + } + let webview = new LogTableWebview(webViewTitle, logData); + } +} + +function hasValidLogContent(context: any, logData: LogData): boolean { + if (logData.logs.length === 0) { + let itemType: string; + if (context && context instanceof TaskNode) { + itemType = 'task'; + } else if (context && context instanceof AzureImageTagNode) { + itemType = 'image'; + } else { + itemType = 'registry'; + } + vscode.window.showInformationMessage(`This ${itemType} has no associated logs`); + return false; + } + return true; +} diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 12bbac334b..f8749dea50 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -31,6 +31,14 @@ export async function createRegistry(): Promise { dockerExplorerProvider.refreshRegistries(); return registry; } +async function acquireSKU(): Promise { + let skus: string[] = ["Standard", "Basic", "Premium"]; + let sku: string; + sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU' }); + if (sku === undefined) { throw new Error('User exit'); } + + return sku; +} /** Acquires a new registry name from a user, validating that the name is unique */ async function acquireRegistryName(client: ContainerRegistryManagementClient): Promise { diff --git a/commands/azureCommands/delete-repository.ts b/commands/azureCommands/delete-repository.ts index 3806ec3705..b2965e9a2f 100644 --- a/commands/azureCommands/delete-repository.ts +++ b/commands/azureCommands/delete-repository.ts @@ -27,7 +27,7 @@ export async function deleteRepository(context?: AzureRepositoryNode): Promise { + + // Step 1: Using getLoginCredentials function to get the username and password. This takes care of all users, even if they don't have the Azure CLI + const credentials = await acrTools.getLoginCredentials(context.registry); + const username = credentials.username; + const password = credentials.password; + const registry = context.registry.loginServer; + + const terminal = vscode.window.createTerminal("Docker"); + terminal.show(); + + // Step 2: docker login command + await terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); + + // Step 3: docker pull command + await terminal.sendText(`docker pull ${registry}/${context.label}`); +} diff --git a/commands/azureCommands/run-task.ts b/commands/azureCommands/run-task.ts new file mode 100644 index 0000000000..6b6ab7ccc4 --- /dev/null +++ b/commands/azureCommands/run-task.ts @@ -0,0 +1,43 @@ +import { TaskRunRequest } from "azure-arm-containerregistry/lib/models"; +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import vscode = require('vscode'); +import { TaskNode } from "../../explorer/models/taskNode"; +import { ext } from '../../extensionVariables'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; + +export async function runTask(context?: TaskNode): Promise { + let taskName: string; + let subscription: Subscription; + let resourceGroup: ResourceGroup; + let registry: Registry; + + if (context) { // Right Click + subscription = context.subscription; + registry = context.registry; + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + taskName = context.task.name; + } else { // Command Palette + subscription = await quickPickSubscription(); + registry = await quickPickACRRegistry(); + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + taskName = (await quickPickTask(registry, subscription, resourceGroup)).name; + } + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let runRequest: TaskRunRequest = { + type: 'TaskRunRequest', + taskName: taskName + }; + + try { + let taskRun = await client.registries.scheduleRun(resourceGroup.name, registry.name, runRequest); + vscode.window.showInformationMessage(`Successfully ran the Task: ${taskName} with ID: ${taskRun.runId}`); + } catch (err) { + ext.outputChannel.append(err); + vscode.window.showErrorMessage(`Failed to ran the Task: ${taskName}`); + } +} diff --git a/commands/azureCommands/show-task.ts b/commands/azureCommands/show-task.ts new file mode 100644 index 0000000000..1ab9808cb3 --- /dev/null +++ b/commands/azureCommands/show-task.ts @@ -0,0 +1,34 @@ +import { Registry } from "azure-arm-containerregistry/lib/models"; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; +import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import { TaskNode } from "../../explorer/models/taskNode"; +import { ext } from '../../extensionVariables'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from "../../utils/azureUtilityManager"; +import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; +import { openTask } from "./task-utils/showTaskManager"; + +export async function showTaskProperties(context?: TaskNode): Promise { + let subscription: Subscription; + let registry: Registry; + let resourceGroup: ResourceGroup; + let task: string; + + if (context) { // Right click + subscription = context.subscription; + registry = context.registry; + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + task = context.task.name; + } else { // Command palette + subscription = await quickPickSubscription(); + registry = await quickPickACRRegistry(); + resourceGroup = await acrTools.getResourceGroup(registry, subscription); + task = (await quickPickTask(registry, subscription, resourceGroup)).name; + } + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let item: any = await client.tasks.get(resourceGroup.name, registry.name, task); + let indentation = 1; + let replacer; + openTask(JSON.stringify(item, replacer, indentation), task); +} diff --git a/commands/azureCommands/task-utils/showTaskManager.ts b/commands/azureCommands/task-utils/showTaskManager.ts new file mode 100644 index 0000000000..7a3ec4be58 --- /dev/null +++ b/commands/azureCommands/task-utils/showTaskManager.ts @@ -0,0 +1,37 @@ +import * as vscode from 'vscode'; + +export class TaskContentProvider implements vscode.TextDocumentContentProvider { + public static scheme: string = 'task'; + private onDidChangeEvent: vscode.EventEmitter = new vscode.EventEmitter(); + + constructor() { } + + public provideTextDocumentContent(uri: vscode.Uri): string { + return decodeBase64(JSON.parse(uri.query).content); + } + + get onDidChange(): vscode.Event { + return this.onDidChangeEvent.event; + } + + public update(uri: vscode.Uri, message: string): void { + this.onDidChangeEvent.fire(uri); + } +} + +export function decodeBase64(str: string): string { + return Buffer.from(str, 'base64').toString('utf8'); +} + +export function encodeBase64(str: string): string { + return Buffer.from(str, 'ascii').toString('base64'); +} + +export function openTask(content: string, title: string): void { + const scheme = 'task'; + let query = JSON.stringify({ 'content': encodeBase64(content) }); + let uri: vscode.Uri = vscode.Uri.parse(`${scheme}://authority/${title}.json?${query}#idk`); + vscode.workspace.openTextDocument(uri).then((doc) => { + return vscode.window.showTextDocument(doc, vscode.ViewColumn.Active + 1, true); + }); +} diff --git a/commands/build-image.ts b/commands/build-image.ts index 1f3e350deb..aea006e310 100644 --- a/commands/build-image.ts +++ b/commands/build-image.ts @@ -31,7 +31,7 @@ function createDockerfileItem(rootFolder: vscode.WorkspaceFolder, uri: vscode.Ur }; } -async function resolveDockerFileItem(rootFolder: vscode.WorkspaceFolder, dockerFileUri: vscode.Uri | undefined): Promise { +export async function resolveDockerFileItem(rootFolder: vscode.WorkspaceFolder, dockerFileUri: vscode.Uri | undefined): Promise { if (dockerFileUri) { return createDockerfileItem(rootFolder, dockerFileUri); } diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index cf4cecc1d9..0e8871cd09 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -3,12 +3,13 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { Registry } from 'azure-arm-containerregistry/lib/models'; +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceGroup } from 'azure-arm-resource/lib/resource/models'; import { Location, Subscription } from 'azure-arm-resource/lib/subscription/models'; import * as opn from 'opn'; import * as vscode from "vscode"; import { IAzureQuickPickItem, UserCancelledError } from 'vscode-azureextensionui'; -import { skus } from '../../constants' +import { imageTagRegExp, skus } from '../../constants' import { ext } from '../../extensionVariables'; import { ResourceManagementClient } from '../../node_modules/azure-arm-resource'; import * as acrTools from '../../utils/Azure/acrTools'; @@ -33,6 +34,16 @@ export async function quickPickACRRepository(registry: Registry, prompt?: string return desiredRepo.data; } +export async function quickPickTask(registry: Registry, subscription: Subscription, resourceGroup: ResourceGroup, prompt?: string): Promise { + const placeHolder = prompt ? prompt : 'Choose a Task'; + + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + let tasks: ContainerModels.Task[] = await client.tasks.list(resourceGroup.name, registry.name); + const quickpPickBTList = tasks.map(task => >{ label: task.name, data: task }); + let desiredTask = await ext.ui.showQuickPick(quickpPickBTList, { 'canPickMany': false, 'placeHolder': placeHolder }); + return desiredTask.data; +} + export async function quickPickACRRegistry(canCreateNew: boolean = false, prompt?: string): Promise { const placeHolder = prompt ? prompt : 'Select registry to use'; let registries = await AzureUtilityManager.getInstance().getRegistries(); @@ -153,7 +164,6 @@ async function createNewResourceGroup(loc: string, subscription?: Subscription): }; let resourceGroupName: string = await ext.ui.showInputBox(opt); - let newResourceGroup: ResourceGroup = { name: resourceGroupName, location: loc, @@ -173,3 +183,23 @@ async function checkForValidResourcegroupName(resourceGroupName: string, resourc return undefined; } + +/*Creates a new resource group within the current subscription */ +export async function quickPickNewImageName(): Promise { + let opt: vscode.InputBoxOptions = { + validateInput: checkForValidTag, + ignoreFocusOut: false, + prompt: 'Enter repository name and tag in format :' + }; + + let tag: string = await ext.ui.showInputBox(opt); + return tag; +} +function checkForValidTag(str: string): string { + if (!imageTagRegExp.test(str)) { + return 'Repository name must have 0-256 alpha-numeric characters, optionally separated by periods, dashes or underscores.' + + 'A tag name must have 0-128 alpha-numeric characters, digits, underscores, periods or dashes. A tag name may not start with a period or a dash.'; + } + return undefined; + +} diff --git a/constants.ts b/constants.ts index d4c4114828..2e96b71f6d 100644 --- a/constants.ts +++ b/constants.ts @@ -27,3 +27,6 @@ export const NULL_GUID = '00000000-0000-0000-0000-000000000000'; //Azure Container Registries export const skus = ["Standard", "Basic", "Premium"]; + +//Repository + Tag format +export const imageTagRegExp = new RegExp('^[a-zA-Z0-9.-_/]{1,256}:(?![.-])[a-zA-Z0-9.-_]{1,128}$'); diff --git a/dockerExtension.ts b/dockerExtension.ts index 37b4bfa90c..cd14cffa63 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -3,312 +3,554 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as opn from 'opn'; -import * as path from 'path'; -import * as request from 'request-promise-native'; -import * as vscode from 'vscode'; -import { AzureUserInput, createTelemetryReporter, IActionContext, parseError, registerCommand as uiRegisterCommand, registerUIExtensionVariables, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui'; -import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; -import { createRegistry } from './commands/azureCommands/create-registry'; -import { deleteAzureImage } from './commands/azureCommands/delete-image'; -import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; -import { deleteRepository } from './commands/azureCommands/delete-repository'; -import { buildImage } from './commands/build-image'; -import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; -import inspectImage from './commands/inspect-image'; -import { openShellContainer } from './commands/open-shell-container'; -import { pushImage } from './commands/push-image'; -import { consolidateDefaultRegistrySettings, setRegistryAsDefault } from './commands/registrySettings'; -import { removeContainer } from './commands/remove-container'; -import { removeImage } from './commands/remove-image'; -import { restartContainer } from './commands/restart-container'; -import { showLogsContainer } from './commands/showlogs-container'; -import { startAzureCLI, startContainer, startContainerInteractive } from './commands/start-container'; -import { stopContainer } from './commands/stop-container'; -import { systemPrune } from './commands/system-prune'; -import { IHasImageDescriptorAndLabel, tagImage } from './commands/tag-image'; -import { docker } from './commands/utils/docker-endpoint'; -import { DefaultTerminalProvider } from './commands/utils/TerminalProvider'; -import { DockerDebugConfigProvider } from './configureWorkspace/configDebugProvider'; -import { configure, configureApi, ConfigureApiOptions } from './configureWorkspace/configure'; -import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider'; -import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider'; -import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo'; -import { DockerComposeParser } from './dockerCompose/dockerComposeParser'; -import { DockerfileCompletionItemProvider } from './dockerfile/dockerfileCompletionItemProvider'; -import DockerInspectDocumentContentProvider, { SCHEME as DOCKER_INSPECT_SCHEME } from './documentContentProviders/dockerInspect'; -import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper'; +import * as opn from "opn"; +import * as path from "path"; +import * as request from "request-promise-native"; +import * as vscode from "vscode"; +import { + AzureUserInput, + createTelemetryReporter, + IActionContext, + registerCommand as uiRegisterCommand, + registerUIExtensionVariables, + TelemetryProperties, + UserCancelledError +} from "vscode-azureextensionui"; +import { + ConfigurationParams, + DidChangeConfigurationNotification, + DocumentSelector, + LanguageClient, + LanguageClientOptions, + Middleware, + ServerOptions, + TransportKind +} from "vscode-languageclient/lib/main"; +import { queueBuild } from "./commands/azureCommands/acr-build"; +import { viewACRLogs } from "./commands/azureCommands/acr-logs"; +import { LogContentProvider } from "./commands/azureCommands/acr-logs-utils/logFileManager"; +import { createRegistry } from "./commands/azureCommands/create-registry"; +import { deleteAzureImage } from "./commands/azureCommands/delete-image"; +import { deleteAzureRegistry } from "./commands/azureCommands/delete-registry"; +import { deleteRepository } from "./commands/azureCommands/delete-repository"; +import { pullFromAzure } from "./commands/azureCommands/pull-from-azure"; +import { runTask } from "./commands/azureCommands/run-task"; +import { showTaskProperties } from "./commands/azureCommands/show-task"; +import { TaskContentProvider } from "./commands/azureCommands/task-utils/showTaskManager"; +import { buildImage } from "./commands/build-image"; +import { + composeDown, + composeRestart, + composeUp +} from "./commands/docker-compose"; +import inspectImage from "./commands/inspect-image"; +import { openShellContainer } from "./commands/open-shell-container"; +import { pushImage } from "./commands/push-image"; +import { + consolidateDefaultRegistrySettings, + setRegistryAsDefault +} from "./commands/registrySettings"; +import { removeContainer } from "./commands/remove-container"; +import { removeImage } from "./commands/remove-image"; +import { restartContainer } from "./commands/restart-container"; +import { showLogsContainer } from "./commands/showlogs-container"; +import { + startAzureCLI, + startContainer, + startContainerInteractive +} from "./commands/start-container"; +import { stopContainer } from "./commands/stop-container"; +import { systemPrune } from "./commands/system-prune"; +import { IHasImageDescriptorAndLabel, tagImage } from "./commands/tag-image"; +import { docker } from "./commands/utils/docker-endpoint"; +import { DefaultTerminalProvider } from "./commands/utils/TerminalProvider"; +import { DockerDebugConfigProvider } from "./configureWorkspace/configDebugProvider"; +import { + configure, + configureApi, + ConfigureApiOptions +} from "./configureWorkspace/configure"; +import { DockerComposeCompletionItemProvider } from "./dockerCompose/dockerComposeCompletionItemProvider"; +import { DockerComposeHoverProvider } from "./dockerCompose/dockerComposeHoverProvider"; +import composeVersionKeys from "./dockerCompose/dockerComposeKeyInfo"; +import { DockerComposeParser } from "./dockerCompose/dockerComposeParser"; +import { DockerfileCompletionItemProvider } from "./dockerfile/dockerfileCompletionItemProvider"; +import DockerInspectDocumentContentProvider, { + SCHEME as DOCKER_INSPECT_SCHEME +} from "./documentContentProviders/dockerInspect"; +import { AzureAccountWrapper } from "./explorer/deploy/azureAccountWrapper"; import * as util from "./explorer/deploy/util"; -import { WebAppCreator } from './explorer/deploy/webAppCreator'; -import { DockerExplorerProvider } from './explorer/dockerExplorer'; -import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes'; -import { ContainerNode } from './explorer/models/containerNode'; -import { connectCustomRegistry, disconnectCustomRegistry } from './explorer/models/customRegistries'; -import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; -import { ImageNode } from './explorer/models/imageNode'; -import { NodeBase } from './explorer/models/nodeBase'; -import { RootNode } from './explorer/models/rootNode'; -import { browseAzurePortal } from './explorer/utils/browseAzurePortal'; -import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; +import { WebAppCreator } from "./explorer/deploy/webAppCreator"; +import { DockerExplorerProvider } from "./explorer/dockerExplorer"; +import { + AzureImageTagNode, + AzureRegistryNode, + AzureRepositoryNode +} from "./explorer/models/azureRegistryNodes"; +import { ContainerNode } from "./explorer/models/containerNode"; +import { + connectCustomRegistry, + disconnectCustomRegistry +} from "./explorer/models/customRegistries"; +import { + DockerHubImageTagNode, + DockerHubOrgNode, + DockerHubRepositoryNode +} from "./explorer/models/dockerHubNodes"; +import { ImageNode } from "./explorer/models/imageNode"; +import { NodeBase } from "./explorer/models/nodeBase"; +import { RootNode } from "./explorer/models/rootNode"; +import { TaskNode } from "./explorer/models/taskNode"; +import { browseAzurePortal } from "./explorer/utils/browseAzurePortal"; +import { + browseDockerHub, + dockerHubLogout +} from "./explorer/utils/dockerHubUtils"; import { ext } from "./extensionVariables"; -import { initializeTelemetryReporter, reporter } from './telemetry/telemetry'; -import { AzureAccount } from './typings/azure-account.api'; -import { addUserAgent } from './utils/addUserAgent'; -import { registerAzureCommand } from './utils/Azure/common'; -import { AzureUtilityManager } from './utils/azureUtilityManager'; -import { Keytar } from './utils/keytar'; +import { initializeTelemetryReporter, reporter } from "./telemetry/telemetry"; +import { AzureAccount } from "./typings/azure-account.api"; +import { addUserAgent } from "./utils/addUserAgent"; +import { registerAzureCommand } from "./utils/Azure/common"; +import { AzureUtilityManager } from "./utils/azureUtilityManager"; +import { Keytar } from "./utils/keytar"; export const FROM_DIRECTIVE_PATTERN = /^\s*FROM\s*([\w-\/:]*)(\s*AS\s*[a-z][a-z0-9-_\\.]*)?$/i; -export const COMPOSE_FILE_GLOB_PATTERN = '**/[dD]ocker-[cC]ompose*.{yaml,yml}'; -export const DOCKERFILE_GLOB_PATTERN = '**/{*.dockerfile,[dD]ocker[fF]ile}'; +export const COMPOSE_FILE_GLOB_PATTERN = "**/[dD]ocker-[cC]ompose*.{yaml,yml}"; +export const DOCKERFILE_GLOB_PATTERN = "**/{*.dockerfile,[dD]ocker[fF]ile}"; export let dockerExplorerProvider: DockerExplorerProvider; -export type KeyInfo = { [keyName: string]: string; }; +export type KeyInfo = { [keyName: string]: string }; export interface ComposeVersionKeys { - All: KeyInfo, - v1: KeyInfo, - v2: KeyInfo + All: KeyInfo; + v1: KeyInfo; + v2: KeyInfo; } let client: LanguageClient; const DOCUMENT_SELECTOR: DocumentSelector = [ - { language: 'dockerfile', scheme: 'file' } + { language: "dockerfile", scheme: "file" } ]; function initializeExtensionVariables(ctx: vscode.ExtensionContext): void { - registerUIExtensionVariables(ext); - - if (!ext.ui) { - // This allows for standard interactions with the end user (as opposed to test input) - ext.ui = new AzureUserInput(ctx.globalState); - } - ext.context = ctx; - ext.outputChannel = util.getOutputChannel(); - if (!ext.terminalProvider) { - ext.terminalProvider = new DefaultTerminalProvider(); - } - initializeTelemetryReporter(createTelemetryReporter(ctx)); - ext.reporter = reporter; - if (!ext.keytar) { - ext.keytar = Keytar.tryCreate(); - } - - // Set up the user agent for all direct 'request' calls in the extension (must use ext.request) - let defaultRequestOptions = {}; - addUserAgent(defaultRequestOptions); - ext.request = request.defaults(defaultRequestOptions); + registerUIExtensionVariables(ext); + + if (!ext.ui) { + // This allows for standard interactions with the end user (as opposed to test input) + ext.ui = new AzureUserInput(ctx.globalState); + } + ext.context = ctx; + ext.outputChannel = util.getOutputChannel(); + if (!ext.terminalProvider) { + ext.terminalProvider = new DefaultTerminalProvider(); + } + initializeTelemetryReporter(createTelemetryReporter(ctx)); + ext.reporter = reporter; + if (!ext.keytar) { + ext.keytar = Keytar.tryCreate(); + } + + // Set up the user agent for all direct 'request' calls in the extension (must use ext.request) + let defaultRequestOptions = {}; + addUserAgent(defaultRequestOptions); + ext.request = request.defaults(defaultRequestOptions); } export async function activate(ctx: vscode.ExtensionContext): Promise { - const installedExtensions = vscode.extensions.all; - let azureAccount: AzureAccount | undefined; - - initializeExtensionVariables(ctx); - - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < installedExtensions.length; i++) { - const extension = installedExtensions[i]; - if (extension.id === 'ms-vscode.azure-account') { - try { - // tslint:disable-next-line:no-unsafe-any - azureAccount = await extension.activate(); - } catch (error) { - console.log('Failed to activate the Azure Account Extension: ' + parseError(error).message); - } - break; - } + const installedExtensions: any[] = vscode.extensions.all; + let azureAccount: AzureAccount | undefined; + + initializeExtensionVariables(ctx); + + // tslint:disable-next-line:prefer-for-of // Grandfathered in + for (let i = 0; i < installedExtensions.length; i++) { + const extension = installedExtensions[i]; + if (extension.id === "ms-vscode.azure-account") { + try { + azureAccount = await extension.activate(); + } catch (error) { + console.log("Failed to activate the Azure Account Extension: " + error); + } + break; } - ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(DOCUMENT_SELECTOR, new DockerfileCompletionItemProvider(), '.')); - - const YAML_MODE_ID: vscode.DocumentFilter = { language: 'yaml', scheme: 'file', pattern: COMPOSE_FILE_GLOB_PATTERN }; - let yamlHoverProvider = new DockerComposeHoverProvider(new DockerComposeParser(), composeVersionKeys.All); - ctx.subscriptions.push(vscode.languages.registerHoverProvider(YAML_MODE_ID, yamlHoverProvider)); - ctx.subscriptions.push(vscode.languages.registerCompletionItemProvider(YAML_MODE_ID, new DockerComposeCompletionItemProvider(), '.')); - - ctx.subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(DOCKER_INSPECT_SCHEME, new DockerInspectDocumentContentProvider())); + } + ctx.subscriptions.push( + vscode.languages.registerCompletionItemProvider( + DOCUMENT_SELECTOR, + new DockerfileCompletionItemProvider(), + "." + ) + ); + + const YAML_MODE_ID: vscode.DocumentFilter = { + language: "yaml", + scheme: "file", + pattern: COMPOSE_FILE_GLOB_PATTERN + }; + let yamlHoverProvider = new DockerComposeHoverProvider( + new DockerComposeParser(), + composeVersionKeys.All + ); + ctx.subscriptions.push( + vscode.languages.registerHoverProvider(YAML_MODE_ID, yamlHoverProvider) + ); + ctx.subscriptions.push( + vscode.languages.registerCompletionItemProvider( + YAML_MODE_ID, + new DockerComposeCompletionItemProvider(), + "." + ) + ); + + ctx.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + DOCKER_INSPECT_SCHEME, + new DockerInspectDocumentContentProvider() + ) + ); + ctx.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + LogContentProvider.scheme, + new LogContentProvider() + ) + ); + ctx.subscriptions.push( + vscode.workspace.registerTextDocumentContentProvider( + TaskContentProvider.scheme, + new TaskContentProvider() + ) + ); + + if (azureAccount) { + AzureUtilityManager.getInstance().setAccount(azureAccount); + } + + registerDockerCommands(azureAccount); + + ctx.subscriptions.push( + vscode.debug.registerDebugConfigurationProvider( + "docker", + new DockerDebugConfigProvider() + ) + ); + + await consolidateDefaultRegistrySettings(); + activateLanguageClient(ctx); +} +async function createWebApp( + context?: AzureImageTagNode | DockerHubImageTagNode, + azureAccount?: AzureAccount +): Promise { + if (context) { if (azureAccount) { - AzureUtilityManager.getInstance().setAccount(azureAccount); + const azureAccountWrapper = new AzureAccountWrapper( + ext.context, + azureAccount + ); + const wizard = new WebAppCreator( + ext.outputChannel, + azureAccountWrapper, + context + ); + const result = await wizard.run(); + if (result.status === "Faulted") { + throw result.error; + } else if (result.status === "Cancelled") { + throw new UserCancelledError(); + } + } else { + const open: vscode.MessageItem = { title: "View in Marketplace" }; + const response = await vscode.window.showErrorMessage( + "Please install the Azure Account extension to deploy to Azure.", + open + ); + if (response === open) { + opn( + "https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account" + ); + } } - - registerDockerCommands(azureAccount); - - ctx.subscriptions.push(vscode.debug.registerDebugConfigurationProvider('docker', new DockerDebugConfigProvider())); - - await consolidateDefaultRegistrySettings(); - activateLanguageClient(ctx); + } } -async function createWebApp(context?: AzureImageTagNode | DockerHubImageTagNode, azureAccount?: AzureAccount): Promise { - if (context) { - if (azureAccount) { - const azureAccountWrapper = new AzureAccountWrapper(ext.context, azureAccount); - const wizard = new WebAppCreator(ext.outputChannel, azureAccountWrapper, context); - const result = await wizard.run(); - if (result.status === 'Faulted') { - throw result.error; - } else if (result.status === 'Cancelled') { - throw new UserCancelledError(); - } - } else { - const open: vscode.MessageItem = { title: "View in Marketplace" }; - const response = await vscode.window.showErrorMessage('Please install the Azure Account extension to deploy to Azure.', open); - if (response === open) { - // tslint:disable-next-line:no-unsafe-any - opn('https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account'); - } +// Remove this when https://github.com/Microsoft/vscode-docker/issues/445 fixed +function registerCommand( + commandId: string, + callback: (this: IActionContext, ...args: any[]) => any +): void { + return uiRegisterCommand( + commandId, + // tslint:disable-next-line:no-function-expression + async function (this: IActionContext, ...args: any[]): Promise { + if (args.length) { + let properties: { + contextValue?: string; + } & TelemetryProperties = this.properties; + const contextArg = args[0]; + + if (contextArg instanceof NodeBase) { + properties.contextValue = contextArg.contextValue; + } else if (contextArg instanceof vscode.Uri) { + properties.contextValue = "Uri"; } - } -} + } -// Remove this when https://github.com/Microsoft/vscode-docker/issues/445 fixed -// tslint:disable-next-line:no-any -function registerCommand(commandId: string, callback: (this: IActionContext, ...args: any[]) => any): void { - return uiRegisterCommand( - commandId, - // tslint:disable-next-line:no-function-expression no-any - async function (this: IActionContext, ...args: any[]): Promise { - if (args.length) { - let properties: { - contextValue?: string; - } & TelemetryProperties = this.properties; - const contextArg = args[0]; - - if (contextArg instanceof NodeBase) { - properties.contextValue = contextArg.contextValue; - } else if (contextArg instanceof vscode.Uri) { - properties.contextValue = 'Uri'; - } - } - - return callback.call(this, ...args); - }); + return callback.call(this, ...args); + } + ); } -function registerDockerCommands(azureAccount: AzureAccount | undefined): void { - dockerExplorerProvider = new DockerExplorerProvider(azureAccount); - vscode.window.registerTreeDataProvider('dockerExplorer', dockerExplorerProvider); - registerCommand('vscode-docker.explorer.refresh', () => dockerExplorerProvider.refresh()); - - registerCommand('vscode-docker.configure', async function (this: IActionContext): Promise { await configure(this, undefined); }); - registerCommand('vscode-docker.api.configure', async function (this: IActionContext, options: ConfigureApiOptions): Promise { - await configureApi(this, options); - }); - - registerCommand('vscode-docker.container.start', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainer(this, node); }); - registerCommand('vscode-docker.container.start.interactive', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainerInteractive(this, node); }); - registerCommand('vscode-docker.container.start.azurecli', async function (this: IActionContext): Promise { await startAzureCLI(this); }); - registerCommand('vscode-docker.container.stop', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await stopContainer(this, node); }); - registerCommand('vscode-docker.container.restart', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await restartContainer(this, node); }); - registerCommand('vscode-docker.container.show-logs', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await showLogsContainer(this, node); }); - registerCommand('vscode-docker.container.open-shell', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await openShellContainer(this, node); }); - registerCommand('vscode-docker.container.remove', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await removeContainer(this, node); }); - registerCommand('vscode-docker.image.build', async function (this: IActionContext, item: vscode.Uri | undefined): Promise { await buildImage(this, item); }); - registerCommand('vscode-docker.image.inspect', async function (this: IActionContext, node: ImageNode | undefined): Promise { await inspectImage(this, node); }); - registerCommand('vscode-docker.image.remove', async function (this: IActionContext, node: ImageNode | RootNode | undefined): Promise { await removeImage(this, node); }); - registerCommand('vscode-docker.image.push', async function (this: IActionContext, node: ImageNode | undefined): Promise { await pushImage(this, node); }); - registerCommand('vscode-docker.image.tag', async function (this: IActionContext, node: ImageNode | undefined): Promise { await tagImage(this, node); }); - registerCommand('vscode-docker.compose.up', composeUp); - registerCommand('vscode-docker.compose.down', composeDown); - registerCommand('vscode-docker.compose.restart', composeRestart); - registerCommand('vscode-docker.system.prune', systemPrune); - registerCommand('vscode-docker.createWebApp', async (context?: AzureImageTagNode | DockerHubImageTagNode) => await createWebApp(context, azureAccount)); - registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout); - registerCommand('vscode-docker.browseDockerHub', (context?: DockerHubImageTagNode | DockerHubRepositoryNode | DockerHubOrgNode) => { - browseDockerHub(context); - }); - registerCommand('vscode-docker.browseAzurePortal', (context?: AzureRegistryNode | AzureRepositoryNode | AzureImageTagNode) => { - browseAzurePortal(context); - }); - registerCommand('vscode-docker.connectCustomRegistry', connectCustomRegistry); - registerCommand('vscode-docker.disconnectCustomRegistry', disconnectCustomRegistry); - registerCommand('vscode-docker.setRegistryAsDefault', setRegistryAsDefault); - registerAzureCommand('vscode-docker.delete-ACR-Registry', deleteAzureRegistry); - registerAzureCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); - registerAzureCommand('vscode-docker.delete-ACR-Repository', deleteRepository); - registerAzureCommand('vscode-docker.create-ACR-Registry', createRegistry); +// tslint:disable-next-line:max-func-body-length +function registerDockerCommands(azureAccount: AzureAccount): void { + dockerExplorerProvider = new DockerExplorerProvider(azureAccount); + vscode.window.registerTreeDataProvider( + "dockerExplorer", + dockerExplorerProvider + ); + registerCommand("vscode-docker.explorer.refresh", () => + dockerExplorerProvider.refresh() + ); + + registerCommand("vscode-docker.configure", async function ( + this: IActionContext + ): Promise { + await configure(this, undefined); + }); + registerCommand("vscode-docker.api.configure", async function ( + this: IActionContext, + options: ConfigureApiOptions + ): Promise { + await configureApi(this, options); + }); + + registerCommand("vscode-docker.container.start", async function ( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await startContainer(this, node); + }); + registerCommand("vscode-docker.container.start.interactive", async function ( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await startContainerInteractive(this, node); + }); + registerCommand("vscode-docker.container.start.azurecli", startAzureCLI); + registerCommand("vscode-docker.container.stop", async function ( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await stopContainer(this, node); + }); + registerCommand("vscode-docker.container.restart", async function ( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await restartContainer(this, node); + }); + registerCommand("vscode-docker.container.show-logs", async function ( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await showLogsContainer(this, node); + }); + registerCommand("vscode-docker.container.open-shell", async function ( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await openShellContainer(this, node); + }); + registerCommand("vscode-docker.container.remove", async function ( + this: IActionContext, + node: ContainerNode | RootNode | undefined + ): Promise { + await removeContainer(this, node); + }); + registerCommand("vscode-docker.image.build", async function ( + this: IActionContext, + item: vscode.Uri | undefined + ): Promise { + await buildImage(this, item); + }); + registerCommand("vscode-docker.image.inspect", async function ( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await inspectImage(this, node); + }); + registerCommand("vscode-docker.image.remove", async function ( + this: IActionContext, + node: ImageNode | RootNode | undefined + ): Promise { + await removeImage(this, node); + }); + registerCommand("vscode-docker.image.push", async function ( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await pushImage(this, node); + }); + registerCommand("vscode-docker.image.tag", async function ( + this: IActionContext, + node: ImageNode | undefined + ): Promise { + await tagImage(this, node); + }); + registerCommand("vscode-docker.compose.up", composeUp); + registerCommand("vscode-docker.compose.down", composeDown); + registerCommand("vscode-docker.compose.restart", composeRestart); + registerCommand("vscode-docker.system.prune", systemPrune); + registerCommand( + "vscode-docker.createWebApp", + async (context?: AzureImageTagNode | DockerHubImageTagNode) => + await createWebApp(context, azureAccount) + ); + registerCommand("vscode-docker.dockerHubLogout", dockerHubLogout); + registerCommand( + "vscode-docker.browseDockerHub", + ( + context?: + | DockerHubImageTagNode + | DockerHubRepositoryNode + | DockerHubOrgNode + ) => { + browseDockerHub(context); + } + ); + registerCommand( + "vscode-docker.browseAzurePortal", + (context?: AzureRegistryNode | AzureRepositoryNode | AzureImageTagNode) => { + browseAzurePortal(context); + } + ); + registerCommand("vscode-docker.connectCustomRegistry", connectCustomRegistry); + registerCommand( + "vscode-docker.disconnectCustomRegistry", + disconnectCustomRegistry + ); + registerCommand("vscode-docker.setRegistryAsDefault", setRegistryAsDefault); + registerAzureCommand( + "vscode-docker.delete-ACR-Registry", + deleteAzureRegistry + ); + registerAzureCommand("vscode-docker.delete-ACR-Image", deleteAzureImage); + registerAzureCommand("vscode-docker.delete-ACR-Repository", deleteRepository); + registerAzureCommand("vscode-docker.create-ACR-Registry", createRegistry); + registerAzureCommand("vscode-docker.pullFromAzure", pullFromAzure); + registerAzureCommand("vscode-docker.acrLogs", viewACRLogs); + registerAzureCommand("vscode-docker.run-ACR-Task", runTask); + registerAzureCommand("vscode-docker.show-ACR-Task", showTaskProperties); + registerCommand("vscode-docker.ACR-queueBuild", queueBuild); } export async function deactivate(): Promise { - if (!client) { - return undefined; - } - // perform cleanup - Configuration.dispose(); - return await client.stop(); + if (!client) { + return undefined; + } + // perform cleanup + Configuration.dispose(); + return await client.stop(); } namespace Configuration { - - let configurationListener: vscode.Disposable; - - export function computeConfiguration(params: ConfigurationParams): vscode.WorkspaceConfiguration[] { - let result: vscode.WorkspaceConfiguration[] = []; - for (let item of params.items) { - let config: vscode.WorkspaceConfiguration; - - if (item.scopeUri) { - config = vscode.workspace.getConfiguration(item.section, client.protocol2CodeConverter.asUri(item.scopeUri)); - } else { - config = vscode.workspace.getConfiguration(item.section); - } - result.push(config); - } - return result; + let configurationListener: vscode.Disposable; + + export function computeConfiguration( + params: ConfigurationParams + ): vscode.WorkspaceConfiguration[] { + let result: vscode.WorkspaceConfiguration[] = []; + for (let item of params.items) { + let config: vscode.WorkspaceConfiguration; + + if (item.scopeUri) { + config = vscode.workspace.getConfiguration( + item.section, + client.protocol2CodeConverter.asUri(item.scopeUri) + ); + } else { + config = vscode.workspace.getConfiguration(item.section); + } + result.push(config); } - - export function initialize(): void { - configurationListener = vscode.workspace.onDidChangeConfiguration((e: vscode.ConfigurationChangeEvent) => { - // notify the language server that settings have change - client.sendNotification(DidChangeConfigurationNotification.type, { settings: null }); - - // Update endpoint and refresh explorer if needed - if (e.affectsConfiguration('docker')) { - docker.refreshEndpoint(); - vscode.commands.executeCommand("vscode-docker.explorer.refresh"); - } + return result; + } + + export function initialize(): void { + configurationListener = vscode.workspace.onDidChangeConfiguration( + (e: vscode.ConfigurationChangeEvent) => { + // notify the language server that settings have change + client.sendNotification(DidChangeConfigurationNotification.type, { + settings: null }); - } - export function dispose(): void { - if (configurationListener) { - // remove this listener when disposed - configurationListener.dispose(); + // Update endpoint and refresh explorer if needed + if (e.affectsConfiguration("docker")) { + docker.refreshEndpoint(); + vscode.commands.executeCommand("vscode-docker.explorer.refresh"); } + } + ); + } + + export function dispose(): void { + if (configurationListener) { + // remove this listener when disposed + configurationListener.dispose(); } + } } function activateLanguageClient(ctx: vscode.ExtensionContext): void { - let serverModule = ctx.asAbsolutePath(path.join("node_modules", "dockerfile-language-server-nodejs", "lib", "server.js")); - let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; - - let serverOptions: ServerOptions = { - run: { module: serverModule, transport: TransportKind.ipc, args: ["--node-ipc"] }, - debug: { module: serverModule, transport: TransportKind.ipc, options: debugOptions } + let serverModule = ctx.asAbsolutePath( + path.join( + "node_modules", + "dockerfile-language-server-nodejs", + "lib", + "server.js" + ) + ); + let debugOptions = { execArgv: ["--nolazy", "--inspect=6009"] }; + + let serverOptions: ServerOptions = { + run: { + module: serverModule, + transport: TransportKind.ipc, + args: ["--node-ipc"] + }, + debug: { + module: serverModule, + transport: TransportKind.ipc, + options: debugOptions } + }; - let middleware: Middleware = { - workspace: { - configuration: Configuration.computeConfiguration - } - }; - - let clientOptions: LanguageClientOptions = { - documentSelector: DOCUMENT_SELECTOR, - synchronize: { - fileEvents: vscode.workspace.createFileSystemWatcher('**/.clientrc') - }, - middleware: middleware + let middleware: Middleware = { + workspace: { + configuration: Configuration.computeConfiguration } - - client = new LanguageClient("dockerfile-langserver", "Dockerfile Language Server", serverOptions, clientOptions); - // tslint:disable-next-line:no-floating-promises - client.onReady().then(() => { - // attach the VS Code settings listener - Configuration.initialize(); - }); - client.start(); + }; + + let clientOptions: LanguageClientOptions = { + documentSelector: DOCUMENT_SELECTOR, + synchronize: { + fileEvents: vscode.workspace.createFileSystemWatcher("**/.clientrc") + }, + middleware: middleware + }; + + client = new LanguageClient( + "dockerfile-langserver", + "Dockerfile Language Server", + serverOptions, + clientOptions + ); + // tslint:disable-next-line:no-floating-promises + client.onReady().then(() => { + // attach the VS Code settings listener + Configuration.initialize(); + }); + client.start(); } diff --git a/explorer/deploy/webAppCreator.ts b/explorer/deploy/webAppCreator.ts index 3af5f72a5a..0178732964 100644 --- a/explorer/deploy/webAppCreator.ts +++ b/explorer/deploy/webAppCreator.ts @@ -9,6 +9,8 @@ import { ResourceManagementClient, ResourceModels, SubscriptionModels } from 'az import { Subscription } from 'azure-arm-resource/lib/subscription/models'; import WebSiteManagementClient = require('azure-arm-website'); import * as WebSiteModels from 'azure-arm-website/lib/models'; +import * as fs from 'fs'; +import * as path from 'path'; import * as vscode from 'vscode'; import { addExtensionUserAgent } from 'vscode-azureextensionui'; import { reporter } from '../../telemetry/telemetry'; diff --git a/explorer/models/azureRegistryNodes.ts b/explorer/models/azureRegistryNodes.ts index 3f8aef822b..1a0b895168 100644 --- a/explorer/models/azureRegistryNodes.ts +++ b/explorer/models/azureRegistryNodes.ts @@ -14,6 +14,7 @@ import { Repository } from '../../utils/Azure/models/repository'; import { getLoginServer, } from '../../utils/nonNull'; import { formatTag } from './commonRegistryUtils'; import { IconPath, NodeBase } from './nodeBase'; +import { TaskRootNode } from './taskNode'; export class AzureRegistryNode extends NodeBase { constructor( @@ -40,8 +41,12 @@ export class AzureRegistryNode extends NodeBase { } } - public async getChildren(element: AzureRegistryNode): Promise { - const repoNodes: AzureRepositoryNode[] = []; + public async getChildren(element: AzureRegistryNode): Promise { + const repoNodes: NodeBase[] = []; + + //Pushing single TaskRootNode under the current registry. All the following nodes added to registryNodes are type AzureRepositoryNode + let taskNode = new TaskRootNode("Tasks", element.azureAccount, element.subscription, element.registry); + repoNodes.push(taskNode); if (!this.azureAccount) { return []; @@ -62,7 +67,6 @@ export class AzureRegistryNode extends NodeBase { return repoNodes; } } - export class AzureRepositoryNode extends NodeBase { constructor( public readonly label: string, diff --git a/explorer/models/imageNode.ts b/explorer/models/imageNode.ts index c87211af96..52af9c5f85 100644 --- a/explorer/models/imageNode.ts +++ b/explorer/models/imageNode.ts @@ -45,5 +45,5 @@ export class ImageNode extends NodeBase { } } - // no children + // No children } diff --git a/explorer/models/registryRootNode.ts b/explorer/models/registryRootNode.ts index 39ee0c7720..b87b2cc141 100644 --- a/explorer/models/registryRootNode.ts +++ b/explorer/models/registryRootNode.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; +import * as ContainerOps from 'azure-arm-containerregistry/lib/operations'; import { SubscriptionModels } from 'azure-arm-resource'; import * as vscode from 'vscode'; import { parseError } from 'vscode-azureextensionui'; @@ -150,6 +151,7 @@ export class RegistryRootNode extends NodeBase { } catch (error) { vscode.window.showErrorMessage(parseError(error).message); } + }); } await subPool.runAll(); diff --git a/explorer/models/rootNode.ts b/explorer/models/rootNode.ts index 6312f58104..415cfc7a48 100644 --- a/explorer/models/rootNode.ts +++ b/explorer/models/rootNode.ts @@ -104,16 +104,20 @@ export class RootNode extends NodeBase { } - public async getChildren(element: NodeBase): Promise { - - if (element.contextValue === 'imagesRootNode') { - return this.getImages(); - } - if (element.contextValue === 'containersRootNode') { - return this.getContainers(); - } - if (element.contextValue === 'registriesRootNode') { - return this.getRegistries() + public async getChildren(element: RootNode): Promise { + switch (element.contextValue) { + case 'imagesRootNode': { + return this.getImages(); + } + case 'containersRootNode': { + return this.getContainers(); + } + case 'registriesRootNode': { + return this.getRegistries(); + } + default: { + break; + } } throw new Error(`Unexpected contextValue ${element.contextValue}`); @@ -183,15 +187,13 @@ export class RootNode extends NodeBase { if (this._containerCache.length !== containers.length) { needToRefresh = true; } else { - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let i = 0; i < this._containerCache.length; i++) { - let ctr: Docker.ContainerDesc = this._containerCache[i]; - // tslint:disable-next-line:prefer-for-of // Grandfathered in - for (let j = 0; j < containers.length; j++) { + for (let cachedContainer of this._containerCache) { + let ctr: Docker.ContainerDesc = cachedContainer; + for (let cont of containers) { // 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) { + if (ctr.Id === cont.Id && + ctr.Image === cont.Image && + ctr.State === cont.State) { found = true; break; } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts new file mode 100644 index 0000000000..dc79ace1d7 --- /dev/null +++ b/explorer/models/taskNode.ts @@ -0,0 +1,86 @@ +import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; +import { SubscriptionModels } from 'azure-arm-resource'; +import * as opn from 'opn'; +import * as path from 'path'; +import * as vscode from 'vscode'; +import { AzureAccount } from '../../typings/azure-account.api'; +import * as acrTools from '../../utils/Azure/acrTools'; +import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { NodeBase } from './nodeBase'; +/* Single TaskRootNode under each Repository. Labeled "Tasks" */ +export class TaskRootNode extends NodeBase { + public static readonly contextValue: string = 'taskRootNode'; + private _onDidChangeTreeData: vscode.EventEmitter = new vscode.EventEmitter(); + public readonly onDidChangeTreeData: vscode.Event = this._onDidChangeTreeData.event; + constructor( + public readonly label: string, + public readonly azureAccount: AzureAccount, + public readonly subscription: SubscriptionModels.Subscription, + public readonly registry: ContainerModels.Registry, + //public readonly iconPath: any = null, + ) { + super(label); + } + + public readonly contextValue: string = 'taskRootNode'; + public name: string; + public readonly iconPath: { light: string | vscode.Uri; dark: string | vscode.Uri } = { + light: path.join(__filename, '..', '..', '..', '..', 'images', 'light', 'tasks_light.svg'), + dark: path.join(__filename, '..', '..', '..', '..', 'images', 'dark', 'tasks_dark.svg') + }; + + public getTreeItem(): vscode.TreeItem { + return { + label: this.label, + collapsibleState: vscode.TreeItemCollapsibleState.Collapsed, + contextValue: TaskRootNode.contextValue, + iconPath: this.iconPath + } + } + + /* Making a list view of TaskNodes, or the Tasks of the current registry */ + public async getChildren(element: TaskRootNode): Promise { + const taskNodes: TaskNode[] = []; + let tasks: ContainerModels.Task[] = []; + const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); + const resourceGroup: string = acrTools.getResourceGroupName(element.registry); + tasks = await client.tasks.list(resourceGroup, element.registry.name); + if (tasks.length === 0) { + vscode.window.showInformationMessage(`You do not have any Tasks in the registry, '${element.registry.name}'. You can create one with ACR Task. `, "Learn More").then(val => { + if (val === "Learn More") { + opn('https://aka.ms/acr/task'); + } + }) + } + + for (let task of tasks) { + let node = new TaskNode(task, element.registry, element.subscription, element); + taskNodes.push(node); + } + return taskNodes; + } +} +export class TaskNode extends NodeBase { + constructor( + public task: ContainerModels.Task, + public registry: ContainerModels.Registry, + + public subscription: SubscriptionModels.Subscription, + public parent: NodeBase + + ) { + super(task.name); + } + + public label: string; + public readonly contextValue: string = 'taskNode'; + + public getTreeItem(): vscode.TreeItem { + return { + label: this.label, + collapsibleState: vscode.TreeItemCollapsibleState.None, + contextValue: this.contextValue, + iconPath: null + } + } +} diff --git a/images/dark/tasks_dark.svg b/images/dark/tasks_dark.svg new file mode 100644 index 0000000000..618310ec7c --- /dev/null +++ b/images/dark/tasks_dark.svg @@ -0,0 +1 @@ +Manufacture_16x \ No newline at end of file diff --git a/images/light/tasks_light.svg b/images/light/tasks_light.svg new file mode 100644 index 0000000000..27f50db2ce --- /dev/null +++ b/images/light/tasks_light.svg @@ -0,0 +1 @@ +Manufacture_16x \ No newline at end of file diff --git a/package.json b/package.json index 16e36630af..08624750ef 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,9 @@ "activationEvents": [ "onLanguage:dockerfile", "onLanguage:yaml", + "onCommand:vscode-docker.acrLogs", "onCommand:vscode-docker.api.configure", + "onCommand:vscode-docker.ACR-queueBuild", "onCommand:vscode-docker.image.build", "onCommand:vscode-docker.image.inspect", "onCommand:vscode-docker.image.remove", @@ -53,12 +55,15 @@ "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", + "onCommand:vscode-docker.show-ACR-Task", "onCommand:vscode-docker.delete-ACR-Registry", + "onCommand:vscode-docker.run-ACR-Task", "onCommand:vscode-docker.delete-ACR-Repository", "onCommand:vscode-docker.delete-ACR-Image", "onCommand:vscode-docker.connectCustomRegistry", "onCommand:vscode-docker.setRegistryAsDefault", "onCommand:vscode-docker.disconnectCustomRegistry", + "onCommand:vscode-docker.pullFromAzure", "onView:dockerExplorer", "onDebugInitialConfigurations" ], @@ -80,6 +85,11 @@ } ], "editor/context": [ + { + "when": "editorLangId == dockerfile", + "command": "vscode-docker.ACR-queueBuild", + "group": "docker" + }, { "when": "editorLangId == dockerfile", "command": "vscode-docker.image.build", @@ -117,6 +127,11 @@ } ], "explorer/context": [ + { + "when": "resourceFilename =~ /[dD]ocker[fF]ile/", + "command": "vscode-docker.ACR-queueBuild", + "group": "docker" + }, { "when": "resourceFilename =~ /[dD]ocker[fF]ile/", "command": "vscode-docker.image.build", @@ -203,18 +218,38 @@ "command": "vscode-docker.create-ACR-Registry", "when": "view == dockerExplorer && viewItem == azureRegistryRootNode" }, + { + "command": "vscode-docker.create-ACR-Registry", + "when": "view == dockerExplorer && viewItem == azureRegistryRootNode" + }, { "command": "vscode-docker.dockerHubLogout", "when": "view == dockerExplorer && viewItem == dockerHubRootNode" }, { - "command": "vscode-docker.delete-ACR-Repository", - "when": "view == dockerExplorer && viewItem == azureRepositoryNode" + "command": "vscode-docker.pullFromAzure", + "when": "view == dockerExplorer && viewItem == azureImageNode" + }, + { + "command": "vscode-docker.browseDockerHub", + "when": "view == dockerExplorer && viewItem == dockerHubImageTag" + }, + { + "command": "vscode-docker.browseDockerHub", + "when": "view == dockerExplorer && viewItem == dockerHubRepository" }, { "command": "vscode-docker.delete-ACR-Image", "when": "view == dockerExplorer && viewItem == azureImageTagNode" }, + { + "command": "vscode-docker.run-ACR-Task", + "when": "view == dockerExplorer && viewItem == taskNode" + }, + { + "command": "vscode-docker.show-ACR-Task", + "when": "view == dockerExplorer && viewItem == taskNode" + }, { "command": "vscode-docker.delete-ACR-Registry", "when": "view == dockerExplorer && viewItem == azureRegistryNode" @@ -225,7 +260,11 @@ }, { "command": "vscode-docker.browseAzurePortal", - "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureRepositoryNode|azureImageTagNode)$/" + "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureRepositoryNode|azureImageNode)$/" + }, + { + "command": "vscode-docker.acrLogs", + "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureImageTagNode|taskNode)$/" }, { "command": "vscode-docker.connectCustomRegistry", @@ -451,6 +490,12 @@ "command": "vscode-docker.api.configure", "title": "Add Docker files to Workspace (API)" }, + { + "command": "vscode-docker.ACR-queueBuild", + "title": "Queue Build", + "description": "Queue a build from a Dockerfile", + "category": "Docker" + }, { "command": "vscode-docker.image.build", "title": "Build Image", @@ -551,6 +596,16 @@ "title": "Delete Azure Repository", "category": "Docker" }, + { + "command": "vscode-docker.delete-ACR-Repository", + "title": "Delete Azure Repository", + "category": "Docker" + }, + { + "command": "vscode-docker.delete-ACR-Image", + "title": "Delete Azure Image", + "category": "Docker" + }, { "command": "vscode-docker.image.push", "title": "Push", @@ -580,6 +635,11 @@ "title": "Deploy Image to Azure App Service", "category": "Docker" }, + { + "command": "vscode-docker.pullFromAzure", + "title": "Pull Image from Azure", + "category": "Docker" + }, { "command": "vscode-docker.dockerHubLogout", "title": "Docker Hub Logout", @@ -595,6 +655,16 @@ "title": "Browse in the Azure Portal", "category": "Docker" }, + { + "command": "vscode-docker.run-ACR-Task", + "title": "Run Task", + "category": "Docker" + }, + { + "command": "vscode-docker.show-ACR-Task", + "title": "Show Task Properties", + "category": "Docker" + }, { "command": "vscode-docker.delete-ACR-Registry", "title": "Delete Azure Registry", @@ -605,6 +675,11 @@ "title": "Delete Azure Image", "category": "Docker" }, + { + "command": "vscode-docker.acrLogs", + "title": "View Azure logs", + "category": "Docker" + }, { "command": "vscode-docker.connectCustomRegistry", "title": "Connect to a Private Registry... (Preview)", @@ -619,6 +694,11 @@ "command": "vscode-docker.disconnectCustomRegistry", "title": "Disconnect from Private Registry", "category": "Docker" + }, + { + "command": "vscode-docker.ACR-Build", + "title": "Cloud run", + "category": "Docker" } ], "views": { @@ -668,7 +748,6 @@ "@types/request-promise-native": "^1.0.15", "@types/semver": "^5.5.0", "adm-zip": "^0.4.11", - "azure-storage": "^2.8.1", "cross-env": "^5.2.0", "gulp": "^3.9.1", "mocha": "5.2.0", @@ -679,20 +758,25 @@ "vscode": "^1.1.18" }, "dependencies": { - "azure-arm-containerregistry": "^2.3.0", + "azure-arm-containerregistry": "^3.0.0", "azure-arm-resource": "^2.0.0-preview", "azure-arm-website": "^1.0.0-preview", "dockerfile-language-server-nodejs": "^0.0.19", + "azure-storage": "^2.8.1", + "clipboardy": "^1.2.3", "dockerode": "^2.5.1", "fs-extra": "^6.0.1", "glob": "7.1.2", "gradle-to-js": "^1.0.1", + "handlebars": "^4.0.11", "moment": "^2.19.3", "opn": "^5.2.0", "pom-parser": "^1.1.1", "request-promise-native": "^1.0.5", "semver": "^5.5.1", "vscode-azureextensionui": "^0.17.0", + "request-promise": "^4.2.2", + "tar": "^4.4.6", "vscode-extension-telemetry": "0.0.18", "vscode-languageclient": "^4.4.0" } diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 8ec082c743..8f1e559b00 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -5,11 +5,15 @@ import { AuthenticationContext } from 'adal-node'; import * as assert from 'assert'; -import { Registry } from "azure-arm-containerregistry/lib/models"; +import ContainerRegistryManagementClient from 'azure-arm-containerregistry'; +import { Registry, Run, RunGetLogResult } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; +import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; +import { BlobService, createBlobServiceWithSas } from "azure-storage"; import { ServiceClientCredentials } from 'ms-rest'; import { TokenResponse } from 'ms-rest-azure'; +import * as vscode from "vscode"; import { NULL_GUID } from "../../constants"; import { getCatalog, getTags, TagInfo } from "../../explorer/models/commonRegistryUtils"; import { ext } from '../../extensionVariables'; @@ -20,7 +24,8 @@ import { AzureImage } from "./models/image"; import { Repository } from "./models/repository"; //General helpers -/** Gets the subscription for a given registry +/** + * @param registry gets the subscription for a given registry * @returns a subscription object */ export function getSubscriptionFromRegistry(registry: Registry): SubscriptionModels.Subscription { @@ -43,6 +48,13 @@ export function getResourceGroupName(registry: Registry): string { return id.slice(id.search('resourceGroups/') + 'resourceGroups/'.length, id.search('/providers/')); } +//Gets resource group object from registry and subscription +export async function getResourceGroup(registry: Registry, subscription: Subscription): Promise { ///to do: move to acr tools + let resourceGroups: ResourceGroup[] = await AzureUtilityManager.getInstance().getResourceGroups(subscription); + const resourceGroupName = getResourceGroupName(registry); + return resourceGroups.find((res) => { return res.name === resourceGroupName }); +} + //Registry item management /** List images under a specific Repository */ export async function getImagesByRepository(element: Repository): Promise { @@ -71,7 +83,7 @@ export async function getRepositoriesByRegistry(registry: Registry): Promise { +export async function getLoginCredentials(registry: Registry): Promise<{ password: string, username: string }> { const subscription: Subscription = getSubscriptionFromRegistry(registry); const session: AzureSession = AzureUtilityManager.getInstance().getSession(subscription) const { aadAccessToken, aadRefreshToken } = await acquireAADTokens(session); @@ -172,3 +184,74 @@ export async function acquireACRAccessToken(registryUrl: string, scope: string, }); return acrAccessTokenResponse.access_token; } + +/** Parses information into a readable format from a blob url */ +export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { + let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); + let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); + let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); + let containerName: string = items[1]; + let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); + let sasToken: string = items[4].slice(items[4].search('[?]') + 1); + let host: string = accountName + '.blob.' + endpointSuffix; + return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; +} + +/** Stream logs from a blob into output channel. + * Note, since output streams don't actually deal with streams directly, text is not actually + * streamed in which prevents updating of already appended lines. Usure if this can be fixed. Nonetheless + * logs do load in chunks every 1 second. + */ +export async function streamLogs(registry: Registry, run: Run, outputChannel: vscode.OutputChannel, providedClient?: ContainerRegistryManagementClient): Promise { + //Prefer passed in client to avoid initialization but if not added obtains own + let client = providedClient ? providedClient : AzureUtilityManager.getInstance().getContainerRegistryManagementClient(getSubscriptionFromRegistry(registry)); + let temp: RunGetLogResult = await client.runs.getLogSasUrl(getResourceGroupName(registry), registry.name, run.runId); + const link = temp.logLink; + let blobInfo = getBlobInfo(link); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); + let available = 0; + let start = 0; + + let obtainLogs = setInterval(async () => { + let props: BlobService.BlobResult; + let metadata: { [key: string]: string; }; + try { + props = await getBlobProperties(blobInfo, blob); + metadata = props.metadata; + } catch (error) { + //Not found happens when the properties havent yet been set, blob is not ready. Wait 1 second and try again + if (error.code === "NotFound") { return; } else { throw error; } + } + available = +props.contentLength; + let text: string; + //Makes sure that if item fails it does so due to network/azure errors not lack of new content + if (available > start) { + text = await getBlobToText(blobInfo, blob, start); + let utf8encoded = (new Buffer(text, 'ascii')).toString('utf8'); + start += text.length; + outputChannel.append(utf8encoded); + } + if (metadata.Complete) { + clearInterval(obtainLogs); + } + }, 1000); +} + +// Promisify getBlobToText for readability and error handling purposes +async function getBlobToText(blobInfo: any, blob: BlobService, rangeStart: number): Promise { + return new Promise((resolve, reject) => { + blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, { rangeStart: rangeStart }, + (error, result) => { + if (error) { reject() } else { resolve(result); } + }); + }); +} + +// Promisify getBlobProperties for readability and error handling purposes +async function getBlobProperties(blobInfo: any, blob: BlobService): Promise { + return new Promise((resolve, reject) => { + blob.getBlobProperties(blobInfo.containerName, blobInfo.blobName, (error, result) => { + if (error) { reject(error) } else { resolve(result); } + }); + }); +} diff --git a/utils/azureUtilityManager.ts b/utils/azureUtilityManager.ts index 4d5efe2e68..271d4df4ea 100644 --- a/utils/azureUtilityManager.ts +++ b/utils/azureUtilityManager.ts @@ -5,6 +5,7 @@ import * as assert from 'assert'; import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry'; +import { Registry } from 'azure-arm-containerregistry/lib/models'; import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { ResourceManagementClient, SubscriptionClient, SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; @@ -18,7 +19,7 @@ import { getSubscriptionId, getTenantId } from './nonNull'; /* Singleton for facilitating communication with Azure account services by providing extended shared functionality and extension wide access to azureAccount. Tool for internal use. - Authors: Esteban Rey L, Jackson Stokes + Authors: Esteban Rey L, Jackson Stokes, Julia Lieberman */ export class AzureUtilityManager { @@ -111,6 +112,7 @@ export class AzureUtilityManager { for (let sub of subs) { subPool.addTask(async () => { const client = this.getContainerRegistryManagementClient(sub); + let subscriptionRegistries: ContainerModels.Registry[] = await client.registries.list(); registries = registries.concat(subscriptionRegistries); }); @@ -133,13 +135,14 @@ export class AzureUtilityManager { const resourceClient = this.getResourceManagementClient(subscription); return await resourceClient.resourceGroups.list(); } - const subs = this.getFilteredSubscriptionList(); + const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptionList(); const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS); let resourceGroups: ResourceGroup[] = []; //Acquire each subscription's data simultaneously - for (let sub of subs) { + + for (let tempSub of subs) { subPool.addTask(async () => { - const resourceClient = this.getResourceManagementClient(sub); + const resourceClient = this.getResourceManagementClient(tempSub); const internalGroups = await resourceClient.resourceGroups.list(); resourceGroups = resourceGroups.concat(internalGroups); }); @@ -151,11 +154,9 @@ export class AzureUtilityManager { public getCredentialByTenantId(tenantIdOrSubscription: string | Subscription): ServiceClientCredentials { let tenantId = typeof tenantIdOrSubscription === 'string' ? tenantIdOrSubscription : getTenantId(tenantIdOrSubscription); const session = this.getAccount().sessions.find((azureSession) => azureSession.tenantId.toLowerCase() === tenantId.toLowerCase()); - if (session) { return session.credentials; } - throw new Error(`Failed to get credentials, tenant ${tenantId} not found.`); } From 3dc9a9caf3db3dc447606c90b2f05059c4c9b4c7 Mon Sep 17 00:00:00 2001 From: Sajay Antony <1821104+sajayantony@users.noreply.github.com> Date: Fri, 21 Sep 2018 18:32:12 -0700 Subject: [PATCH 67/77] Removed filter for top (#88) --- commands/azureCommands/acr-logs-utils/tableDataManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index cb3b235558..73c3e81544 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -57,7 +57,7 @@ export class LogData { if (filter && Object.keys(filter).length) { if (!filter.runId) { options.filter = await this.parseFilter(filter); - options.top = 1; + //Add top filter for image #87 runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); } else { runListResult = []; From 123dd9adbec90bef4adc15ce8f7303ce08ebb543 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Mon, 24 Sep 2018 13:52:50 -0700 Subject: [PATCH 68/77] fixing Image Log Filter --- commands/azureCommands/acr-logs-utils/tableDataManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index cb3b235558..7ebe3f8f27 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -53,11 +53,12 @@ export class LogData { */ public async loadLogs(loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { let runListResult: RunListResult; + // tslint:disable-next-line:no-any let options: any = {}; if (filter && Object.keys(filter).length) { if (!filter.runId) { options.filter = await this.parseFilter(filter); - options.top = 1; + if (filter.image) { options.top = 1; } runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); } else { runListResult = []; From 552310ad363ca05599012f3d5a18cbfb68800e16 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Thu, 11 Oct 2018 16:56:18 -0700 Subject: [PATCH 69/77] fixing tslint error messages --- commands/azureCommands/acr-build.ts | 44 ++++++++++--------- .../acr-logs-utils/logFileManager.ts | 7 +-- .../acr-logs-utils/tableDataManager.ts | 14 +++--- .../acr-logs-utils/tableViewManager.ts | 2 +- commands/azureCommands/acr-logs.ts | 11 ++--- commands/azureCommands/pull-from-azure.ts | 6 +-- commands/azureCommands/run-task.ts | 6 +-- commands/azureCommands/show-task.ts | 7 ++- .../task-utils/showTaskManager.ts | 3 +- commands/build-image.ts | 2 +- dockerExtension.ts | 7 +-- explorer/models/taskNode.ts | 3 +- utils/Azure/acrTools.ts | 43 ++++++++++++------ utils/addUserAgent.ts | 1 - utils/nonNull.ts | 2 - 15 files changed, 89 insertions(+), 69 deletions(-) diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 51f9b020be..048879fbb0 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -1,6 +1,8 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry/lib/containerRegistryManagementClient'; +import { Run, SourceUploadDefinition } from 'azure-arm-containerregistry/lib/models'; import { DockerBuildRequest } from "azure-arm-containerregistry/lib/models/dockerBuildRequest"; import { Registry } from 'azure-arm-containerregistry/lib/models/registry'; +import { Subscription } from 'azure-arm-resource/lib/subscription/models'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as fs from 'fs'; import * as os from 'os'; @@ -10,9 +12,9 @@ import * as url from 'url'; import * as vscode from "vscode"; import { IAzureQuickPickItem } from 'vscode-azureextensionui'; import { ext } from '../../extensionVariables'; -import { getBlobInfo, getResourceGroupName, streamLogs } from "../../utils/Azure/acrTools"; +import { getBlobInfo, getResourceGroupName, IBlobInfo, streamLogs } from "../../utils/Azure/acrTools"; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; -import { resolveDockerFileItem } from '../build-image'; +import { Item, resolveDockerFileItem } from '../build-image'; import { quickPickACRRegistry, quickPickNewImageName, quickPickSubscription } from '../utils/quick-pick-azure'; const idPrecision = 6; @@ -24,12 +26,12 @@ const status = vscode.window.createOutputChannel('ACR Build status'); // Selected source code must contain a path to the desired dockerfile. export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { //Acquire information from user - const subscription = await quickPickSubscription(); + const subscription: Subscription = await quickPickSubscription(); - const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + const client: ContainerRegistryManagementClient = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); const registry: Registry = await quickPickACRRegistry(true); - const resourceGroupName = getResourceGroupName(registry); + const resourceGroupName: string = getResourceGroupName(registry); const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); const osType: string = (await ext.ui.showQuickPick(osPick, { 'canPickMany': false, 'placeHolder': 'Select image base OS' })).data; @@ -43,13 +45,13 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { folder = vscode.workspace.workspaceFolders[0]; } else { - folder = await (vscode).window.showWorkspaceFolderPick(); + folder = await vscode.window.showWorkspaceFolderPick(); } - const dockerItem = await resolveDockerFileItem(folder, dockerFileUri); + const dockerItem: Item = await resolveDockerFileItem(folder, dockerFileUri); const sourceLocation: string = folder.uri.path; - const tarFilePath = getTempSourceArchivePath(); + const tarFilePath: string = getTempSourceArchivePath(); - const uploadedSourceLocation = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); + const uploadedSourceLocation: string = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); status.appendLine("Uploaded Source Code to " + tarFilePath); const runRequest: DockerBuildRequest = { @@ -62,7 +64,7 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { }; status.appendLine("Set up Run Request"); - const run = await client.registries.scheduleRun(resourceGroupName, registry.name, runRequest); + const run: Run = await client.registries.scheduleRun(resourceGroupName, registry.name, runRequest); status.appendLine("Schedule Run " + run.runId); streamLogs(registry, run, status, client); @@ -70,8 +72,8 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { status.appendLine(" Sending source code to temp file"); - let source = sourceLocation.substring(1); - let current = process.cwd(); + let source: string = sourceLocation.substring(1); + let current: string = process.cwd(); process.chdir(source); fs.readdir(source, (err, items) => { items = filter(items); @@ -83,30 +85,30 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis }); status.appendLine(" Getting Build Source Upload Url "); - let sourceUploadLocation = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); - let upload_url = sourceUploadLocation.uploadUrl; - let relative_path = sourceUploadLocation.relativePath; + let sourceUploadLocation: SourceUploadDefinition = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); + let upload_url: string = sourceUploadLocation.uploadUrl; + let relative_path: string = sourceUploadLocation.relativePath; status.appendLine(" Getting blob info from Upload Url "); // Right now, accountName and endpointSuffix are unused, but will be used for streaming logs later. - let { accountName, endpointSuffix, containerName, blobName, sasToken, host } = getBlobInfo(upload_url); + let blobInfo: IBlobInfo = getBlobInfo(upload_url); status.appendLine(" Creating Blob Service "); - let blob: BlobService = createBlobServiceWithSas(host, sasToken); + let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); status.appendLine(" Creating Block Blob "); - blob.createBlockBlobFromLocalFile(containerName, blobName, tarFilePath, (): void => { }); + blob.createBlockBlobFromLocalFile(blobInfo.containerName, blobInfo.blobName, tarFilePath, (): void => { }); return relative_path; } function getTempSourceArchivePath(): string { /* tslint:disable-next-line:insecure-random */ - let id = Math.floor(Math.random() * Math.pow(10, idPrecision)); + let id: number = Math.floor(Math.random() * Math.pow(10, idPrecision)); status.appendLine("Setting up temp file with 'sourceArchive" + id + ".tar.gz' "); - let tarFilePath = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); + let tarFilePath: string = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); return tarFilePath; } function filter(list: string[]): string[] { - let result = []; + let result: string[] = []; for (let file of list) { if (vcsIgnoreList.indexOf(file) === -1) { result.push(file); diff --git a/commands/azureCommands/acr-logs-utils/logFileManager.ts b/commands/azureCommands/acr-logs-utils/logFileManager.ts index f377d78538..58fbbb5de3 100644 --- a/commands/azureCommands/acr-logs-utils/logFileManager.ts +++ b/commands/azureCommands/acr-logs-utils/logFileManager.ts @@ -1,7 +1,7 @@ import { BlobService, createBlobServiceWithSas } from 'azure-storage'; import * as fs from 'fs'; import * as vscode from 'vscode'; -import { getBlobInfo } from '../../../utils/Azure/acrTools'; +import { getBlobInfo, IBlobInfo } from '../../../utils/Azure/acrTools'; export class LogContentProvider implements vscode.TextDocumentContentProvider { public static scheme: string = 'purejs'; @@ -10,7 +10,8 @@ export class LogContentProvider implements vscode.TextDocumentContentProvider { constructor() { } public provideTextDocumentContent(uri: vscode.Uri): string { - return decodeBase64(JSON.parse(uri.query).log); + let parse: { log: string } = JSON.parse(uri.query); + return decodeBase64(parse.log); } get onDidChange(): vscode.Event { @@ -33,7 +34,7 @@ export function encodeBase64(str: string): string { /** Loads log text from remote url using azure blobservices */ export function accessLog(url: string, title: string, download: boolean): void { - let blobInfo = getBlobInfo(url); + let blobInfo: IBlobInfo = getBlobInfo(url); let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, async (error, text, result, response) => { if (response) { diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index 7ebe3f8f27..223734032b 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -1,8 +1,6 @@ import ContainerRegistryManagementClient from "azure-arm-containerregistry"; import { Registry, Run, RunGetLogResult, RunListResult } from "azure-arm-containerregistry/lib/models"; import request = require('request-promise'); -import { registryRequest } from "../../../explorer/models/commonRegistryUtils"; -import { Manifest } from "../../../explorer/utils/dockerHubUtils"; import { acquireACRAccessTokenFromRegistry } from "../../../utils/Azure/acrTools"; /** Class to manage data and data acquisition for logs */ export class LogData { @@ -53,8 +51,13 @@ export class LogData { */ public async loadLogs(loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { let runListResult: RunListResult; - // tslint:disable-next-line:no-any - let options: any = {}; + let options: { + filter?: string, + top?: number, + customHeaders?: { + [headerName: string]: string; + }; + } = {}; if (filter && Object.keys(filter).length) { if (!filter.runId) { options.filter = await this.parseFilter(filter); @@ -114,11 +117,10 @@ export class LogData { headers: { accept: 'application/vnd.docker.distribution.manifest.v2+json; 0.5, application/vnd.docker.distribution.manifest.list.v2+json; 0.6' } - }, (err, httpResponse, body) => { + }, (err, httpResponse: { headers: string }, body) => { digest = httpResponse.headers['docker-content-digest']; }); - //let manifest: any = await registryRequest(this.registry.loginServer, `v2/${items[0]}/manifests/${items[1]}`, { bearer: acrAccessToken }); if (parsedFilter.length > 0) { parsedFilter += ' and '; } parsedFilter += `contains(OutputImageManifests, '${items[0]}@${digest}')`; } diff --git a/commands/azureCommands/acr-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts index 1743d511e7..f084c782b3 100644 --- a/commands/azureCommands/acr-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -3,7 +3,7 @@ import { ImageDescriptor, Run } from "azure-arm-containerregistry/lib/models"; import * as clipboardy from 'clipboardy' import * as path from 'path'; import * as vscode from "vscode"; -import { accessLog, downloadLog } from './logFileManager'; +import { accessLog } from './logFileManager'; import { LogData } from './tableDataManager' export class LogTableWebview { private logData: LogData; diff --git a/commands/azureCommands/acr-logs.ts b/commands/azureCommands/acr-logs.ts index 05ebd416eb..5694c9f1fa 100644 --- a/commands/azureCommands/acr-logs.ts +++ b/commands/azureCommands/acr-logs.ts @@ -1,3 +1,5 @@ +"use strict"; + import { Registry, Run } from "azure-arm-containerregistry/lib/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import * as vscode from "vscode"; @@ -29,11 +31,10 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode // Fuiltering provided if (context && context instanceof AzureImageTagNode) { //ACR Image Logs - let imageRun = await logData.loadLogs(false, false, { image: context.label }); + await logData.loadLogs(false, false, { image: context.label }); if (!hasValidLogContent(context, logData)) { return; } - logData.getLink(0).then((url) => { - accessLog(url, logData.logs[0].runId, false); - }); + const url = await logData.getLink(0); + accessLog(url, logData.logs[0].runId, false); } else { if (context && context instanceof TaskNode) { //ACR Task Logs @@ -51,7 +52,7 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode } } -function hasValidLogContent(context: any, logData: LogData): boolean { +function hasValidLogContent(context: AzureRegistryNode | AzureImageTagNode | TaskNode, logData: LogData): boolean { if (logData.logs.length === 0) { let itemType: string; if (context && context instanceof TaskNode) { diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index 64755d7019..f5fa335f47 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -3,7 +3,7 @@ import { AzureImageTagNode } from '../../explorer/models/azureRegistryNodes'; import * as acrTools from '../../utils/Azure/acrTools'; /* Pulls an image from Azure. The context is the image node the user has right clicked on */ -export async function pullFromAzure(context?: AzureImageTagNode): Promise { +export async function pullFromAzure(context?: AzureImageTagNode): Promise { // Step 1: Using getLoginCredentials function to get the username and password. This takes care of all users, even if they don't have the Azure CLI const credentials = await acrTools.getLoginCredentials(context.registry); @@ -15,8 +15,8 @@ export async function pullFromAzure(context?: AzureImageTagNode): Promise { terminal.show(); // Step 2: docker login command - await terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); + terminal.sendText(`docker login ${registry} -u ${username} -p ${password}`); // Step 3: docker pull command - await terminal.sendText(`docker pull ${registry}/${context.label}`); + terminal.sendText(`docker pull ${registry}/${context.label}`); } diff --git a/commands/azureCommands/run-task.ts b/commands/azureCommands/run-task.ts index 6b6ab7ccc4..bf4483c01d 100644 --- a/commands/azureCommands/run-task.ts +++ b/commands/azureCommands/run-task.ts @@ -3,13 +3,14 @@ import { Registry } from "azure-arm-containerregistry/lib/models"; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import vscode = require('vscode'); +import { parseError } from "vscode-azureextensionui"; import { TaskNode } from "../../explorer/models/taskNode"; import { ext } from '../../extensionVariables'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; -export async function runTask(context?: TaskNode): Promise { +export async function runTask(context?: TaskNode): Promise { let taskName: string; let subscription: Subscription; let resourceGroup: ResourceGroup; @@ -37,7 +38,6 @@ export async function runTask(context?: TaskNode): Promise { let taskRun = await client.registries.scheduleRun(resourceGroup.name, registry.name, runRequest); vscode.window.showInformationMessage(`Successfully ran the Task: ${taskName} with ID: ${taskRun.runId}`); } catch (err) { - ext.outputChannel.append(err); - vscode.window.showErrorMessage(`Failed to ran the Task: ${taskName}`); + vscode.window.showErrorMessage(`Failed to ran the Task: ${taskName}\nError: ${parseError(err).message}`); } } diff --git a/commands/azureCommands/show-task.ts b/commands/azureCommands/show-task.ts index 1ab9808cb3..d38b2772f7 100644 --- a/commands/azureCommands/show-task.ts +++ b/commands/azureCommands/show-task.ts @@ -1,14 +1,13 @@ -import { Registry } from "azure-arm-containerregistry/lib/models"; +import { Registry, Task } from "azure-arm-containerregistry/lib/models"; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import { TaskNode } from "../../explorer/models/taskNode"; -import { ext } from '../../extensionVariables'; import * as acrTools from '../../utils/Azure/acrTools'; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; import { quickPickACRRegistry, quickPickSubscription, quickPickTask } from '../utils/quick-pick-azure'; import { openTask } from "./task-utils/showTaskManager"; -export async function showTaskProperties(context?: TaskNode): Promise { +export async function showTaskProperties(context?: TaskNode): Promise { let subscription: Subscription; let registry: Registry; let resourceGroup: ResourceGroup; @@ -27,7 +26,7 @@ export async function showTaskProperties(context?: TaskNode): Promise { } const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); - let item: any = await client.tasks.get(resourceGroup.name, registry.name, task); + let item: Task = await client.tasks.get(resourceGroup.name, registry.name, task); let indentation = 1; let replacer; openTask(JSON.stringify(item, replacer, indentation), task); diff --git a/commands/azureCommands/task-utils/showTaskManager.ts b/commands/azureCommands/task-utils/showTaskManager.ts index 7a3ec4be58..0d780a796d 100644 --- a/commands/azureCommands/task-utils/showTaskManager.ts +++ b/commands/azureCommands/task-utils/showTaskManager.ts @@ -7,7 +7,8 @@ export class TaskContentProvider implements vscode.TextDocumentContentProvider { constructor() { } public provideTextDocumentContent(uri: vscode.Uri): string { - return decodeBase64(JSON.parse(uri.query).content); + const parse: { content: string } = JSON.parse(uri.query); + return decodeBase64(parse.content); } get onDidChange(): vscode.Event { diff --git a/commands/build-image.ts b/commands/build-image.ts index aea006e310..057073ddfa 100644 --- a/commands/build-image.ts +++ b/commands/build-image.ts @@ -15,7 +15,7 @@ async function getDockerFileUris(folder: vscode.WorkspaceFolder): Promise { - const installedExtensions: any[] = vscode.extensions.all; + const installedExtensions: vscode.Extension[] = vscode.extensions.all; let azureAccount: AzureAccount | undefined; initializeExtensionVariables(ctx); @@ -169,7 +169,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise { const extension = installedExtensions[i]; if (extension.id === "ms-vscode.azure-account") { try { - azureAccount = await extension.activate(); + azureAccount = await extension.activate(); } catch (error) { console.log("Failed to activate the Azure Account Extension: " + error); } @@ -279,11 +279,12 @@ async function createWebApp( // Remove this when https://github.com/Microsoft/vscode-docker/issues/445 fixed function registerCommand( commandId: string, + // tslint:disable-next-line: no-any callback: (this: IActionContext, ...args: any[]) => any ): void { return uiRegisterCommand( commandId, - // tslint:disable-next-line:no-function-expression + // tslint:disable-next-line:no-function-expression no-any async function (this: IActionContext, ...args: any[]): Promise { if (args.length) { let properties: { diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index dc79ace1d7..ab7ffba38a 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -1,3 +1,4 @@ +import ContainerRegistryManagementClient from 'azure-arm-containerregistry'; import * as ContainerModels from 'azure-arm-containerregistry/lib/models'; import { SubscriptionModels } from 'azure-arm-resource'; import * as opn from 'opn'; @@ -42,7 +43,7 @@ export class TaskRootNode extends NodeBase { public async getChildren(element: TaskRootNode): Promise { const taskNodes: TaskNode[] = []; let tasks: ContainerModels.Task[] = []; - const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); + const client: ContainerRegistryManagementClient = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(element.subscription); const resourceGroup: string = acrTools.getResourceGroupName(element.registry); tasks = await client.tasks.list(resourceGroup, element.registry.name); if (tasks.length === 0) { diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 8f1e559b00..29dc8411ed 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -4,7 +4,6 @@ *--------------------------------------------------------------------------------------------*/ import { AuthenticationContext } from 'adal-node'; -import * as assert from 'assert'; import ContainerRegistryManagementClient from 'azure-arm-containerregistry'; import { Registry, Run, RunGetLogResult } from "azure-arm-containerregistry/lib/models"; import { SubscriptionModels } from 'azure-arm-resource'; @@ -185,16 +184,32 @@ export async function acquireACRAccessToken(registryUrl: string, scope: string, return acrAccessTokenResponse.access_token; } +export interface IBlobInfo { + accountName: string; + endpointSuffix: string; + containerName: string; + blobName: string; + sasToken: string; + host: string; +} + /** Parses information into a readable format from a blob url */ -export function getBlobInfo(blobUrl: string): { accountName: string, endpointSuffix: string, containerName: string, blobName: string, sasToken: string, host: string } { +export function getBlobInfo(blobUrl: string): IBlobInfo { let items: string[] = blobUrl.slice(blobUrl.search('https://') + 'https://'.length).split('/'); - let accountName: string = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); - let endpointSuffix: string = items[0].slice(items[0].search('.blob.') + '.blob.'.length); - let containerName: string = items[1]; - let blobName: string = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); - let sasToken: string = items[4].slice(items[4].search('[?]') + 1); - let host: string = accountName + '.blob.' + endpointSuffix; - return { accountName, endpointSuffix, containerName, blobName, sasToken, host }; + const accountName = blobUrl.slice(blobUrl.search('https://') + 'https://'.length, blobUrl.search('.blob')); + const endpointSuffix = items[0].slice(items[0].search('.blob.') + '.blob.'.length); + const containerName = items[1]; + const blobName = items[2] + '/' + items[3] + '/' + items[4].slice(0, items[4].search('[?]')); + const sasToken = items[4].slice(items[4].search('[?]') + 1); + const host = accountName + '.blob.' + endpointSuffix; + return { + accountName: accountName, + endpointSuffix: endpointSuffix, + containerName: containerName, + blobName: blobName, + sasToken: sasToken, + host: host + }; } /** Stream logs from a blob into output channel. @@ -207,7 +222,7 @@ export async function streamLogs(registry: Registry, run: Run, outputChannel: vs let client = providedClient ? providedClient : AzureUtilityManager.getInstance().getContainerRegistryManagementClient(getSubscriptionFromRegistry(registry)); let temp: RunGetLogResult = await client.runs.getLogSasUrl(getResourceGroupName(registry), registry.name, run.runId); const link = temp.logLink; - let blobInfo = getBlobInfo(link); + let blobInfo: IBlobInfo = getBlobInfo(link); let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); let available = 0; let start = 0; @@ -238,8 +253,8 @@ export async function streamLogs(registry: Registry, run: Run, outputChannel: vs } // Promisify getBlobToText for readability and error handling purposes -async function getBlobToText(blobInfo: any, blob: BlobService, rangeStart: number): Promise { - return new Promise((resolve, reject) => { +async function getBlobToText(blobInfo: IBlobInfo, blob: BlobService, rangeStart: number): Promise { + return new Promise((resolve, reject) => { blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, { rangeStart: rangeStart }, (error, result) => { if (error) { reject() } else { resolve(result); } @@ -248,8 +263,8 @@ async function getBlobToText(blobInfo: any, blob: BlobService, rangeStart: numbe } // Promisify getBlobProperties for readability and error handling purposes -async function getBlobProperties(blobInfo: any, blob: BlobService): Promise { - return new Promise((resolve, reject) => { +async function getBlobProperties(blobInfo: IBlobInfo, blob: BlobService): Promise { + return new Promise((resolve, reject) => { blob.getBlobProperties(blobInfo.containerName, blobInfo.blobName, (error, result) => { if (error) { reject(error) } else { resolve(result); } }); diff --git a/utils/addUserAgent.ts b/utils/addUserAgent.ts index 39a5eb6b0b..fa82a77b53 100644 --- a/utils/addUserAgent.ts +++ b/utils/addUserAgent.ts @@ -9,7 +9,6 @@ import { appendExtensionUserAgent } from 'vscode-azureextensionui'; const userAgentKey = 'User-Agent'; export function addUserAgent(options: { headers?: OutgoingHttpHeaders }): void { - // tslint:disable-next-line:no-any if (!options.headers) { options.headers = {}; } diff --git a/utils/nonNull.ts b/utils/nonNull.ts index 82aeb1aeaa..c824517db7 100644 --- a/utils/nonNull.ts +++ b/utils/nonNull.ts @@ -12,7 +12,6 @@ import { isNullOrUndefined } from 'util'; * for the property and will give a compile error if the given name is not a property of the source. */ export function nonNullProp(source: TSource, name: TKey): NonNullable { - // tslint:disable-next-line:no-any let value = >source[name]; return nonNullValue(value, name); } @@ -20,7 +19,6 @@ export function nonNullProp(source: TSource /** * Validates that a given value is not null and not undefined. */ -// tslint:disable-next-line:no-any export function nonNullValue(value: T | undefined, propertyNameOrMessage?: string): T { if (isNullOrUndefined(value)) { throw new Error( From 078e45caf450f647d481dd0d128ce8f8180b0f83 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Mon, 15 Oct 2018 17:58:07 -0700 Subject: [PATCH 70/77] tslint fixes 2 --- commands/azureCommands/acr-build.ts | 13 +++++-------- .../azureCommands/acr-logs-utils/logFileManager.ts | 4 ++-- .../acr-logs-utils/tableDataManager.ts | 1 + .../acr-logs-utils/tableViewManager.ts | 14 +++++++++++--- commands/azureCommands/show-task.ts | 3 +-- .../azureCommands/task-utils/showTaskManager.ts | 2 +- commands/registrySettings.ts | 1 - commands/start-container.ts | 1 - commands/utils/quick-pick-container.ts | 1 - dockerExtension.ts | 5 ++--- explorer/models/taskNode.ts | 1 + utils/Azure/acrTools.ts | 6 ++++-- 12 files changed, 28 insertions(+), 24 deletions(-) diff --git a/commands/azureCommands/acr-build.ts b/commands/azureCommands/acr-build.ts index 048879fbb0..5dc6cfb750 100644 --- a/commands/azureCommands/acr-build.ts +++ b/commands/azureCommands/acr-build.ts @@ -1,7 +1,6 @@ import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry/lib/containerRegistryManagementClient'; -import { Run, SourceUploadDefinition } from 'azure-arm-containerregistry/lib/models'; -import { DockerBuildRequest } from "azure-arm-containerregistry/lib/models/dockerBuildRequest"; -import { Registry } from 'azure-arm-containerregistry/lib/models/registry'; +import { Registry, Run, SourceUploadDefinition } from 'azure-arm-containerregistry/lib/models'; +import { DockerBuildRequest } from "azure-arm-containerregistry/lib/models"; import { Subscription } from 'azure-arm-resource/lib/subscription/models'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; import * as fs from 'fs'; @@ -67,7 +66,7 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { const run: Run = await client.registries.scheduleRun(resourceGroupName, registry.name, runRequest); status.appendLine("Schedule Run " + run.runId); - streamLogs(registry, run, status, client); + await streamLogs(registry, run, status, client); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { @@ -77,10 +76,8 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis process.chdir(source); fs.readdir(source, (err, items) => { items = filter(items); - tar.c( - {}, - items - ).pipe(fs.createWriteStream(tarFilePath)); + // tslint:disable-next-line:no-unsafe-any + tar.c({}, items).pipe(fs.createWriteStream(tarFilePath)); process.chdir(current); }); diff --git a/commands/azureCommands/acr-logs-utils/logFileManager.ts b/commands/azureCommands/acr-logs-utils/logFileManager.ts index 58fbbb5de3..218996b3e0 100644 --- a/commands/azureCommands/acr-logs-utils/logFileManager.ts +++ b/commands/azureCommands/acr-logs-utils/logFileManager.ts @@ -10,7 +10,7 @@ export class LogContentProvider implements vscode.TextDocumentContentProvider { constructor() { } public provideTextDocumentContent(uri: vscode.Uri): string { - let parse: { log: string } = JSON.parse(uri.query); + let parse: { log: string } = <{ log: string }>JSON.parse(uri.query); return decodeBase64(parse.log); } @@ -39,7 +39,7 @@ export function accessLog(url: string, title: string, download: boolean): void { blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, async (error, text, result, response) => { if (response) { if (download) { - downloadLog(text, title); + await downloadLog(text, title); } else { openLogInNewWindow(text, title); } diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index 223734032b..fb8fb3eb93 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -110,6 +110,7 @@ export class LogData { let items: string[] = filter.image.split(':') const { acrAccessToken } = await acquireACRAccessTokenFromRegistry(this.registry, 'repository:' + items[0] + ':pull'); let digest; + // tslint:disable-next-line:no-unsafe-any await request.get('https://' + this.registry.loginServer + `/v2/${items[0]}/manifests/${items[1]}`, { auth: { bearer: acrAccessToken diff --git a/commands/azureCommands/acr-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts index f084c782b3..634dd0832a 100644 --- a/commands/azureCommands/acr-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -4,7 +4,7 @@ import * as clipboardy from 'clipboardy' import * as path from 'path'; import * as vscode from "vscode"; import { accessLog } from './logFileManager'; -import { LogData } from './tableDataManager' +import { Filter, LogData } from './tableDataManager' export class LogTableWebview { private logData: LogData; private panel: vscode.WebviewPanel; @@ -27,16 +27,17 @@ export class LogTableWebview { //Post Opening communication from webview /** Setup communication with the webview sorting out received mesages from its javascript file */ private setupIncomingListeners(): void { - this.panel.webview.onDidReceiveMessage(async (message) => { + this.panel.webview.onDidReceiveMessage(async (message: IMessage) => { if (message.logRequest) { const itemNumber: number = +message.logRequest.id; - this.logData.getLink(itemNumber).then((url) => { + await this.logData.getLink(itemNumber).then((url) => { if (url !== 'requesting') { accessLog(url, this.logData.logs[itemNumber].runId, message.logRequest.download); } }); } else if (message.copyRequest) { + // tslint:disable-next-line:no-unsafe-any clipboardy.writeSync(message.copyRequest.text); } else if (message.loadMore) { @@ -260,3 +261,10 @@ export class LogTableWebview { return Math.floor(secs / 31536000) + " years ago"; } } + +interface IMessage { + logRequest?: { id: number; download: boolean }; + copyRequest?: { text: string }; + loadMore?: string; + loadFiltered?: { filterString: Filter }; +} diff --git a/commands/azureCommands/show-task.ts b/commands/azureCommands/show-task.ts index d38b2772f7..c87af02649 100644 --- a/commands/azureCommands/show-task.ts +++ b/commands/azureCommands/show-task.ts @@ -28,6 +28,5 @@ export async function showTaskProperties(context?: TaskNode): Promise { const client = AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); let item: Task = await client.tasks.get(resourceGroup.name, registry.name, task); let indentation = 1; - let replacer; - openTask(JSON.stringify(item, replacer, indentation), task); + openTask(JSON.stringify(item, undefined, indentation), task); } diff --git a/commands/azureCommands/task-utils/showTaskManager.ts b/commands/azureCommands/task-utils/showTaskManager.ts index 0d780a796d..19d5b2ff78 100644 --- a/commands/azureCommands/task-utils/showTaskManager.ts +++ b/commands/azureCommands/task-utils/showTaskManager.ts @@ -7,7 +7,7 @@ export class TaskContentProvider implements vscode.TextDocumentContentProvider { constructor() { } public provideTextDocumentContent(uri: vscode.Uri): string { - const parse: { content: string } = JSON.parse(uri.query); + const parse: { content: string } = <{ content: string }>JSON.parse(uri.query); return decodeBase64(parse.content); } diff --git a/commands/registrySettings.ts b/commands/registrySettings.ts index dceb316126..e264a46eeb 100644 --- a/commands/registrySettings.ts +++ b/commands/registrySettings.ts @@ -3,7 +3,6 @@ * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ -import * as assert from 'assert'; import * as vscode from 'vscode'; import { DialogResponses } from 'vscode-azureextensionui'; import { configurationKeys } from '../constants'; diff --git a/commands/start-container.ts b/commands/start-container.ts index 8f78ece3eb..f93b16a50b 100644 --- a/commands/start-container.ts +++ b/commands/start-container.ts @@ -11,7 +11,6 @@ import { IActionContext, parseError } from 'vscode-azureextensionui'; import { ImageNode } from '../explorer/models/imageNode'; import { RootNode } from '../explorer/models/rootNode'; import { ext } from '../extensionVariables'; -import { reporter } from '../telemetry/telemetry'; import { docker, DockerEngineType } from './utils/docker-endpoint'; import { ImageItem, quickPickImage } from './utils/quick-pick-image'; diff --git a/commands/utils/quick-pick-container.ts b/commands/utils/quick-pick-container.ts index 09e2b56e28..738ba9093b 100644 --- a/commands/utils/quick-pick-container.ts +++ b/commands/utils/quick-pick-container.ts @@ -9,7 +9,6 @@ import * as os from 'os'; import vscode = require('vscode'); import { IActionContext, parseError, TelemetryProperties } from 'vscode-azureextensionui'; import { ext } from '../../extensionVariables'; -import { openShellContainer } from '../open-shell-container'; import { docker } from './docker-endpoint'; export interface ContainerItem extends vscode.QuickPickItem { diff --git a/dockerExtension.ts b/dockerExtension.ts index ddcb9f72a0..7158e2a1d8 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -280,9 +280,8 @@ async function createWebApp( open ); if (response === open) { - opn( - 'https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account' - ); + // tslint:disable-next-line:no-unsafe-any + opn('https://marketplace.visualstudio.com/items?itemName=ms-vscode.azure-account'); } } } diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index ab7ffba38a..33f6751e56 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -49,6 +49,7 @@ export class TaskRootNode extends NodeBase { if (tasks.length === 0) { vscode.window.showInformationMessage(`You do not have any Tasks in the registry, '${element.registry.name}'. You can create one with ACR Task. `, "Learn More").then(val => { if (val === "Learn More") { + // tslint:disable-next-line:no-unsafe-any opn('https://aka.ms/acr/task'); } }) diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 891cdd00d6..c845eed24c 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -13,6 +13,7 @@ import { BlobService, createBlobServiceWithSas } from "azure-storage"; import { ServiceClientCredentials } from 'ms-rest'; import { TokenResponse } from 'ms-rest-azure'; import * as vscode from "vscode"; +import { parseError } from 'vscode-azureextensionui'; import { NULL_GUID } from "../../constants"; import { getCatalog, getTags, TagInfo } from "../../explorer/models/commonRegistryUtils"; import { ext } from '../../extensionVariables'; @@ -235,9 +236,10 @@ export async function streamLogs(registry: Registry, run: Run, outputChannel: vs try { props = await getBlobProperties(blobInfo, blob); metadata = props.metadata; - } catch (error) { + } catch (err) { + const error = parseError(err); //Not found happens when the properties havent yet been set, blob is not ready. Wait 1 second and try again - if (error.code === "NotFound") { return; } else { throw error; } + if (error.errorType === "NotFound") { return; } else { throw error; } } available = +props.contentLength; let text: string; From 1edab2cb76a1d40ac9c2ff33e70305c90b04cec5 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Fri, 2 Nov 2018 20:10:12 -0700 Subject: [PATCH 71/77] First PR #506 review Update include: Deletion of resizable.js Package.json commands alphabetically ordered. General typos, grammar and naming fixes. Change from fs - fs-extra. Other general improvments. --- .../acr-logs-utils/logFileManager.ts | 2 +- .../acr-logs-utils/logScripts.js | 21 +- .../azureCommands/acr-logs-utils/resizable.js | 166 ------- .../acr-logs-utils/tableDataManager.ts | 21 +- .../acr-logs-utils/tableViewManager.ts | 18 +- commands/azureCommands/acr-logs.ts | 7 +- commands/azureCommands/create-registry.ts | 2 +- commands/azureCommands/delete-repository.ts | 2 +- .../{acr-build.ts => quick-build.ts} | 30 +- commands/azureCommands/run-task.ts | 6 +- commands/azureCommands/show-task.ts | 4 +- commands/utils/quick-pick-azure.ts | 5 +- dockerExtension.ts | 231 ++-------- explorer/models/rootNode.ts | 4 +- explorer/models/taskNode.ts | 2 +- package.json | 420 +++++++++--------- thirdpartynotices.txt | 29 ++ utils/Azure/acrTools.ts | 6 +- 18 files changed, 331 insertions(+), 645 deletions(-) delete mode 100644 commands/azureCommands/acr-logs-utils/resizable.js rename commands/azureCommands/{acr-build.ts => quick-build.ts} (85%) diff --git a/commands/azureCommands/acr-logs-utils/logFileManager.ts b/commands/azureCommands/acr-logs-utils/logFileManager.ts index 218996b3e0..ddc2e8b6f7 100644 --- a/commands/azureCommands/acr-logs-utils/logFileManager.ts +++ b/commands/azureCommands/acr-logs-utils/logFileManager.ts @@ -1,5 +1,5 @@ import { BlobService, createBlobServiceWithSas } from 'azure-storage'; -import * as fs from 'fs'; +import * as fs from 'fs-extra'; import * as vscode from 'vscode'; import { getBlobInfo, IBlobInfo } from '../../../utils/Azure/acrTools'; diff --git a/commands/azureCommands/acr-logs-utils/logScripts.js b/commands/azureCommands/acr-logs-utils/logScripts.js index 8d722d913b..b6538d59e2 100644 --- a/commands/azureCommands/acr-logs-utils/logScripts.js +++ b/commands/azureCommands/acr-logs-utils/logScripts.js @@ -6,7 +6,7 @@ const status = { 'Failed': 1 } -var currentN = 4; +var currentItemsCount = 4; var currentDir = "asc" var triangles = { 'down': ' ', @@ -39,7 +39,7 @@ document.onkeydown = function (event) { * it allows a low stuttering experience that allowed rapid testing. * I will improve it soon.*/ function sortTable(n, dir = "asc", holdDir = false) { - currentN = n; + currentItemsCount = n; let table, rows, switching, i, x, y, shouldSwitch, switchcount = 0; let cmpFunc = acquireCompareFunction(n); table = document.getElementById("core"); @@ -147,11 +147,11 @@ window.addEventListener('message', event => { setDigestListener(digestClickables); } else if (message.type === 'endContinued') { - sortTable(currentN, currentDir, true); + sortTable(currentItemsCount, currentDir, true); loading(); } else if (message.type === 'end') { - window.addEventListener("resize", manageWidth); - manageWidth(); + window.addEventListener("resize", setAccordionTableWidth); + setAccordionTableWidth(); setTableSorter(); loading(); } @@ -231,17 +231,6 @@ function setDigestListener(digestClickables) { } } -function manageWidth() { - // let headerCells = document.querySelectorAll("#headerTable th"); - // let topRow = document.querySelector("#core tr"); - // let topRowCells = topRow.querySelectorAll("td"); - // for (let i = 0; i < topRowCells.length; i++) { - // let width = parseInt(getComputedStyle(topRowCells[i]).width); - // headerCells[i].style.width = width + "px"; - // } - setAccordionTableWidth(); -} - let openAccordions = []; function setAccordionTableWidth() { diff --git a/commands/azureCommands/acr-logs-utils/resizable.js b/commands/azureCommands/acr-logs-utils/resizable.js deleted file mode 100644 index 909f35c0e2..0000000000 --- a/commands/azureCommands/acr-logs-utils/resizable.js +++ /dev/null @@ -1,166 +0,0 @@ -// **** ADAPTED FROM HTML DEMO AT **** -// DEMO = http://bz.var.ru/comp/web/resizable.html -// JS = http://bz.var.ru/comp/web/resizable-tables.js -// ******* ORIGINAL SCRIPT HEADER ******* -// Resizable Table Columns. -// version: 1.0 -// -// (c) 2006, bz -// -// 25.12.2006: first working prototype -// 26.12.2006: now works in IE as well but not in Opera (Opera is @#$%!) -// 27.12.2006: changed initialization, now just make class='resizable' in table and load script -//===================================================== -//Changelog -//-Removed cookies -//-limited functionality to manually selected table - -function preventEvent(e) { - let ev = e || window.event; - if (ev.preventDefault) ev.preventDefault(); - else ev.returnValue = false; - if (ev.stopPropagation) - ev.stopPropagation(); - return false; -} - -function getStyle(x, styleProp) { - let y; - if (x.currentStyle) - y = x.currentStyle[styleProp]; - else if (window.getComputedStyle) - y = document.defaultView.getComputedStyle(x, null).getPropertyValue(styleProp); - return y; -} - -function getWidth(x) { - return document.defaultView.getComputedStyle(x, null).getPropertyValue("width"); -} - -// main class prototype -function ColumnResize(table) { - this.id = table.id; - // private data - let self = this; - - let dragColumns = table.rows[0].cells; // first row columns, used for changing of width - if (!dragColumns) return; // return if no table exists or no one row exists - - let dragColumnNo; // current dragging column - let dragX; // last event X mouse coordinate - - let saveOnmouseup; // save document onmouseup event handler - let saveOnmousemove; // save document onmousemove event handler - let saveBodyCursor; // save body cursor property - - // do changes columns widths - // returns true if success and false otherwise - this.changeColumnWidth = function (no, w) { - if (no === 0) return false; // Ignores first item - if (no === dragColumns.length - 1) return false; // Ignores last item - - if (parseInt(dragColumns[no].style.width) <= -w) return false; - if (dragColumns[no + 1] && parseInt(dragColumns[no + 1].style.width) <= w) return false; - - dragColumns[no].style.width = parseInt(dragColumns[no].style.width) + w + 'px'; - if (dragColumns[no + 1]) - dragColumns[no + 1].style.width = parseInt(dragColumns[no + 1].style.width) - w + 'px'; - - return true; - } - - // do drag column width - this.columnDrag = function (e) { - var e = e || window.event; - var X = e.clientX || e.pageX; - if (!self.changeColumnWidth(dragColumnNo, X - dragX)) { - // stop drag! - self.stopColumnDrag(e); - } - - dragX = X; - // prevent other event handling - preventEvent(e); - return false; - } - - // stops column dragging - this.stopColumnDrag = function (e) { - var e = e || window.event; - if (!dragColumns) return; - - // restore handlers & cursor - document.onmouseup = saveOnmouseup; - document.onmousemove = saveOnmousemove; - document.body.style.cursor = saveBodyCursor; - preventEvent(e); - } - - // init data and start dragging - this.startColumnDrag = function (e) { - var e = e || window.event; - - // if not first button was clicked - //if (e.button != 0) return; - - // remember dragging object - dragColumnNo = (e.target || e.srcElement).parentNode.parentNode.cellIndex; - dragX = e.clientX || e.pageX; - - // set up current columns widths in their particular attributes - // do it in two steps to avoid jumps on page! - let colWidth = []; - for (let i = 0; i < dragColumns.length; i++) { - colWidth[i] = parseInt(getWidth(dragColumns[i])); - } - for (let i = 0; i < dragColumns.length; i++) { - dragColumns[i].width = ""; // for sure - dragColumns[i].style.width = colWidth[i] + "px"; - } - - saveOnmouseup = document.onmouseup; - document.onmouseup = self.stopColumnDrag; - - saveBodyCursor = document.body.style.cursor; - document.body.style.cursor = 'w-resize'; - - // fire! - saveOnmousemove = document.onmousemove; - document.onmousemove = self.columnDrag; - - preventEvent(e); - } - - // prepare table header to be draggable - // it runs during class creation - for (let i = 1; i < dragColumns.length - 1; i++) { - dragColumns[i].innerHTML = "
          " + - "
          " + - "
          " + - dragColumns[i].innerHTML + - "
          "; - dragColumns[i].firstChild.firstChild.onmousedown = this.startColumnDrag; - } -} - -// select all tables and make resizable those that have 'resizable' class -let resizableTables = []; - -function ResizableColumns() { - - var tables = document.getElementsByTagName('table'); - for (var i = 0; tables.item(i); i++) { - if (tables[i].className.match(/resizable/)) { - // generate id - if (!tables[i].id) tables[i].id = 'table' + (i + 1); - // make table resizable - resizableTables[resizableTables.length] = new ColumnResize(tables[i]); - } - } -} - -try { - window.addEventListener('load', ResizableColumns, false); -} catch (e) { - window.onload = ResizableColumns; -} diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index fb8fb3eb93..cdbb26bf06 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -1,6 +1,6 @@ import ContainerRegistryManagementClient from "azure-arm-containerregistry"; import { Registry, Run, RunGetLogResult, RunListResult } from "azure-arm-containerregistry/lib/models"; -import request = require('request-promise'); +import { ext } from "../../../extensionVariables"; import { acquireACRAccessTokenFromRegistry } from "../../../utils/Azure/acrTools"; /** Class to manage data and data acquisition for logs */ export class LogData { @@ -109,18 +109,25 @@ export class LogData { } else if (filter.image) { //Image let items: string[] = filter.image.split(':') const { acrAccessToken } = await acquireACRAccessTokenFromRegistry(this.registry, 'repository:' + items[0] + ':pull'); - let digest; - // tslint:disable-next-line:no-unsafe-any - await request.get('https://' + this.registry.loginServer + `/v2/${items[0]}/manifests/${items[1]}`, { + let digest = await new Promise((resolve, reject) => ext.request.get('https://' + this.registry.loginServer + `/v2/${items[0]}/manifests/${items[1]}`, { auth: { bearer: acrAccessToken }, headers: { accept: 'application/vnd.docker.distribution.manifest.v2+json; 0.5, application/vnd.docker.distribution.manifest.list.v2+json; 0.6' } - }, (err, httpResponse: { headers: string }, body) => { - digest = httpResponse.headers['docker-content-digest']; - }); + }, (err, httpResponse, body) => { + if (err) { + reject(err); + } else { + const imageDigest = httpResponse.headers['docker-content-digest']; + if (imageDigest instanceof Array) { + reject(new Error('docker-content-digest should be a string not an array.')) + } else { + resolve(imageDigest); + } + } + })); if (parsedFilter.length > 0) { parsedFilter += ' and '; } parsedFilter += `contains(OutputImageManifests, '${items[0]}@${digest}')`; diff --git a/commands/azureCommands/acr-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts index 634dd0832a..47c85e3c0e 100644 --- a/commands/azureCommands/acr-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -3,6 +3,7 @@ import { ImageDescriptor, Run } from "azure-arm-containerregistry/lib/models"; import * as clipboardy from 'clipboardy' import * as path from 'path'; import * as vscode from "vscode"; +import { ext } from "../../../extensionVariables"; import { accessLog } from './logFileManager'; import { Filter, LogData } from './tableDataManager' export class LogTableWebview { @@ -14,13 +15,11 @@ export class LogTableWebview { this.panel = vscode.window.createWebviewPanel('log Viewer', webviewName, vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true }); //Get path to resource on disk - let extensionPath = vscode.extensions.getExtension("PeterJausovec.vscode-docker").extensionPath; - const scriptFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); - const resizingScript = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'resizable.js')).with({ scheme: 'vscode-resource' }); - const styleFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); - const iconStyle = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); + const scriptFile = vscode.Uri.file(path.join(ext.context.extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); + const styleFile = vscode.Uri.file(path.join(ext.context.extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); + const iconStyle = vscode.Uri.file(path.join(ext.context.extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); //Populate Webview - this.panel.webview.html = this.getBaseHtml(scriptFile, resizingScript, styleFile, iconStyle); + this.panel.webview.html = this.getBaseHtml(scriptFile, styleFile, iconStyle); this.setupIncomingListeners(); this.addLogsToWebView(); } @@ -91,7 +90,7 @@ export class LogTableWebview { //HTML Content Loaders /** Create the table in which to push the logs */ - private getBaseHtml(scriptFile: vscode.Uri, resizingScript: vscode.Uri, stylesheet: vscode.Uri, iconStyles: vscode.Uri): string { + private getBaseHtml(scriptFile: vscode.Uri, stylesheet: vscode.Uri, iconStyles: vscode.Uri): string { return ` @@ -114,10 +113,6 @@ export class LogTableWebview { Filter by Task:
          -
          - Filter by Image:
          - -
          @@ -146,7 +141,6 @@ export class LogTableWebview {
          - `; } diff --git a/commands/azureCommands/acr-logs.ts b/commands/azureCommands/acr-logs.ts index e0c64bef2a..89bfc7d88d 100644 --- a/commands/azureCommands/acr-logs.ts +++ b/commands/azureCommands/acr-logs.ts @@ -18,7 +18,6 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode let subscription: Subscription; if (!context) { registry = await quickPickACRRegistry(); - if (!registry) { return; } subscription = await getSubscriptionFromRegistry(registry); } else { registry = context.registry; @@ -28,7 +27,7 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode const client = await AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); let logData: LogData = new LogData(client, registry, resourceGroup); - // Fuiltering provided + // Filtering provided if (context && context instanceof AzureImageTagNode) { //ACR Image Logs await logData.loadLogs(false, false, { image: context.label }); @@ -44,11 +43,11 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode await logData.loadLogs(false); } if (!hasValidLogContent(context, logData)) { return; } - let webViewTitle: string = registry.name; + let webViewTitle = registry.name; if (context instanceof TaskNode) { webViewTitle += '/' + context.label; } - let webview = new LogTableWebview(webViewTitle, logData); + const webview = new LogTableWebview(webViewTitle, logData); } } diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 6ef8aa3341..895723f5a1 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -7,6 +7,7 @@ import { Registry, RegistryNameStatus } from "azure-arm-containerregistry/lib/mo import { SubscriptionModels } from 'azure-arm-resource'; import { ResourceGroup } from "azure-arm-resource/lib/resource/models"; import * as vscode from "vscode"; +import { skus } from '../../constants'; import { dockerExplorerProvider } from '../../dockerExtension'; import { ext } from '../../extensionVariables'; import { isValidAzureName } from '../../utils/Azure/common'; @@ -32,7 +33,6 @@ export async function createRegistry(): Promise { return registry; } async function acquireSKU(): Promise { - let skus: string[] = ["Standard", "Basic", "Premium"]; let sku: string; sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU' }); if (sku === undefined) { throw new Error('User exit'); } diff --git a/commands/azureCommands/delete-repository.ts b/commands/azureCommands/delete-repository.ts index b2965e9a2f..3806ec3705 100644 --- a/commands/azureCommands/delete-repository.ts +++ b/commands/azureCommands/delete-repository.ts @@ -27,7 +27,7 @@ export async function deleteRepository(context?: AzureRepositoryNode): Promise = new Set(['.git', '.gitignore', '.bzr', 'bzrignore', '.hg', '.hgignore', '.svn']); +const status = vscode.window.createOutputChannel('ACR Build Status'); // Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. // The user is then asked to name & tag the image. A build is queued for the image in the selected registry. // Selected source code must contain a path to the desired dockerfile. -export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { +export async function quickBuild(dockerFileUri?: vscode.Uri): Promise { //Acquire information from user const subscription: Subscription = await quickPickSubscription(); @@ -40,12 +41,7 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { //Begin readying build status.show(); - let folder: vscode.WorkspaceFolder; - if (vscode.workspace.workspaceFolders && vscode.workspace.workspaceFolders.length === 1) { - folder = vscode.workspace.workspaceFolders[0]; - } else { - folder = await vscode.window.showWorkspaceFolderPick(); - } + let folder: vscode.WorkspaceFolder = await quickPickWorkspaceFolder("To quick build Docker files you must first open a folder or workspace in VS Code."); const dockerItem: Item = await resolveDockerFileItem(folder, dockerFileUri); const sourceLocation: string = folder.uri.path; const tarFilePath: string = getTempSourceArchivePath(); @@ -64,7 +60,7 @@ export async function queueBuild(dockerFileUri?: vscode.Uri): Promise { status.appendLine("Set up Run Request"); const run: Run = await client.registries.scheduleRun(resourceGroupName, registry.name, runRequest); - status.appendLine("Schedule Run " + run.runId); + status.appendLine("Scheduled Run " + run.runId); await streamLogs(registry, run, status, client); } @@ -75,7 +71,7 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis let current: string = process.cwd(); process.chdir(source); fs.readdir(source, (err, items) => { - items = filter(items); + items = items.filter(i => !(i in vcsIgnoreList)); // tslint:disable-next-line:no-unsafe-any tar.c({}, items).pipe(fs.createWriteStream(tarFilePath)); process.chdir(current); @@ -103,13 +99,3 @@ function getTempSourceArchivePath(): string { let tarFilePath: string = url.resolve(os.tmpdir(), `sourceArchive${id}.tar.gz`); return tarFilePath; } - -function filter(list: string[]): string[] { - let result: string[] = []; - for (let file of list) { - if (vcsIgnoreList.indexOf(file) === -1) { - result.push(file); - } - } - return result; -} diff --git a/commands/azureCommands/run-task.ts b/commands/azureCommands/run-task.ts index 87ab059576..96fa83c683 100644 --- a/commands/azureCommands/run-task.ts +++ b/commands/azureCommands/run-task.ts @@ -35,8 +35,10 @@ export async function runTask(context?: TaskNode): Promise { try { let taskRun = await client.registries.scheduleRun(resourceGroup.name, registry.name, runRequest); - vscode.window.showInformationMessage(`Successfully ran the Task: ${taskName} with ID: ${taskRun.runId}`); + vscode.window.showInformationMessage(`Successfully scheduled the Task: ${taskName} with ID: ${taskRun.runId}`); } catch (err) { - vscode.window.showErrorMessage(`Failed to ran the Task: ${taskName}\nError: ${parseError(err).message}`); + const error = parseError(err); + vscode.window.showErrorMessage(`Failed to schedule the Task: ${taskName}\nError: ${error.message}`); + throw error; } } diff --git a/commands/azureCommands/show-task.ts b/commands/azureCommands/show-task.ts index daca1a3611..e515eda150 100644 --- a/commands/azureCommands/show-task.ts +++ b/commands/azureCommands/show-task.ts @@ -27,6 +27,6 @@ export async function showTaskProperties(context?: TaskNode): Promise { const client = await AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); let item: Task = await client.tasks.get(resourceGroup.name, registry.name, task); - let indentation = 1; - openTask(JSON.stringify(item, undefined, indentation), task); + let indentation = 2; + openTask(JSON.stringify(item, undefined, indentation), task); } diff --git a/commands/utils/quick-pick-azure.ts b/commands/utils/quick-pick-azure.ts index 880a6c9379..61d01ee27d 100644 --- a/commands/utils/quick-pick-azure.ts +++ b/commands/utils/quick-pick-azure.ts @@ -17,6 +17,7 @@ import { isValidAzureName } from '../../utils/Azure/common'; import { AzureImage } from "../../utils/Azure/models/image"; import { Repository } from "../../utils/Azure/models/repository"; import { AzureUtilityManager } from '../../utils/azureUtilityManager'; +import { createRegistry } from '../azureCommands/create-registry'; export async function quickPickACRImage(repository: Repository, prompt?: string): Promise { const placeHolder = prompt ? prompt : 'Select image to use'; @@ -58,7 +59,7 @@ export async function quickPickACRRegistry(canCreateNew: boolean = false, prompt }); let registry: Registry; if (desiredReg === createNewItem) { - registry = await vscode.commands.executeCommand("vscode-docker.create-ACR-Registry"); + registry = await createRegistry(); } else { registry = desiredReg.data; } @@ -189,7 +190,7 @@ export async function quickPickNewImageName(): Promise { let opt: vscode.InputBoxOptions = { validateInput: checkForValidTag, ignoreFocusOut: false, - prompt: 'Enter repository name and tag in format :' + prompt: 'Enter repository name and tag in format :' }; let tag: string = await ext.ui.showInputBox(opt); diff --git a/dockerExtension.ts b/dockerExtension.ts index 37359e3ea2..643c859f54 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -9,28 +9,8 @@ import * as assert from 'assert'; import * as path from 'path'; import * as request from 'request-promise-native'; import * as vscode from 'vscode'; -import { - AzureUserInput, - callWithTelemetryAndErrorHandling, - createTelemetryReporter, - IActionContext, - parseError, - registerCommand as uiRegisterCommand, - registerUIExtensionVariables, - TelemetryProperties, - UserCancelledError -} from 'vscode-azureextensionui'; -import { - ConfigurationParams, - DidChangeConfigurationNotification, - DocumentSelector, - LanguageClient, - LanguageClientOptions, - Middleware, - ServerOptions, - TransportKind -} from 'vscode-languageclient/lib/main'; -import { queueBuild } from "./commands/azureCommands/acr-build"; +import { AzureUserInput, callWithTelemetryAndErrorHandling, createTelemetryReporter, IActionContext, registerCommand as uiRegisterCommand, registerUIExtensionVariables, TelemetryProperties, UserCancelledError } from 'vscode-azureextensionui'; +import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient/lib/main'; import { viewACRLogs } from "./commands/azureCommands/acr-logs"; import { LogContentProvider } from "./commands/azureCommands/acr-logs-utils/logFileManager"; import { createRegistry } from './commands/azureCommands/create-registry'; @@ -38,83 +18,50 @@ import { deleteAzureImage } from './commands/azureCommands/delete-image'; import { deleteAzureRegistry } from './commands/azureCommands/delete-registry'; import { deleteRepository } from './commands/azureCommands/delete-repository'; import { pullFromAzure } from './commands/azureCommands/pull-from-azure'; +import { quickBuild } from "./commands/azureCommands/quick-build"; import { runTask } from "./commands/azureCommands/run-task"; import { showTaskProperties } from "./commands/azureCommands/show-task"; import { TaskContentProvider } from "./commands/azureCommands/task-utils/showTaskManager"; import { buildImage } from './commands/build-image'; -import { - composeDown, - composeRestart, - composeUp -} from './commands/docker-compose'; +import { composeDown, composeRestart, composeUp } from './commands/docker-compose'; import inspectImage from './commands/inspect-image'; import { openShellContainer } from './commands/open-shell-container'; import { pushImage } from './commands/push-image'; -import { - consolidateDefaultRegistrySettings, - setRegistryAsDefault -} from './commands/registrySettings'; +import { consolidateDefaultRegistrySettings, setRegistryAsDefault } from './commands/registrySettings'; import { removeContainer } from './commands/remove-container'; import { removeImage } from './commands/remove-image'; import { restartContainer } from './commands/restart-container'; import { showLogsContainer } from './commands/showlogs-container'; -import { - startAzureCLI, - startContainer, - startContainerInteractive -} from './commands/start-container'; +import { startAzureCLI, startContainer, startContainerInteractive } from './commands/start-container'; import { stopContainer } from './commands/stop-container'; import { systemPrune } from './commands/system-prune'; import { tagImage } from './commands/tag-image'; import { docker } from './commands/utils/docker-endpoint'; import { DefaultTerminalProvider } from './commands/utils/TerminalProvider'; import { DockerDebugConfigProvider } from './configureWorkspace/configDebugProvider'; -import { - configure, - configureApi, - ConfigureApiOptions -} from './configureWorkspace/configure'; +import { configure, configureApi, ConfigureApiOptions } from './configureWorkspace/configure'; import { registerDebugConfigurationProvider } from './debugging/coreclr/registerDebugger'; import { DockerComposeCompletionItemProvider } from './dockerCompose/dockerComposeCompletionItemProvider'; import { DockerComposeHoverProvider } from './dockerCompose/dockerComposeHoverProvider'; import composeVersionKeys from './dockerCompose/dockerComposeKeyInfo'; import { DockerComposeParser } from './dockerCompose/dockerComposeParser'; import { DockerfileCompletionItemProvider } from './dockerfile/dockerfileCompletionItemProvider'; -import DockerInspectDocumentContentProvider, { - SCHEME as DOCKER_INSPECT_SCHEME -} from './documentContentProviders/dockerInspect'; +import DockerInspectDocumentContentProvider, { SCHEME as DOCKER_INSPECT_SCHEME } from './documentContentProviders/dockerInspect'; import { AzureAccountWrapper } from './explorer/deploy/azureAccountWrapper'; import * as util from './explorer/deploy/util'; import { WebAppCreator } from './explorer/deploy/webAppCreator'; import { DockerExplorerProvider } from './explorer/dockerExplorer'; -import { - AzureImageTagNode, - AzureRegistryNode, - AzureRepositoryNode -} from './explorer/models/azureRegistryNodes'; +import { AzureImageTagNode, AzureRegistryNode, AzureRepositoryNode } from './explorer/models/azureRegistryNodes'; import { ContainerNode } from './explorer/models/containerNode'; -import { - connectCustomRegistry, - disconnectCustomRegistry -} from './explorer/models/customRegistries'; -import { - DockerHubImageTagNode, - DockerHubOrgNode, - DockerHubRepositoryNode -} from './explorer/models/dockerHubNodes'; +import { connectCustomRegistry, disconnectCustomRegistry } from './explorer/models/customRegistries'; +import { DockerHubImageTagNode, DockerHubOrgNode, DockerHubRepositoryNode } from './explorer/models/dockerHubNodes'; import { ImageNode } from './explorer/models/imageNode'; import { NodeBase } from './explorer/models/nodeBase'; import { RootNode } from './explorer/models/rootNode'; import { browseAzurePortal } from './explorer/utils/browseAzurePortal'; -import { - browseDockerHub, - dockerHubLogout -} from './explorer/utils/dockerHubUtils'; +import { browseDockerHub, dockerHubLogout } from './explorer/utils/dockerHubUtils'; import { ext } from './extensionVariables'; -import { - initializeTelemetryReporter, - reporter -} from './telemetry/telemetry'; +import { initializeTelemetryReporter, reporter } from './telemetry/telemetry'; import { addUserAgent } from './utils/addUserAgent'; import { AzureUtilityManager } from './utils/azureUtilityManager'; import { Keytar } from './utils/keytar'; @@ -291,129 +238,49 @@ function registerDockerCommands(): void { 'dockerExplorer', dockerExplorerProvider ); - registerCommand('vscode-docker.explorer.refresh', () => - dockerExplorerProvider.refresh() - ); - registerCommand('vscode-docker.configure', async function ( - this: IActionContext - ): Promise { - await configure(this, undefined); - }); - registerCommand('vscode-docker.api.configure', async function ( - this: IActionContext, - options: ConfigureApiOptions - ): Promise { - await configureApi(this, options); - }); + registerCommand('vscode-docker.acr.createRegistry', createRegistry); + registerCommand('vscode-docker.acr.deleteImage', deleteAzureImage); + registerCommand('vscode-docker.acr.deleteRegistry', deleteAzureRegistry); + registerCommand('vscode-docker.acr.deleteRepository', deleteRepository); + registerCommand('vscode-docker.acr.pullImage', pullFromAzure); + registerCommand('vscode-docker.acr.quickBuild', quickBuild); + registerCommand('vscode-docker.acr.runTask', runTask); + registerCommand('vscode-docker.acr.showTask', showTaskProperties); + registerCommand('vscode-docker.acr.viewLogs', viewACRLogs); + + registerCommand('vscode-docker.api.configure', async function (this: IActionContext, options: ConfigureApiOptions): Promise { await configureApi(this, options); }); + registerCommand('vscode-docker.browseDockerHub', (context?: DockerHubImageTagNode | DockerHubRepositoryNode | DockerHubOrgNode) => { browseDockerHub(context); }); + registerCommand('vscode-docker.browseAzurePortal', (context?: AzureRegistryNode | AzureRepositoryNode | AzureImageTagNode) => { browseAzurePortal(context); }); - registerCommand('vscode-docker.container.start', async function ( - this: IActionContext, - node: ImageNode | undefined - ): Promise { - await startContainer(this, node); - }); - registerCommand('vscode-docker.container.start.interactive', async function ( - this: IActionContext, - node: ImageNode | undefined - ): Promise { - await startContainerInteractive(this, node); - }); - registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); - registerCommand('vscode-docker.container.stop', async function ( - this: IActionContext, - node: ContainerNode | RootNode | undefined - ): Promise { - await stopContainer(this, node); - }); - registerCommand('vscode-docker.container.restart', async function ( - this: IActionContext, - node: ContainerNode | RootNode | undefined - ): Promise { - await restartContainer(this, node); - }); - registerCommand('vscode-docker.container.show-logs', async function ( - this: IActionContext, - node: ContainerNode | RootNode | undefined - ): Promise { - await showLogsContainer(this, node); - }); - registerCommand('vscode-docker.container.open-shell', async function ( - this: IActionContext, - node: ContainerNode | RootNode | undefined - ): Promise { - await openShellContainer(this, node); - }); - registerCommand('vscode-docker.container.remove', async function ( - this: IActionContext, - node: ContainerNode | RootNode | undefined - ): Promise { - await removeContainer(this, node); - }); - registerCommand('vscode-docker.image.build', async function ( - this: IActionContext, - item: vscode.Uri | undefined - ): Promise { - await buildImage(this, item); - }); - registerCommand('vscode-docker.image.inspect', async function ( - this: IActionContext, - node: ImageNode | undefined - ): Promise { - await inspectImage(this, node); - }); - registerCommand('vscode-docker.image.remove', async function ( - this: IActionContext, - node: ImageNode | RootNode | undefined - ): Promise { - await removeImage(this, node); - }); - registerCommand('vscode-docker.image.push', async function ( - this: IActionContext, - node: ImageNode | undefined - ): Promise { - await pushImage(this, node); - }); - registerCommand('vscode-docker.image.tag', async function ( - this: IActionContext, - node: ImageNode | undefined - ): Promise { - await tagImage(this, node); - }); - registerCommand('vscode-docker.compose.up', composeUp); registerCommand('vscode-docker.compose.down', composeDown); registerCommand('vscode-docker.compose.restart', composeRestart); - registerCommand('vscode-docker.system.prune', systemPrune); - registerCommand( - 'vscode-docker.createWebApp', - async (context?: AzureImageTagNode | DockerHubImageTagNode) => - await createWebApp(context) - ); - registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout); - registerCommand( - 'vscode-docker.browseDockerHub', - (context?: DockerHubImageTagNode | DockerHubRepositoryNode | DockerHubOrgNode) => { - browseDockerHub(context); - } - ); - registerCommand( - 'vscode-docker.browseAzurePortal', - (context?: AzureRegistryNode | AzureRepositoryNode | AzureImageTagNode) => { - browseAzurePortal(context); - } - ); + registerCommand('vscode-docker.compose.up', composeUp); + registerCommand('vscode-docker.configure', async function (this: IActionContext): Promise { await configure(this, undefined); }); registerCommand('vscode-docker.connectCustomRegistry', connectCustomRegistry); + + registerCommand('vscode-docker.container.open-shell', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await openShellContainer(this, node); }); + registerCommand('vscode-docker.container.remove', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await removeContainer(this, node); }); + registerCommand('vscode-docker.container.restart', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await restartContainer(this, node); }); + registerCommand('vscode-docker.container.show-logs', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await showLogsContainer(this, node); }); + registerCommand('vscode-docker.container.start', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainer(this, node); }); + registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); + registerCommand('vscode-docker.container.start.interactive', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainerInteractive(this, node); }); + registerCommand('vscode-docker.container.stop', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await stopContainer(this, node); }); + + registerCommand('vscode-docker.createWebApp', async (context?: AzureImageTagNode | DockerHubImageTagNode) => await createWebApp(context)); registerCommand('vscode-docker.disconnectCustomRegistry', disconnectCustomRegistry); + registerCommand('vscode-docker.dockerHubLogout', dockerHubLogout); + registerCommand('vscode-docker.explorer.refresh', () => dockerExplorerProvider.refresh()); + + registerCommand('vscode-docker.image.build', async function (this: IActionContext, item: vscode.Uri | undefined): Promise { await buildImage(this, item); }); + registerCommand('vscode-docker.image.inspect', async function (this: IActionContext, node: ImageNode | undefined): Promise { await inspectImage(this, node); }); + registerCommand('vscode-docker.image.push', async function (this: IActionContext, node: ImageNode | undefined): Promise { await pushImage(this, node); }); + registerCommand('vscode-docker.image.remove', async function (this: IActionContext, node: ImageNode | RootNode | undefined): Promise { await removeImage(this, node); }); + registerCommand('vscode-docker.image.tag', async function (this: IActionContext, node: ImageNode | undefined): Promise { await tagImage(this, node); }); + registerCommand('vscode-docker.setRegistryAsDefault', setRegistryAsDefault); - registerCommand('vscode-docker.ACR-queueBuild', queueBuild); - registerCommand('vscode-docker.delete-ACR-Registry', deleteAzureRegistry); - registerCommand('vscode-docker.delete-ACR-Image', deleteAzureImage); - registerCommand('vscode-docker.delete-ACR-Repository', deleteRepository); - registerCommand('vscode-docker.create-ACR-Registry', createRegistry); - registerCommand('vscode-docker.pullFromAzure', pullFromAzure); - registerCommand('vscode-docker.acrLogs', viewACRLogs); - registerCommand('vscode-docker.run-ACR-Task', runTask); - registerCommand('vscode-docker.show-ACR-Task', showTaskProperties); + registerCommand('vscode-docker.system.prune', systemPrune); } export async function deactivate(): Promise { diff --git a/explorer/models/rootNode.ts b/explorer/models/rootNode.ts index 4e67cadbe9..915d121334 100644 --- a/explorer/models/rootNode.ts +++ b/explorer/models/rootNode.ts @@ -114,11 +114,9 @@ export class RootNode extends NodeBase { return this.getRegistries(); } default: { - break; + throw new Error(`Unexpected contextValue ${element.contextValue}`); } } - - throw new Error(`Unexpected contextValue ${element.contextValue}`); } private async getImages(): Promise { diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 5c7ba1e6ff..88dda3eb69 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -47,7 +47,7 @@ export class TaskRootNode extends NodeBase { const resourceGroup: string = acrTools.getResourceGroupName(element.registry); tasks = await client.tasks.list(resourceGroup, element.registry.name); if (tasks.length === 0) { - vscode.window.showInformationMessage(`You do not have any Tasks in the registry, '${element.registry.name}'. You can create one with ACR Task. `, "Learn More").then(val => { + vscode.window.showInformationMessage(`You do not have any Tasks in the registry '${element.registry.name}'. You can create one with Azure Container Registry Task. `, "Learn How").then(val => { if (val === "Learn More") { // tslint:disable-next-line:no-unsafe-any opn('https://aka.ms/acr/task'); diff --git a/package.json b/package.json index fb02aad2f0..2f8afa483c 100644 --- a/package.json +++ b/package.json @@ -28,51 +28,56 @@ }, "homepage": "https://github.com/Microsoft/vscode-docker/blob/master/README.md", "activationEvents": [ - "onLanguage:dockerfile", - "onLanguage:yaml", - "onCommand:vscode-docker.acrLogs", + "onCommand:vscode-docker.acr.createRegistry", + "onCommand:vscode-docker.acr.deleteImage", + "onCommand:vscode-docker.acr.deleteRegistry", + "onCommand:vscode-docker.acr.deleteRepository", + "onCommand:vscode-docker.acr.pullImage", + "onCommand:vscode-docker.acr.quickBuild", + "onCommand:vscode-docker.acr.runTask", + "onCommand:vscode-docker.acr.showTask", + "onCommand:vscode-docker.acr.viewLogs", "onCommand:vscode-docker.api.configure", - "onCommand:vscode-docker.ACR-queueBuild", - "onCommand:vscode-docker.image.build", - "onCommand:vscode-docker.image.inspect", - "onCommand:vscode-docker.image.remove", - "onCommand:vscode-docker.image.push", - "onCommand:vscode-docker.image.tag", - "onCommand:vscode-docker.container.start", - "onCommand:vscode-docker.container.start.interactive", - "onCommand:vscode-docker.container.start.azurecli", - "onCommand:vscode-docker.container.stop", - "onCommand:vscode-docker.container.restart", - "onCommand:vscode-docker.container.show-logs", - "onCommand:vscode-docker.container.open-shell", - "onCommand:vscode-docker.compose.up", + "onCommand:vscode-docker.browseAzurePortal", + "onCommand:vscode-docker.browseDockerHub", "onCommand:vscode-docker.compose.down", "onCommand:vscode-docker.compose.restart", + "onCommand:vscode-docker.compose.up", "onCommand:vscode-docker.configure", + "onCommand:vscode-docker.connectCustomRegistry", + "onCommand:vscode-docker.container.open-shell", + "onCommand:vscode-docker.container.remove", + "onCommand:vscode-docker.container.restart", + "onCommand:vscode-docker.container.show-logs", + "onCommand:vscode-docker.container.start", + "onCommand:vscode-docker.container.start.azurecli", + "onCommand:vscode-docker.container.start.interactive", + "onCommand:vscode-docker.container.stop", "onCommand:vscode-docker.createWebApp", - "onCommand:vscode-docker.create-ACR-Registry", - "onCommand:vscode-docker.system.prune", + "onCommand:vscode-docker.disconnectCustomRegistry", "onCommand:vscode-docker.dockerHubLogout", - "onCommand:vscode-docker.browseDockerHub", - "onCommand:vscode-docker.browseAzurePortal", "onCommand:vscode-docker.explorer.refresh", - "onCommand:vscode-docker.show-ACR-Task", - "onCommand:vscode-docker.delete-ACR-Registry", - "onCommand:vscode-docker.run-ACR-Task", - "onCommand:vscode-docker.delete-ACR-Repository", - "onCommand:vscode-docker.delete-ACR-Image", - "onCommand:vscode-docker.connectCustomRegistry", + "onCommand:vscode-docker.image.build", + "onCommand:vscode-docker.image.inspect", + "onCommand:vscode-docker.image.push", + "onCommand:vscode-docker.image.remove", + "onCommand:vscode-docker.image.tag", "onCommand:vscode-docker.setRegistryAsDefault", - "onCommand:vscode-docker.disconnectCustomRegistry", - "onCommand:vscode-docker.pullFromAzure", - "onView:dockerExplorer", + "onCommand:vscode-docker.system.prune", "onDebugInitialConfigurations", - "onDebugResolve:docker-coreclr" + "onDebugResolve:docker-coreclr", + "onLanguage:dockerfile", + "onLanguage:yaml", + "onView:dockerExplorer" ], "main": "./out/dockerExtension", "contributes": { "menus": { "commandPalette": [ + { + "command": "vscode-docker.api.configure", + "when": "never" + }, { "command": "vscode-docker.browseDockerHub", "when": "false" @@ -80,78 +85,74 @@ { "command": "vscode-docker.createWebApp", "when": "false" - }, - { - "command": "vscode-docker.api.configure", - "when": "never" } ], "editor/context": [ { "when": "editorLangId == dockerfile", - "command": "vscode-docker.ACR-queueBuild", + "command": "vscode-docker.acr.quickBuild", "group": "docker" }, { - "when": "editorLangId == dockerfile", - "command": "vscode-docker.image.build", + "when": "resourceFilename == docker-compose.yml", + "command": "vscode-docker.compose.down", "group": "docker" }, { "when": "resourceFilename == docker-compose.yml", - "command": "vscode-docker.compose.up", + "command": "vscode-docker.compose.restart", "group": "docker" }, { "when": "resourceFilename == docker-compose.yml", - "command": "vscode-docker.compose.down", + "command": "vscode-docker.compose.up", "group": "docker" }, { - "when": "resourceFilename == docker-compose.yml", - "command": "vscode-docker.compose.restart", + "when": "resourceFilename == docker-compose.debug.yml", + "command": "vscode-docker.compose.down", "group": "docker" }, { "when": "resourceFilename == docker-compose.debug.yml", - "command": "vscode-docker.compose.up", + "command": "vscode-docker.compose.restart", "group": "docker" }, { "when": "resourceFilename == docker-compose.debug.yml", - "command": "vscode-docker.compose.down", + "command": "vscode-docker.compose.up", "group": "docker" }, { - "when": "resourceFilename == docker-compose.debug.yml", - "command": "vscode-docker.compose.restart", + "when": "editorLangId == dockerfile", + "command": "vscode-docker.image.build", "group": "docker" } ], "explorer/context": [ { "when": "resourceFilename =~ /[dD]ocker[fF]ile/", - "command": "vscode-docker.ACR-queueBuild", + "command": "vscode-docker.acr.quickBuild", "group": "docker" }, { - "when": "resourceFilename =~ /[dD]ocker[fF]ile/", - "command": "vscode-docker.image.build", + "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", + "command": "vscode-docker.compose.down", "group": "docker" }, { "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", - "command": "vscode-docker.compose.up", + "command": "vscode-docker.compose.restart", "group": "docker" }, { "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", - "command": "vscode-docker.compose.down", + "command": "vscode-docker.compose.up", "group": "docker" }, { - "when": "resourceFilename =~ /[dD]ocker-[cC]ompose/", - "command": "vscode-docker.compose.restart", + "when": "resourceFilename =~ /[dD]ocker[fF]ile/", + "command": "vscode-docker.image.build", "group": "docker" } ], @@ -169,112 +170,108 @@ ], "view/item/context": [ { - "command": "vscode-docker.container.start", - "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" + "command": "vscode-docker.acr.createRegistry", + "when": "view == dockerExplorer && viewItem == azureRegistryRootNode" }, { - "command": "vscode-docker.container.start.interactive", - "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" + "command": "vscode-docker.acr.deleteImage", + "when": "view == dockerExplorer && viewItem == azureImageTagNode" }, { - "command": "vscode-docker.image.push", - "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" + "command": "vscode-docker.acr.deleteRegistry", + "when": "view == dockerExplorer && viewItem == azureRegistryNode" }, { - "command": "vscode-docker.image.remove", - "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" + "command": "vscode-docker.acr.deleteRepository", + "when": "view == dockerExplorer && viewItem == azureRepositoryNode" }, { - "command": "vscode-docker.image.inspect", - "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" + "command": "vscode-docker.acr.pullImage", + "when": "view == dockerExplorer && viewItem == azureImageNode" }, { - "command": "vscode-docker.image.tag", - "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" + "command": "vscode-docker.acr.runTask", + "when": "view == dockerExplorer && viewItem == taskNode" }, { - "command": "vscode-docker.container.stop", - "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|containersRootNode)$/" + "command": "vscode-docker.acr.showTask", + "when": "view == dockerExplorer && viewItem == taskNode" }, { - "command": "vscode-docker.container.restart", - "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|stoppedLocalContainerNode|containersRootNode)$/" + "command": "vscode-docker.acr.viewLogs", + "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureImageTagNode|taskNode)$/" }, { - "command": "vscode-docker.container.show-logs", - "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|stoppedLocalContainerNode|containersRootNode)$/" + "command": "vscode-docker.browseDockerHub", + "when": "view == dockerExplorer && viewItem =~ /^(dockerHubImageTagNode|dockerHubRepositoryNode|dockerHubOrgNode)$/" }, { - "command": "vscode-docker.container.open-shell", - "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|containersRootNode)$/" + "command": "vscode-docker.browseAzurePortal", + "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureRepositoryNode|azureImageNode)$/" }, { - "command": "vscode-docker.container.remove", - "when": "view == dockerExplorer && viewItem =~ /^(stoppedLocalContainerNode|runningLocalContainerNode|containersRootNode)$/" + "command": "vscode-docker.connectCustomRegistry", + "when": "view == dockerExplorer && viewItem == customRootNode" }, { - "command": "vscode-docker.createWebApp", - "when": "view == dockerExplorer && viewItem =~ /^(azureImageTagNode|dockerHubImageTagNode|customImageTagNode)$/" + "command": "vscode-docker.container.open-shell", + "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|containersRootNode)$/" }, { - "command": "vscode-docker.create-ACR-Registry", - "when": "view == dockerExplorer && viewItem == azureRegistryRootNode" + "command": "vscode-docker.container.remove", + "when": "view == dockerExplorer && viewItem =~ /^(stoppedLocalContainerNode|runningLocalContainerNode|containersRootNode)$/" }, { - "command": "vscode-docker.dockerHubLogout", - "when": "view == dockerExplorer && viewItem == dockerHubRootNode" + "command": "vscode-docker.container.restart", + "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|stoppedLocalContainerNode|containersRootNode)$/" }, { - "command": "vscode-docker.pullFromAzure", - "when": "view == dockerExplorer && viewItem == azureImageNode" + "command": "vscode-docker.container.show-logs", + "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|stoppedLocalContainerNode|containersRootNode)$/" }, { - "command": "vscode-docker.browseDockerHub", - "when": "view == dockerExplorer && viewItem == dockerHubImageTag" + "command": "vscode-docker.container.start", + "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, { - "command": "vscode-docker.browseDockerHub", - "when": "view == dockerExplorer && viewItem == dockerHubRepository" + "command": "vscode-docker.container.start.interactive", + "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, { - "command": "vscode-docker.delete-ACR-Image", - "when": "view == dockerExplorer && viewItem == azureImageTagNode" + "command": "vscode-docker.container.stop", + "when": "view == dockerExplorer && viewItem =~ /^(runningLocalContainerNode|containersRootNode)$/" }, { - "command": "vscode-docker.run-ACR-Task", - "when": "view == dockerExplorer && viewItem == taskNode" + "command": "vscode-docker.createWebApp", + "when": "view == dockerExplorer && viewItem =~ /^(azureImageTagNode|dockerHubImageTagNode|customImageTagNode)$/" }, { - "command": "vscode-docker.show-ACR-Task", - "when": "view == dockerExplorer && viewItem == taskNode" + "command": "vscode-docker.disconnectCustomRegistry", + "when": "view == dockerExplorer && viewItem =~ /^(customRegistryNode)$/" }, { - "command": "vscode-docker.delete-ACR-Registry", - "when": "view == dockerExplorer && viewItem == azureRegistryNode" + "command": "vscode-docker.dockerHubLogout", + "when": "view == dockerExplorer && viewItem == dockerHubRootNode" }, { - "command": "vscode-docker.browseDockerHub", - "when": "view == dockerExplorer && viewItem =~ /^(dockerHubImageTagNode|dockerHubRepositoryNode|dockerHubOrgNode)$/" + "command": "vscode-docker.image.inspect", + "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, { - "command": "vscode-docker.browseAzurePortal", - "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureRepositoryNode|azureImageNode)$/" + "command": "vscode-docker.image.push", + "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, { - "command": "vscode-docker.acrLogs", - "when": "view == dockerExplorer && viewItem =~ /^(azureRegistryNode|azureImageTagNode|taskNode)$/" + "command": "vscode-docker.image.remove", + "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, { - "command": "vscode-docker.connectCustomRegistry", - "when": "view == dockerExplorer && viewItem == customRootNode" + "command": "vscode-docker.image.tag", + "when": "view == dockerExplorer && viewItem =~ /^(localImageNode|imagesRootNode)$/" }, { "command": "vscode-docker.setRegistryAsDefault", "when": "view == dockerExplorer && viewItem =~ /^(customRegistryNode|azureRegistryNode|dockerHubOrgNode)$/" - }, - { - "command": "vscode-docker.disconnectCustomRegistry", - "when": "view == dockerExplorer && viewItem =~ /^(customRegistryNode)$/" } ] }, @@ -586,91 +583,75 @@ }, "commands": [ { - "command": "vscode-docker.configure", - "title": "Add Docker files to workspace", - "description": "Add Dockerfile, docker-compose.yml files", + "command": "vscode-docker.acr.createRegistry", + "title": "Create Azure Registry", "category": "Docker" }, { - "command": "vscode-docker.api.configure", - "title": "Add Docker files to Workspace (API)" - }, - { - "command": "vscode-docker.ACR-queueBuild", - "title": "Queue Build", - "description": "Queue a build from a Dockerfile", + "command": "vscode-docker.acr.deleteImage", + "title": "Delete Azure Image", "category": "Docker" }, { - "command": "vscode-docker.image.build", - "title": "Build Image", - "description": "Build a Docker image from a Dockerfile", + "command": "vscode-docker.acr.deleteRegistry", + "title": "Delete Azure Registry", "category": "Docker" }, { - "command": "vscode-docker.image.inspect", - "title": "Inspect Image", - "description": "Inspect the metadata of a Docker image", + "command": "vscode-docker.acr.deleteRepository", + "title": "Delete Azure Repository", "category": "Docker" }, { - "command": "vscode-docker.image.remove", - "title": "Remove Image", - "description": "Remove a Docker image", + "command": "vscode-docker.acr.pullImage", + "title": "Pull Image from Azure", "category": "Docker" }, { - "command": "vscode-docker.image.tag", - "title": "Tag Image", - "description": "Tag a Docker image", + "command": "vscode-docker.acr.quickBuild", + "title": "ACR: Quick Build", + "description": "Queue an Azure build from a Dockerfile", "category": "Docker" }, { - "command": "vscode-docker.container.start", - "title": "Run", - "description": "Starts a container from an image", + "command": "vscode-docker.acr.runTask", + "title": "Run Task", "category": "Docker" }, { - "command": "vscode-docker.container.start.interactive", - "title": "Run Interactive", - "description": "Starts a container from an image and runs it interactively", + "command": "vscode-docker.acr.showTask", + "title": "Show Task Properties", "category": "Docker" }, { - "command": "vscode-docker.container.start.azurecli", - "title": "Azure CLI", - "description": "Starts a container from the Azure CLI image and runs it interactively", + "command": "vscode-docker.acr.viewLogs", + "title": "View Azure Logs", "category": "Docker" }, { - "command": "vscode-docker.container.stop", - "title": "Stop Container", - "description": "Stop a running container", - "category": "Docker" + "command": "vscode-docker.api.configure", + "title": "Add Docker files to Workspace (API)" }, { - "command": "vscode-docker.container.restart", - "title": "Restart Container", - "description": "Restart one or more containers", + "command": "vscode-docker.browseDockerHub", + "title": "Browse in Docker Hub", "category": "Docker" }, { - "command": "vscode-docker.container.remove", - "title": "Remove Container", - "description": "Remove a stopped container", + "command": "vscode-docker.browseAzurePortal", + "title": "Browse in the Azure Portal", "category": "Docker" }, { - "command": "vscode-docker.container.show-logs", - "title": "Show Logs", - "description": "Show the logs of a running container", + "command": "vscode-docker.compose.down", + "title": "Compose Down", + "description": "Stops a composition of containers", "category": "Docker" }, { - "command": "vscode-docker.container.open-shell", - "title": "Attach Shell", - "description": "Open a terminal with an interactive shell for a running container", + "command": "vscode-docker.compose.restart", + "title": "Compose Restart", + "description": "Restarts a composition of containers", "category": "Docker" }, { @@ -680,114 +661,116 @@ "category": "Docker" }, { - "command": "vscode-docker.compose.down", - "title": "Compose Down", - "description": "Stops a composition of containers", + "command": "vscode-docker.configure", + "title": "Add Docker files to workspace", + "description": "Add Dockerfile, docker-compose.yml files", "category": "Docker" }, { - "command": "vscode-docker.compose.restart", - "title": "Compose Restart", - "description": "Restarts a composition of containers", + "command": "vscode-docker.connectCustomRegistry", + "title": "Connect to a Private Registry... (Preview)", "category": "Docker" }, { - "command": "vscode-docker.create-ACR-Registry", - "title": "Create Azure Registry", + "command": "vscode-docker.container.open-shell", + "title": "Attach Shell", + "description": "Open a terminal with an interactive shell for a running container", "category": "Docker" }, { - "command": "vscode-docker.delete-ACR-Repository", - "title": "Delete Azure Repository", + "command": "vscode-docker.container.remove", + "title": "Remove Container", + "description": "Remove a stopped container", "category": "Docker" }, { - "command": "vscode-docker.delete-ACR-Repository", - "title": "Delete Azure Repository", + "command": "vscode-docker.container.restart", + "title": "Restart Container", + "description": "Restart one or more containers", "category": "Docker" }, { - "command": "vscode-docker.delete-ACR-Image", - "title": "Delete Azure Image", + "command": "vscode-docker.container.show-logs", + "title": "Show Logs", + "description": "Show the logs of a running container", "category": "Docker" }, { - "command": "vscode-docker.image.push", - "title": "Push", - "description": "Push an image to a registry", + "command": "vscode-docker.container.start", + "title": "Run", + "description": "Starts a container from an image", "category": "Docker" }, { - "command": "vscode-docker.system.prune", - "title": "System Prune", - "category": "Docker", - "icon": { - "light": "images/light/prune.svg", - "dark": "images/dark/prune.svg" - } - }, - { - "command": "vscode-docker.explorer.refresh", - "title": "Refresh Explorer", - "category": "Docker", - "icon": { - "light": "images/light/refresh.svg", - "dark": "images/dark/refresh.svg" - } + "command": "vscode-docker.container.start.azurecli", + "title": "Azure CLI", + "description": "Starts a container from the Azure CLI image and runs it interactively", + "category": "Docker" }, { - "command": "vscode-docker.createWebApp", - "title": "Deploy Image to Azure App Service", + "command": "vscode-docker.container.start.interactive", + "title": "Run Interactive", + "description": "Starts a container from an image and runs it interactively", "category": "Docker" }, { - "command": "vscode-docker.pullFromAzure", - "title": "Pull Image from Azure", + "command": "vscode-docker.container.stop", + "title": "Stop Container", + "description": "Stop a running container", "category": "Docker" }, { - "command": "vscode-docker.dockerHubLogout", - "title": "Docker Hub Logout", + "command": "vscode-docker.createWebApp", + "title": "Deploy Image to Azure App Service", "category": "Docker" }, { - "command": "vscode-docker.browseDockerHub", - "title": "Browse in Docker Hub", + "command": "vscode-docker.disconnectCustomRegistry", + "title": "Disconnect from Private Registry", "category": "Docker" }, { - "command": "vscode-docker.browseAzurePortal", - "title": "Browse in the Azure Portal", + "command": "vscode-docker.dockerHubLogout", + "title": "Docker Hub Logout", "category": "Docker" }, { - "command": "vscode-docker.run-ACR-Task", - "title": "Run Task", - "category": "Docker" + "command": "vscode-docker.explorer.refresh", + "title": "Refresh Explorer", + "category": "Docker", + "icon": { + "light": "images/light/refresh.svg", + "dark": "images/dark/refresh.svg" + } }, { - "command": "vscode-docker.show-ACR-Task", - "title": "Show Task Properties", + "command": "vscode-docker.image.build", + "title": "Build Image", + "description": "Build a Docker image from a Dockerfile", "category": "Docker" }, { - "command": "vscode-docker.delete-ACR-Registry", - "title": "Delete Azure Registry", + "command": "vscode-docker.image.inspect", + "title": "Inspect Image", + "description": "Inspect the metadata of a Docker image", "category": "Docker" }, { - "command": "vscode-docker.delete-ACR-Image", - "title": "Delete Azure Image", + "command": "vscode-docker.image.push", + "title": "Push", + "description": "Push an image to a registry", "category": "Docker" }, { - "command": "vscode-docker.acrLogs", - "title": "View Azure logs", + "command": "vscode-docker.image.remove", + "title": "Remove Image", + "description": "Remove a Docker image", "category": "Docker" }, { - "command": "vscode-docker.connectCustomRegistry", - "title": "Connect to a Private Registry... (Preview)", + "command": "vscode-docker.image.tag", + "title": "Tag Image", + "description": "Tag a Docker image", "category": "Docker" }, { @@ -796,14 +779,13 @@ "category": "Docker" }, { - "command": "vscode-docker.disconnectCustomRegistry", - "title": "Disconnect from Private Registry", - "category": "Docker" - }, - { - "command": "vscode-docker.ACR-Build", - "title": "Cloud run", - "category": "Docker" + "command": "vscode-docker.system.prune", + "title": "System Prune", + "category": "Docker", + "icon": { + "light": "images/light/prune.svg", + "dark": "images/dark/prune.svg" + } } ], "views": { @@ -875,14 +857,12 @@ "fs-extra": "^6.0.1", "glob": "7.1.2", "gradle-to-js": "^1.0.1", - "handlebars": "^4.0.11", "moment": "^2.19.3", "opn": "^5.2.0", "pom-parser": "^1.1.1", "request-promise-native": "^1.0.5", "semver": "^5.5.1", "vscode-azureextensionui": "^0.17.0", - "request-promise": "^4.2.2", "tar": "^4.4.6", "vscode-extension-telemetry": "^0.0.22", "vscode-languageclient": "^4.4.0" diff --git a/thirdpartynotices.txt b/thirdpartynotices.txt index 8d30120152..23e175f98b 100644 --- a/thirdpartynotices.txt +++ b/thirdpartynotices.txt @@ -418,3 +418,32 @@ 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. +13. clipboardy (https://github.com/sindresorhus/clipboardy) + +MIT License + +Copyright (c) Sindre Sorhus (sindresorhus.com) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN 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. + +14. tar (https://github.com/npm/node-tar) + +The ISC License + +Copyright (c) Isaac Z. Schlueter 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. diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index 68f0ea14d9..d5fc0e9dcf 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -49,7 +49,7 @@ export function getResourceGroupName(registry: Registry): string { } //Gets resource group object from registry and subscription -export async function getResourceGroup(registry: Registry, subscription: Subscription): Promise { ///to do: move to acr tools +export async function getResourceGroup(registry: Registry, subscription: Subscription): Promise { let resourceGroups: ResourceGroup[] = await AzureUtilityManager.getInstance().getResourceGroups(subscription); const resourceGroupName = getResourceGroupName(registry); return resourceGroups.find((res) => { return res.name === resourceGroupName }); @@ -85,7 +85,7 @@ export async function getRepositoriesByRegistry(registry: Registry): Promise((resolve, reject) => { blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, { rangeStart: rangeStart }, (error, result) => { - if (error) { reject() } else { resolve(result); } + if (error) { reject(error) } else { resolve(result); } }); }); } From 75f3c64a6bebe101f874f313c3044f6882106d58 Mon Sep 17 00:00:00 2001 From: Stephen Weatherford Date: Mon, 5 Nov 2018 15:28:55 -0800 Subject: [PATCH 72/77] Hide Azure Quick Build if Azure Account not available --- package.json | 2 +- utils/azureUtilityManager.ts | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index eddfcc7b99..46802dda42 100644 --- a/package.json +++ b/package.json @@ -89,7 +89,7 @@ ], "editor/context": [ { - "when": "editorLangId == dockerfile", + "when": "editorLangId == dockerfile && isAzureAccountInstalled", "command": "vscode-docker.acr.quickBuild", "group": "docker" }, diff --git a/utils/azureUtilityManager.ts b/utils/azureUtilityManager.ts index 03e8e3ecf9..9c2cc02157 100644 --- a/utils/azureUtilityManager.ts +++ b/utils/azureUtilityManager.ts @@ -41,6 +41,8 @@ export class AzureUtilityManager { if (azureAccountExtension) { azureAccount = await azureAccountExtension.activate(); } + + vscode.commands.executeCommand('setContext', 'isAzureAccountInstalled', !!azureAccount); } catch (error) { throw new Error('Failed to activate the Azure Account Extension: ' + parseError(error).message); } From c7eb3044bb077edf3073683c671e9a96d3d4a0d8 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Mon, 5 Nov 2018 23:56:20 -0800 Subject: [PATCH 73/77] Second PR #506 review update. Quick Build Image name and dockerFile selection Improvements. Upgrade from fs to fs-extra. Log table bug fixes. Error Handeling Improvements. Other general improvements. --- .../acr-logs-utils/logFileManager.ts | 25 ++++----- .../acr-logs-utils/logScripts.js | 2 - .../acr-logs-utils/tableDataManager.ts | 35 +++++++----- .../acr-logs-utils/tableViewManager.ts | 11 ++-- commands/azureCommands/acr-logs.ts | 2 +- commands/azureCommands/create-registry.ts | 7 --- commands/azureCommands/pull-from-azure.ts | 4 +- commands/azureCommands/quick-build.ts | 29 +++++----- commands/azureCommands/run-task.ts | 6 +- commands/start-container.ts | 8 +-- commands/utils/quick-pick-azure.ts | 20 ------- commands/utils/quick-pick-image.ts | 55 ++++++++++++++++++- configureWorkspace/configure.ts | 6 +- debugging/coreclr/vsdbgClient.ts | 4 +- dockerExtension.ts | 4 +- explorer/deploy/webAppCreator.ts | 2 - explorer/models/taskNode.ts | 2 +- package.json | 2 +- utils/Azure/acrTools.ts | 4 +- 19 files changed, 124 insertions(+), 104 deletions(-) diff --git a/commands/azureCommands/acr-logs-utils/logFileManager.ts b/commands/azureCommands/acr-logs-utils/logFileManager.ts index ddc2e8b6f7..a44b0d6518 100644 --- a/commands/azureCommands/acr-logs-utils/logFileManager.ts +++ b/commands/azureCommands/acr-logs-utils/logFileManager.ts @@ -1,7 +1,7 @@ import { BlobService, createBlobServiceWithSas } from 'azure-storage'; -import * as fs from 'fs-extra'; +import * as fse from 'fs-extra'; import * as vscode from 'vscode'; -import { getBlobInfo, IBlobInfo } from '../../../utils/Azure/acrTools'; +import { getBlobInfo, getBlobToText, IBlobInfo } from '../../../utils/Azure/acrTools'; export class LogContentProvider implements vscode.TextDocumentContentProvider { public static scheme: string = 'purejs'; @@ -33,20 +33,15 @@ export function encodeBase64(str: string): string { } /** Loads log text from remote url using azure blobservices */ -export function accessLog(url: string, title: string, download: boolean): void { +export async function accessLog(url: string, title: string, download: boolean): Promise { let blobInfo: IBlobInfo = getBlobInfo(url); let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); - blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, async (error, text, result, response) => { - if (response) { - if (download) { - await downloadLog(text, title); - } else { - openLogInNewWindow(text, title); - } - } else if (error) { - throw error; - } - }); + let text1 = await getBlobToText(blobInfo, blob, 0); + if (download) { + await downloadLog(text1, title); + } else { + openLogInNewWindow(text1, title); + } } function openLogInNewWindow(content: string, title: string): void { @@ -63,7 +58,7 @@ export async function downloadLog(content: string, title: string): Promise filters: { 'Log': ['.log', '.txt'] }, defaultUri: vscode.Uri.file(`${title}.log`) }); - fs.writeFile(uri.fsPath, content, + fse.writeFile(uri.fsPath, content, (err) => { if (err) { throw err; } }); diff --git a/commands/azureCommands/acr-logs-utils/logScripts.js b/commands/azureCommands/acr-logs-utils/logScripts.js index b6538d59e2..a69911fdf6 100644 --- a/commands/azureCommands/acr-logs-utils/logScripts.js +++ b/commands/azureCommands/acr-logs-utils/logScripts.js @@ -283,8 +283,6 @@ function getFilterString(inputFields) { filter.runId = inputFields[0].value; } else if (inputFields[1].value.length > 0) { //Task id filter.task = inputFields[1].value; - } else if (inputFields[2].value.length > 0) { //Image - filter.image = inputFields[2].value; } return filter; } diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index cdbb26bf06..4cec40e2df 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -1,5 +1,6 @@ import ContainerRegistryManagementClient from "azure-arm-containerregistry"; import { Registry, Run, RunGetLogResult, RunListResult } from "azure-arm-containerregistry/lib/models"; +import { parseError } from "vscode-azureextensionui"; import { ext } from "../../../extensionVariables"; import { acquireACRAccessTokenFromRegistry } from "../../../utils/Azure/acrTools"; /** Class to manage data and data acquisition for logs */ @@ -65,7 +66,14 @@ export class LogData { runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); } else { runListResult = []; - runListResult.push(await this.client.runs.get(this.resourceGroup, this.registry.name, filter.runId)); + // Temporal fix. Issue documented: runId must exist in the registry. + try { + runListResult.push(await this.client.runs.get(this.resourceGroup, this.registry.name, filter.runId)); + } catch (err) { + if (parseError(err).errorType !== "EntityNotFound") { + throw err; + } + } } } else { if (loadNext) { @@ -78,30 +86,29 @@ export class LogData { runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name); } } - if (removeOld) { this.clearLogItems() } + if (removeOld) { + //Clear Log Items + this.logs = []; + this.links = []; + this.nextLink = ''; + } this.nextLink = runListResult.nextLink; - this.addLogs(runListResult); - } - - public addLogs(logs: Run[]): void { - this.logs = this.logs.concat(logs); + this.logs = this.logs.concat(runListResult); - const itemCount = logs.length; + const itemCount = runListResult.length; for (let i = 0; i < itemCount; i++) { this.links.push({ 'requesting': false }); } } - public clearLogItems(): void { - this.logs = []; - this.links = []; - this.nextLink = ''; - } - public hasNextPage(): boolean { return this.nextLink !== undefined; } + public isEmpty(): boolean { + return this.logs.length === 0; + } + private async parseFilter(filter: Filter): Promise { let parsedFilter = ""; if (filter.task) { //Task id diff --git a/commands/azureCommands/acr-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts index 47c85e3c0e..35980a835a 100644 --- a/commands/azureCommands/acr-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -15,9 +15,10 @@ export class LogTableWebview { this.panel = vscode.window.createWebviewPanel('log Viewer', webviewName, vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true }); //Get path to resource on disk - const scriptFile = vscode.Uri.file(path.join(ext.context.extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); - const styleFile = vscode.Uri.file(path.join(ext.context.extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); - const iconStyle = vscode.Uri.file(path.join(ext.context.extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); + const extensionPath = ext.context.extensionPath; + const scriptFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); + const styleFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); + const iconStyle = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); //Populate Webview this.panel.webview.html = this.getBaseHtml(scriptFile, styleFile, iconStyle); this.setupIncomingListeners(); @@ -29,9 +30,9 @@ export class LogTableWebview { this.panel.webview.onDidReceiveMessage(async (message: IMessage) => { if (message.logRequest) { const itemNumber: number = +message.logRequest.id; - await this.logData.getLink(itemNumber).then((url) => { + await this.logData.getLink(itemNumber).then(async (url) => { if (url !== 'requesting') { - accessLog(url, this.logData.logs[itemNumber].runId, message.logRequest.download); + await accessLog(url, this.logData.logs[itemNumber].runId, message.logRequest.download); } }); diff --git a/commands/azureCommands/acr-logs.ts b/commands/azureCommands/acr-logs.ts index 89bfc7d88d..e0362c8f17 100644 --- a/commands/azureCommands/acr-logs.ts +++ b/commands/azureCommands/acr-logs.ts @@ -33,7 +33,7 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode await logData.loadLogs(false, false, { image: context.label }); if (!hasValidLogContent(context, logData)) { return; } const url = await logData.getLink(0); - accessLog(url, logData.logs[0].runId, false); + await accessLog(url, logData.logs[0].runId, false); } else { if (context && context instanceof TaskNode) { //ACR Task Logs diff --git a/commands/azureCommands/create-registry.ts b/commands/azureCommands/create-registry.ts index 895723f5a1..d81196d48b 100644 --- a/commands/azureCommands/create-registry.ts +++ b/commands/azureCommands/create-registry.ts @@ -32,13 +32,6 @@ export async function createRegistry(): Promise { dockerExplorerProvider.refreshRegistries(); return registry; } -async function acquireSKU(): Promise { - let sku: string; - sku = await vscode.window.showQuickPick(skus, { 'canPickMany': false, 'placeHolder': 'Choose a SKU' }); - if (sku === undefined) { throw new Error('User exit'); } - - return sku; -} /** Acquires a new registry name from a user, validating that the name is unique */ async function acquireRegistryName(client: ContainerRegistryManagementClient): Promise { diff --git a/commands/azureCommands/pull-from-azure.ts b/commands/azureCommands/pull-from-azure.ts index f708a1dda9..b2c2bd91dd 100644 --- a/commands/azureCommands/pull-from-azure.ts +++ b/commands/azureCommands/pull-from-azure.ts @@ -6,7 +6,7 @@ import * as assert from 'assert'; import { Registry } from "azure-arm-containerregistry/lib/models"; import { exec } from 'child_process'; -import * as fs from 'fs'; +import * as fse from 'fs-extra'; import * as path from "path"; import vscode = require('vscode'); import { callWithTelemetryAndErrorHandling, IActionContext, parseError } from 'vscode-azureextensionui'; @@ -97,7 +97,7 @@ async function isLoggedIntoDocker(registryName: string): Promise<{ configPath: s await callWithTelemetryAndErrorHandling('findDockerConfig', async function (this: IActionContext): Promise { this.suppressTelemetry = true; - buffer = fs.readFileSync(configPath); + buffer = fse.readFileSync(configPath); }); let index = buffer.indexOf(registryName); diff --git a/commands/azureCommands/quick-build.ts b/commands/azureCommands/quick-build.ts index 4c8a6ffd1e..a69c5e59a1 100644 --- a/commands/azureCommands/quick-build.ts +++ b/commands/azureCommands/quick-build.ts @@ -3,18 +3,19 @@ import { Registry, Run, SourceUploadDefinition } from 'azure-arm-containerregist import { DockerBuildRequest } from "azure-arm-containerregistry/lib/models"; import { Subscription } from 'azure-arm-resource/lib/subscription/models'; import { BlobService, createBlobServiceWithSas } from "azure-storage"; -import * as fs from 'fs-extra'; +import * as fse from 'fs-extra'; import * as os from 'os'; import * as process from 'process'; import * as tar from 'tar'; import * as url from 'url'; import * as vscode from "vscode"; -import { IAzureQuickPickItem } from 'vscode-azureextensionui'; +import { IActionContext, IAzureQuickPickItem } from 'vscode-azureextensionui'; import { ext } from '../../extensionVariables'; import { getBlobInfo, getResourceGroupName, IBlobInfo, streamLogs } from "../../utils/Azure/acrTools"; import { AzureUtilityManager } from "../../utils/azureUtilityManager"; -import { Item, resolveDockerFileItem } from '../build-image'; -import { quickPickACRRegistry, quickPickNewImageName, quickPickSubscription } from '../utils/quick-pick-azure'; +import { Item } from '../build-image'; +import { quickPickACRRegistry, quickPickSubscription } from '../utils/quick-pick-azure'; +import { quickPickDockerFileItem, quickPickImageName } from '../utils/quick-pick-image'; import { quickPickWorkspaceFolder } from '../utils/quickPickWorkspaceFolder'; const idPrecision = 6; @@ -24,8 +25,13 @@ const status = vscode.window.createOutputChannel('ACR Build Status'); // Prompts user to select a subscription, resource group, then registry from drop down. If there are multiple folders in the workspace, the source folder must also be selected. // The user is then asked to name & tag the image. A build is queued for the image in the selected registry. // Selected source code must contain a path to the desired dockerfile. -export async function quickBuild(dockerFileUri?: vscode.Uri): Promise { +export async function quickBuild(actionContext: IActionContext, dockerFileUri?: vscode.Uri | undefined): Promise { //Acquire information from user + let rootFolder: vscode.WorkspaceFolder = await quickPickWorkspaceFolder("To quick build Docker files you must first open a folder or workspace in VS Code."); + const dockerItem: Item = await quickPickDockerFileItem(actionContext, dockerFileUri, rootFolder); + const sourceLocation: string = rootFolder.uri.path; + const tarFilePath: string = getTempSourceArchivePath(); + const subscription: Subscription = await quickPickSubscription(); const client: ContainerRegistryManagementClient = await AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); @@ -36,17 +42,12 @@ export async function quickBuild(dockerFileUri?: vscode.Uri): Promise { const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); const osType: string = (await ext.ui.showQuickPick(osPick, { 'canPickMany': false, 'placeHolder': 'Select image base OS' })).data; - const imageName: string = await quickPickNewImageName(); + const imageName: string = await quickPickImageName(actionContext, rootFolder, dockerItem); //Begin readying build status.show(); - let folder: vscode.WorkspaceFolder = await quickPickWorkspaceFolder("To quick build Docker files you must first open a folder or workspace in VS Code."); - const dockerItem: Item = await resolveDockerFileItem(folder, dockerFileUri); - const sourceLocation: string = folder.uri.path; - const tarFilePath: string = getTempSourceArchivePath(); - - const uploadedSourceLocation: string = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, folder); + const uploadedSourceLocation: string = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, rootFolder); status.appendLine("Uploaded Source Code to " + tarFilePath); const runRequest: DockerBuildRequest = { @@ -70,10 +71,10 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis let source: string = sourceLocation.substring(1); let current: string = process.cwd(); process.chdir(source); - fs.readdir(source, (err, items) => { + fse.readdir(source, (err, items) => { items = items.filter(i => !(i in vcsIgnoreList)); // tslint:disable-next-line:no-unsafe-any - tar.c({}, items).pipe(fs.createWriteStream(tarFilePath)); + tar.c({}, items).pipe(fse.createWriteStream(tarFilePath)); process.chdir(current); }); diff --git a/commands/azureCommands/run-task.ts b/commands/azureCommands/run-task.ts index 96fa83c683..f4f6f0dc37 100644 --- a/commands/azureCommands/run-task.ts +++ b/commands/azureCommands/run-task.ts @@ -35,10 +35,8 @@ export async function runTask(context?: TaskNode): Promise { try { let taskRun = await client.registries.scheduleRun(resourceGroup.name, registry.name, runRequest); - vscode.window.showInformationMessage(`Successfully scheduled the Task: ${taskName} with ID: ${taskRun.runId}`); + vscode.window.showInformationMessage(`Successfully scheduled the Task '${taskName}' with ID '${taskRun.runId}'.`); } catch (err) { - const error = parseError(err); - vscode.window.showErrorMessage(`Failed to schedule the Task: ${taskName}\nError: ${error.message}`); - throw error; + throw new Error(`Failed to schedule the Task '${taskName}'\nError: '${parseError(err).message}'`); } } diff --git a/commands/start-container.ts b/commands/start-container.ts index f93b16a50b..e6d30709eb 100644 --- a/commands/start-container.ts +++ b/commands/start-container.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import * as cp from 'child_process'; -import * as fs from 'fs'; +import * as fse from 'fs-extra'; import os = require('os'); import vscode = require('vscode'); import { IActionContext, parseError } from 'vscode-azureextensionui'; @@ -92,13 +92,13 @@ export async function startAzureCLI(actionContext: IActionContext): Promise { - let opt: vscode.InputBoxOptions = { - validateInput: checkForValidTag, - ignoreFocusOut: false, - prompt: 'Enter repository name and tag in format :' - }; - - let tag: string = await ext.ui.showInputBox(opt); - return tag; -} -function checkForValidTag(str: string): string { - if (!imageTagRegExp.test(str)) { - return 'Repository name must have 0-256 alpha-numeric characters, optionally separated by periods, dashes or underscores.' - + 'A tag name must have 0-128 alpha-numeric characters, digits, underscores, periods or dashes. A tag name may not start with a period or a dash.'; - } - return undefined; - -} diff --git a/commands/utils/quick-pick-image.ts b/commands/utils/quick-pick-image.ts index a9c9454c42..8abcaa4327 100644 --- a/commands/utils/quick-pick-image.ts +++ b/commands/utils/quick-pick-image.ts @@ -2,11 +2,14 @@ * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See LICENSE.md in the project root for license information. *--------------------------------------------------------------------------------------------*/ - import * as Docker from 'dockerode'; +import * as path from "path"; import vscode = require('vscode'); -import { IActionContext, parseError, TelemetryProperties } from 'vscode-azureextensionui'; +import { DialogResponses, IActionContext, parseError, TelemetryProperties } from 'vscode-azureextensionui'; +import { delay } from '../../explorer/utils/utils'; import { ext } from '../../extensionVariables'; +import { Item, resolveDockerFileItem } from '../build-image'; +import { addImageTaggingTelemetry, getTagFromUserInput } from '../tag-image'; import { docker } from './docker-endpoint'; export interface ImageItem extends vscode.QuickPickItem { @@ -78,3 +81,51 @@ export async function quickPickImage(actionContext: IActionContext, includeAll?: return response; } } + +export async function quickPickImageName(actionContext: IActionContext, rootFolder: vscode.WorkspaceFolder, dockerFileItem: Item | undefined): Promise { + let absFilePath: string = path.join(rootFolder.uri.fsPath, dockerFileItem.relativeFilePath); + let dockerFileKey = `ACR_buildTag_${absFilePath}`; + let prevImageName: string | undefined = ext.context.globalState.get(dockerFileKey); + let suggestedImageName: string; + + if (!prevImageName) { + // Get imageName based on name of subfolder containing the Dockerfile, or else workspacefolder + suggestedImageName = path.basename(dockerFileItem.relativeFolderPath).toLowerCase(); + if (suggestedImageName === '.') { + suggestedImageName = path.basename(rootFolder.uri.fsPath).toLowerCase().replace(/\s/g, ''); + } + + suggestedImageName += ":{{.Run.ID}}" + } else { + suggestedImageName = prevImageName; + } + + // Temporary work-around for vscode bug where valueSelection can be messed up if a quick pick is followed by a showInputBox + await delay(500); + + addImageTaggingTelemetry(actionContext, suggestedImageName, '.before'); + const imageName: string = await getTagFromUserInput(suggestedImageName, !prevImageName); + addImageTaggingTelemetry(actionContext, imageName, '.after'); + + await ext.context.globalState.update(dockerFileKey, imageName); + return imageName; +} + +export async function quickPickDockerFileItem(actionContext: IActionContext, dockerFileUri: vscode.Uri | undefined, rootFolder: vscode.WorkspaceFolder): Promise { + let dockerFileItem: Item | undefined; + + while (!dockerFileItem) { + let resolvedItem: Item | undefined = await resolveDockerFileItem(rootFolder, dockerFileUri); + if (resolvedItem) { + dockerFileItem = resolvedItem; + } else { + let msg = "Couldn't find a Dockerfile in your workspace. Would you like to add Docker files to the workspace?"; + actionContext.properties.cancelStep = msg; + await ext.ui.showWarningMessage(msg, DialogResponses.yes, DialogResponses.cancel); + actionContext.properties.cancelStep = undefined; + await vscode.commands.executeCommand('vscode-docker.configure'); + // Try again + } + } + return dockerFileItem; +} diff --git a/configureWorkspace/configure.ts b/configureWorkspace/configure.ts index 1b99dea27f..4b1c6cf9a7 100644 --- a/configureWorkspace/configure.ts +++ b/configureWorkspace/configure.ts @@ -4,10 +4,8 @@ *--------------------------------------------------------------------------------------------*/ import * as assert from 'assert'; -import * as fs from 'fs'; import * as fse from 'fs-extra'; import * as gradleParser from "gradle-to-js/lib/parser"; -import { EOL } from 'os'; import * as path from "path"; import * as pomParser from "pom-parser"; import * as vscode from "vscode"; @@ -147,7 +145,7 @@ async function readPackageJson(folderPath: string): Promise<{ packagePath?: stri if (uris && uris.length > 0) { packagePath = uris[0].fsPath; - const json = JSON.parse(fs.readFileSync(packagePath, 'utf8')); + const json = JSON.parse(fse.readFileSync(packagePath, 'utf8')); if (json.scripts && typeof json.scripts.start === "string") { packageInfo.npmStart = true; @@ -426,7 +424,7 @@ async function configureCore(actionContext: IActionContext, options: ConfigureAp // Paths in the docker files should be relative to the Dockerfile (which is in the output folder) let fileContents = generatorFunction(serviceNameAndPathRelativeToOutput, platformType, os, port, packageInfo); if (fileContents) { - fs.writeFileSync(filePath, fileContents, { encoding: 'utf8' }); + fse.writeFileSync(filePath, fileContents, { encoding: 'utf8' }); filesWritten.push(filePath); } } diff --git a/debugging/coreclr/vsdbgClient.ts b/debugging/coreclr/vsdbgClient.ts index 58f8b33548..0e2131f0af 100644 --- a/debugging/coreclr/vsdbgClient.ts +++ b/debugging/coreclr/vsdbgClient.ts @@ -4,8 +4,8 @@ import * as path from 'path'; import * as process from 'process'; -import * as request from 'request-promise-native'; import { Memento } from 'vscode'; +import { ext } from '../../extensionVariables'; import { FileSystemProvider } from './fsProvider'; import { OSProvider } from './osProvider'; import { OutputManager } from './outputManager'; @@ -101,7 +101,7 @@ export class RemoteVsDbgClient implements VsDbgClient { await this.fileSystemProvider.makeDir(this.vsdbgPath); } - const script = await request(this.options.url); + const script = await ext.request(this.options.url); await this.fileSystemProvider.writeFile(vsdbgAcquisitionScriptPath, script); diff --git a/dockerExtension.ts b/dockerExtension.ts index 643c859f54..4bbcf84bf5 100644 --- a/dockerExtension.ts +++ b/dockerExtension.ts @@ -244,7 +244,7 @@ function registerDockerCommands(): void { registerCommand('vscode-docker.acr.deleteRegistry', deleteAzureRegistry); registerCommand('vscode-docker.acr.deleteRepository', deleteRepository); registerCommand('vscode-docker.acr.pullImage', pullFromAzure); - registerCommand('vscode-docker.acr.quickBuild', quickBuild); + registerCommand('vscode-docker.acr.quickBuild', async function (this: IActionContext, item: vscode.Uri | undefined): Promise { await quickBuild(this, item); }); registerCommand('vscode-docker.acr.runTask', runTask); registerCommand('vscode-docker.acr.showTask', showTaskProperties); registerCommand('vscode-docker.acr.viewLogs', viewACRLogs); @@ -264,7 +264,7 @@ function registerDockerCommands(): void { registerCommand('vscode-docker.container.restart', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await restartContainer(this, node); }); registerCommand('vscode-docker.container.show-logs', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await showLogsContainer(this, node); }); registerCommand('vscode-docker.container.start', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainer(this, node); }); - registerCommand('vscode-docker.container.start.azurecli', startAzureCLI); + registerCommand('vscode-docker.container.start.azurecli', async function (this: IActionContext): Promise { await startAzureCLI(this); }); registerCommand('vscode-docker.container.start.interactive', async function (this: IActionContext, node: ImageNode | undefined): Promise { await startContainerInteractive(this, node); }); registerCommand('vscode-docker.container.stop', async function (this: IActionContext, node: ContainerNode | RootNode | undefined): Promise { await stopContainer(this, node); }); diff --git a/explorer/deploy/webAppCreator.ts b/explorer/deploy/webAppCreator.ts index 0178732964..3af5f72a5a 100644 --- a/explorer/deploy/webAppCreator.ts +++ b/explorer/deploy/webAppCreator.ts @@ -9,8 +9,6 @@ import { ResourceManagementClient, ResourceModels, SubscriptionModels } from 'az import { Subscription } from 'azure-arm-resource/lib/subscription/models'; import WebSiteManagementClient = require('azure-arm-website'); import * as WebSiteModels from 'azure-arm-website/lib/models'; -import * as fs from 'fs'; -import * as path from 'path'; import * as vscode from 'vscode'; import { addExtensionUserAgent } from 'vscode-azureextensionui'; import { reporter } from '../../telemetry/telemetry'; diff --git a/explorer/models/taskNode.ts b/explorer/models/taskNode.ts index 88dda3eb69..f0cb25ef2a 100644 --- a/explorer/models/taskNode.ts +++ b/explorer/models/taskNode.ts @@ -47,7 +47,7 @@ export class TaskRootNode extends NodeBase { const resourceGroup: string = acrTools.getResourceGroupName(element.registry); tasks = await client.tasks.list(resourceGroup, element.registry.name); if (tasks.length === 0) { - vscode.window.showInformationMessage(`You do not have any Tasks in the registry '${element.registry.name}'. You can create one with Azure Container Registry Task. `, "Learn How").then(val => { + vscode.window.showInformationMessage(`You do not have any Tasks in the registry '${element.registry.name}'.`, "Learn How to Create Build Tasks").then(val => { if (val === "Learn More") { // tslint:disable-next-line:no-unsafe-any opn('https://aka.ms/acr/task'); diff --git a/package.json b/package.json index eddfcc7b99..dbce52549a 100644 --- a/package.json +++ b/package.json @@ -609,7 +609,7 @@ }, { "command": "vscode-docker.acr.quickBuild", - "title": "ACR: Quick Build", + "title": "ACR Tasks: Build Image", "description": "Queue an Azure build from a Dockerfile", "category": "Docker" }, diff --git a/utils/Azure/acrTools.ts b/utils/Azure/acrTools.ts index d5fc0e9dcf..b0b2e79707 100644 --- a/utils/Azure/acrTools.ts +++ b/utils/Azure/acrTools.ts @@ -49,7 +49,7 @@ export function getResourceGroupName(registry: Registry): string { } //Gets resource group object from registry and subscription -export async function getResourceGroup(registry: Registry, subscription: Subscription): Promise { +export async function getResourceGroup(registry: Registry, subscription: Subscription): Promise { let resourceGroups: ResourceGroup[] = await AzureUtilityManager.getInstance().getResourceGroups(subscription); const resourceGroupName = getResourceGroupName(registry); return resourceGroups.find((res) => { return res.name === resourceGroupName }); @@ -258,7 +258,7 @@ export async function streamLogs(registry: Registry, run: Run, outputChannel: vs } // Promisify getBlobToText for readability and error handling purposes -async function getBlobToText(blobInfo: IBlobInfo, blob: BlobService, rangeStart: number): Promise { +export async function getBlobToText(blobInfo: IBlobInfo, blob: BlobService, rangeStart: number): Promise { return new Promise((resolve, reject) => { blob.getBlobToText(blobInfo.containerName, blobInfo.blobName, { rangeStart: rangeStart }, (error, result) => { From 50bc53b4bb7cdec517cd9eb37b3db989c5901995 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Tue, 6 Nov 2018 16:26:55 -0800 Subject: [PATCH 74/77] Improving Logs generation Error handeling --- .../acr-logs-utils/tableDataManager.ts | 11 +++++++--- .../acr-logs-utils/tableViewManager.ts | 21 ++++++++++++------- commands/azureCommands/acr-logs.ts | 6 +++--- 3 files changed, 24 insertions(+), 14 deletions(-) diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index 4cec40e2df..5d71f64579 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -1,5 +1,6 @@ import ContainerRegistryManagementClient from "azure-arm-containerregistry"; import { Registry, Run, RunGetLogResult, RunListResult } from "azure-arm-containerregistry/lib/models"; +import vscode = require('vscode'); import { parseError } from "vscode-azureextensionui"; import { ext } from "../../../extensionVariables"; import { acquireACRAccessTokenFromRegistry } from "../../../utils/Azure/acrTools"; @@ -50,7 +51,7 @@ export class LogData { * the next page of logs will be saved and all preexisting data will be deleted. * @param filter Specifies a filter for log items, if run Id is specified this will take precedence */ - public async loadLogs(loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { + public async loadLogs(webViewEvent: boolean, loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { let runListResult: RunListResult; let options: { filter?: string, @@ -66,12 +67,14 @@ export class LogData { runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); } else { runListResult = []; - // Temporal fix. Issue documented: runId must exist in the registry. try { runListResult.push(await this.client.runs.get(this.resourceGroup, this.registry.name, filter.runId)); } catch (err) { - if (parseError(err).errorType !== "EntityNotFound") { + const error = parseError(err); + if (!webViewEvent) { throw err; + } else if (error.errorType !== "EntityNotFound") { + vscode.window.showErrorMessage(`Error '${error.errorType}': ${error.message}`); } } } @@ -79,6 +82,8 @@ export class LogData { if (loadNext) { if (this.nextLink) { runListResult = await this.client.runs.listNext(this.nextLink); + } else if (webViewEvent) { + vscode.window.showErrorMessage("No more logs to show."); } else { throw new Error('No more logs to show'); } diff --git a/commands/azureCommands/acr-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts index 35980a835a..2a5664c3f6 100644 --- a/commands/azureCommands/acr-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -3,6 +3,7 @@ import { ImageDescriptor, Run } from "azure-arm-containerregistry/lib/models"; import * as clipboardy from 'clipboardy' import * as path from 'path'; import * as vscode from "vscode"; +import { parseError } from "vscode-azureextensionui"; import { ext } from "../../../extensionVariables"; import { accessLog } from './logFileManager'; import { Filter, LogData } from './tableDataManager' @@ -30,23 +31,27 @@ export class LogTableWebview { this.panel.webview.onDidReceiveMessage(async (message: IMessage) => { if (message.logRequest) { const itemNumber: number = +message.logRequest.id; - await this.logData.getLink(itemNumber).then(async (url) => { - if (url !== 'requesting') { - await accessLog(url, this.logData.logs[itemNumber].runId, message.logRequest.download); - } - }); - + try { + await this.logData.getLink(itemNumber).then(async (url) => { + if (url !== 'requesting') { + await accessLog(url, this.logData.logs[itemNumber].runId, message.logRequest.download); + } + }); + } catch (err) { + const error = parseError(err); + vscode.window.showErrorMessage(`Error '${error.errorType}': ${error.message}`); + } } else if (message.copyRequest) { // tslint:disable-next-line:no-unsafe-any clipboardy.writeSync(message.copyRequest.text); } else if (message.loadMore) { const alreadyLoaded = this.logData.logs.length; - await this.logData.loadLogs(true); + await this.logData.loadLogs(true, true); this.addLogsToWebView(alreadyLoaded); } else if (message.loadFiltered) { - await this.logData.loadLogs(false, true, message.loadFiltered.filterString); + await this.logData.loadLogs(true, false, true, message.loadFiltered.filterString); this.addLogsToWebView(); } }); diff --git a/commands/azureCommands/acr-logs.ts b/commands/azureCommands/acr-logs.ts index e0362c8f17..ad0684f8fa 100644 --- a/commands/azureCommands/acr-logs.ts +++ b/commands/azureCommands/acr-logs.ts @@ -30,17 +30,17 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode // Filtering provided if (context && context instanceof AzureImageTagNode) { //ACR Image Logs - await logData.loadLogs(false, false, { image: context.label }); + await logData.loadLogs(false, false, false, { image: context.label }); if (!hasValidLogContent(context, logData)) { return; } const url = await logData.getLink(0); await accessLog(url, logData.logs[0].runId, false); } else { if (context && context instanceof TaskNode) { //ACR Task Logs - await logData.loadLogs(false, false, { task: context.label }); + await logData.loadLogs(false, false, false, { task: context.label }); } else { //ACR Registry Logs - await logData.loadLogs(false); + await logData.loadLogs(false, false); } if (!hasValidLogContent(context, logData)) { return; } let webViewTitle = registry.name; From 6d9d350095dde33014b905fcd8ac3c0335191312 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Wed, 7 Nov 2018 15:28:38 -0800 Subject: [PATCH 75/77] Third PR #506 review update. loadLogs parameters update. uploadSourceCode Improvements. --- .../acr-logs-utils/tableDataManager.ts | 37 ++++++++++--------- .../acr-logs-utils/tableViewManager.ts | 12 +++++- commands/azureCommands/acr-logs.ts | 21 +++++++++-- commands/azureCommands/quick-build.ts | 19 ++++------ commands/utils/quick-pick-image.ts | 6 +-- 5 files changed, 56 insertions(+), 39 deletions(-) diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts index 5d71f64579..354523d7b4 100644 --- a/commands/azureCommands/acr-logs-utils/tableDataManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -51,27 +51,28 @@ export class LogData { * the next page of logs will be saved and all preexisting data will be deleted. * @param filter Specifies a filter for log items, if run Id is specified this will take precedence */ - public async loadLogs(webViewEvent: boolean, loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { + public async loadLogs(options: { webViewEvent: boolean, loadNext: boolean, removeOld?: boolean, filter?: Filter }): Promise { let runListResult: RunListResult; - let options: { - filter?: string, - top?: number, - customHeaders?: { - [headerName: string]: string; - }; - } = {}; - if (filter && Object.keys(filter).length) { - if (!filter.runId) { - options.filter = await this.parseFilter(filter); - if (filter.image) { options.top = 1; } - runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); + + if (options.filter && Object.keys(options.filter).length) { + if (!options.filter.runId) { + let runOptions: { + filter?: string, + top?: number, + customHeaders?: { + [headerName: string]: string; + }; + } = {}; + runOptions.filter = await this.parseFilter(options.filter); + if (options.filter.image) { runOptions.top = 1; } + runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, runOptions); } else { runListResult = []; try { - runListResult.push(await this.client.runs.get(this.resourceGroup, this.registry.name, filter.runId)); + runListResult.push(await this.client.runs.get(this.resourceGroup, this.registry.name, options.filter.runId)); } catch (err) { const error = parseError(err); - if (!webViewEvent) { + if (!options.webViewEvent) { throw err; } else if (error.errorType !== "EntityNotFound") { vscode.window.showErrorMessage(`Error '${error.errorType}': ${error.message}`); @@ -79,10 +80,10 @@ export class LogData { } } } else { - if (loadNext) { + if (options.loadNext) { if (this.nextLink) { runListResult = await this.client.runs.listNext(this.nextLink); - } else if (webViewEvent) { + } else if (options.webViewEvent) { vscode.window.showErrorMessage("No more logs to show."); } else { throw new Error('No more logs to show'); @@ -91,7 +92,7 @@ export class LogData { runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name); } } - if (removeOld) { + if (options.removeOld) { //Clear Log Items this.logs = []; this.links = []; diff --git a/commands/azureCommands/acr-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts index 2a5664c3f6..6d8e6cec6b 100644 --- a/commands/azureCommands/acr-logs-utils/tableViewManager.ts +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -47,11 +47,19 @@ export class LogTableWebview { } else if (message.loadMore) { const alreadyLoaded = this.logData.logs.length; - await this.logData.loadLogs(true, true); + await this.logData.loadLogs({ + webViewEvent: true, + loadNext: true + }); this.addLogsToWebView(alreadyLoaded); } else if (message.loadFiltered) { - await this.logData.loadLogs(true, false, true, message.loadFiltered.filterString); + await this.logData.loadLogs({ + webViewEvent: true, + loadNext: false, + removeOld: true, + filter: message.loadFiltered.filterString + }); this.addLogsToWebView(); } }); diff --git a/commands/azureCommands/acr-logs.ts b/commands/azureCommands/acr-logs.ts index ad0684f8fa..ee09302b88 100644 --- a/commands/azureCommands/acr-logs.ts +++ b/commands/azureCommands/acr-logs.ts @@ -1,6 +1,6 @@ "use strict"; -import { Registry, Run } from "azure-arm-containerregistry/lib/models"; +import { Registry } from "azure-arm-containerregistry/lib/models"; import { Subscription } from "azure-arm-resource/lib/subscription/models"; import * as vscode from "vscode"; import { AzureImageTagNode, AzureRegistryNode } from '../../explorer/models/azureRegistryNodes'; @@ -30,17 +30,30 @@ export async function viewACRLogs(context: AzureRegistryNode | AzureImageTagNode // Filtering provided if (context && context instanceof AzureImageTagNode) { //ACR Image Logs - await logData.loadLogs(false, false, false, { image: context.label }); + await logData.loadLogs({ + webViewEvent: false, + loadNext: false, + removeOld: false, + filter: { image: context.label } + }); if (!hasValidLogContent(context, logData)) { return; } const url = await logData.getLink(0); await accessLog(url, logData.logs[0].runId, false); } else { if (context && context instanceof TaskNode) { //ACR Task Logs - await logData.loadLogs(false, false, false, { task: context.label }); + await logData.loadLogs({ + webViewEvent: false, + loadNext: false, + removeOld: false, + filter: { task: context.label } + }); } else { //ACR Registry Logs - await logData.loadLogs(false, false); + await logData.loadLogs({ + webViewEvent: false, + loadNext: false + }); } if (!hasValidLogContent(context, logData)) { return; } let webViewTitle = registry.name; diff --git a/commands/azureCommands/quick-build.ts b/commands/azureCommands/quick-build.ts index a69c5e59a1..58cebfcee0 100644 --- a/commands/azureCommands/quick-build.ts +++ b/commands/azureCommands/quick-build.ts @@ -29,25 +29,20 @@ export async function quickBuild(actionContext: IActionContext, dockerFileUri?: //Acquire information from user let rootFolder: vscode.WorkspaceFolder = await quickPickWorkspaceFolder("To quick build Docker files you must first open a folder or workspace in VS Code."); const dockerItem: Item = await quickPickDockerFileItem(actionContext, dockerFileUri, rootFolder); - const sourceLocation: string = rootFolder.uri.path; - const tarFilePath: string = getTempSourceArchivePath(); - const subscription: Subscription = await quickPickSubscription(); - - const client: ContainerRegistryManagementClient = await AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); const registry: Registry = await quickPickACRRegistry(true); - - const resourceGroupName: string = getResourceGroupName(registry); - const osPick = ['Linux', 'Windows'].map(item => >{ label: item, data: item }); const osType: string = (await ext.ui.showQuickPick(osPick, { 'canPickMany': false, 'placeHolder': 'Select image base OS' })).data; - const imageName: string = await quickPickImageName(actionContext, rootFolder, dockerItem); + const resourceGroupName: string = getResourceGroupName(registry); + const tarFilePath: string = getTempSourceArchivePath(); + const client: ContainerRegistryManagementClient = await AzureUtilityManager.getInstance().getContainerRegistryManagementClient(subscription); + //Begin readying build status.show(); - const uploadedSourceLocation: string = await uploadSourceCode(client, registry.name, resourceGroupName, sourceLocation, tarFilePath, rootFolder); + const uploadedSourceLocation: string = await uploadSourceCode(client, registry.name, resourceGroupName, rootFolder, tarFilePath); status.appendLine("Uploaded Source Code to " + tarFilePath); const runRequest: DockerBuildRequest = { @@ -66,9 +61,9 @@ export async function quickBuild(actionContext: IActionContext, dockerFileUri?: await streamLogs(registry, run, status, client); } -async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, sourceLocation: string, tarFilePath: string, folder: vscode.WorkspaceFolder): Promise { +async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.WorkspaceFolder, tarFilePath: string): Promise { status.appendLine(" Sending source code to temp file"); - let source: string = sourceLocation.substring(1); + let source: string = rootFolder.uri.fsPath; let current: string = process.cwd(); process.chdir(source); fse.readdir(source, (err, items) => { diff --git a/commands/utils/quick-pick-image.ts b/commands/utils/quick-pick-image.ts index 8abcaa4327..4ffd483bfa 100644 --- a/commands/utils/quick-pick-image.ts +++ b/commands/utils/quick-pick-image.ts @@ -104,15 +104,15 @@ export async function quickPickImageName(actionContext: IActionContext, rootFold await delay(500); addImageTaggingTelemetry(actionContext, suggestedImageName, '.before'); - const imageName: string = await getTagFromUserInput(suggestedImageName, !prevImageName); + const imageName: string = await getTagFromUserInput(suggestedImageName, false); addImageTaggingTelemetry(actionContext, imageName, '.after'); await ext.context.globalState.update(dockerFileKey, imageName); return imageName; } -export async function quickPickDockerFileItem(actionContext: IActionContext, dockerFileUri: vscode.Uri | undefined, rootFolder: vscode.WorkspaceFolder): Promise { - let dockerFileItem: Item | undefined; +export async function quickPickDockerFileItem(actionContext: IActionContext, dockerFileUri: vscode.Uri | undefined, rootFolder: vscode.WorkspaceFolder): Promise { + let dockerFileItem: Item; while (!dockerFileItem) { let resolvedItem: Item | undefined = await resolveDockerFileItem(rootFolder, dockerFileUri); From c3dea7d484c82ce4d9bcf7bbf1dec3b6b6c7a038 Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Thu, 8 Nov 2018 17:47:20 -0800 Subject: [PATCH 76/77] UploadSourceCode no longer has to change process working directory. --- commands/azureCommands/quick-build.ts | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/commands/azureCommands/quick-build.ts b/commands/azureCommands/quick-build.ts index 58cebfcee0..b0d1348d76 100644 --- a/commands/azureCommands/quick-build.ts +++ b/commands/azureCommands/quick-build.ts @@ -59,19 +59,16 @@ export async function quickBuild(actionContext: IActionContext, dockerFileUri?: status.appendLine("Scheduled Run " + run.runId); await streamLogs(registry, run, status, client); + fse.unlink(tarFilePath); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.WorkspaceFolder, tarFilePath: string): Promise { status.appendLine(" Sending source code to temp file"); let source: string = rootFolder.uri.fsPath; - let current: string = process.cwd(); - process.chdir(source); - fse.readdir(source, (err, items) => { - items = items.filter(i => !(i in vcsIgnoreList)); - // tslint:disable-next-line:no-unsafe-any - tar.c({}, items).pipe(fse.createWriteStream(tarFilePath)); - process.chdir(current); - }); + let items = await fse.readdir(source); + items = items.filter(i => !(i in vcsIgnoreList)); + // tslint:disable-next-line:no-unsafe-any + tar.c({ cwd: source }, items).pipe(fse.createWriteStream(tarFilePath)); status.appendLine(" Getting Build Source Upload Url "); let sourceUploadLocation: SourceUploadDefinition = await client.registries.getBuildSourceUploadUrl(resourceGroupName, registryName); @@ -85,6 +82,7 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); status.appendLine(" Creating Block Blob "); blob.createBlockBlobFromLocalFile(blobInfo.containerName, blobInfo.blobName, tarFilePath, (): void => { }); + return relative_path; } From ea6e7947dcf6f994386ac03c7d3e09abba1391db Mon Sep 17 00:00:00 2001 From: rosanch <43052640+rosanch@users.noreply.github.com> Date: Thu, 8 Nov 2018 18:08:21 -0800 Subject: [PATCH 77/77] lint fix --- commands/azureCommands/quick-build.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/commands/azureCommands/quick-build.ts b/commands/azureCommands/quick-build.ts index b0d1348d76..9e107db2c4 100644 --- a/commands/azureCommands/quick-build.ts +++ b/commands/azureCommands/quick-build.ts @@ -59,7 +59,7 @@ export async function quickBuild(actionContext: IActionContext, dockerFileUri?: status.appendLine("Scheduled Run " + run.runId); await streamLogs(registry, run, status, client); - fse.unlink(tarFilePath); + await fse.unlink(tarFilePath); } async function uploadSourceCode(client: ContainerRegistryManagementClient, registryName: string, resourceGroupName: string, rootFolder: vscode.WorkspaceFolder, tarFilePath: string): Promise { @@ -82,7 +82,6 @@ async function uploadSourceCode(client: ContainerRegistryManagementClient, regis let blob: BlobService = createBlobServiceWithSas(blobInfo.host, blobInfo.sasToken); status.appendLine(" Creating Block Blob "); blob.createBlockBlobFromLocalFile(blobInfo.containerName, blobInfo.blobName, tarFilePath, (): void => { }); - return relative_path; }

          Q`Nx|=SdABz#~1kfMQ^d=41P=Y^W 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 literal 0 HcmV?d00001 diff --git a/commands/azureCommands/acr-logs-utils/style/stylesheet.css b/commands/azureCommands/acr-logs-utils/style/stylesheet.css new file mode 100644 index 0000000000..112a760315 --- /dev/null +++ b/commands/azureCommands/acr-logs-utils/style/stylesheet.css @@ -0,0 +1,387 @@ +.accordion { + background-color: var(--vscode-editor-background); + color: var(--color); + cursor: pointer; + margin: 0px; + height: 30px; + width: 100%; + border: none; + text-align: left; + outline: none; + font-size: var(--vscode-editor-font-size); + font-family: var(--vscode-editor-font-family); + transition: 0.4s; + text-align: left; +} + +.accordion:hover { + cursor: pointer; + background-color: var(--vscode-list-hoverBackground); +} + +.accordion:focus { + background-color: var(--vscode-list-hoverBackground); +} + +.active { + background-color: var(--vscode-list-activeSelectionBackground); +} + +.active.accordion:focus, +.active.accordion:hover { + background-color: var(--vscode-list-activeSelectionBackground); +} + +.panel { + width: 100%; + display: none; + max-height: 0; + overflow: hidden; + transition: max-height 0.2s ease-out; +} + +table { + text-align: left; + border-collapse: collapse; + width: 100%; +} + +.widthControl { + box-sizing: border-box; + text-align: left; +} + +.solidBackground { + background-color: var(--vscode-editor-background); +} + +h2 { + padding-left: 10px; + font-size: var(--vscode-editor-font-size); + font-family: var(--vscode-editor-font-family); +} + +#core { + padding-top: 0.2cm; + table-layout: auto; + box-sizing: border-box; +} + +#core td, +#core th { + box-sizing: border-box; + padding-right: 0.35cm; + padding-left: 0.35cm; +} + +.colTitle { + cursor: pointer; + align-items: center; + display: flex; +} + +body { + padding: 0px; + width: 100%; + color: var(--color); +} + +.logConsole { + height: 100px; + overflow-y: auto; +} + +.innerTable td { + box-sizing: border-box; + border-bottom: 1px solid rgba(196, 196, 196, 0.2); + font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); +} + +.innerTable td.lastTd { + border-bottom: 0px; +} + +.innerTable td.arrowHolder { + border-bottom: 0px; + padding-right: 0.7cm; +} + +.innerTable td, +.innerTable th { + text-align: left; +} + +.button-holder { + box-sizing: border-box; + display: flex; + justify-content: center; + align-content: center; + align-items: center; + width: 100%; + padding-left: 0.7cm; +} + +.viewLog { + background-color: var(--vscode-button-background); + border: none; + color: var(--vscode-button-foreground); + padding: 5px 13px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: var(--vscode-editor-font-size); + cursor: pointer; + align-items: center; +} + +.loadMoreBtn { + display: none; + justify-content: center; + align-content: center; + align-items: center; + width: 100%; + margin-top: 1cm; + margin-bottom: 1cm; +} + +.viewLog:hover { + background-color: var(--vscode-button-hoverBackground); +} + +.arrow { + -webkit-transition: -webkit-transform .2s ease-in-out; + transition: transform .2s ease-in-out; +} + +.rotate180 { + transform: rotate(180deg); +} + +.activeArrow { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.paddingDiv { + display: flex; + width: 100%; + padding-top: 10px; + padding-bottom: 10px; +} + +.copy:hover { + cursor: pointer; +} + +.borderLimit { + padding-left: 40px; +} + +.sort { + display: flex; + align-items: center; +} + +#digestVisualizer { + position: absolute; + width: 1px; + visibility: hidden; +} + +.arrowHolder { + box-sizing: border-box; + width: 4.6%; + padding-right: 0.7cm; + text-align: center; +} + +.overflowX { + overflow-x: auto; +} + +.IconContainer { + font-size: 14px; + float: left; + margin: 0 5px 5px 0; + width: 50px; + height: 50px; + line-height: 51px; +} + +.IconContainer-icon { + text-align: center; +} + +.IconContainer-name, +.IconContainer-unicode { + display: none; +} + +.holder { + border-bottom: 1px solid rgba(196, 196, 196, 0.2); +} + +.textAlignRight { + text-align: right; +} + +p { + margin: 0px; +} + +.innerTable th { + border-bottom: 1px solid rgba(196, 196, 196, 0.5); +} + +.doubleLine { + border-bottom: 2px solid rgba(196, 196, 196, 0.5); +} + +main { + display: grid; + box-sizing: border-box; + margin-left: 10px; + margin-right: 10px; +} + +@media screen and (min-width: 1399px) { + main { + margin-left: 5%; + margin-right: 5% + } +} + +@media screen and (min-width: 1920px) { + main { + width: 1920px; + } +} + +#tableHead { + border-bottom: 1.1px solid var(--vscode-editor-foreground); + box-shadow: 0px 1px var(--vscode-editor-foreground); +} + +#tableHead tr { + height: 0.85cm; +} + +.dragLine { + position: absolute; + height: 80%; + width: 1px; + background-color: white; + background-clip: content-box; + padding-left: 0.35cm; + padding-right: 0.35cm; + left: 100%; + top: 10%; + cursor: w-resize; + z-index: 10; +} + +.dragWrapper { + position: relative; + height: 100%; + width: 100%; + display: flex; + justify-content: first baseline; +} + +.searchBoxes { + padding-top: 8px; + display: flex; + flex-direction: row; +} + +.searchBoxes .middle { + padding-right: 0.5%; + padding-left: 0.5%; + width: 34%; + box-sizing: border-box; +} + +.searchBoxes div { + padding-right: 0px; + width: 33%; + box-sizing: border-box; +} + +.searchBoxes div input { + padding: 5px; + border: 0px; + width: 100%; + box-sizing: border-box; + background-color: var(--vscode-list-hoverBackground); + color: var(--vscode-dropdown-foreground); +} + +.tooltip { + position: relative; +} + +.tooltip .tooltiptext { + box-sizing: border-box; + display: none; + background-color: var(--vscode-editor-foreground); + color: var(--vscode-activityBar-background); + text-align: center; + padding: 5px 16px; + position: absolute; + z-index: 1; + left: 50%; + bottom: 100%; + transform: translateX(-50%); + opacity: 0; + transition: opacity 0.5s; +} + +.tooltip .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: var(--vscode-editor-foreground) transparent transparent transparent; +} + +.tooltip:hover .tooltiptext { + display: inline; + opacity: 1; +} + +#loading { + display: inline-block; + border: 3px solid var(--vscode-editor-foreground); + border-radius: 50%; + border-top-color: var(--vscode-editor-background); + animation: spin 1s ease-in-out infinite; + -webkit-animation: spin 1s ease-in-out infinite; + height: calc(var(--vscode-editor-font-size)*1.5); + width: calc(var(--vscode-editor-font-size)*1.5); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@-webkit-keyframes spin { + to { + transform: rotate(360deg); + } +} + +#loadingDiv { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + width: 100%; + margin-top: 1cm; + margin-bottom: 1cm; +} diff --git a/commands/azureCommands/acr-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-logs-utils/tableDataManager.ts new file mode 100644 index 0000000000..cb3b235558 --- /dev/null +++ b/commands/azureCommands/acr-logs-utils/tableDataManager.ts @@ -0,0 +1,132 @@ +import ContainerRegistryManagementClient from "azure-arm-containerregistry"; +import { Registry, Run, RunGetLogResult, RunListResult } from "azure-arm-containerregistry/lib/models"; +import request = require('request-promise'); +import { registryRequest } from "../../../explorer/models/commonRegistryUtils"; +import { Manifest } from "../../../explorer/utils/dockerHubUtils"; +import { acquireACRAccessTokenFromRegistry } from "../../../utils/Azure/acrTools"; +/** Class to manage data and data acquisition for logs */ +export class LogData { + public registry: Registry; + public resourceGroup: string; + public links: { requesting: boolean, url?: string }[]; + public logs: Run[]; + public client: ContainerRegistryManagementClient; + private nextLink: string; + + constructor(client: ContainerRegistryManagementClient, registry: Registry, resourceGroup: string) { + this.registry = registry; + this.resourceGroup = resourceGroup; + this.client = client; + this.logs = []; + this.links = []; + } + /** Acquires Links from an item number corresponding to the index of the corresponding log, caches + * logs in order to avoid unecessary requests if opened multiple times. + */ + public async getLink(itemNumber: number): Promise { + if (itemNumber >= this.links.length) { + throw new Error('Log for which the link was requested has not been added'); + } + + if (this.links[itemNumber].url) { + return this.links[itemNumber].url; + } + + //If user is simply clicking many times impatiently it makes sense to only have one request at once + if (this.links[itemNumber].requesting) { return 'requesting' } + + this.links[itemNumber].requesting = true; + const temp: RunGetLogResult = await this.client.runs.getLogSasUrl(this.resourceGroup, this.registry.name, this.logs[itemNumber].runId); + this.links[itemNumber].url = temp.logLink; + this.links[itemNumber].requesting = false; + return this.links[itemNumber].url + } + + //contains(TaskName, 'testTask') + //`TaskName eq 'testTask' + // + /** Loads logs from azure + * @param loadNext Determines if the next page of logs should be loaded, will throw an error if there are no more logs to load + * @param removeOld Cleans preexisting information on links and logs imediately before new requests, if loadNext is specified + * the next page of logs will be saved and all preexisting data will be deleted. + * @param filter Specifies a filter for log items, if run Id is specified this will take precedence + */ + public async loadLogs(loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { + let runListResult: RunListResult; + let options: any = {}; + if (filter && Object.keys(filter).length) { + if (!filter.runId) { + options.filter = await this.parseFilter(filter); + options.top = 1; + runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name, options); + } else { + runListResult = []; + runListResult.push(await this.client.runs.get(this.resourceGroup, this.registry.name, filter.runId)); + } + } else { + if (loadNext) { + if (this.nextLink) { + runListResult = await this.client.runs.listNext(this.nextLink); + } else { + throw new Error('No more logs to show'); + } + } else { + runListResult = await this.client.runs.list(this.resourceGroup, this.registry.name); + } + } + if (removeOld) { this.clearLogItems() } + this.nextLink = runListResult.nextLink; + this.addLogs(runListResult); + } + + public addLogs(logs: Run[]): void { + this.logs = this.logs.concat(logs); + + const itemCount = logs.length; + for (let i = 0; i < itemCount; i++) { + this.links.push({ 'requesting': false }); + } + } + + public clearLogItems(): void { + this.logs = []; + this.links = []; + this.nextLink = ''; + } + + public hasNextPage(): boolean { + return this.nextLink !== undefined; + } + + private async parseFilter(filter: Filter): Promise { + let parsedFilter = ""; + if (filter.task) { //Task id + parsedFilter = `TaskName eq '${filter.task}'`; + } else if (filter.image) { //Image + let items: string[] = filter.image.split(':') + const { acrAccessToken } = await acquireACRAccessTokenFromRegistry(this.registry, 'repository:' + items[0] + ':pull'); + let digest; + await request.get('https://' + this.registry.loginServer + `/v2/${items[0]}/manifests/${items[1]}`, { + auth: { + bearer: acrAccessToken + }, + headers: { + accept: 'application/vnd.docker.distribution.manifest.v2+json; 0.5, application/vnd.docker.distribution.manifest.list.v2+json; 0.6' + } + }, (err, httpResponse, body) => { + digest = httpResponse.headers['docker-content-digest']; + }); + + //let manifest: any = await registryRequest(this.registry.loginServer, `v2/${items[0]}/manifests/${items[1]}`, { bearer: acrAccessToken }); + if (parsedFilter.length > 0) { parsedFilter += ' and '; } + parsedFilter += `contains(OutputImageManifests, '${items[0]}@${digest}')`; + } + return parsedFilter; + } +} + +export interface Filter { + image?: string; + runId?: string; + task?: string; +} diff --git a/commands/azureCommands/acr-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-logs-utils/tableViewManager.ts new file mode 100644 index 0000000000..1743d511e7 --- /dev/null +++ b/commands/azureCommands/acr-logs-utils/tableViewManager.ts @@ -0,0 +1,262 @@ + +import { ImageDescriptor, Run } from "azure-arm-containerregistry/lib/models"; +import * as clipboardy from 'clipboardy' +import * as path from 'path'; +import * as vscode from "vscode"; +import { accessLog, downloadLog } from './logFileManager'; +import { LogData } from './tableDataManager' +export class LogTableWebview { + private logData: LogData; + private panel: vscode.WebviewPanel; + + constructor(webviewName: string, logData: LogData) { + this.logData = logData; + this.panel = vscode.window.createWebviewPanel('log Viewer', webviewName, vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true }); + + //Get path to resource on disk + let extensionPath = vscode.extensions.getExtension("PeterJausovec.vscode-docker").extensionPath; + const scriptFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); + const resizingScript = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'resizable.js')).with({ scheme: 'vscode-resource' }); + const styleFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); + const iconStyle = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); + //Populate Webview + this.panel.webview.html = this.getBaseHtml(scriptFile, resizingScript, styleFile, iconStyle); + this.setupIncomingListeners(); + this.addLogsToWebView(); + } + //Post Opening communication from webview + /** Setup communication with the webview sorting out received mesages from its javascript file */ + private setupIncomingListeners(): void { + this.panel.webview.onDidReceiveMessage(async (message) => { + if (message.logRequest) { + const itemNumber: number = +message.logRequest.id; + this.logData.getLink(itemNumber).then((url) => { + if (url !== 'requesting') { + accessLog(url, this.logData.logs[itemNumber].runId, message.logRequest.download); + } + }); + + } else if (message.copyRequest) { + clipboardy.writeSync(message.copyRequest.text); + + } else if (message.loadMore) { + const alreadyLoaded = this.logData.logs.length; + await this.logData.loadLogs(true); + this.addLogsToWebView(alreadyLoaded); + + } else if (message.loadFiltered) { + await this.logData.loadLogs(false, true, message.loadFiltered.filterString); + this.addLogsToWebView(); + } + }); + } + + //Content Management + /** Communicates with the webview javascript file through post requests to populate the log table */ + private addLogsToWebView(startItem?: number): void { + const begin = startItem ? startItem : 0; + for (let i = begin; i < this.logData.logs.length; i++) { + const log = this.logData.logs[i]; + this.panel.webview.postMessage({ + 'type': 'populate', + 'id': i, + 'logComponent': this.getLogTableItem(log, i) + }); + } + if (startItem) { + this.panel.webview.postMessage({ 'type': 'endContinued', 'canLoadMore': this.logData.hasNextPage() }); + } else { + this.panel.webview.postMessage({ 'type': 'end', 'canLoadMore': this.logData.hasNextPage() }); + } + } + + private getImageOutputTable(log: Run): string { + let imageOutput: string = ''; + if (log.outputImages) { + //Adresses strange error where the image list can exist and contain only one null item. + if (!log.outputImages[0]) { + imageOutput += this.getImageItem(true); + } else { + for (let j = 0; j < log.outputImages.length; j++) { + let img = log.outputImages[j] + imageOutput += this.getImageItem(j === log.outputImages.length - 1, img); + } + } + } else { + imageOutput += this.getImageItem(true); + } + return imageOutput; + } + + //HTML Content Loaders + /** Create the table in which to push the logs */ + private getBaseHtml(scriptFile: vscode.Uri, resizingScript: vscode.Uri, stylesheet: vscode.Uri, iconStyles: vscode.Uri): string { + return ` + + + + + + + + Logs + + + +

          yW?rvBcoc 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

      Q`Nx|=SdABz#~1kfMQ^d=41P=Y^W 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 literal 0 HcmV?d00001 diff --git a/commands/azureCommands/acr-build-logs-utils/style/stylesheet.css b/commands/azureCommands/acr-build-logs-utils/style/stylesheet.css new file mode 100644 index 0000000000..112a760315 --- /dev/null +++ b/commands/azureCommands/acr-build-logs-utils/style/stylesheet.css @@ -0,0 +1,387 @@ +.accordion { + background-color: var(--vscode-editor-background); + color: var(--color); + cursor: pointer; + margin: 0px; + height: 30px; + width: 100%; + border: none; + text-align: left; + outline: none; + font-size: var(--vscode-editor-font-size); + font-family: var(--vscode-editor-font-family); + transition: 0.4s; + text-align: left; +} + +.accordion:hover { + cursor: pointer; + background-color: var(--vscode-list-hoverBackground); +} + +.accordion:focus { + background-color: var(--vscode-list-hoverBackground); +} + +.active { + background-color: var(--vscode-list-activeSelectionBackground); +} + +.active.accordion:focus, +.active.accordion:hover { + background-color: var(--vscode-list-activeSelectionBackground); +} + +.panel { + width: 100%; + display: none; + max-height: 0; + overflow: hidden; + transition: max-height 0.2s ease-out; +} + +table { + text-align: left; + border-collapse: collapse; + width: 100%; +} + +.widthControl { + box-sizing: border-box; + text-align: left; +} + +.solidBackground { + background-color: var(--vscode-editor-background); +} + +h2 { + padding-left: 10px; + font-size: var(--vscode-editor-font-size); + font-family: var(--vscode-editor-font-family); +} + +#core { + padding-top: 0.2cm; + table-layout: auto; + box-sizing: border-box; +} + +#core td, +#core th { + box-sizing: border-box; + padding-right: 0.35cm; + padding-left: 0.35cm; +} + +.colTitle { + cursor: pointer; + align-items: center; + display: flex; +} + +body { + padding: 0px; + width: 100%; + color: var(--color); +} + +.logConsole { + height: 100px; + overflow-y: auto; +} + +.innerTable td { + box-sizing: border-box; + border-bottom: 1px solid rgba(196, 196, 196, 0.2); + font-family: var(--vscode-editor-font-family); + font-size: var(--vscode-editor-font-size); +} + +.innerTable td.lastTd { + border-bottom: 0px; +} + +.innerTable td.arrowHolder { + border-bottom: 0px; + padding-right: 0.7cm; +} + +.innerTable td, +.innerTable th { + text-align: left; +} + +.button-holder { + box-sizing: border-box; + display: flex; + justify-content: center; + align-content: center; + align-items: center; + width: 100%; + padding-left: 0.7cm; +} + +.viewLog { + background-color: var(--vscode-button-background); + border: none; + color: var(--vscode-button-foreground); + padding: 5px 13px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: var(--vscode-editor-font-size); + cursor: pointer; + align-items: center; +} + +.loadMoreBtn { + display: none; + justify-content: center; + align-content: center; + align-items: center; + width: 100%; + margin-top: 1cm; + margin-bottom: 1cm; +} + +.viewLog:hover { + background-color: var(--vscode-button-hoverBackground); +} + +.arrow { + -webkit-transition: -webkit-transform .2s ease-in-out; + transition: transform .2s ease-in-out; +} + +.rotate180 { + transform: rotate(180deg); +} + +.activeArrow { + -webkit-transform: rotate(90deg); + transform: rotate(90deg); +} + +.paddingDiv { + display: flex; + width: 100%; + padding-top: 10px; + padding-bottom: 10px; +} + +.copy:hover { + cursor: pointer; +} + +.borderLimit { + padding-left: 40px; +} + +.sort { + display: flex; + align-items: center; +} + +#digestVisualizer { + position: absolute; + width: 1px; + visibility: hidden; +} + +.arrowHolder { + box-sizing: border-box; + width: 4.6%; + padding-right: 0.7cm; + text-align: center; +} + +.overflowX { + overflow-x: auto; +} + +.IconContainer { + font-size: 14px; + float: left; + margin: 0 5px 5px 0; + width: 50px; + height: 50px; + line-height: 51px; +} + +.IconContainer-icon { + text-align: center; +} + +.IconContainer-name, +.IconContainer-unicode { + display: none; +} + +.holder { + border-bottom: 1px solid rgba(196, 196, 196, 0.2); +} + +.textAlignRight { + text-align: right; +} + +p { + margin: 0px; +} + +.innerTable th { + border-bottom: 1px solid rgba(196, 196, 196, 0.5); +} + +.doubleLine { + border-bottom: 2px solid rgba(196, 196, 196, 0.5); +} + +main { + display: grid; + box-sizing: border-box; + margin-left: 10px; + margin-right: 10px; +} + +@media screen and (min-width: 1399px) { + main { + margin-left: 5%; + margin-right: 5% + } +} + +@media screen and (min-width: 1920px) { + main { + width: 1920px; + } +} + +#tableHead { + border-bottom: 1.1px solid var(--vscode-editor-foreground); + box-shadow: 0px 1px var(--vscode-editor-foreground); +} + +#tableHead tr { + height: 0.85cm; +} + +.dragLine { + position: absolute; + height: 80%; + width: 1px; + background-color: white; + background-clip: content-box; + padding-left: 0.35cm; + padding-right: 0.35cm; + left: 100%; + top: 10%; + cursor: w-resize; + z-index: 10; +} + +.dragWrapper { + position: relative; + height: 100%; + width: 100%; + display: flex; + justify-content: first baseline; +} + +.searchBoxes { + padding-top: 8px; + display: flex; + flex-direction: row; +} + +.searchBoxes .middle { + padding-right: 0.5%; + padding-left: 0.5%; + width: 34%; + box-sizing: border-box; +} + +.searchBoxes div { + padding-right: 0px; + width: 33%; + box-sizing: border-box; +} + +.searchBoxes div input { + padding: 5px; + border: 0px; + width: 100%; + box-sizing: border-box; + background-color: var(--vscode-list-hoverBackground); + color: var(--vscode-dropdown-foreground); +} + +.tooltip { + position: relative; +} + +.tooltip .tooltiptext { + box-sizing: border-box; + display: none; + background-color: var(--vscode-editor-foreground); + color: var(--vscode-activityBar-background); + text-align: center; + padding: 5px 16px; + position: absolute; + z-index: 1; + left: 50%; + bottom: 100%; + transform: translateX(-50%); + opacity: 0; + transition: opacity 0.5s; +} + +.tooltip .tooltiptext::after { + content: ""; + position: absolute; + top: 100%; + left: 50%; + margin-left: -5px; + border-width: 5px; + border-style: solid; + border-color: var(--vscode-editor-foreground) transparent transparent transparent; +} + +.tooltip:hover .tooltiptext { + display: inline; + opacity: 1; +} + +#loading { + display: inline-block; + border: 3px solid var(--vscode-editor-foreground); + border-radius: 50%; + border-top-color: var(--vscode-editor-background); + animation: spin 1s ease-in-out infinite; + -webkit-animation: spin 1s ease-in-out infinite; + height: calc(var(--vscode-editor-font-size)*1.5); + width: calc(var(--vscode-editor-font-size)*1.5); +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +@-webkit-keyframes spin { + to { + transform: rotate(360deg); + } +} + +#loadingDiv { + display: flex; + justify-content: center; + align-items: center; + text-align: center; + width: 100%; + margin-top: 1cm; + margin-bottom: 1cm; +} diff --git a/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts new file mode 100644 index 0000000000..c92cc052ff --- /dev/null +++ b/commands/azureCommands/acr-build-logs-utils/tableDataManager.ts @@ -0,0 +1,131 @@ +import ContainerRegistryManagementClient from "azure-arm-containerregistry"; +import { Build, BuildGetLogResult, BuildListResult, Registry } from "azure-arm-containerregistry/lib/models"; +import request = require('request-promise'); +import { registryRequest } from "../../../explorer/models/commonRegistryUtils"; +import { Manifest } from "../../../explorer/utils/dockerHubUtils"; +import { acquireACRAccessTokenFromRegistry } from "../../../utils/Azure/acrTools"; +/** Class to manage data and data acquisition for logs */ +export class LogData { + public registry: Registry; + public resourceGroup: string; + public links: { requesting: boolean, url?: string }[]; + public logs: Build[]; + public client: ContainerRegistryManagementClient; + private nextLink: string; + + constructor(client: ContainerRegistryManagementClient, registry: Registry, resourceGroup: string) { + this.registry = registry; + this.resourceGroup = resourceGroup; + this.client = client; + this.logs = []; + this.links = []; + } + /** Acquires Links from an item number corresponding to the index of the corresponding log, caches + * logs in order to avoid unecessary requests if opened multiple times. + */ + public async getLink(itemNumber: number): Promise { + if (itemNumber >= this.links.length) { + throw new Error('Log for which the link was requested has not been added'); + } + + if (this.links[itemNumber].url) { + return this.links[itemNumber].url; + } + + //If user is simply clicking many times impatiently it makes sense to only have one request at once + if (this.links[itemNumber].requesting) { return 'requesting' } + + this.links[itemNumber].requesting = true; + const temp: BuildGetLogResult = await this.client.builds.getLogLink(this.resourceGroup, this.registry.name, this.logs[itemNumber].buildId); + this.links[itemNumber].url = temp.logLink; + this.links[itemNumber].requesting = false; + return this.links[itemNumber].url + } + + //contains(BuildTaskName, 'testTask') + //`BuildTaskName eq 'testTask' + // + /** Loads logs from azure + * @param loadNext Determines if the next page of logs should be loaded, will throw an error if there are no more logs to load + * @param removeOld Cleans preexisting information on links and logs imediately before new requests, if loadNext is specified + * the next page of logs will be saved and all preexisting data will be deleted. + * @param filter Specifies a filter for log items, if build Id is specified this will take precedence + */ + public async loadLogs(loadNext: boolean, removeOld?: boolean, filter?: Filter): Promise { + let buildListResult: BuildListResult; + let options: any = {}; + if (filter && Object.keys(filter).length) { + if (!filter.buildId) { + options.filter = await this.parseFilter(filter); + buildListResult = await this.client.builds.list(this.resourceGroup, this.registry.name, options); + } else { + buildListResult = []; + buildListResult.push(await this.client.builds.get(this.resourceGroup, this.registry.name, filter.buildId)); + } + } else { + if (loadNext) { + if (this.nextLink) { + buildListResult = await this.client.builds.listNext(this.nextLink); + } else { + throw new Error('No more logs to show'); + } + } else { + buildListResult = await this.client.builds.list(this.resourceGroup, this.registry.name); + } + } + if (removeOld) { this.clearLogItems() } + this.nextLink = buildListResult.nextLink; + this.addLogs(buildListResult); + } + + public addLogs(logs: Build[]): void { + this.logs = this.logs.concat(logs); + + const itemCount = logs.length; + for (let i = 0; i < itemCount; i++) { + this.links.push({ 'requesting': false }); + } + } + + public clearLogItems(): void { + this.logs = []; + this.links = []; + this.nextLink = ''; + } + + public hasNextPage(): boolean { + return this.nextLink !== undefined; + } + + private async parseFilter(filter: Filter): Promise { + let parsedFilter = ""; + if (filter.buildTask) { // Build Task id + parsedFilter = `BuildTaskName eq '${filter.buildTask}'`; + } else if (filter.image) { //Image + let items: string[] = filter.image.split(':') + const { acrAccessToken } = await acquireACRAccessTokenFromRegistry(this.registry, 'repository:' + items[0] + ':pull'); + let digest; + await request.get('https://' + this.registry.loginServer + `/v2/${items[0]}/manifests/${items[1]}`, { + auth: { + bearer: acrAccessToken + }, + accept: { + application: 'vnd.docker.distribution.manifest.v2+json' + } + }, (err, httpResponse, body) => { + digest = httpResponse.headers['docker-content-digest']; + }); + + //let manifest: any = await registryRequest(this.registry.loginServer, `v2/${items[0]}/manifests/${items[1]}`, { bearer: acrAccessToken }); + if (parsedFilter.length > 0) { parsedFilter += ' and '; } + parsedFilter += `contains(OutputImageManifests, '${items[0]}@${digest}')`; + } + return parsedFilter; + } +} + +export interface Filter { + image?: string; + buildId?: string; + buildTask?: string; +} diff --git a/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts b/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts new file mode 100644 index 0000000000..22a9e6a4da --- /dev/null +++ b/commands/azureCommands/acr-build-logs-utils/tableViewManager.ts @@ -0,0 +1,262 @@ + +import { Build, ImageDescriptor } from "azure-arm-containerregistry/lib/models"; +import * as clipboardy from 'clipboardy' +import * as path from 'path'; +import * as vscode from "vscode"; +import { accessLog, downloadLog } from './logFileManager'; +import { LogData } from './tableDataManager' +export class LogTableWebview { + private logData: LogData; + private panel: vscode.WebviewPanel; + + constructor(webviewName: string, logData: LogData) { + this.logData = logData; + this.panel = vscode.window.createWebviewPanel('log Viewer', webviewName, vscode.ViewColumn.One, { enableScripts: true, retainContextWhenHidden: true }); + + //Get path to resource on disk + let extensionPath = vscode.extensions.getExtension("PeterJausovec.vscode-docker").extensionPath; + const scriptFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'logScripts.js')).with({ scheme: 'vscode-resource' }); + const resizingScript = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'resizable.js')).with({ scheme: 'vscode-resource' }); + const styleFile = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'style', 'stylesheet.css')).with({ scheme: 'vscode-resource' }); + const iconStyle = vscode.Uri.file(path.join(extensionPath, 'commands', 'azureCommands', 'acr-build-logs-utils', 'style', 'fabric-components', 'css', 'vscmdl2-icons.css')).with({ scheme: 'vscode-resource' }); + //Populate Webview + this.panel.webview.html = this.getBaseHtml(scriptFile, resizingScript, styleFile, iconStyle); + this.setupIncomingListeners(); + this.addLogsToWebView(); + } + //Post Opening communication from webview + /** Setup communication with the webview sorting out received mesages from its javascript file */ + private setupIncomingListeners(): void { + this.panel.webview.onDidReceiveMessage(async (message) => { + if (message.logRequest) { + const itemNumber: number = +message.logRequest.id; + this.logData.getLink(itemNumber).then((url) => { + if (url !== 'requesting') { + accessLog(url, this.logData.logs[itemNumber].buildId, message.logRequest.download); + } + }); + + } else if (message.copyRequest) { + clipboardy.writeSync(message.copyRequest.text); + + } else if (message.loadMore) { + const alreadyLoaded = this.logData.logs.length; + await this.logData.loadLogs(true); + this.addLogsToWebView(alreadyLoaded); + + } else if (message.loadFiltered) { + await this.logData.loadLogs(false, true, message.loadFiltered.filterString); + this.addLogsToWebView(); + } + }); + } + + //Content Management + /** Communicates with the webview javascript file through post requests to populate the log table */ + private addLogsToWebView(startItem?: number): void { + const begin = startItem ? startItem : 0; + for (let i = begin; i < this.logData.logs.length; i++) { + const log = this.logData.logs[i]; + this.panel.webview.postMessage({ + 'type': 'populate', + 'id': i, + 'logComponent': this.getLogTableItem(log, i) + }); + } + if (startItem) { + this.panel.webview.postMessage({ 'type': 'endContinued', 'canLoadMore': this.logData.hasNextPage() }); + } else { + this.panel.webview.postMessage({ 'type': 'end', 'canLoadMore': this.logData.hasNextPage() }); + } + } + + private getImageOutputTable(log: Build): string { + let imageOutput: string = ''; + if (log.outputImages) { + //Adresses strange error where the image list can exist and contain only one null item. + if (!log.outputImages[0]) { + imageOutput += this.getImageItem(true); + } else { + for (let j = 0; j < log.outputImages.length; j++) { + let img = log.outputImages[j] + imageOutput += this.getImageItem(j === log.outputImages.length - 1, img); + } + } + } else { + imageOutput += this.getImageItem(true); + } + return imageOutput; + } + + //HTML Content Loaders + /** Create the table in which to push the build logs */ + private getBaseHtml(scriptFile: vscode.Uri, resizingScript: vscode.Uri, stylesheet: vscode.Uri, iconStyles: vscode.Uri): string { + return ` + + + + + + + + Logs + + + +