import { ServiceClientCredentials, RequestPrepareOptions } from "ms-rest"; import MonitorManagementClient from "azure-arm-monitor"; import SubscriptionClient from "azure-asm-subscription/lib/subscriptionClient"; import ResourceManagementClient from "azure-arm-resource/lib/resource/resourceManagementClient"; import ManagementLockClient from "azure-arm-resource/lib/lock/managementLockClient"; import ComputeManagementClient from "azure-arm-compute/lib/computeManagementClient"; import GraphRbacManagementClient from "azure-graph"; import { Subscription } from "azure-asm-subscription/lib/models"; import { Metric } from "azure-arm-monitor/lib/models"; import { Application, User, ADGroup } from "azure-graph/lib/models"; import { GenericResource, Resource, ResourceGroup } from "azure-arm-resource/lib/resource/models"; import { ManagementLockObject } from "azure-arm-resource/lib/lock/models"; import { flatten, getSubscriptionId, memoize, unique } from "./utils"; import { duration } from "moment"; import { Usage } from "azure-arm-compute/lib/models"; import { loginWithServicePrincipalSecret } from "ms-rest-azure"; import { Config } from "./linter"; import { logger } from "./common/logger"; export interface Tenant { readonly objectId: string; readonly displayName: string; readonly domain: string; } export class Location implements Resource { readonly id: string; readonly name: string; readonly location: string; constructor(subscriptionId: string, location: string) { this.id = `/subscriptions/${subscriptionId}/locations/${location}`; this.name = location; this.location = location; } } export class UsageItem { readonly location: Location; readonly usage: Usage; constructor(location: Location, usage: Usage) { this.location = location; this.usage = usage; } } export interface Client { getTenant(): Promise; listSubscriptions(): Promise; listResourceGroups(subscriptionId?: string): Promise; listLocations(subscriptionId?: string): Promise; listResources(subscriptionId?: string): Promise; listApplications(): Promise; listUsers(): Promise; listGroups(): Promise; getGroup(objectId: string): Promise; listGroupMembers(objectId: string): Promise; listLocks(subscriptionId?: string): Promise; listUsages(subscriptionId?: string, location?: string): Promise; getMetrics( resource: GenericResource, timespan: string, interval: string, aggregation?: string ): Promise; } export async function createClient(config: Config): Promise { const credentials = await loginWithServicePrincipalSecret( config.appId, config.secret, config.tenant ); const graphCredentials = await loginWithServicePrincipalSecret( config.appId, config.secret, config.tenant, { tokenAudience: "graph" } ); const client = new DefaultClient( config.tenant, credentials, graphCredentials ); logger.trace("Client initialized!"); return memoize(client); } class DefaultClient implements Client { private readonly credentials: ServiceClientCredentials; private readonly subscriptionClient: SubscriptionClient; private readonly graphRbacManagementClient: GraphRbacManagementClient; constructor( tenantId: string, credentials: ServiceClientCredentials, graphCredentials: ServiceClientCredentials ) { this.credentials = credentials; this.subscriptionClient = new SubscriptionClient(credentials); this.graphRbacManagementClient = new GraphRbacManagementClient( graphCredentials, tenantId ); } async getTenant(): Promise { const req: any = { pathTemplate: "/{tenantID}/tenantDetails?api-version={apiVersion}", pathParameters: { tenantID: this.graphRbacManagementClient.tenantID, apiVersion: this.graphRbacManagementClient.apiVersion }, baseUrl: "https://graph.windows.net", method: "GET" }; const data: any = await this.graphRbacManagementClient.sendRequest(req); const tenant = data.value[0]; const defaultDomain = tenant.verifiedDomains.find( (domain: any) => domain.default === true ); return { objectId: tenant.objectId, displayName: tenant.displayName, domain: defaultDomain ? defaultDomain.name : "" }; } listSubscriptions(): Promise { log("Subscription"); return this.subscriptionClient.subscriptions.list(); } listResourceGroups(subscriptionId?: string): Promise { if (subscriptionId) { log("ResourceGroup", subscriptionId); const client = this.getResourceManagementClient(subscriptionId); return client.resourceGroups.list(); } else { return this.forEachSubscription(this.listResourceGroups); } } listLocations(subscriptionId?: string) { return this.listResourceGroups(subscriptionId).then(resourceGroups => unique(resourceGroups.map(rg => rg.location)) ); } async listResources(subscriptionId?: string): Promise { if (subscriptionId) { log("Resource", subscriptionId); const client = this.getResourceManagementClient(subscriptionId); return client.resources.list(); } else { return this.forEachSubscription(this.listResources); } } listApplications(): Promise { log("Application"); return this.graphRbacManagementClient.applications.list(); } listUsers(): Promise { log("User"); return this.graphRbacManagementClient.users.list(); } listGroups(): Promise { log("Group"); return this.graphRbacManagementClient.groups.list(); } getGroup(objectId: string): Promise { logger.trace("Getting Group:", objectId); return this.graphRbacManagementClient.groups.get(objectId); } listGroupMembers(objectId: string): Promise { log("GroupMember", "", `objectId:${objectId}`); return this.graphRbacManagementClient.groups.getGroupMembers(objectId); } async listLocks(subscriptionId?: string): Promise { if (subscriptionId) { log("Lock", subscriptionId); const client = new ManagementLockClient(this.credentials, subscriptionId); return client.managementLocks.listAtSubscriptionLevel(); } else { return this.forEachSubscription(this.listLocks); } } listUsages(subscriptionId?: string, location?: string): Promise { const listUsageItems = (subscriptionId: string, location: string) => { log("Usage", subscriptionId, `location:${location}`); const client = this.getComputeManagementClient(subscriptionId); return client.usageOperations .list(location) .then(usages => usages.map( usage => new UsageItem(new Location(subscriptionId, location), usage) ) ); }; if (subscriptionId) { if (location) { return listUsageItems(subscriptionId, location); } else { return this.listLocations(subscriptionId).then(locations => Promise.all( locations.map(location => listUsageItems(subscriptionId, location)) ).then(flatten) ); } } else { return this.forEachSubscription(this.listUsages); } } async getMetrics( resource: GenericResource, timespan: string = "P7D", interval: string = "24:00:00", aggregation: string = "Average" ): Promise { const subscriptionId = getSubscriptionId(resource); log("Metrics", subscriptionId, `resource:${resource.name}`); const client = new MonitorManagementClient( this.credentials, subscriptionId ); const options = { timespan, interval: duration(interval), aggregation }; return client.metrics .list(resource.id!, options) .then(response => response.value); } private async forEachSubscription( handler: (subscriptionId: string) => Promise ): Promise { return this.listSubscriptions() .then(subscriptions => Promise.all( subscriptions.map(subscription => handler(subscription.subscriptionId!!) ) ) ) .then(flatten); } private getResourceManagementClient(subscriptionId: string) { return new ResourceManagementClient(this.credentials, subscriptionId); } private getComputeManagementClient(subscriptionId: string) { return new ComputeManagementClient(this.credentials, subscriptionId); } } function log(object: string, subscriptionId?: string, ...args: string[]) { logger.trace( "Listing", object, subscriptionId ? `subscriptionId:${subscriptionId}` : "", ...args ); }