Add codeLens to Update ASA job info (#492)
* Add codeLens to Update ASA job info * Disable command palette, and add module name in progress tips * Move ASA module update logic to streamAnalyticsManager, update Codelens pattern, remove unnecessary command in package.json * Use api-version=2019-06-01 * Use singleton for streamAnalyticsManager * Remove unnecessary code, and reinstall packages to update package-lock.json * rename queryASAJobInfo to publishAndQueryASAJobInfo, fix ci failed issue
This commit is contained in:
Родитель
b249394625
Коммит
94068b777b
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -455,7 +455,7 @@
|
|||
"@types/node": "^6.0.40",
|
||||
"@types/request-promise": "^4.1.41",
|
||||
"@types/semver": "^5.5.0",
|
||||
"@types/sinon": "^5.0.5",
|
||||
"@types/sinon": "^7.0.13",
|
||||
"@types/strip-json-comments": "0.0.30",
|
||||
"@types/tmp": "0.0.33",
|
||||
"azure-arm-resource": "^3.1.1-preview",
|
||||
|
@ -475,19 +475,20 @@
|
|||
"azure-arm-containerregistry": "^2.2.0",
|
||||
"azure-arm-machinelearningservices": "^1.0.0-preview",
|
||||
"azure-arm-streamanalytics": "^1.0.0-preview",
|
||||
"body-parser": "^1.18.2",
|
||||
"dotenv": "^5.0.1",
|
||||
"download-git-repo": "^1.0.2",
|
||||
"express": "^4.16.3",
|
||||
"fs-extra": "^4.0.2",
|
||||
"is-port-reachable": "^2.0.0",
|
||||
"json-source-map": "^0.6.1",
|
||||
"jsonc-parser": "^1.0.1",
|
||||
"request": "^2.88.0",
|
||||
"request-promise": "^4.2.2",
|
||||
"semver": "^5.6.0",
|
||||
"strip-json-comments": "^2.0.1",
|
||||
"tmp": "0.0.33",
|
||||
"vscode-extension-telemetry": "0.0.18",
|
||||
"body-parser": "^1.18.2",
|
||||
"express": "^4.16.3"
|
||||
"vscode-extension-telemetry": "0.0.18"
|
||||
},
|
||||
"extensionDependencies": [
|
||||
"ms-vscode.azure-account",
|
||||
|
|
|
@ -187,6 +187,14 @@ export class Constants {
|
|||
public static extModuleKeyPrefixTemplate(dir: string): string {
|
||||
return `MODULEDIR<${dir}>`;
|
||||
}
|
||||
|
||||
public static newASAJobAvailableMsg(asaModuleName: string): string {
|
||||
return `Configurations of Stream Analytics Job "${asaModuleName}" have been changed, do you want to update them now?`;
|
||||
}
|
||||
|
||||
public static noNewASAJobFoundMsg(asaModuleName: string): string {
|
||||
return `No configuration changes has been found for Stream Analytics Job: "${asaModuleName}".`;
|
||||
}
|
||||
}
|
||||
|
||||
export enum ContainerState {
|
||||
|
|
|
@ -4,40 +4,128 @@
|
|||
import StreamAnalyticsManagementClient = require("azure-arm-streamanalytics");
|
||||
import { StreamingJob } from "azure-arm-streamanalytics/lib/models";
|
||||
import { StreamingJobs } from "azure-arm-streamanalytics/lib/operations";
|
||||
import * as fse from "fs-extra";
|
||||
import * as request from "request-promise";
|
||||
import * as vscode from "vscode";
|
||||
import { Constants } from "../common/constants";
|
||||
import { UserCancelledError } from "../common/UserCancelledError";
|
||||
import { Utility } from "../common/utility";
|
||||
import { AzureAccount } from "../typings/azure-account.api";
|
||||
import { AzureAccount, AzureSession, AzureSubscription } from "../typings/azure-account.api";
|
||||
import { StreamAnalyticsPickItem } from "./models/streamAnalyticsPickItem";
|
||||
|
||||
export enum ASAUpdateStatus {
|
||||
Idle,
|
||||
CheckingUpdate,
|
||||
Updating,
|
||||
}
|
||||
|
||||
export class StreamAnalyticsManager {
|
||||
public static getInstance(): StreamAnalyticsManager {
|
||||
if (!StreamAnalyticsManager.instance) {
|
||||
StreamAnalyticsManager.instance = new StreamAnalyticsManager();
|
||||
}
|
||||
return StreamAnalyticsManager.instance;
|
||||
}
|
||||
|
||||
private static instance: StreamAnalyticsManager;
|
||||
private readonly azureAccount: AzureAccount;
|
||||
private readonly MaximumRetryCount: number = 3;
|
||||
private asaUpdateStatus: ASAUpdateStatus;
|
||||
|
||||
constructor() {
|
||||
private constructor() {
|
||||
this.azureAccount = vscode.extensions.getExtension<AzureAccount>("ms-vscode.azure-account")!.exports;
|
||||
this.asaUpdateStatus = ASAUpdateStatus.Idle;
|
||||
}
|
||||
|
||||
public async selectStreamingJob(): Promise<StreamAnalyticsPickItem> {
|
||||
public async getJobInfo(): Promise<any> {
|
||||
await Utility.waitForAzLogin(this.azureAccount);
|
||||
|
||||
const job: StreamAnalyticsPickItem = await vscode.window.showQuickPick(this.loadAllStreamingJobs(), { placeHolder: `Select ${Constants.asaJobDesc}`, ignoreFocusOut: true });
|
||||
if (!job) {
|
||||
const streamingJob: StreamAnalyticsPickItem = await vscode.window.showQuickPick(this.loadAllStreamingJobs(), { placeHolder: `Select ${Constants.asaJobDesc}`, ignoreFocusOut: true });
|
||||
if (!streamingJob) {
|
||||
throw new UserCancelledError();
|
||||
}
|
||||
return job;
|
||||
|
||||
return await vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "Querying Stream Analytics Job information",
|
||||
}, async (): Promise<object> => {
|
||||
const ASAJobResourceId: string = streamingJob.job.id;
|
||||
return await this.publishAndQueryASAJobInfo(ASAJobResourceId, streamingJob.azureSubscription.session);
|
||||
});
|
||||
}
|
||||
|
||||
public async getJobInfo(streamingJob: StreamAnalyticsPickItem): Promise<object> {
|
||||
public async checkAndUpdateASAJob(templateFile: string, moduleName: string) {
|
||||
if (this.asaUpdateStatus === ASAUpdateStatus.Idle) {
|
||||
await Utility.waitForAzLogin(this.azureAccount);
|
||||
try {
|
||||
this.asaUpdateStatus = ASAUpdateStatus.CheckingUpdate;
|
||||
const isUpdateAvailable = await this.isASAJobUpdateAvailable(templateFile, moduleName);
|
||||
this.asaUpdateStatus = ASAUpdateStatus.Idle;
|
||||
if (isUpdateAvailable) {
|
||||
const yesOption = "Yes";
|
||||
const option = await vscode.window.showInformationMessage(Constants.newASAJobAvailableMsg(moduleName), yesOption);
|
||||
if (option === yesOption) {
|
||||
this.asaUpdateStatus = ASAUpdateStatus.Updating;
|
||||
await this.updateASAJobInfoModuleTwin(templateFile, moduleName);
|
||||
this.asaUpdateStatus = ASAUpdateStatus.Idle;
|
||||
}
|
||||
} else {
|
||||
await vscode.window.showInformationMessage(Constants.noNewASAJobFoundMsg(moduleName));
|
||||
}
|
||||
} catch (err) {
|
||||
this.asaUpdateStatus = ASAUpdateStatus.Idle;
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async updateJobInfo(templateFile: string, moduleName: string): Promise<any> {
|
||||
return await vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "Updating Stream Analytics Job information",
|
||||
}, async (): Promise<object> => {
|
||||
const ASAInfo = await this.getJobInfoFromDeploymentTemplate(templateFile, moduleName);
|
||||
const subscription = await this.getJobSubscription(ASAInfo);
|
||||
const ASAJobResourceId: string = ASAInfo.ASAJobResourceId;
|
||||
return await this.publishAndQueryASAJobInfo(ASAJobResourceId, subscription.session);
|
||||
});
|
||||
}
|
||||
|
||||
private async isASAJobUpdateAvailable(templateFile: string, moduleName: string): Promise<boolean> {
|
||||
return await vscode.window.withProgress({
|
||||
location: vscode.ProgressLocation.Notification,
|
||||
title: "Checking configurations update for Stream Analytics Job",
|
||||
}, async (): Promise<boolean> => {
|
||||
const ASAInfo = await this.getJobInfoFromDeploymentTemplate(templateFile, moduleName);
|
||||
const GetASAJobApiUrl: string = `https://management.azure.com${ASAInfo.ASAJobResourceId}?api-version=2019-06-01`;
|
||||
const curEtag = ASAInfo.ASAJobEtag;
|
||||
const subscription = await this.getJobSubscription(ASAInfo);
|
||||
const { aadAccessToken } = await Utility.acquireAadToken(subscription.session);
|
||||
const jobInfo = await request.get(GetASAJobApiUrl, {
|
||||
auth: {
|
||||
bearer: aadAccessToken,
|
||||
},
|
||||
resolveWithFullResponse: true,
|
||||
});
|
||||
|
||||
const latestETag = jobInfo.headers.etag;
|
||||
return latestETag !== curEtag;
|
||||
});
|
||||
}
|
||||
|
||||
private async updateASAJobInfoModuleTwin(templateFile: string, moduleName: string) {
|
||||
const jobInfo = await this.updateJobInfo(templateFile, moduleName);
|
||||
const moduleTwin = jobInfo.twin.content;
|
||||
const templateJson = await fse.readJson(templateFile);
|
||||
templateJson.modulesContent[moduleName] = moduleTwin;
|
||||
await fse.writeFile(templateFile, JSON.stringify(templateJson, null, 2), { encoding: "utf8" });
|
||||
}
|
||||
|
||||
private async publishAndQueryASAJobInfo(resourceId: string, session: AzureSession) {
|
||||
try {
|
||||
const id: string = streamingJob.job.id;
|
||||
const publishJobUrl: string = `https://management.azure.com${id}/publishedgepackage?api-version=2017-04-01-preview`;
|
||||
const apiUrl: string = `https://management.azure.com${resourceId}/publishedgepackage?api-version=2019-06-01`;
|
||||
const { aadAccessToken } = await Utility.acquireAadToken(session);
|
||||
|
||||
const { aadAccessToken } = await Utility.acquireAadToken(streamingJob.azureSubscription.session);
|
||||
|
||||
const publishResponse = await request.post(publishJobUrl, {
|
||||
const publishResponse = await request.post(apiUrl, {
|
||||
auth: {
|
||||
bearer: aadAccessToken,
|
||||
},
|
||||
|
@ -74,11 +162,32 @@ export class StreamAnalyticsManager {
|
|||
}
|
||||
}
|
||||
} catch (error) {
|
||||
error.message = `Cannot parse Stream Analytics publish job information: ${error.message}`;
|
||||
error.message = `Parse Stream Analytics jobs info failed: ${error.message}`;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
private async getJobSubscription(ASAInfo): Promise<AzureSubscription> {
|
||||
for (const azureSubscription of this.azureAccount.subscriptions) {
|
||||
if (ASAInfo.ASAJobResourceId.indexOf(azureSubscription.subscription.id) >= 0) {
|
||||
return azureSubscription;
|
||||
}
|
||||
}
|
||||
|
||||
const ASAJobName: string = ASAInfo.ASAJobResourceId.split("/").pop();
|
||||
throw new Error(`Cannot find Stream Analytics Job '${ASAJobName}' in your Azure account, please make sure to login to the right acount.`);
|
||||
}
|
||||
|
||||
private async getJobInfoFromDeploymentTemplate(templateFile: string, moduleName) {
|
||||
try {
|
||||
const templateJson = await fse.readJson(templateFile);
|
||||
const ASAInfo = templateJson.modulesContent[moduleName]["properties.desired"];
|
||||
return ASAInfo;
|
||||
} catch (err) {
|
||||
throw new Error("Cannot parse Stream Analytics Job information from module twin: " + err.message);
|
||||
}
|
||||
}
|
||||
|
||||
private async loadAllStreamingJobs(): Promise<StreamAnalyticsPickItem[]> {
|
||||
try {
|
||||
await this.azureAccount.waitForFilters();
|
||||
|
|
|
@ -271,6 +271,11 @@ export class EdgeManager {
|
|||
}
|
||||
}
|
||||
|
||||
public async checkAndUpdateASAJob(templateFile: string, moduleName: string) {
|
||||
const saManager = StreamAnalyticsManager.getInstance();
|
||||
await saManager.checkAndUpdateASAJob(templateFile, moduleName);
|
||||
}
|
||||
|
||||
private async generateDebugCreateOptions(moduleName: string, template: string): Promise<{ debugImageName: string, debugCreateOptions: any }> {
|
||||
let debugCreateOptions = {};
|
||||
switch (template) {
|
||||
|
@ -634,9 +639,8 @@ export class EdgeManager {
|
|||
debugImageName = imageName = await amlManager.selectAmlImage();
|
||||
repositoryName = Utility.getRepositoryNameFromImageName(imageName);
|
||||
} else if (template === Constants.STREAM_ANALYTICS) {
|
||||
const saManager = new StreamAnalyticsManager();
|
||||
const job = await saManager.selectStreamingJob();
|
||||
const JobInfo: any = await saManager.getJobInfo(job);
|
||||
const saManager = StreamAnalyticsManager.getInstance();
|
||||
const JobInfo: any = await saManager.getJobInfo();
|
||||
debugImageName = imageName = JobInfo.settings.image;
|
||||
moduleTwin = JobInfo.twin.content;
|
||||
env = JobInfo.env;
|
||||
|
|
|
@ -18,6 +18,7 @@ import { ContainerManager } from "./container/containerManager";
|
|||
import { EdgeManager } from "./edge/edgeManager";
|
||||
import { Simulator } from "./edge/simulator";
|
||||
import { Gallery } from "./gallery/gallery";
|
||||
import { ASAModuleUpdateCodeLensProvider } from "./intelliSense/ASAModuleUpdateCodeLensProvider";
|
||||
import { ConfigCompletionItemProvider } from "./intelliSense/configCompletionItemProvider";
|
||||
import { ConfigDefinitionProvider } from "./intelliSense/configDefinitionProvider";
|
||||
import { ConfigDiagnosticProvider } from "./intelliSense/configDiagnosticProvider";
|
||||
|
@ -77,6 +78,8 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("edge-coreclr", {resolveDebugConfiguration}));
|
||||
context.subscriptions.push(vscode.debug.registerDebugConfigurationProvider("edge-node", {resolveDebugConfiguration}));
|
||||
|
||||
context.subscriptions.push(vscode.languages.registerCodeLensProvider({ pattern: `**/{deployment.*.template.json,deployment.template.json}` }, new ASAModuleUpdateCodeLensProvider()));
|
||||
|
||||
initCommandAsync(context, outputChannel,
|
||||
"azure-iot-edge.buildModuleImage",
|
||||
(fileUri?: vscode.Uri): Promise<void> => {
|
||||
|
@ -187,6 +190,12 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
return edgeManager.addModuleInfo(templateFile, outputChannel, isNewSolution, template, moduleInfo);
|
||||
});
|
||||
|
||||
initCommandAsync(context, outputChannel,
|
||||
"azure-iot-edge.internal.checkUpdateForASAModule",
|
||||
async (templateFile: string, moduleName: string): Promise<void> => {
|
||||
return edgeManager.checkAndUpdateASAJob(templateFile, moduleName);
|
||||
});
|
||||
|
||||
context.subscriptions.push(vscode.window.onDidCloseTerminal((closedTerminal: vscode.Terminal) => {
|
||||
Executor.onDidCloseTerminal(closedTerminal);
|
||||
}));
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
"use strict";
|
||||
|
||||
import * as jsonMap from "json-source-map";
|
||||
import * as vscode from "vscode";
|
||||
|
||||
export class ASAModuleUpdateCodeLensProvider implements vscode.CodeLensProvider {
|
||||
private templateFilePath: string;
|
||||
private ASAModuleTwinPathRoot: string = "/modulesContent/";
|
||||
|
||||
public async provideCodeLenses(document: vscode.TextDocument, token: vscode.CancellationToken): Promise<vscode.CodeLens[]> {
|
||||
const deploymentJsonString = document.getText();
|
||||
this.templateFilePath = document.uri.fsPath;
|
||||
return Promise.resolve(this.generateCodeLens(deploymentJsonString));
|
||||
}
|
||||
|
||||
public generateCodeLens(deploymentJsonString: string): vscode.CodeLens[] {
|
||||
const codeLensArr: vscode.CodeLens[] = [];
|
||||
const result = jsonMap.parse(deploymentJsonString);
|
||||
const deploymentJson = result.data;
|
||||
const ASAModuleNamesArr = [];
|
||||
for (const name in deploymentJson.modulesContent) {
|
||||
if (deploymentJson.modulesContent[name]["properties.desired"].ASAJobEtag) {
|
||||
ASAModuleNamesArr.push(name);
|
||||
}
|
||||
}
|
||||
|
||||
for (const name of ASAModuleNamesArr) {
|
||||
const lineNum = result.pointers[this.ASAModuleTwinPathRoot + name].key.line;
|
||||
const range = new vscode.Range(lineNum, 0, lineNum, 100);
|
||||
const cmd: vscode.Command = {
|
||||
title: "Check Configurations Update for ASA Job",
|
||||
command: "azure-iot-edge.internal.checkUpdateForASAModule",
|
||||
arguments: [this.templateFilePath, name],
|
||||
};
|
||||
codeLensArr.push(new vscode.CodeLens(range, cmd));
|
||||
}
|
||||
|
||||
return codeLensArr;
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче