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

Added Azure Credentials Manager Singleton #18

Merged
merged 3 commits into from
Jul 25, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions dockerExtension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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}';
Expand Down Expand Up @@ -142,6 +143,10 @@ export async function activate(ctx: vscode.ExtensionContext): Promise<void> {

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

if (azureAccount) {
AzureCredentialsManager.getInstance().setAccount(azureAccount);
}

activateLanguageClient(ctx);
}

Expand Down
140 changes: 140 additions & 0 deletions utils/azureCredentialsManager.ts
Original file line number Diff line number Diff line change
@@ -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();

Choose a reason for hiding this comment

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

Do no initialize here, defeats the purpose of lazy initialization.

Copy link
Author

Choose a reason for hiding this comment

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

Oh that's my bad, I thought Id gotten that fixed.

private azureAccount: AzureAccount;

private constructor() {
AzureCredentialsManager._instance = this;

Choose a reason for hiding this comment

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

You can remove this: unreachable code.

Copy link
Author

Choose a reason for hiding this comment

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

Would I just maintain an empty private constructor then?

Choose a reason for hiding this comment

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

Yes, making the constructor private ensures this class cannot be instantiated. Hence, the only way is to use static object creation getInstance where a singleton instance is created only the first time its called. Singleton is just a fancy way of using a global shared object.

}

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<SubscriptionModels.Subscription>(filter => {
return {
id: filter.subscription.id,
session: filter.session,
subscriptionId: filter.subscription.subscriptionId,
tenantId: filter.session.tenantId,
displayName: filter.subscription.displayName,
state: filter.subscription.state,
subscriptionPolicies: filter.subscription.subscriptionPolicies,
authorizationSource: filter.subscription.authorizationSource
};
});
}

public getContainerRegistryManagementClient(subscription: SubscriptionModels.Subscription): ContainerRegistryManagementClient {

Choose a reason for hiding this comment

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

Looks like this method will be used internally and can be made private?

Copy link
Author

Choose a reason for hiding this comment

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

That method is meant to be for external use, maybe providing a different name/ providing additional filtering and labeling it getSubscriptions where this particular filtering is default would be better?

Choose a reason for hiding this comment

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

You can keep it public if intended for external use. I think naming is fine the way it is and can be modified in the future if needed.

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<ContainerModels.Registry[]> {
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<ResourceGroup[]> {
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<boolean> {
if (!this.azureAccount) {
return false;
}
return await this.azureAccount.waitForLogin();
}
}