Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Estebanreyl/admin enabled fixes #341

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion dockerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
import * as opn from 'opn';
import * as path from 'path';
import * as vscode from 'vscode';
import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExtensionVariables } from 'vscode-azureextensionui';
import { AzureUserInput, createTelemetryReporter, registerCommand, registerUIExtensionVariables, UserCancelledError } from 'vscode-azureextensionui';
import { ConfigurationParams, DidChangeConfigurationNotification, DocumentSelector, LanguageClient, LanguageClientOptions, Middleware, ServerOptions, TransportKind } from 'vscode-languageclient';
import { buildImage } from './commands/build-image';
import { composeDown, composeRestart, composeUp } from './commands/docker-compose';
Expand Down Expand Up @@ -75,6 +75,7 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
ext.ui = new AzureUserInput(ctx.globalState);
}
ext.context = ctx;
ext.outputChannel = outputChannel;
if (!ext.terminalProvider) {
ext.terminalProvider = new DefaultTerminalProvider();
}
Expand Down Expand Up @@ -132,6 +133,11 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {
const azureAccountWrapper = new AzureAccountWrapper(ctx, azureAccount);
const wizard = new WebAppCreator(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);
Expand Down
49 changes: 41 additions & 8 deletions explorer/deploy/webAppCreator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/

import { ContainerRegistryManagementClient } from 'azure-arm-containerregistry';
import { Registry } from 'azure-arm-containerregistry/lib/models';
import { ResourceManagementClient, ResourceModels, SubscriptionModels } from 'azure-arm-resource';
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';
Expand Down Expand Up @@ -172,9 +173,18 @@ class ResourceGroupStep extends WebAppCreatorStepBase {
resourceGroups = results[0];
locations = results[1];
resourceGroups.forEach(rg => {
const location: SubscriptionModels.Location = locations.find(l => l.name.toLowerCase() === rg.location.toLowerCase());
let locationDisplayName: string;

if (location) {
locationDisplayName = location.displayName;
} else {
locationDisplayName = rg.location;
}

quickPickItems.push({
label: rg.name,
description: `(${locations.find(l => l.name.toLowerCase() === rg.location.toLowerCase()).displayName})`,
description: locationDisplayName,
detail: '',
data: rg
});
Expand Down Expand Up @@ -408,13 +418,23 @@ class WebsiteStep extends WebAppCreatorStepBase {
private _serverUserName: string;
private _serverPassword: string;
private _imageName: string;
private _imageSubscription: Subscription;
private _registry: Registry;

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

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

this._imageName = context.label;

}
Expand Down Expand Up @@ -447,7 +467,7 @@ class WebsiteStep extends WebAppCreatorStepBase {
await vscode.window.showWarningMessage(nameAvailability.message);
}
}

await this.acquireRegistryLoginCredentials();
let linuxFXVersion: string;
if (this._serverUrl.length > 0) {
// azure container registry
Expand Down Expand Up @@ -476,7 +496,6 @@ class WebsiteStep extends WebAppCreatorStepBase {
const subscription = this.getSelectedSubscription();
const rg = this.getSelectedResourceGroup();
const websiteClient = new WebSiteManagementClient(this.azureAccount.getCredentialByTenantId(subscription.tenantId), subscription.subscriptionId);

// If the plan is also newly created, its resource ID won't be available at this step's prompt stage, but should be available now.
if (!this._website.serverFarmId) {
this._website.serverFarmId = this.getSelectedAppServicePlan().id;
Expand Down Expand Up @@ -505,7 +524,6 @@ class WebsiteStep extends WebAppCreatorStepBase {
"DOCKER_REGISTRY_SERVER_PASSWORD": this._serverPassword
}
};

}

await websiteClient.webApps.updateApplicationSettings(rg.name, this._website.name, appSettings);
Expand Down Expand Up @@ -543,4 +561,19 @@ class WebsiteStep extends WebAppCreatorStepBase {
}
}

//Implements new Service principal model for ACR container registries while maintaining old admin enabled use
private async acquireRegistryLoginCredentials(): Promise<void> {
if (this._serverPassword && this._serverUserName) { return; }

if (this._registry.adminUserEnabled) {
const client = new ContainerRegistryManagementClient(this.azureAccount.getCredentialByTenantId(this._imageSubscription.tenantId), this._imageSubscription.subscriptionId);
const resourceGroup: string = this._registry.id.slice(this._registry.id.search('resourceGroups/') + 'resourceGroups/'.length, this._registry.id.search('/providers/'));
let creds = await client.registries.listCredentials(resourceGroup, this._registry.name);
this._serverPassword = creds.passwords[0].value;
this._serverUserName = creds.username;
} else {
throw new Error('Azure App service currently only supports running images from Azure Container Registries with admin enabled');
}
}

}
50 changes: 24 additions & 26 deletions explorer/models/azureRegistryNodes.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import * as ContainerModels from 'azure-arm-containerregistry/lib/models';
import { 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 { parseError } from 'vscode-azureextensionui';
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'
Expand All @@ -23,11 +24,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 {
Expand Down Expand Up @@ -96,12 +95,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);
}
}
Expand All @@ -127,12 +124,10 @@ export class AzureRepositoryNode extends NodeBase {

public accessTokenARC: string;
public azureAccount: AzureAccount
public password: string;
public refreshTokenARC: string;
public registry: ContainerModels.Registry;
public repository: string;
public subscription: SubscriptionModels.Subscription;
public userName: string;

public getTreeItem(): vscode.TreeItem {
return {
Expand Down Expand Up @@ -202,23 +197,28 @@ export class AzureRepositoryNode extends NodeBase {
// tslint:disable-next-line:prefer-for-of // Grandfathered in
for (let i = 0; i < tags.length; i++) {
pool.addTask(async () => {
let data = await request.get('https://' + element.repository + '/v2/' + element.label + `/manifests/${tags[i]}`, {
auth: {
bearer: accessTokenARC
}
});

//Acquires each image's manifest to acquire build time.
let manifest = JSON.parse(data);
node = new AzureImageNode(`${element.label}:${tags[i]}`, 'azureImageNode');
node.azureAccount = element.azureAccount;
node.password = element.password;
node.registry = element.registry;
node.serverUrl = element.repository;
node.subscription = element.subscription;
node.userName = element.userName;
node.created = moment(new Date(JSON.parse(manifest.history[0].v1Compatibility).created)).fromNow();
imageNodes.push(node);
let data: string;
try {
data = await request.get('https://' + element.repository + '/v2/' + element.label + `/manifests/${tags[i]}`, {
auth: {
bearer: accessTokenARC
}
});
} catch (error) {
vscode.window.showErrorMessage(parseError(error).message);
}

if (data) {
//Acquires each image's manifest to acquire build time.
let manifest = JSON.parse(data);
node = new AzureImageNode(`${element.label}:${tags[i]}`, 'azureImageNode');
node.azureAccount = element.azureAccount;
node.registry = element.registry;
node.serverUrl = element.repository;
node.subscription = element.subscription;
node.created = moment(new Date(JSON.parse(manifest.history[0].v1Compatibility).created)).fromNow();
imageNodes.push(node);
}
});
}
await pool.runAll();
Expand All @@ -242,11 +242,9 @@ export class AzureImageNode extends NodeBase {

public azureAccount: AzureAccount
public created: string;
public password: string;
public registry: ContainerModels.Registry;
public serverUrl: string;
public subscription: SubscriptionModels.Subscription;
public userName: string;

public getTreeItem(): vscode.TreeItem {
let displayName: string = this.label;
Expand Down
28 changes: 14 additions & 14 deletions explorer/models/registryRootNode.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,13 @@
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 { parseError } from 'vscode-azureextensionui';
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'
Expand Down Expand Up @@ -133,43 +134,42 @@ export class RegistryRootNode extends NodeBase {
const subs: SubscriptionModels.Subscription[] = this.getFilteredSubscriptions();

const subPool = new AsyncPool(MAX_CONCURRENT_SUBSCRIPTON_REQUESTS);
let subsAndRegistries: { 'subscription': SubscriptionModels.Subscription, 'registries': ContainerModels.RegistryListResult, 'client': any }[] = [];
let subsAndRegistries: { 'subscription': SubscriptionModels.Subscription, 'registries': ContainerModels.RegistryListResult }[] = [];
//Acquire each subscription's data simultaneously
// 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 {
let regs: ContainerModels.Registry[] = await client.registries.list();
subsAndRegistries.push({
'subscription': subs[i],
'registries': regs
});
} catch (error) {
vscode.window.showErrorMessage(parseError(error).message);
}
});
}
await subPool.runAll();

const regPool = new AsyncPool(MAX_CONCURRENT_REQUESTS);
// tslint:disable-next-line:prefer-for-of // Grandfathered in
for (let i = 0; i < subsAndRegistries.length; i++) {
const client = subsAndRegistries[i].client;
const registries = subsAndRegistries[i].registries;
const subscription = subsAndRegistries[i].subscription;

//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/'));
if (!registries[j].sku.tier.includes('Classic')) {
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')
};
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);
Expand Down