Initial changes to send information to deployment azure

This commit is contained in:
Himanshu Yadav 2019-09-17 22:32:19 +05:30
Родитель 623e2ed349
Коммит 5252ef66da
11 изменённых файлов: 247 добавлений и 19 удалений

9
package-lock.json сгенерированный
Просмотреть файл

@ -783,6 +783,15 @@
"ms-rest-azure": "^2.3.3"
}
},
"azure-arm-website": {
"version": "5.7.0",
"resolved": "https://registry.npmjs.org/azure-arm-website/-/azure-arm-website-5.7.0.tgz",
"integrity": "sha512-GnwqaelTIhv40YI3Ch8+Q272X6XXWTq99Y1aYWZb1cejSP4gjrWWeppwor4HtjlVU9i9YIvYO91TRjQt8FrHVA==",
"requires": {
"ms-rest": "^2.3.3",
"ms-rest-azure": "^2.5.5"
}
},
"azure-pipelines-language-server": {
"version": "0.5.9",
"resolved": "https://registry.npmjs.org/azure-pipelines-language-server/-/azure-pipelines-language-server-0.5.9.tgz",

Просмотреть файл

@ -134,6 +134,7 @@
},
"dependencies": {
"azure-arm-resource": "7.3.0",
"azure-arm-website": "5.7.0",
"azure-pipelines-language-server": "0.5.9",
"mustache": "3.0.1",
"q": "1.5.1",

Просмотреть файл

@ -1,28 +1,33 @@
import { AzureResourceClient } from './azureResourceClient';
import { Messages } from '../../resources/messages';
const uuid = require('uuid/v4');
import { ResourceListResult, GenericResource } from 'azure-arm-resource/lib/resource/models';
import { WebSiteManagementClient } from 'azure-arm-website';
import { SiteConfigResource, StringDictionary, Deployment } from 'azure-arm-website/lib/models';
import { ServiceClientCredentials } from 'ms-rest';
import { WebAppKind } from '../../model/models';
import { AzureResourceClient } from './azureResourceClient';
import { WebAppKind, ParsedAzureResourceId } from '../../model/models';
import * as Constants from '../../resources/constants';
import {Build} from '../../model/azureDevOps';
import {Messages} from '../../resources/messages';
export class AppServiceClient extends AzureResourceClient {
private static apiVersion = '2019-05-01';
private static resourceType = 'Microsoft.Web/sites';
private webSiteManagementClient: WebSiteManagementClient;
constructor(credentials: ServiceClientCredentials, subscriptionId: string) {
super(credentials, subscriptionId);
this.webSiteManagementClient = new WebSiteManagementClient(credentials, subscriptionId);
}
public async getAppServiceResource(resourceId): Promise<GenericResource> {
if (!resourceId) {
throw new Error(Messages.resourceIdMissing);
}
return await this.getResource(resourceId, AppServiceClient.apiVersion);
public async getAppServiceResource(resourceId: string): Promise<GenericResource> {
let parsedResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(resourceId);
return await this.webSiteManagementClient.webApps.get(parsedResourceId.resourceGroup, parsedResourceId.resourceName);
}
public async GetAppServices(filterForResourceKind: WebAppKind): Promise<ResourceListResult> {
let resourceList: ResourceListResult = await this.getResourceList(AppServiceClient.resourceType);
// this.webSiteManagementClient.webApps.list();
if (!!filterForResourceKind) {
let filteredResourceList: ResourceListResult = [];
resourceList.forEach((resource) => {
@ -36,4 +41,76 @@ export class AppServiceClient extends AzureResourceClient {
return resourceList;
}
public async getDeploymentCenterUrl(resourceId: string): string {
}
public async getVstsPipelineUrl(resourceId: string): Promise<string> {
let metadata = await this.getSiteMetadata(resourceId);
if (metadata.properties['VSTSRM_BuildDefinitionWebAccessUrl']) {
return metadata.properties['VSTSRM_BuildDefinitionWebAccessUrl'];
}
throw new Error(Messages.cannotFindPipelineUrlInMetaDataException);
}
public async getAppServiceConfig(resourceId: string): Promise<SiteConfigResource> {
let parsedResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(resourceId);
return this.webSiteManagementClient.webApps.getConfiguration(parsedResourceId.resourceGroup, parsedResourceId.resourceName);
}
public async updateScmType(resourceId: string): Promise<SiteConfigResource> {
let siteConfig = await this.getAppServiceConfig(resourceId);
siteConfig.scmType = Constants.VstsRmScmType;
let parsedResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(resourceId);
return this.webSiteManagementClient.webApps.updateConfiguration(parsedResourceId.resourceGroup, parsedResourceId.resourceName, siteConfig);
}
public async updateAppServiceConfig(resourceId: string, siteConfig: SiteConfigResource): Promise<SiteConfigResource> {
let parsedResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(resourceId);
return this.webSiteManagementClient.webApps.updateConfiguration(parsedResourceId.resourceGroup, parsedResourceId.resourceName, siteConfig);
}
public async getSiteMetadata(resourceId: string): Promise<StringDictionary> {
let parsedResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(resourceId);
return this.webSiteManagementClient.webApps.listMetadata(parsedResourceId.resourceGroup, parsedResourceId.resourceName);
}
public async updateSiteMetadata(resourceId: string, metadata: StringDictionary): Promise<StringDictionary> {
let parsedResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(resourceId);
return this.webSiteManagementClient.webApps.updateMetadata(parsedResourceId.resourceGroup, parsedResourceId.resourceName, metadata);
}
public async publishDeploymentToAppService(resourceId: string, buildDefinitionUrl: string, releaseDefinitionUrl: string, triggeredBuildUrl: string): Promise<Deployment> {
let parsedResourceId: ParsedAzureResourceId = new ParsedAzureResourceId(resourceId);
// create deployment object
let deploymentId = uuid();
let deployment = this.createDeploymentObject(deploymentId, buildDefinitionUrl, releaseDefinitionUrl, triggeredBuildUrl);
return this.webSiteManagementClient.webApps.createDeployment(parsedResourceId.resourceGroup, parsedResourceId.resourceName, deploymentId, deployment);
}
private createDeploymentObject(deploymentId: string, buildDefinitionUrl: string, releaseDefinitionUrl: string, triggeredBuildUrl: string): Deployment {
let deployment: Deployment = {
id: deploymentId
};
let deploymentMessage: DeploymentMessage = {
type: "CDDeploymentConfiguration",
message: "Successfully set up continuous delivery from VS Code and triggered deployment to Azure Web App.",
VSTSRM_BuildDefinitionWebAccessUrl: `${buildDefinitionUrl}`,
VSTSRM_ConfiguredCDEndPoint: `${releaseDefinitionUrl}`,
VSTSRM_BuildWebAccessUrl: `${triggeredBuildUrl}`,
};
deployment.message = JSON.stringify(deploymentMessage);
return deployment;
}
}
interface DeploymentMessage {
type: string,
message: string,
VSTSRM_BuildDefinitionWebAccessUrl: string,
VSTSRM_ConfiguredCDEndPoint: string,
VSTSRM_BuildWebAccessUrl: string
};

Просмотреть файл

@ -21,6 +21,7 @@ import { Result, telemetryHelper } from './helper/telemetryHelper';
import { ControlProvider } from './helper/controlProvider';
import { GitHubProvider } from './helper/gitHubHelper';
import { getSubscriptionSession } from './helper/azureSessionHelper';
import {Build} from './model/azureDevOps';
const Layer: string = 'configure';
@ -89,7 +90,7 @@ class PipelineConfigurer {
await this.checkInPipelineFileToRepository();
telemetryHelper.setCurrentStep('CreateAndRunPipeline');
let queuedPipelineUrl = await vscode.window.withProgress<string>({ location: vscode.ProgressLocation.Notification, title: Messages.configuringPipelineAndDeployment }, async () => {
let queuedPipeline = await vscode.window.withProgress<Build>({ location: vscode.ProgressLocation.Notification, title: Messages.configuringPipelineAndDeployment }, async () => {
try {
let pipelineName = `${this.inputs.targetResource.resource.name}-${this.uniqueResourceNameSuffix}`;
return await this.azureDevOpsHelper.createAndRunPipeline(pipelineName, this.inputs);
@ -100,13 +101,14 @@ class PipelineConfigurer {
}
});
this.updateScmType(queuedPipeline);
telemetryHelper.setCurrentStep('DisplayCreatedPipeline');
vscode.window.showInformationMessage(Messages.pipelineSetupSuccessfully, Messages.browsePipeline)
.then((action: string) => {
if (action && action.toLowerCase() === Messages.browsePipeline.toLowerCase()) {
telemetryHelper.setTelemetry(TelemetryKeys.BrowsePipelineClicked, 'true');
vscode.env.openExternal(vscode.Uri.parse(queuedPipelineUrl));
vscode.env.openExternal(vscode.Uri.parse(queuedPipeline._links.web.href));
}
});
}
@ -342,6 +344,8 @@ class PipelineConfigurer {
default:
throw new Error(utils.format(Messages.resourceTypeIsNotSupported, azureResource.type));
}
await this.validateIfPipelineCanBeSetupOnResource(azureResource.id);
}
catch (error) {
telemetryHelper.logError(Layer, TracePoints.ExtractAzureResourceFromNodeFailed, error);
@ -451,9 +455,60 @@ class PipelineConfigurer {
.then((webApps) => webApps.map(x => { return { label: x.name, data: x }; })),
{ placeHolder: Messages.selectWebApp },
TelemetryKeys.WebAppListCount);
await this.validateIfPipelineCanBeSetupOnResource(selectedResource.data.id);
this.inputs.targetResource.resource = selectedResource.data;
}
private async validateIfPipelineCanBeSetupOnResource(resourceId: string): Promise<void> {
// Check for SCM type, if its value is set then a pipeline is already setup.
let siteConfig = await this.appServiceClient.getAppServiceConfig(resourceId);
if (siteConfig.scmType && siteConfig.scmType.toLowerCase() == 'VSTSRM') {
// if pipeline is already setup, the ask the user if we should continue.
telemetryHelper.setTelemetry(TelemetryKeys.PipelineAlreadyConfigured, 'true');
telemetryHelper.setTelemetry(TelemetryKeys.ScmType, siteConfig.scmType);
let browsePipeline = await this.controlProvider.showInformationBox(
constants.PipelineAlreadyConfigure,
Messages.pipelineAlreadyConfigured,
constants.BrowsePipeline);
if (browsePipeline) {
let existingPipelineUrl = await this.appServiceClient.getVstsPipelineUrl(resourceId);
telemetryHelper.setTelemetry(TelemetryKeys.BrowsedExistingPipeline, 'true');
vscode.env.openExternal(vscode.Uri.parse(existingPipelineUrl));
throw new UserCancelledError();
}
}
else if (siteConfig.scmType && siteConfig.scmType.toLowerCase() != '') {
let result = await this.controlProvider.showInformationBox(constants.DeploymentResourceAlreadyConfigured, Messages.deploymentCenterAlreadyConfigured, constants.BrowseDeploymentSource);
if (result == constants.BrowsePipeline) {
let deploymentCenterUrl: string = await this.appServiceClient.getDeploymentCenterUrl(resourceId);
telemetryHelper.setTelemetry(TelemetryKeys.OpenedDeploymentCenter, 'true');
await vscode.env.openExternal(vscode.Uri.parse(deploymentCenterUrl));
throw new UserCancelledError();
}
}
}
private async updateScmType(queuedPipeline: Build) {
await this.appServiceClient.updateScmType(this.inputs.targetResource.resource.id);
let metadata = await this.appServiceClient.getSiteMetadata(this.inputs.targetResource.resource.id);
// insert BD, RD and other information
metadata["properties"] = {
"CURRENT_STACK": "dotnetcore",
"VSTSRM_ProjectId": `${this.inputs.project.id}`,
"VSTSRM_AccountId": `${this.inputs.organizationName}`,
"VSTSRM_BuildDefinitionId": `${queuedPipeline.id}`,
"VSTSRM_BuildDefinitionWebAccessUrl": `${queuedPipeline._links.web.href}`,
"VSTSRM_ConfiguredCDEndPoint": `${queuedPipeline._links.web.href}`,
"VSTSRM_ReleaseDefinitionId": `${queuedPipeline.id}`,
"VSTSRM_ProdAppName": `${this.inputs.targetResource.resource.name}`,
};
await this.appServiceClient.updateSiteMetadata(this.inputs.targetResource.resource.id, metadata);
await this.appServiceClient.publishDeploymentToAppService(this.inputs.targetResource.resource.id);
}
private async createGithubServiceConnection(): Promise<void> {
if (!this.serviceConnectionHelper) {
this.serviceConnectionHelper = new ServiceConnectionHelper(this.inputs.organizationName, this.inputs.project.name, this.azureDevOpsClient);

Просмотреть файл

@ -1,8 +1,9 @@
import { QuickPickItem, InputBoxOptions } from 'vscode';
import { IAzureQuickPickOptions } from 'vscode-azureextensionui';
import { QuickPickItem, InputBoxOptions, window, env, Uri } from 'vscode';
import { IAzureQuickPickOptions, UserCancelledError } from 'vscode-azureextensionui';
import { telemetryHelper } from '../helper/telemetryHelper'
import { extensionVariables } from '../model/models';
import { TelemetryKeys } from '../resources/telemetryKeys';
import {Messages} from '../resources/messages';
export class ControlProvider {
public async showQuickPick<T extends QuickPickItem>(listName: string, listItems: T[] | Thenable<T[]>, options: IAzureQuickPickOptions, itemCountTelemetryKey?: string): Promise<T> {
@ -21,4 +22,20 @@ export class ControlProvider {
telemetryHelper.setTelemetry(TelemetryKeys.CurrentUserInput, inputName);
return await extensionVariables.ui.showInputBox(options);
}
public async showInformationBox(informationIdentifier: string, informationMessage: string, actions?): Promise<any> {
telemetryHelper.setTelemetry(TelemetryKeys.CurrentUserInput, informationIdentifier);
if (!!actions && actions.length > 0) {
let result = await window.showInformationMessage(informationMessage, actions);
if (!result) {
throw new UserCancelledError(Messages.userCancelledExcecption);
}
return result;
}
else {
return await window.showInformationMessage(informationMessage, actions);
}
}
}

Просмотреть файл

@ -77,12 +77,12 @@ export class AzureDevOpsHelper {
}
}
public async createAndRunPipeline(pipelineName: string, inputs: WizardInputs): Promise<string> {
public async createAndRunPipeline(pipelineName: string, inputs: WizardInputs): Promise<Build> {
try {
let buildDefinitionPayload = await this.getBuildDefinitionPayload(pipelineName, inputs);
let definition = await this.azureDevOpsClient.createBuildDefinition(inputs.organizationName, buildDefinitionPayload);
let build = await this.azureDevOpsClient.queueBuild(inputs.organizationName, this.getQueueBuildPayload(inputs, definition.id, definition.project.id));
return build._links.web.href;
return build;
}
catch (error) {
throw new Error(util.format(Messages.failedToCreateAzurePipeline, error.message));

Просмотреть файл

@ -44,7 +44,9 @@ export interface YamlProcess {
}
export interface Build {
definition: { id: number };
_links: { web: { href: string } };
id: string;
definition: { id: number, _links: { web: { href: string } } };
project: { id: string };
sourceBranch: string;
sourceVersion: string;

Просмотреть файл

@ -4,6 +4,7 @@ import { OutputChannel, ExtensionContext, QuickPickItem } from 'vscode';
import { ServiceClientCredentials } from 'ms-rest';
import { SubscriptionModels } from 'azure-arm-resource';
import { UIExtensionVariables, IAzureUserInput, ITelemetryReporter } from 'vscode-azureextensionui';
import { Messages } from '../resources/messages';
class ExtensionVariables implements UIExtensionVariables {
public azureAccountExtensionApi: AzureAccountExtensionExports;
@ -121,6 +122,58 @@ export class QuickPickItemWithData implements QuickPickItem {
detail?: string;
}
export class ParsedAzureResourceId {
public resourceId: string;
public subscription: string;
public resourceGroup: string;
public resourceType: string;
public resourceProvider: string;
public resourceName: string;
public childResourceType: string;
public childResource: string;
constructor(resourceId: string) {
if (!resourceId) {
throw Messages.invalidAzureResourceId;
}
this.resourceId = resourceId;
this.parseId();
}
private parseId() {
// remove all empty parts in the resource to avoid failing in case there are leading/trailing/extra '/'
let parts = this.resourceId.split('/').filter((part) => !!part);
if (!!parts) {
for (let i = 0; i < parts.length; i++) {
switch (i) {
case 1:
this.subscription = parts[1];
break;
case 3:
this.resourceGroup = parts[3];
break;
case 5:
this.resourceProvider = parts[5];
break;
case 6:
this.resourceType = parts[6];
break;
case 7:
this.resourceName = parts[7];
break;
case 8:
this.childResourceType = parts[8];
break;
case 9:
this.childResource = parts[9];
break;
}
}
}
}
}
export interface Token {
session: AzureSession;
accessToken: string;

Просмотреть файл

@ -142,4 +142,9 @@ export const SelectSubscription = 'selectSubscription';
export const SelectWebApp = 'selectWebApp';
export const GitHubPat = 'gitHubPat';
export const SelectFromMultipleWorkSpace = 'selectFromMultipleWorkSpace';
export const SelectRemoteForRepo = 'selectRemoteForRepo';
export const SelectRemoteForRepo = 'selectRemoteForRepo';
export const VstsRmScmType = 'VSTSRM';
export const DeploymentResourceAlreadyConfigured = 'DeploymentResourceAlreadyConfigured';
export const PipelineAlreadyConfigure = 'PipelineAlreadyConfigure';
export const BrowseDeploymentSource = 'BrowseDeploymentSource';
export const BrowsePipeline = 'BrowsePipeline';

Просмотреть файл

@ -52,4 +52,9 @@ export class Messages {
public static azureServicePrincipalFailedMessage: string =`Failed while creating Azure service principal.`;
public static roleAssignmentFailedMessage: string =`Failed while role assignement.`;
public static waitForAzureSignIn: string =`Waiting for Azure sign-in...`;
public static invalidAzureResourceId: string = 'Azure Resource Id: %s, could not be parsed correctly. Kindly verify if the Id is correct.'
public static userCancelledExcecption = 'User cancelled the action';
public static deploymentCenterAlreadyConfigured = 'Deployment source of type: %s is already setup for your web app, you can view more by browsing to deployment center.';
public static pipelineAlreadyConfigured = 'Pipeline is already setup for your web app.';
public static cannotFindPipelineUrlInMetaDataException = 'We were unable to find pipeline url for the app service. This can be caused due to corrupt/invalida metadata of site. You can open deployment center for more information.'
}

Просмотреть файл

@ -10,6 +10,10 @@ export class TelemetryKeys {
public static BrowsePipelineClicked: string = 'browsePipelineClicked';
public static MultipleWorkspaceFolders: string = 'multipleWorkspaceFolders';
public static GitFolderExists: string = 'gitFolderExists';
public static PipelineAlreadyConfigured: string = 'pipelineAlreadyConfigured';
public static ScmType: string = 'scmType';
public static OpenedDeploymentCenter = 'openedDeploymentCenter';
public static BrowsedExistingPipeline = 'browsedExistingPipeline';
// Durations
public static ExtensionActivationDuration = 'extensionActivationDuration';