Skip to content

Commit

Permalink
Rutusamai/list build tasks for each registry (#37)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rsamai authored Aug 7, 2018
1 parent 05df67c commit 126c01e
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 61 deletions.
2 changes: 1 addition & 1 deletion commands/azureCommands/create-registry.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export async function createRegistry(): Promise<void> {

// INPUT HELPERS
async function acquireSKU(): Promise<string> {
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'); }
Expand Down
37 changes: 23 additions & 14 deletions explorer/models/azureRegistryNodes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -38,9 +39,17 @@ export class AzureRegistryNode extends NodeBase {
}
}

public async getChildren(element: AzureRegistryNode): Promise<AzureRepositoryNode[]> {
const repoNodes: AzureRepositoryNode[] = [];
let node: AzureRepositoryNode;
public async getChildren(element: AzureRegistryNode): Promise<NodeBase[]> {
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) {
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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;
}
}

Expand All @@ -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')
}
Expand Down Expand Up @@ -199,18 +209,17 @@ 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
}
});

//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;
Expand Down Expand Up @@ -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 {
Expand Down
5 changes: 4 additions & 1 deletion explorer/models/registryRootNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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 = {
Expand Down
76 changes: 33 additions & 43 deletions explorer/models/rootNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,6 @@ export class RootNode extends NodeBase {
// clearInterval(this._imageDebounceTimer);
// return;
// }

clearInterval(this._imageDebounceTimer);

if (refreshInterval > 0) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -108,18 +103,21 @@ export class RootNode extends NodeBase {

}

public async getChildren(element: NodeBase): Promise<NodeBase[]> {

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<NodeBase[]> {
switch (element.contextValue) {
case 'imagesRootNode': {
return this.getImages();
}
case 'containersRootNode': {
return this.getContainers();
}
case 'registriesRootNode': {
return this.getRegistries();
}
default: {
break;
}
}

}

private async getImages(): Promise<ImageNode[]> {
Expand All @@ -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(`<none>:<none>`, "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);
}
}
Expand All @@ -168,7 +162,6 @@ export class RootNode extends NodeBase {
// clearInterval(this._containerDebounceTimer);
// return;
// }

clearInterval(this._containerDebounceTimer);

if (refreshInterval > 0) {
Expand All @@ -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;
}
Expand Down Expand Up @@ -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'),
Expand All @@ -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);
}

Expand Down
67 changes: 67 additions & 0 deletions explorer/models/taskNode.ts
Original file line number Diff line number Diff line change
@@ -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<BuildTaskNode[]> {
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);
}
}
1 change: 1 addition & 0 deletions images/dark/buildTasks_dark.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 126c01e

Please sign in to comment.