From e9111e28495ee70119c4401e493b7402967dd266 Mon Sep 17 00:00:00 2001 From: Sergey Nesterov Date: Wed, 26 Apr 2023 20:25:09 +0200 Subject: [PATCH] Revert WIF changes with migration to arm-rest-v2 (#18196) --- .../azure-arm-app-service-kudu.ts | 710 +++++++++++++++ .../azure-arm-rest/azure-arm-app-service.ts | 824 ++++++++++++++++++ .../azurermwebappdeployment.ts | 6 +- .../AzureRmWebAppDeploymentProvider.ts | 21 +- .../BuiltInLinuxWebAppDeploymentProvider.ts | 16 +- .../ConsumptionWebAppDeploymentProvider.ts | 24 +- .../deploymentProvider/DeploymentFactory.ts | 10 +- .../WindowsWebAppRunFromZipProvider.ts | 20 +- .../WindowsWebAppWarDeployProvider.ts | 2 +- .../WindowsWebAppZipDeployProvider.ts | 16 +- Tasks/AzureFunctionAppV1/make.json | 8 - ...tilityExt.ts => AzureAppServiceUtility.ts} | 310 ++++++- .../operations/FileTransformsUtility.ts | 29 - .../operations/KuduServiceUtility.ts | 25 +- .../operations/ReleaseAnnotationUtility.ts | 8 +- Tasks/AzureFunctionAppV1/package-lock.json | 362 ++------ Tasks/AzureFunctionAppV1/package.json | 5 +- Tasks/AzureFunctionAppV1/task.json | 2 +- Tasks/AzureFunctionAppV1/task.loc.json | 2 +- Tasks/AzureFunctionAppV1/taskparameters.ts | 25 +- .../azure-arm-app-service-kudu.ts | 712 +++++++++++++++ .../azure-arm-rest/azure-arm-app-service.ts | 824 ++++++++++++++++++ .../azurermwebappdeployment.ts | 4 +- .../AzureRmWebAppDeploymentProvider.ts | 19 +- .../BuiltInLinuxWebAppDeploymentProvider.ts | 14 +- .../ConsumptionWebAppDeploymentProvider.ts | 24 +- .../deploymentProvider/DeploymentFactory.ts | 10 +- .../WindowsWebAppRunFromZipProvider.ts | 20 +- .../WindowsWebAppZipDeployProvider.ts | 16 +- Tasks/AzureFunctionAppV2/make.json | 8 - ...tilityExt.ts => AzureAppServiceUtility.ts} | 322 ++++++- .../operations/FileTransformsUtility.ts | 29 - .../operations/KuduServiceUtility.ts | 25 +- .../operations/ReleaseAnnotationUtility.ts | 8 +- Tasks/AzureFunctionAppV2/package-lock.json | 256 +----- Tasks/AzureFunctionAppV2/package.json | 5 +- Tasks/AzureFunctionAppV2/task.json | 2 +- Tasks/AzureFunctionAppV2/task.loc.json | 2 +- Tasks/AzureFunctionAppV2/taskparameters.ts | 25 +- 39 files changed, 3923 insertions(+), 827 deletions(-) create mode 100644 Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service-kudu.ts create mode 100644 Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service.ts rename Tasks/AzureFunctionAppV1/operations/{AzureAppServiceUtilityExt.ts => AzureAppServiceUtility.ts} (56%) delete mode 100644 Tasks/AzureFunctionAppV1/operations/FileTransformsUtility.ts create mode 100644 Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service-kudu.ts create mode 100644 Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service.ts rename Tasks/AzureFunctionAppV2/operations/{AzureAppServiceUtilityExt.ts => AzureAppServiceUtility.ts} (55%) delete mode 100644 Tasks/AzureFunctionAppV2/operations/FileTransformsUtility.ts diff --git a/Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service-kudu.ts b/Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service-kudu.ts new file mode 100644 index 000000000000..74f9c6545129 --- /dev/null +++ b/Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service-kudu.ts @@ -0,0 +1,710 @@ +import tl = require('azure-pipelines-task-lib/task'); +import fs = require('fs'); +import util = require('util'); +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); +import { WebJob, SiteExtension } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; +import { KUDU_DEPLOYMENT_CONSTANTS } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/constants'; + +export class KuduServiceManagementClient { + private _scmUri; + private _accessToken: string; + private _cookie: string[]; + private _scmAccessCheck : boolean; + + constructor(scmUri: string, accessToken: string, scmAccessCheck: boolean) { + this._accessToken = accessToken; + this._scmUri = scmUri; + this._scmAccessCheck = scmAccessCheck; + } + + public async beginRequest(request: webClient.WebRequest, reqOptions?: webClient.WebRequestOptions, contentType?: string): Promise { + request.headers = request.headers || {}; + if(this._scmAccessCheck === false || this._scmAccessCheck == null) { + request.headers["Authorization"] = "Bearer " + this._accessToken; + tl.debug('Using Bearer Authentication'); + let authMethodtelemetry = '{"authMethod":"Bearer"}'; + console.log("##vso[telemetry.publish area=TaskDeploymentMethod;feature=AzureFunctionAppDeployment]" + authMethodtelemetry); + } + else{ + request.headers["Authorization"] = "Basic " + this._accessToken; + tl.debug('Using Basic Authentication'); + let authMethodtelemetry = '{"authMethod":"Basic"}'; + console.log("##vso[telemetry.publish area=TaskDeploymentMethod;feature=AzureFunctionAppDeployment]" + authMethodtelemetry); + } + + + request.headers['Content-Type'] = contentType || 'application/json; charset=utf-8'; + + if(!!this._cookie) { + tl.debug(`setting affinity cookie ${JSON.stringify(this._cookie)}`); + request.headers['Cookie'] = this._cookie; + } + + let retryCount = reqOptions && util.isNumber(reqOptions.retryCount) ? reqOptions.retryCount : 5; + + while(retryCount >= 0) { + try { + let httpResponse = await webClient.sendRequest(request, reqOptions); + if(httpResponse.headers['set-cookie'] && !this._cookie) { + this._cookie = httpResponse.headers['set-cookie']; + tl.debug(`loaded affinity cookie ${JSON.stringify(this._cookie)}`); + } + + return httpResponse; + } + catch(exception) { + let exceptionString: string = exception.toString(); + if(exceptionString.indexOf("Hostname/IP doesn't match certificates's altnames") != -1 + || exceptionString.indexOf("unable to verify the first certificate") != -1 + || exceptionString.indexOf("unable to get local issuer certificate") != -1) { + tl.warning(tl.loc('ASE_SSLIssueRecommendation')); + } + + if(retryCount > 0 && exceptionString.indexOf('Request timeout') != -1 && (!reqOptions || reqOptions.retryRequestTimedout)) { + tl.debug('encountered request timedout issue in Kudu. Retrying again'); + retryCount -= 1; + continue; + } + + throw new Error(exceptionString); + } + } + + } + + public getRequestUri(uriFormat: string, queryParameters?: Array) { + uriFormat = uriFormat[0] == "/" ? uriFormat : "/" + uriFormat; + + if(queryParameters && queryParameters.length > 0) { + uriFormat = uriFormat + '?' + queryParameters.join('&'); + } + + return this._scmUri + uriFormat; + } + + public getScmUri(): string { + return this._scmUri; + } +} + +export class Kudu { + private _client: KuduServiceManagementClient; + + constructor(scmUri: string, accessToken: string, scmPolicyCheck: boolean) { + this._client = new KuduServiceManagementClient(scmUri, accessToken, scmPolicyCheck); + } + + public async updateDeployment(requestBody: any): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(requestBody); + httpRequest.uri = this._client.getRequestUri(`/api/deployments/${requestBody.id}`); + + try { + let webRequestOptions: webClient.WebRequestOptions = {retriableErrorCodes: [], retriableStatusCodes: null, retryCount: 5, retryIntervalInSeconds: 5, retryRequestTimedout: true}; + var response = await this._client.beginRequest(httpRequest, webRequestOptions); + tl.debug(`updateDeployment. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc("Successfullyupdateddeploymenthistory", response.body.url)); + return response.body.id; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('Failedtoupdatedeploymenthistory', this._getFormattedError(error))); + } + } + + + public async getContinuousJobs(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/continuouswebjobs`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getContinuousJobs. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body as Array; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetContinuousWebJobs', this._getFormattedError(error))) + } + } + + public async startContinuousWebJob(jobName: string): Promise { + console.log(tl.loc('StartingWebJob', jobName)); + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/continuouswebjobs/${jobName}/start`); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`startContinuousWebJob. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc('StartedWebJob', jobName)); + return response.body as WebJob; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToStartContinuousWebJob', jobName, this._getFormattedError(error))); + } + } + + public async stopContinuousWebJob(jobName: string): Promise { + console.log(tl.loc('StoppingWebJob', jobName)); + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/continuouswebjobs/${jobName}/stop`); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`stopContinuousWebJob. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc('StoppedWebJob', jobName)); + return response.body as WebJob; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToStopContinuousWebJob', jobName, this._getFormattedError(error))); + } + } + + public async installSiteExtension(extensionID: string): Promise { + console.log(tl.loc("InstallingSiteExtension", extensionID)); + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/siteextensions/${extensionID}`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`installSiteExtension. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc("SiteExtensionInstalled", extensionID)); + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToInstallSiteExtension', extensionID, this._getFormattedError(error))) + } + } + + public async getSiteExtensions(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/siteextensions`, ['checkLatest=false']); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getSiteExtensions. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body as Array; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetSiteExtensions', this._getFormattedError(error))) + } + } + + public async getAllSiteExtensions(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/extensionfeed`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getAllSiteExtensions. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body as Array; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetAllSiteExtensions', this._getFormattedError(error))) + } + } + + public async getProcess(processID: number): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/processes/${processID}`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getProcess. status code: ${response.statusCode} - ${response.statusMessage}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetProcess', this._getFormattedError(error))) + } + } + + public async killProcess(processID: number): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'DELETE'; + httpRequest.uri = this._client.getRequestUri(`/api/processes/${processID}`); + var reqOptions: webClient.WebRequestOptions = { + retriableErrorCodes: ["ETIMEDOUT"], + retriableStatusCodes: [503], + retryCount: 1, + retryIntervalInSeconds: 5, + retryRequestTimedout: true + }; + try { + var response = await this._client.beginRequest(httpRequest, reqOptions); + tl.debug(`killProcess. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 502) { + tl.debug(`Killed Process ${processID}`); + return; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToKillProcess', this._getFormattedError(error))) + } + } + + public async getAppSettings(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/settings`); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getAppSettings. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToFetchKuduAppSettings', this._getFormattedError(error))); + } + } + + public async listDir(physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`listFiles. Data: ${JSON.stringify(response)}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + else if(response.statusCode === 404) { + return null; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToListPath', physicalPath, this._getFormattedError(error))); + } + } + + public async getFileContent(physicalPath: string, fileName: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/${fileName}`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getFileContent. Status code: ${response.statusCode} - ${response.statusMessage}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + else if(response.statusCode === 404) { + return null; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToGetFileContent', physicalPath, fileName, this._getFormattedError(error))); + } + } + + public async uploadFile(physicalPath: string, fileName: string, filePath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + if(!tl.exist(filePath)) { + throw new Error(tl.loc('FilePathInvalid', filePath)); + } + + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/${fileName}`); + httpRequest.headers = { + 'If-Match': '*' + }; + httpRequest.body = fs.createReadStream(filePath); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`uploadFile. Data: ${JSON.stringify(response)}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToUploadFile', physicalPath, fileName, this._getFormattedError(error))); + } + } + + public async createPath(physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`createPath. Data: ${JSON.stringify(response)}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToCreatePath', physicalPath, this._getFormattedError(error))); + } + } + + public async runCommand(physicalPath: string, command: string): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/command`); + httpRequest.headers = { + 'Content-Type': 'multipart/form-data', + 'If-Match': '*' + }; + httpRequest.body = JSON.stringify({ + 'command': command, + 'dir': physicalPath + }); + + try { + tl.debug('Executing Script on Kudu. Command: ' + command); + let webRequestOptions: webClient.WebRequestOptions = {retriableErrorCodes: null, retriableStatusCodes: null, retryCount: 5, retryIntervalInSeconds: 5, retryRequestTimedout: false}; + var response = await this._client.beginRequest(httpRequest, webRequestOptions); + tl.debug(`runCommand. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(error.toString()); + } + } + + public async extractZIP(webPackage: string, physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/zip/${physicalPath}/`); + httpRequest.headers = { + 'Content-Type': 'multipart/form-data', + 'If-Match': '*' + }; + httpRequest.body = fs.createReadStream(webPackage); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`extractZIP. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('Failedtodeploywebapppackageusingkuduservice', this._getFormattedError(error))); + } + } + + public getKuduStackTrace() { + let stackTraceUrl = this._client.getRequestUri(`/api/vfs/LogFiles/kudu/trace`); + return stackTraceUrl; + } + + public async zipDeploy(webPackage: string, queryParameters?: Array): Promise { + let httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/zipdeploy`, queryParameters); + httpRequest.body = fs.createReadStream(webPackage); + let requestOptions = new webClient.WebRequestOptions(); + //Bydefault webclient.sendRequest retries for [408, 409, 500, 502, 503, 504] as suggested by appservice team for zipdeploy api + //408 and 409 should not be retried as it will never turn into success + requestOptions.retriableStatusCodes = [500, 502, 503, 504]; + requestOptions.retryIntervalInSeconds = 5; + try { + let response = await this._client.beginRequest(httpRequest, requestOptions, 'application/octet-stream'); + tl.debug(`ZIP Deploy response: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + tl.debug('Deployment passed'); + return null; + } + else if(response.statusCode == 202) { + let pollableURL: string = response.headers.location; + if(!!pollableURL) { + tl.debug(`Polling for ZIP Deploy URL: ${pollableURL}`); + return await this._getDeploymentDetailsFromPollURL(pollableURL); + } + else { + tl.debug('zip deploy returned 202 without pollable URL.'); + return null; + } + } + else { + throw response; + } + } + catch(error) { + throw new Error(tl.loc('PackageDeploymentFailed', this._getFormattedError(error))); + } + } + + public async warDeploy(webPackage: string, queryParameters?: Array): Promise { + let httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/wardeploy`, queryParameters); + httpRequest.body = fs.createReadStream(webPackage); + + try { + let response = await this._client.beginRequest(httpRequest, null, 'application/octet-stream'); + tl.debug(`War Deploy response: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + tl.debug('Deployment passed'); + return null; + } + else if(response.statusCode == 202) { + let pollableURL: string = response.headers.location; + if(!!pollableURL) { + tl.debug(`Polling for War Deploy URL: ${pollableURL}`); + return await this._getDeploymentDetailsFromPollURL(pollableURL); + } + else { + tl.debug('war deploy returned 202 without pollable URL.'); + return null; + } + } + else { + throw response; + } + } + catch(error) { + throw new Error(tl.loc('PackageDeploymentFailed', this._getFormattedError(error))); + } + } + + + public async getDeploymentDetails(deploymentID: string): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/deployments/${deploymentID}`); ; + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getDeploymentDetails. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetDeploymentLogs', this._getFormattedError(error))) + } + } + + public async getDeploymentLogs(log_url: string): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = log_url; + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getDeploymentLogs. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetDeploymentLogs', this._getFormattedError(error))) + } + } + + public async deleteFile(physicalPath: string, fileName: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'DELETE'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/${fileName}`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`deleteFile. Data: ${JSON.stringify(response)}`); + if([200, 201, 204, 404].indexOf(response.statusCode) != -1) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToDeleteFile', physicalPath, fileName, this._getFormattedError(error))); + } + } + + public async deleteFolder(physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'DELETE'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`deleteFolder. Data: ${JSON.stringify(response)}`); + if([200, 201, 204, 404].indexOf(response.statusCode) != -1) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToDeleteFolder', physicalPath, this._getFormattedError(error))); + } + } + + private async _getDeploymentDetailsFromPollURL(pollURL: string):Promise { + let httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = pollURL; + httpRequest.headers = {}; + + while(true) { + let response = await this._client.beginRequest(httpRequest); + if(response.statusCode == 200 || response.statusCode == 202) { + var result = response.body; + tl.debug(`POLL URL RESULT: ${JSON.stringify(response)}`); + if(result.status == KUDU_DEPLOYMENT_CONSTANTS.SUCCESS || result.status == KUDU_DEPLOYMENT_CONSTANTS.FAILED) { + return result; + } + else { + tl.debug(`Deployment status: ${result.status} '${result.status_text}'. retry after 5 seconds`); + await webClient.sleepFor(5); + continue; + } + } + else { + throw response; + } + } + } + + private _getFormattedError(error: any) { + if(error && error.message) { + if(error.statusCode) { + error.message = `${typeof error.message.valueOf() == 'string' ? error.message : error.message.Code + " - " + error.message.Message } (CODE: ${error.statusCode})` + } + + return error.message; + } + if(error && error.statusCode) { + return `${error.statusMessage} (CODE: ${error.statusCode})`; + } + return error; + } + + public async validateZipDeploy(webPackage: string, queryParameters?: Array): Promise { + try { + var stats = fs.statSync(webPackage); + var fileSizeInBytes = stats.size; + let httpRequest: webClient.WebRequest = { + method: 'POST', + uri: this._client.getRequestUri(`/api/zipdeploy/validate`, queryParameters), + body: fs.createReadStream(webPackage), + headers: { + 'Content-Length': fileSizeInBytes + }, + }; + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retriableStatusCodes = [500, 502, 503, 504]; + requestOptions.retryIntervalInSeconds = 5; + + let response = await this._client.beginRequest(httpRequest, requestOptions, 'application/octet-stream'); + if(response.statusCode == 200) { + tl.debug(`Validation passed response: ${JSON.stringify(response)}`); + if (response.body && response.body.result){ + tl.warning(`${JSON.stringify(response.body.result)}`); + } + return null; + } + else if(response.statusCode == 400) { + tl.debug(`Validation failed response: ${JSON.stringify(response)}`); + throw response; + } + else { + tl.debug(`Skipping validation with status: ${response.statusCode}`); + return null; + } + } + catch(error) { + if (error && error.body && error.body.result && typeof error.body.result.valueOf() == 'string' && error.body.result.includes('ZipDeploy Validation ERROR')) { + throw Error(JSON.stringify(error.body.result)); + } + else { + tl.debug(`Skipping validation with error: ${error}`); + return null; + } + } + } +} diff --git a/Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service.ts b/Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service.ts new file mode 100644 index 000000000000..a0df5706f08d --- /dev/null +++ b/Tasks/AzureFunctionAppV1/azure-arm-rest/azure-arm-app-service.ts @@ -0,0 +1,824 @@ +import tl = require('azure-pipelines-task-lib/task'); +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); +import { + AzureEndpoint, + AzureAppServiceConfigurationDetails +} from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; + +import { + ServiceClient, + ToError +} from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/AzureServiceClient'; +import constants = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/constants'); +const CorrelationIdInResponse = "x-ms-correlation-request-id"; + +export class ServiceClient_1 extends ServiceClient{ + public async beginRequest(request: webClient.WebRequest, reqOptions?: webClient.WebRequestOptions): Promise { + var token = await this.getCredentials().getToken(); + + request.headers = request.headers || {}; + request.headers["Authorization"] = "Bearer " + token; + if (this.acceptLanguage) { + request.headers['accept-language'] = this.acceptLanguage; + } + request.headers['Content-Type'] = 'application/json; charset=utf-8'; + + var httpResponse = null; + + try + { + httpResponse = await webClient.sendRequest(request, reqOptions); + if (httpResponse.statusCode === 401 && httpResponse.body && httpResponse.body.error && httpResponse.body.error.code === "ExpiredAuthenticationToken") { + // The access token might have expire. Re-issue the request after refreshing the token. + token = await this.getCredentials().getToken(true); + request.headers["Authorization"] = "Bearer " + token; + httpResponse = await webClient.sendRequest(request, reqOptions); + } + + if(!!httpResponse.headers[CorrelationIdInResponse]) { + tl.debug(`Correlation ID from ARM api call response : ${httpResponse.headers[CorrelationIdInResponse]}`); + } + } catch(exception) { + let exceptionString: string = exception.toString(); + if(exceptionString.indexOf("Hostname/IP doesn't match certificates's altnames") != -1 + || exceptionString.indexOf("unable to verify the first certificate") != -1 + || exceptionString.indexOf("unable to get local issuer certificate") != -1) { + tl.warning(tl.loc('ASE_SSLIssueRecommendation')); + } + + throw exception; + } + + return httpResponse; + } +} + +export class AzureAppService { + private _resourceGroup: string; + private _name: string; + private _slot: string; + private _appKind: string; + private _isConsumptionApp: boolean; + public _client: ServiceClient_1; + private _appServiceConfigurationDetails: AzureAppServiceConfigurationDetails; + private _appServicePublishingProfile: any; + private _appServiceApplicationSetings: AzureAppServiceConfigurationDetails; + private _appServiceConfigurationSettings: AzureAppServiceConfigurationDetails; + private _appServiceConnectionString: AzureAppServiceConfigurationDetails; + + constructor(endpoint: AzureEndpoint, resourceGroup: string, name: string, slot?: string, appKind?: string, isConsumptionApp?: boolean) { + this._client = new ServiceClient_1(endpoint.applicationTokenCredentials, endpoint.subscriptionID, 30); + this._resourceGroup = resourceGroup; + this._name = name; + this._slot = (slot && slot.toLowerCase() == constants.productionSlot) ? null : slot; + this._appKind = appKind; + this._isConsumptionApp = isConsumptionApp; + } + + public async start(): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/start`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name + }, null, '2016-08-01'); + + console.log(tl.loc('StartingAppService', this._getFormattedName())); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('StartedAppService', this._getFormattedName())); + } + catch(error) { + throw Error(tl.loc('FailedToStartAppService', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async stop(): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/stop`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name + }, null, '2016-08-01'); + + console.log(tl.loc('StoppingAppService', this._getFormattedName())); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('StoppedAppService', this._getFormattedName())); + } + catch(error) { + throw Error(tl.loc('FailedToStopAppService', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async restart(): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/restart`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name + }, null, '2016-08-01'); + + console.log(tl.loc('RestartingAppService', this._getFormattedName())); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('RestartedAppService', this._getFormattedName())); + } + catch(error) { + throw Error(tl.loc('FailedToRestartAppService', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async swap(slotName: string, preserveVNet?: boolean): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + webRequest.body = JSON.stringify({ + targetSlot: slotName, + preserveVnet: preserveVNet + }); + + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/slotsswap`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name, + '{slotUrl}': slotUrl + }, null, '2016-08-01'); + + console.log(tl.loc('SwappingAppServiceSlotSlots', this._name, this.getSlot(), slotName)); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode == 202) { + response= await this._client.getLongRunningOperationResult(response); + } + + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('SwappedAppServiceSlotSlots', this._name, this.getSlot(), slotName)); + } + catch(error) { + throw Error(tl.loc('FailedToSwapAppServiceSlotSlots', this._name, this.getSlot(), slotName, this._client.getFormattedError(error))); + } + } + + public async get(force?: boolean): Promise { + if(force || !this._appServiceConfigurationDetails) { + this._appServiceConfigurationDetails = await this._get(); + } + + return this._appServiceConfigurationDetails; + } + + public async getPublishingProfileWithSecrets(force?: boolean): Promise{ + if(force || !this._appServicePublishingProfile) { + this._appServicePublishingProfile = await this._getPublishingProfileWithSecrets(); + } + + return this._appServicePublishingProfile; + } + + public async getPublishingCredentials(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/publishingcredentials/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServicePublishingCredentials', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async getApplicationSettings(force?: boolean): Promise { + if(force || !this._appServiceApplicationSetings) { + this._appServiceApplicationSetings = await this._getApplicationSettings(); + } + + return this._appServiceApplicationSetings; + } + + public async updateApplicationSettings(applicationSettings): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(applicationSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/appsettings`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceApplicationSettings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchApplicationSettings(addProperties: any, deleteProperties?: any, formatJSON?: boolean): Promise { + var applicationSettings = await this.getApplicationSettings(); + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(formatJSON) { + if(JSON.stringify(applicationSettings.properties[key]) != JSON.stringify(addProperties[key])) { + tl.debug(`Value of ${key} has been changed to ${JSON.stringify(addProperties[key])}`); + isNewValueUpdated = true; + } + else { + tl.debug(`${key} is already present.`); + } + } + else { + if(applicationSettings.properties[key] != addProperties[key]) { + tl.debug(`Value of ${key} has been changed to ${addProperties[key]}`); + isNewValueUpdated = true; + } + else { + tl.debug(`${key} is already present.`); + } + } + + applicationSettings.properties[key] = addProperties[key]; + } + for(var key in deleteProperties) { + if(key in applicationSettings.properties) { + delete applicationSettings.properties[key]; + tl.debug(`Removing app setting : ${key}`); + isNewValueUpdated = true; + } + } + + if(isNewValueUpdated) { + applicationSettings.properties[constants.WebsiteEnableSyncUpdateSiteKey] = this._isConsumptionApp ? 'false' : 'true'; + await this.updateApplicationSettings(applicationSettings); + } + + return isNewValueUpdated; + } + + public async patchApplicationSettingsSlot(addProperties: any): Promise { + var appSettingsSlotSettings = await this.getSlotConfigurationNames(); + let appSettingNames = appSettingsSlotSettings.properties.appSettingNames; + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(!appSettingNames) { + appSettingsSlotSettings.properties.appSettingNames = []; + appSettingNames = appSettingsSlotSettings.properties.appSettingNames; + } + if(addProperties[key].slotSetting == true) { + if((appSettingNames.length == 0) || (!appSettingNames.includes(addProperties[key].name))) { + appSettingNames.push(addProperties[key].name); + } + tl.debug(`Slot setting updated for key : ${addProperties[key].name}`); + isNewValueUpdated = true; + } + else if ((addProperties[key].slotSetting == false || (addProperties[key].slotSetting == null)) && appSettingNames != null ) { + const index = appSettingNames.indexOf(addProperties[key].name, 0); + if (index > -1) { + appSettingNames.splice(index, 1); + } + isNewValueUpdated = true; + } + } + + if(isNewValueUpdated) { + await this.updateSlotConfigSettings(appSettingsSlotSettings); + } + + } + + public async syncFunctionTriggers(): Promise { + try { + let i = 0; + let retryCount = 5; + let retryIntervalInSeconds = 2; + let timeToWait: number = retryIntervalInSeconds; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/syncfunctiontriggers`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + while(true) { + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode == 200) { + return response.body; + } + else if(response.statusCode == 400) { + if (++i < retryCount) { + await webClient.sleepFor(timeToWait); + timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds; + continue; + } + else { + throw ToError(response); + } + } + else { + throw ToError(response); + } + } + } + catch(error) { + throw Error(tl.loc('FailedToSyncTriggers', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async getConfiguration(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/web`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2018-02-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceConfiguration', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async updateConfiguration(applicationSettings): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(applicationSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/web`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2018-02-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceConfiguration', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchConfiguration(properties: any): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PATCH'; + httpRequest.body = JSON.stringify(properties); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/web`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2018-02-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToPatchAppServiceConfiguration', this._getFormattedName(), this._client.getFormattedError(error))); + } + + } + + public async getConnectionStrings(force?: boolean): Promise { + if(force || !this._appServiceConnectionString) { + this._appServiceConnectionString = await this._getConnectionStrings(); + } + + return this._appServiceConnectionString; + } + + public async getSlotConfigurationNames(force?: boolean): Promise { + if(force || !this._appServiceConfigurationSettings) { + this._appServiceConfigurationSettings = await this._getSlotConfigurationNames(); + } + + return this._appServiceConfigurationSettings; + } + + public async patchConnectionString(addProperties: any): Promise { + var connectionStringSettings = await this.getConnectionStrings(); + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(JSON.stringify(connectionStringSettings.properties[key]) != JSON.stringify(addProperties[key])) { + tl.debug(`Value of ${key} has been changed to ${JSON.stringify(addProperties[key])}`); + isNewValueUpdated = true; + } + else { + tl.debug(`${key} is already present.`); + } + connectionStringSettings.properties[key] = addProperties[key]; + } + + if(isNewValueUpdated) { + await this.updateConnectionStrings(connectionStringSettings); + } + } + + public async updateConnectionStrings(connectionStringSettings: any): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(connectionStringSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/connectionstrings`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceConnectionStrings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchConnectionStringSlot(addProperties: any): Promise { + var connectionStringSlotSettings = await this.getSlotConfigurationNames(); + let connectionStringNames = connectionStringSlotSettings.properties.connectionStringNames; + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(!connectionStringNames) { + connectionStringSlotSettings.properties.connectionStringNames = []; + connectionStringNames = connectionStringSlotSettings.properties.connectionStringNames; + } + if(addProperties[key].slotSetting == true) { + if((connectionStringNames.length == 0) || (!connectionStringNames.includes(key))) { + connectionStringNames.push(key); + } + tl.debug(`Slot setting updated for key : ${key}`); + isNewValueUpdated = true; + } + } + + if(isNewValueUpdated) { + await this.updateSlotConfigSettings(connectionStringSlotSettings); + } + } + + public async updateSlotConfigSettings(SlotConfigSettings: any): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(SlotConfigSettings); + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/slotConfigNames`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceConfigSlotSettings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async getMetadata(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/metadata/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceMetadata', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async updateMetadata(applicationSettings): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(applicationSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/metadata`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceMetadata', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchMetadata(properties): Promise { + var applicationSettings = await this.getMetadata(); + for(var key in properties) { + applicationSettings.properties[key] = properties[key]; + } + + await this.updateMetadata(applicationSettings); + } + + public getSlot(): string { + return this._slot ? this._slot : "production"; + } + + private async _getPublishingProfileWithSecrets(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/publishxml`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + var publishingProfile = response.body; + return publishingProfile; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServicePublishingProfile', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _getApplicationSettings(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/appsettings/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceApplicationSettings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _getConnectionStrings(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/connectionstrings/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceConnectionStrings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _getSlotConfigurationNames(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/slotConfigNames`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceSlotConfigurationNames', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _get(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + var appDetails = response.body; + return appDetails as AzureAppServiceConfigurationDetails; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceDetails', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private _getFormattedName(): string { + return this._slot ? `${this._name}-${this._slot}` : this._name; + } + + public getName(): string { + return this._name; + } + + public async getSitePublishingCredentialPolicies(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/basicPublishingCredentialsPolicies/scm`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get SitePublishingCredentialPolicies. Error: ${this._client.getFormattedError(error)}`); + } + } + + public async getSiteVirtualNetworkConnections(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/virtualNetworkConnections`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get Virtual Network Connections. Error: ${this._client.getFormattedError(error)}`); + } + } + + public async getSitePrivateEndpointConnections(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/privateEndpointConnections`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get Private Endpoint Connections. Error: ${this._client.getFormattedError(error)}`); + } + } + + public async getConnectionStringValidation(connectionDetails): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.body = JSON.stringify(connectionDetails); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/extensions/DaaS/api/connectionstringvalidation/validate/`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get Connection String Validation. Error: ${this._client.getFormattedError(error)}`); + } + } + } \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV1/azurermwebappdeployment.ts b/Tasks/AzureFunctionAppV1/azurermwebappdeployment.ts index 49b076a49ee6..ae574bda782c 100644 --- a/Tasks/AzureFunctionAppV1/azurermwebappdeployment.ts +++ b/Tasks/AzureFunctionAppV1/azurermwebappdeployment.ts @@ -1,6 +1,6 @@ import tl = require('azure-pipelines-task-lib/task'); import path = require('path'); -import * as Endpoint from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-endpoint'; +import * as Endpoint from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azure-arm-endpoint'; import { TaskParameters, TaskParametersUtility } from './taskparameters'; import { DeploymentFactory } from './deploymentProvider/DeploymentFactory'; @@ -9,7 +9,7 @@ async function main() { try { tl.setResourcePath(path.join( __dirname, 'task.json')); - tl.setResourcePath(path.join( __dirname, 'node_modules/azure-pipelines-tasks-azure-arm-rest-v2/module.json')); + tl.setResourcePath(path.join( __dirname, 'node_modules/azure-pipelines-tasks-azurermdeploycommon/module.json')); var taskParams: TaskParameters = await TaskParametersUtility.getParameters(); var deploymentFactory: DeploymentFactory = new DeploymentFactory(taskParams); var deploymentProvider = await deploymentFactory.GetDeploymentProvider(); @@ -29,7 +29,7 @@ async function main() { if(deploymentProvider != null) { await deploymentProvider.UpdateDeploymentStatus(isDeploymentSuccess); } - + Endpoint.dispose(); tl.debug(isDeploymentSuccess ? "Deployment Succeded" : "Deployment failed"); diff --git a/Tasks/AzureFunctionAppV1/deploymentProvider/AzureRmWebAppDeploymentProvider.ts b/Tasks/AzureFunctionAppV1/deploymentProvider/AzureRmWebAppDeploymentProvider.ts index 6200338f5b7e..fc4e585e09b0 100644 --- a/Tasks/AzureFunctionAppV1/deploymentProvider/AzureRmWebAppDeploymentProvider.ts +++ b/Tasks/AzureFunctionAppV1/deploymentProvider/AzureRmWebAppDeploymentProvider.ts @@ -1,12 +1,11 @@ import tl = require('azure-pipelines-task-lib/task'); -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { Kudu } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service-kudu'; -import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azure-arm-rest-v2/constants'; -import { AzureAppServiceUtility } from 'azure-pipelines-tasks-azure-arm-rest-v2/azureAppServiceUtility'; -import { PackageUtility } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' +import { PackageUtility } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' +import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azurermdeploycommon/Constants'; import { TaskParameters } from '../taskparameters'; -import { AzureAppServiceUtilityExt } from '../operations/AzureAppServiceUtilityExt'; +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; +import { Kudu } from '../azure-arm-rest/azure-arm-app-service-kudu'; +import { AzureAppServiceUtility } from '../operations/AzureAppServiceUtility'; import { KuduServiceUtility } from '../operations/KuduServiceUtility'; import { addReleaseAnnotation } from '../operations/ReleaseAnnotationUtility'; import { IWebAppDeploymentProvider } from './IWebAppDeploymentProvider'; @@ -16,7 +15,6 @@ export class AzureRmWebAppDeploymentProvider implements IWebAppDeploymentProvide protected appService: AzureAppService; protected kuduService: Kudu; protected appServiceUtility: AzureAppServiceUtility; - protected appServiceUtilityExt: AzureAppServiceUtilityExt; protected kuduServiceUtility: KuduServiceUtility; protected virtualApplicationPath: string = ""; protected activeDeploymentID; @@ -31,12 +29,11 @@ export class AzureRmWebAppDeploymentProvider implements IWebAppDeploymentProvide this.appService = new AzureAppService(this.taskParams.azureEndpoint, this.taskParams.ResourceGroupName, this.taskParams.WebAppName, this.taskParams.SlotName, this.taskParams.WebAppKind); this.appServiceUtility = new AzureAppServiceUtility(this.appService); - this.appServiceUtilityExt = new AzureAppServiceUtilityExt(this.appService); this.kuduService = await this.appServiceUtility.getKuduService(); this.kuduServiceUtility = new KuduServiceUtility(this.kuduService); - await this.appServiceUtilityExt.getFuntionAppNetworkingCheck(this.taskParams.isLinuxApp); + await this.appServiceUtility.getFuntionAppNetworkingCheck(this.taskParams.isLinuxApp); } public async DeployWebAppStep() {} @@ -63,9 +60,9 @@ export class AzureRmWebAppDeploymentProvider implements IWebAppDeploymentProvide if (this.taskParams.ConfigurationSettings) { var customApplicationSettings = ParameterParser.parse(this.taskParams.ConfigurationSettings); - await this.appService.updateConfigurationSettings(customApplicationSettings); + await this.appServiceUtility.updateConfigurationSettings(customApplicationSettings); } - await this.appServiceUtilityExt.updateScmTypeAndConfigurationDetails(); + await this.appServiceUtility.updateScmTypeAndConfigurationDetails(); } } \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV1/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts b/Tasks/AzureFunctionAppV1/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts index 011630349229..75cf82e5d6f1 100644 --- a/Tasks/AzureFunctionAppV1/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts +++ b/Tasks/AzureFunctionAppV1/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts @@ -1,9 +1,9 @@ import tl = require('azure-pipelines-task-lib/task'); import path = require('path'); -var webCommonUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' +var webCommonUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; const linuxFunctionStorageSetting: string = '-WEBSITES_ENABLE_APP_SERVICE_STORAGE true'; @@ -40,7 +40,7 @@ export class BuiltInLinuxWebAppDeploymentProvider extends AzureRmWebAppDeploymen tl.debug('Performing Linux built-in package deployment'); var isNewValueUpdated: boolean = false; - + var linuxFunctionRuntimeSetting = ""; if(this.taskParams.RuntimeStack && linuxFunctionRuntimeSettingValue.get(this.taskParams.RuntimeStack)) { linuxFunctionRuntimeSetting = linuxFunctionRuntimeSettingName + linuxFunctionRuntimeSettingValue.get(this.taskParams.RuntimeStack); @@ -52,11 +52,11 @@ export class BuiltInLinuxWebAppDeploymentProvider extends AzureRmWebAppDeploymen var customApplicationSetting = ParameterParser.parse(linuxFunctionAppSetting); isNewValueUpdated = await this.appServiceUtility.updateAndMonitorAppSettings(customApplicationSetting); - + if(!isNewValueUpdated) { await this.kuduServiceUtility.warmpUp(); } - + switch(packageType){ case PackageType.folder: let tempPackagePath = webCommonUtility.generateTemporaryFolderOrZipPath(tl.getVariable('AGENT.TEMPDIRECTORY'), false); @@ -94,7 +94,7 @@ export class BuiltInLinuxWebAppDeploymentProvider extends AzureRmWebAppDeploymen case PackageType.war: tl.debug("Initiated deployment via kudu service for webapp war package : "+ this.taskParams.Package.getPath()); var warName = webCommonUtility.getFileNameFromPath(this.taskParams.Package.getPath(), ".war"); - this.zipDeploymentID = await this.kuduServiceUtility.deployUsingWarDeploy(this.taskParams.Package.getPath(), + this.zipDeploymentID = await this.kuduServiceUtility.deployUsingWarDeploy(this.taskParams.Package.getPath(), { slotName: this.appService.getSlot() }, warName); break; diff --git a/Tasks/AzureFunctionAppV1/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts b/Tasks/AzureFunctionAppV1/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts index b2ceeeccf51e..30b38ec169cd 100644 --- a/Tasks/AzureFunctionAppV1/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts +++ b/Tasks/AzureFunctionAppV1/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts @@ -1,13 +1,13 @@ import tl = require('azure-pipelines-task-lib/task'); import Q = require('q'); -var webCommonUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); +var webCommonUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); var azureStorage = require('azure-storage'); -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { sleepFor } from 'azure-pipelines-tasks-azure-arm-rest-v2/webClient'; -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility'; -import { AzureAppServiceUtilityExt } from '../operations/AzureAppServiceUtilityExt'; +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; +import { sleepFor } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'; +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility'; +import { AzureAppServiceUtility } from '../operations/AzureAppServiceUtility'; import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeploymentProvider { @@ -15,7 +15,7 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment public async PreDeploymentStep() { this.appService = new AzureAppService(this.taskParams.azureEndpoint, this.taskParams.ResourceGroupName, this.taskParams.WebAppName, this.taskParams.SlotName, this.taskParams.WebAppKind, true); - this.appServiceUtilityExt = new AzureAppServiceUtilityExt(this.appService); + this.appServiceUtility = new AzureAppServiceUtility(this.appService); } public async DeployWebAppStep() { @@ -89,7 +89,7 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment let expiryDate = new Date(startDate); expiryDate.setFullYear(startDate.getUTCFullYear() + 1); startDate.setMinutes(startDate.getMinutes()-5); - + let sharedAccessPolicy = { AccessPolicy: { Permissions: azureStorage.BlobUtilities.SharedAccessPermissions.READ, @@ -97,7 +97,7 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment Expiry: expiryDate } }; - + let token = blobService.generateSharedAccessSignature(containerName, blobName, sharedAccessPolicy); let sasUrl = blobService.getUrl(containerName, blobName, token); let index = sasUrl.indexOf("?"); @@ -128,10 +128,10 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment protected async PostDeploymentStep() { if(this.taskParams.ConfigurationSettings) { var customApplicationSettings = ParameterParser.parse(this.taskParams.ConfigurationSettings); - await this.appService.updateConfigurationSettings(customApplicationSettings); + await this.appServiceUtility.updateConfigurationSettings(customApplicationSettings); } - await this.appServiceUtilityExt.updateScmTypeAndConfigurationDetails(); + await this.appServiceUtility.updateScmTypeAndConfigurationDetails(); } private _getUserDefinedAppSettings() { diff --git a/Tasks/AzureFunctionAppV1/deploymentProvider/DeploymentFactory.ts b/Tasks/AzureFunctionAppV1/deploymentProvider/DeploymentFactory.ts index fa8160607436..e59d1e0fb7f2 100644 --- a/Tasks/AzureFunctionAppV1/deploymentProvider/DeploymentFactory.ts +++ b/Tasks/AzureFunctionAppV1/deploymentProvider/DeploymentFactory.ts @@ -1,5 +1,5 @@ import tl = require('azure-pipelines-task-lib/task'); -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; import { TaskParameters, DeploymentType } from '../taskparameters'; import { BuiltInLinuxWebAppDeploymentProvider } from './BuiltInLinuxWebAppDeploymentProvider'; import { ConsumptionWebAppDeploymentProvider } from './ConsumptionWebAppDeploymentProvider'; @@ -23,7 +23,7 @@ export class DeploymentFactory { return new ConsumptionWebAppDeploymentProvider(this._taskParams); } else { return new BuiltInLinuxWebAppDeploymentProvider(this._taskParams); - } + } } else { tl.debug("Deployment started for windows app service"); return await this._getWindowsDeploymentProvider() @@ -45,11 +45,11 @@ export class DeploymentFactory { private async _getWindowsDeploymentProviderForZipAndFolderPackageType(): Promise { if(this._taskParams.DeploymentType != DeploymentType.auto) { return await this._getUserSelectedDeploymentProviderForWindow(); - } else { - var _isMSBuildPackage = await this._taskParams.Package.isMSBuildPackage(); + } else { + var _isMSBuildPackage = await this._taskParams.Package.isMSBuildPackage(); if(_isMSBuildPackage) { throw new Error(tl.loc('MsBuildPackageNotSupported', this._taskParams.Package.getPath())); - } else { + } else { return new WindowsWebAppRunFromZipProvider(this._taskParams); } } diff --git a/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppRunFromZipProvider.ts b/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppRunFromZipProvider.ts index 4e09e4609390..47f8f702ea8a 100644 --- a/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppRunFromZipProvider.ts +++ b/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppRunFromZipProvider.ts @@ -1,10 +1,10 @@ import tl = require('azure-pipelines-task-lib/task'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' +var deployUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' import { DeploymentType } from '../taskparameters'; -import { FileTransformsUtility } from '../operations/FileTransformsUtility'; +import { FileTransformsUtility } from 'azure-pipelines-tasks-azurermdeploycommon/operations/FileTransformsUtility.js'; import { addReleaseAnnotation } from '../operations/ReleaseAnnotationUtility'; import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; @@ -36,7 +36,7 @@ export class WindowsWebAppRunFromZipProvider extends AzureRmWebAppDeploymentProv } tl.debug("Initiated deployment via kudu service for webapp package : "); - + var addCustomApplicationSetting = ParameterParser.parse(runFromZipAppSetting); var deleteCustomApplicationSetting = ParameterParser.parse(oldRunFromZipAppSetting); var isNewValueUpdated: boolean = await this.appServiceUtility.updateAndMonitorAppSettings(addCustomApplicationSetting, deleteCustomApplicationSetting); @@ -45,16 +45,16 @@ export class WindowsWebAppRunFromZipProvider extends AzureRmWebAppDeploymentProv await this.kuduServiceUtility.warmpUp(); } - await this.kuduServiceUtility.getZipDeployValidation(webPackage); - await this.kuduServiceUtility.deployUsingRunFromZip(webPackage, + await this.kuduServiceUtility.getZipDeployValidation(webPackage); + await this.kuduServiceUtility.deployUsingRunFromZip(webPackage, { slotName: this.appService.getSlot() }); await this.PostDeploymentStep(); } - + public async UpdateDeploymentStatus(isDeploymentSuccess: boolean) { await addReleaseAnnotation(this.taskParams.azureEndpoint, this.appService, isDeploymentSuccess); - + let appServiceApplicationUrl: string = await this.appServiceUtility.getApplicationURL(); console.log(tl.loc('AppServiceApplicationURL', appServiceApplicationUrl)); tl.setVariable('AppServiceApplicationUrl', appServiceApplicationUrl); diff --git a/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppWarDeployProvider.ts b/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppWarDeployProvider.ts index 6f0196606728..5ea62a6e511d 100644 --- a/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppWarDeployProvider.ts +++ b/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppWarDeployProvider.ts @@ -1,5 +1,5 @@ import tl = require('azure-pipelines-task-lib/task'); -var webCommonUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); +var webCommonUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; export class WindowsWebAppWarDeployProvider extends AzureRmWebAppDeploymentProvider { diff --git a/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppZipDeployProvider.ts b/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppZipDeployProvider.ts index 761cb875b1e0..d3dcdae0f306 100644 --- a/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppZipDeployProvider.ts +++ b/Tasks/AzureFunctionAppV1/deploymentProvider/WindowsWebAppZipDeployProvider.ts @@ -1,10 +1,10 @@ import tl = require('azure-pipelines-task-lib/task'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' +var deployUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' import { DeploymentType } from '../taskparameters'; -import { FileTransformsUtility } from '../operations/FileTransformsUtility'; +import { FileTransformsUtility } from 'azure-pipelines-tasks-azurermdeploycommon/operations/FileTransformsUtility.js'; import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; const removeRunFromZipAppSetting: string = '-WEBSITE_RUN_FROM_ZIP -WEBSITE_RUN_FROM_PACKAGE'; @@ -36,7 +36,7 @@ export class WindowsWebAppZipDeployProvider extends AzureRmWebAppDeploymentProvi } tl.debug("Initiated deployment via kudu service for webapp package : "); - + var deleteApplicationSetting = ParameterParser.parse(removeRunFromZipAppSetting) var isNewValueUpdated: boolean = await this.appServiceUtility.updateAndMonitorAppSettings(null, deleteApplicationSetting); @@ -44,12 +44,12 @@ export class WindowsWebAppZipDeployProvider extends AzureRmWebAppDeploymentProvi await this.kuduServiceUtility.warmpUp(); } - await this.kuduServiceUtility.getZipDeployValidation(webPackage); + await this.kuduServiceUtility.getZipDeployValidation(webPackage); this.zipDeploymentID = await this.kuduServiceUtility.deployUsingZipDeploy(webPackage); await this.PostDeploymentStep(); } - + public async UpdateDeploymentStatus(isDeploymentSuccess: boolean) { if(this.kuduServiceUtility) { await super.UpdateDeploymentStatus(isDeploymentSuccess); diff --git a/Tasks/AzureFunctionAppV1/make.json b/Tasks/AzureFunctionAppV1/make.json index 8733fe153102..65aef0088694 100644 --- a/Tasks/AzureFunctionAppV1/make.json +++ b/Tasks/AzureFunctionAppV1/make.json @@ -1,12 +1,4 @@ { - "rm": [ - { - "items": [ - "node_modules/azure-pipelines-tasks-azure-arm-rest-v2/node_modules/azure-pipelines-task-lib" - ], - "options": "-Rf" - } - ], "externals": { "archivePackages": [ { diff --git a/Tasks/AzureFunctionAppV1/operations/AzureAppServiceUtilityExt.ts b/Tasks/AzureFunctionAppV1/operations/AzureAppServiceUtility.ts similarity index 56% rename from Tasks/AzureFunctionAppV1/operations/AzureAppServiceUtilityExt.ts rename to Tasks/AzureFunctionAppV1/operations/AzureAppServiceUtility.ts index 6c1cda775605..6b027f5522b0 100644 --- a/Tasks/AzureFunctionAppV1/operations/AzureAppServiceUtilityExt.ts +++ b/Tasks/AzureFunctionAppV1/operations/AzureAppServiceUtility.ts @@ -1,10 +1,14 @@ import tl = require('azure-pipelines-task-lib/task'); -var glob = require("glob"); +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); +var parseString = require('xml2js').parseString; +import Q = require('q'); +import { Kudu } from '../azure-arm-rest/azure-arm-app-service-kudu'; +import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azurermdeploycommon/Constants'; import * as os from "os"; -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azure-arm-rest-v2/constants'; +var glob = require("glob"); -export class AzureAppServiceUtilityExt { +export class AzureAppServiceUtility { private _appService: AzureAppService; constructor(appService: AzureAppService) { this._appService = appService; @@ -57,6 +61,169 @@ export class AzureAppServiceUtilityExt { } } + public async getWebDeployPublishingProfile(): Promise { + var publishingProfile = await this._appService.getPublishingProfileWithSecrets(); + var defer = Q.defer(); + parseString(publishingProfile, (error, result) => { + if(!!error) { + defer.reject(error); + } + var publishProfile = result && result.publishData && result.publishData.publishProfile ? result.publishData.publishProfile : null; + if(publishProfile) { + for (var index in publishProfile) { + if (publishProfile[index].$ && publishProfile[index].$.publishMethod === "MSDeploy") { + defer.resolve(result.publishData.publishProfile[index].$); + } + } + } + + defer.reject(tl.loc('ErrorNoSuchDeployingMethodExists')); + }); + + return defer.promise; + } + + public async getApplicationURL(virtualApplication?: string): Promise { + let webDeployProfile: any = await this.getWebDeployPublishingProfile(); + return await webDeployProfile.destinationAppUrl + ( virtualApplication ? "/" + virtualApplication : "" ); + } + + public async pingApplication(): Promise { + try { + var applicationUrl: string = await this.getApplicationURL(); + + if(!applicationUrl) { + tl.debug("Application Url not found."); + return; + } + await AzureAppServiceUtility.pingApplication(applicationUrl); + } catch(error) { + tl.debug("Unable to ping App Service. Error: ${error}"); + } + } + + public static async pingApplication(applicationUrl: string) { + if(!applicationUrl) { + tl.debug('Application Url empty.'); + return; + } + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'GET'; + webRequest.uri = applicationUrl; + let webRequestOptions: webClient.WebRequestOptions = {retriableErrorCodes: [], retriableStatusCodes: [], retryCount: 1, retryIntervalInSeconds: 5, retryRequestTimedout: true}; + var response = await webClient.sendRequest(webRequest, webRequestOptions); + tl.debug(`App Service status Code: '${response.statusCode}'. Status Message: '${response.statusMessage}'`); + } + catch(error) { + tl.debug(`Unable to ping App Service. Error: ${error}`); + } + } + + public async getKuduService(): Promise { + var publishingCredentials = await this._appService.getPublishingCredentials(); + var scmPolicyCheck = await this.isSitePublishingCredentialsEnabled(); + + + if(publishingCredentials.properties["scmUri"]) { + if(scmPolicyCheck === false) { + tl.debug('Gettting Bearer token'); + var accessToken = await this._appService._client.getCredentials().getToken(); + } + else{ + tl.setVariable(`AZURE_APP_SERVICE_KUDU_${this._appService.getSlot()}_PASSWORD`, publishingCredentials.properties["publishingPassword"], true); + var accessToken = (new Buffer(publishingCredentials.properties["publishingUserName"] + ':' + publishingCredentials.properties["publishingPassword"]).toString('base64')); + } + return new Kudu(publishingCredentials.properties["scmUri"], accessToken, scmPolicyCheck); + } + + throw Error(tl.loc('KuduSCMDetailsAreEmpty')); + } + + public async getPhysicalPath(virtualApplication: string): Promise { + + if(!virtualApplication) { + return '/site/wwwroot'; + } + + virtualApplication = (virtualApplication.startsWith("/")) ? virtualApplication.substr(1) : virtualApplication; + + var physicalToVirtualPathMap = await this._getPhysicalToVirtualPathMap(virtualApplication); + + if(!physicalToVirtualPathMap) { + throw Error(tl.loc("VirtualApplicationDoesNotExist", virtualApplication)); + } + + tl.debug(`Virtual Application Map: Physical path: '${physicalToVirtualPathMap.physicalPath}'. Virtual path: '${physicalToVirtualPathMap.virtualPath}'.`); + return physicalToVirtualPathMap.physicalPath; + } + + public async updateConfigurationSettings(properties: any, formatJSON?: boolean) : Promise { + if(formatJSON) { + var configurationSettingsProperties = properties[0]; + console.log(tl.loc('UpdatingAppServiceConfigurationSettings', JSON.stringify(configurationSettingsProperties))); + await this._appService.patchConfiguration({'properties': configurationSettingsProperties}); + } + else + { + for(var property in properties) { + if(!!properties[property] && properties[property].value !== undefined) { + properties[property] = properties[property].value; + } + } + + console.log(tl.loc('UpdatingAppServiceConfigurationSettings', JSON.stringify(properties))); + await this._appService.patchConfiguration({'properties': properties}); + } + console.log(tl.loc('UpdatedAppServiceConfigurationSettings')); + } + + public async updateAndMonitorAppSettings(addProperties?: any, deleteProperties?: any, formatJSON?: boolean): Promise { + if(formatJSON) { + var appSettingsProperties = {}; + for(var property in addProperties) { + appSettingsProperties[addProperties[property].name] = addProperties[property].value; + } + + if(!!addProperties) { + console.log(tl.loc('UpdatingAppServiceApplicationSettings', JSON.stringify(appSettingsProperties))); + } + + if(!!deleteProperties) { + console.log(tl.loc('DeletingAppServiceApplicationSettings', JSON.stringify(Object.keys(deleteProperties)))); + } + + var isNewValueUpdated: boolean = await this._appService.patchApplicationSettings(appSettingsProperties, deleteProperties, true); + } + else { + for(var property in addProperties) { + if(!!addProperties[property] && addProperties[property].value !== undefined) { + addProperties[property] = addProperties[property].value; + } + } + + if(!!addProperties) { + console.log(tl.loc('UpdatingAppServiceApplicationSettings', JSON.stringify(addProperties))); + } + + if(!!deleteProperties) { + console.log(tl.loc('DeletingAppServiceApplicationSettings', JSON.stringify(Object.keys(deleteProperties)))); + } + + var isNewValueUpdated: boolean = await this._appService.patchApplicationSettings(addProperties, deleteProperties); + } + + if(!!isNewValueUpdated) { + console.log(tl.loc('UpdatedAppServiceApplicationSettings')); + } + else { + console.log(tl.loc('AppServiceApplicationSettingsAlreadyPresent')); + } + + await this._appService.patchApplicationSettingsSlot(addProperties); + return isNewValueUpdated; + } + public async updateConnectionStrings(addProperties: any): Promise { var connectionStringProperties = {}; for(var property in addProperties) { @@ -84,13 +251,69 @@ export class AzureAppServiceUtilityExt { return isNewValueUpdated; } + public async enableRenameLockedFiles(): Promise { + try { + var webAppSettings = await this._appService.getApplicationSettings(); + if(webAppSettings && webAppSettings.properties) { + if(webAppSettings.properties.MSDEPLOY_RENAME_LOCKED_FILES !== '1') { + tl.debug(`Rename locked files value found to be ${webAppSettings.properties.MSDEPLOY_RENAME_LOCKED_FILES}. Updating the value to 1`); + await this.updateAndMonitorAppSettings({ 'MSDEPLOY_RENAME_LOCKED_FILES' : '1' }); + console.log(tl.loc('RenameLockedFilesEnabled')); + } + else { + tl.debug('Rename locked files is already enabled in App Service'); + } + } + } + catch(error) { + throw new Error(tl.loc('FailedToEnableRenameLockedFiles', error)); + } + } + + public async updateStartupCommandAndRuntimeStack(runtimeStack: string, startupCommand?: string): Promise { + var configDetails = await this._appService.getConfiguration(); + var appCommandLine: string = configDetails.properties.appCommandLine; + startupCommand = (!!startupCommand) ? startupCommand : appCommandLine; + var linuxFxVersion: string = configDetails.properties.linuxFxVersion; + runtimeStack = (!!runtimeStack) ? runtimeStack : linuxFxVersion; + + if (startupCommand != appCommandLine || runtimeStack != linuxFxVersion) { + await this.updateConfigurationSettings({linuxFxVersion: runtimeStack, appCommandLine: startupCommand}); + } + else { + tl.debug(`Skipped updating the values. linuxFxVersion: ${linuxFxVersion} : appCommandLine: ${appCommandLine}`) + } + } + + private async _getPhysicalToVirtualPathMap(virtualApplication: string): Promise { + // construct URL depending on virtualApplication or root of webapplication + var physicalPath = null; + var virtualPath = "/" + virtualApplication; + var appConfigSettings = await this._appService.getConfiguration(); + var virtualApplicationMappings = appConfigSettings.properties && appConfigSettings.properties.virtualApplications; + + if(virtualApplicationMappings) { + for( var mapping of virtualApplicationMappings ) { + if(mapping.virtualPath.toLowerCase() == virtualPath.toLowerCase()) { + physicalPath = mapping.physicalPath; + break; + } + } + } + + return physicalPath ? { + 'virtualPath': virtualPath, + 'physicalPath': physicalPath + }: null; + } + private _getNewMetadata(): any { var collectionUri = tl.getVariable("system.teamfoundationCollectionUri"); var projectId = tl.getVariable("system.teamprojectId"); var releaseDefinitionId = tl.getVariable("release.definitionId"); // Log metadata properties based on whether task is running in build OR release. - + let newProperties = { VSTSRM_ProjectId: projectId, VSTSRM_AccountId: tl.getVariable("system.collectionId") @@ -100,7 +323,7 @@ export class AzureAppServiceUtilityExt { // Task is running in Release var artifactAlias = tl.getVariable(AzureDeployPackageArtifactAlias); tl.debug("Artifact Source Alias is: "+ artifactAlias); - + let buildDefinitionUrl = ""; let buildDefintionId = ""; @@ -122,7 +345,7 @@ export class AzureAppServiceUtilityExt { buildDefinitionUrl = collectionUri + buildProjectId + "/_build?_a=simple-process&definitionId=" + buildDefintionId; } } - + newProperties["VSTSRM_BuildDefinitionId"] = buildDefintionId; newProperties["VSTSRM_ReleaseDefinitionId"] = releaseDefinitionId; newProperties["VSTSRM_BuildDefinitionWebAccessUrl"] = buildDefinitionUrl; @@ -144,27 +367,27 @@ export class AzureAppServiceUtilityExt { try{ let isFuncPrivate = await this.isFuncWithPrivateEndpoint(); let isMicrosoftHostedAgent = await this.isMicrosoftHostedAgent(); - + if (isFuncPrivate == "true" && isMicrosoftHostedAgent == "true"){ //will NOT be able to reach kudu site if isFuncPrivate and isMicrosoftHostedAgent tl.warning("ERROR: Function app has private endpoint(s). But you are not running this pipeline from a self-hosted agent that has access to the Functions App. Relevant documentation: " + "https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&tabs=browser#install"); } - else if (isFuncPrivate == "true"){ + else if (isFuncPrivate == "true"){ //just FYI console.log("NOTE: Function app has private endpoint(s). Therefore, make sure that you are running this pipeline from a self-hosted agent that has access to the Functions App."); } - + let isFuncVNet = await this.isFuncVnetIntegrated(); if (isFuncVNet == "true"){ //just FYI console.log("NOTE: Function app is VNet integrated."); } - + //network validation only avaliable for Windows (NOT Linux) if (!isLinuxApp) { let errormessage = await this.isAzureWebJobsStorageAccessible(); - + if (errormessage){ //AzureWebJobsStorage connection string is NOT accessible from Kudu tl.warning(`ERROR: ${errormessage}`); @@ -173,12 +396,12 @@ export class AzureAppServiceUtilityExt { if (isFuncVNet == "false"){ console.log("NOTE: Function app is NOT VNet integrated."); } - } - } + } + } } catch(error){ tl.debug(`Skipping networking check with error: ${error}`); - } + } } public async isFuncWithPrivateEndpoint(): Promise{ @@ -189,7 +412,7 @@ export class AzureAppServiceUtilityExt { tl.debug("Function app has Private Endpoints."); return "true"; } - else { + else { tl.debug("Function app has NO Private Endpoints."); return "false"; } @@ -197,28 +420,47 @@ export class AzureAppServiceUtilityExt { catch(error){ tl.debug(`Skipping private endpoint check: ${error}`); return null; - } + } } public async isFuncVnetIntegrated(): Promise{ try{ let vnet: any = await this._appService.getSiteVirtualNetworkConnections(); tl.debug(`VNET check: ${JSON.stringify(vnet)}`); - if (vnet && vnet.length && vnet.length > 0){ + if (vnet && vnet.length && vnet.length > 0){ tl.debug("Function app is VNet integrated."); return "true"; } - else { + else { tl.debug("Function app is NOT VNet integrated."); return "false"; - } + } } catch(error){ tl.debug(`Skipping VNET check: ${error}`); return null; - } + } } + public async isSitePublishingCredentialsEnabled(): Promise{ + try{ + let scmAuthPolicy: any = await this._appService.getSitePublishingCredentialPolicies(); + tl.debug(`Site Publishing Policy check: ${JSON.stringify(scmAuthPolicy)}`); + if(scmAuthPolicy && scmAuthPolicy.properties.allow) { + tl.debug("Function App does allow SCM access"); + return true; + } + else { + tl.debug("Function App does not allow SCM Access"); + return false; + } + } + catch(error){ + tl.debug(`Call to get SCM Policy check failed: ${error}`); + return false; + } + } + public async isMicrosoftHostedAgent(): Promise{ try{ let agentos = os.type(); @@ -226,10 +468,10 @@ export class AzureAppServiceUtilityExt { if (agentos.match(/^Window/)){ tl.debug(`Windows Agent`); - dir = "C:\\agents\\*\\.setup_info"; + dir = "C:\\agents\\*\\.setup_info"; } - else if (agentos.match(/^Linux/)){ - tl.debug(`Linux Agent`); + else if (agentos.match(/^Linux/)){ + tl.debug(`Linux Agent`); dir = `${process.env.HOME}/agents/*/.setup_info`; } else if (agentos.match(/^Darwin/)){ @@ -237,7 +479,7 @@ export class AzureAppServiceUtilityExt { dir = `${process.env.HOME}/runners/*/.setup_info`; } - var files = glob.sync(dir); + var files = glob.sync(dir); if (files && files.length && files.length > 0) { tl.debug(`Running on Microsoft-hosted agent.`); return "true"; @@ -250,16 +492,16 @@ export class AzureAppServiceUtilityExt { } } - public async isAzureWebJobsStorageAccessible(): Promise{ + public async isAzureWebJobsStorageAccessible(): Promise{ let errormessage = ""; let propertyName = "AzureWebJobsStorage"; let appSettings = await this._appService.getApplicationSettings(); if(appSettings && appSettings.properties && appSettings.properties.AzureWebJobsStorage) { let connectionDetails = {}; - connectionDetails['ConnectionString'] = appSettings.properties.AzureWebJobsStorage; + connectionDetails['ConnectionString'] = appSettings.properties.AzureWebJobsStorage; connectionDetails['Type'] = 'StorageAccount'; - + if(connectionDetails['ConnectionString'].includes('@Microsoft.KeyVault')){ console.log("NOTE: Skipping AzureWebJobsStorage connection string validation since Key Vault reference is used."); } @@ -281,7 +523,7 @@ export class AzureAppServiceUtilityExt { * MalformedConnectionString, * UnknownError */ - if (validation && validation.StatusText && validation.StatusText != "Success"){ + if (validation && validation.StatusText && validation.StatusText != "Success"){ switch (validation.StatusText) { case "MalformedConnectionString": @@ -298,8 +540,8 @@ export class AzureAppServiceUtilityExt { break; case "Forbidden": // Some authentication failures come through as Forbidden so check the exception data - if(validation.Exception != undefined && - validation.Exception.RequestInformation != undefined && + if(validation.Exception != undefined && + validation.Exception.RequestInformation != undefined && JSON.stringify(validation.Exception.RequestInformation).includes("AuthenticationFailed")) { errormessage = `Authentication failure - The credentials in the "${propertyName}" connection string are either invalid or expired. Please update the app setting "${propertyName}" with a valid connection string.`; } else { @@ -313,9 +555,9 @@ export class AzureAppServiceUtilityExt { } // Show the exception message as it contains useful information to fix the issue. Don't show it unless its accompanied with other explanations. errormessage += (errormessage != "" && validation.Exception ? `\r\n\r\nException encountered while connecting: ${validation.Exception.Message}` : undefined); - } + } } - } - return errormessage; + } + return errormessage; } } \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV1/operations/FileTransformsUtility.ts b/Tasks/AzureFunctionAppV1/operations/FileTransformsUtility.ts deleted file mode 100644 index fdf1fef18a03..000000000000 --- a/Tasks/AzureFunctionAppV1/operations/FileTransformsUtility.ts +++ /dev/null @@ -1,29 +0,0 @@ -import tl = require('azure-pipelines-task-lib/task'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var generateWebConfigUtil = require('azure-pipelines-tasks-webdeployment-common/webconfigutil'); -import { parse } from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility'; -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; - -export class FileTransformsUtility { - private static rootDirectoryPath: string = "D:\\home\\site\\wwwroot"; - public static async applyTransformations(webPackage: string, parameters: string, packageType: PackageType): Promise { - tl.debug("WebConfigParameters is "+ parameters); - if (parameters) { - var isFolderBasedDeployment: boolean = tl.stats(webPackage).isDirectory(); - var folderPath = await deployUtility.generateTemporaryFolderForDeployment(isFolderBasedDeployment, webPackage, packageType); - if (parameters) { - tl.debug('parsing web.config parameters'); - var webConfigParameters = parse(parameters); - generateWebConfigUtil.addWebConfigFile(folderPath, webConfigParameters, this.rootDirectoryPath); - } - - var output = await deployUtility.archiveFolderForDeployment(isFolderBasedDeployment, folderPath); - webPackage = output.webDeployPkg; - } - else { - tl.debug('File Tranformation not enabled'); - } - - return webPackage; - } -} \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV1/operations/KuduServiceUtility.ts b/Tasks/AzureFunctionAppV1/operations/KuduServiceUtility.ts index c2b9d790c135..1726052df4eb 100644 --- a/Tasks/AzureFunctionAppV1/operations/KuduServiceUtility.ts +++ b/Tasks/AzureFunctionAppV1/operations/KuduServiceUtility.ts @@ -1,10 +1,11 @@ import tl = require('azure-pipelines-task-lib/task'); import path = require('path'); -import webClient = require('azure-pipelines-tasks-azure-arm-rest-v2/webClient'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import { Kudu } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service-kudu'; -import { AzureDeployPackageArtifactAlias, KUDU_DEPLOYMENT_CONSTANTS } from 'azure-pipelines-tasks-azure-arm-rest-v2/constants'; +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); +var deployUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import { Kudu } from '../azure-arm-rest/azure-arm-app-service-kudu'; +import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azurermdeploycommon/Constants'; +import { KUDU_DEPLOYMENT_CONSTANTS } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/constants'; const physicalRootPath: string = '/site/wwwroot'; const deploymentFolder: string = 'site/deployments'; @@ -89,7 +90,7 @@ export class KuduServiceUtility { return deploymentDetails.id; } catch(error) { - let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTraceUrl(); + let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTrace(); tl.error(tl.loc('PackageDeploymentFailed')); tl.error(tl.loc('KuduStackTraceURL', stackTraceUrl)); throw Error(error); @@ -112,7 +113,7 @@ export class KuduServiceUtility { console.log("NOTE: Run From Package makes wwwroot read-only, so you will receive an error when writing files to this directory."); } catch(error) { - let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTraceUrl(); + let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTrace(); tl.error(tl.loc('PackageDeploymentFailed')); tl.error(tl.loc('KuduStackTraceURL', stackTraceUrl)); throw Error(error); @@ -126,7 +127,7 @@ export class KuduServiceUtility { let queryParameters: Array = [ 'isAsync=true' ]; - + if(targetFolderName) { queryParameters.push('name=' + encodeURIComponent(targetFolderName)); } @@ -283,9 +284,9 @@ export class KuduServiceUtility { repositoryUrl = tl.getVariable("build.repository.uri") || ""; branch = tl.getVariable("build.sourcebranchname") || tl.getVariable("build.sourcebranch"); } - + deploymentID = !!deploymentID ? deploymentID : this.getDeploymentID(); - + var message = { type : "deployment", commitId : commitId, @@ -308,7 +309,7 @@ export class KuduServiceUtility { for(var attribute in customMessage) { message[attribute] = customMessage[attribute]; } - + } var deploymentLogType: string = message['type']; var active: boolean = false; @@ -327,7 +328,7 @@ export class KuduServiceUtility { } public async getZipDeployValidation(packagePath: string, zipLanguage?: string, zipIs64Bit?: string): Promise { - try { + try { console.log("Validating deployment package for functions app before Zip Deploy"); let queryParameters: Array = [ 'zipLanguage=' + !!zipLanguage ? zipLanguage : '', diff --git a/Tasks/AzureFunctionAppV1/operations/ReleaseAnnotationUtility.ts b/Tasks/AzureFunctionAppV1/operations/ReleaseAnnotationUtility.ts index 56998219c1ad..cf0a69baf837 100644 --- a/Tasks/AzureFunctionAppV1/operations/ReleaseAnnotationUtility.ts +++ b/Tasks/AzureFunctionAppV1/operations/ReleaseAnnotationUtility.ts @@ -1,7 +1,7 @@ import tl = require('azure-pipelines-task-lib/task'); -import { AzureApplicationInsights, ApplicationInsightsResources} from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-appinsights'; -import { AzureEndpoint } from 'azure-pipelines-tasks-azure-arm-rest-v2/azureModels'; -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; +import { AzureApplicationInsights, ApplicationInsightsResources} from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azure-arm-appinsights'; +import { AzureEndpoint } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; var uuidV4 = require("uuid/v4"); @@ -42,7 +42,7 @@ function getReleaseAnnotation(isDeploymentSuccess: boolean): {[key: string]: any else if (!!buildUri) { annotationName = `${tl.getVariable("Build.DefinitionName")} - ${tl.getVariable("Build.BuildNumber")}`; } - + let releaseAnnotationProperties = { "Label": isDeploymentSuccess ? "Success" : "Error", // Label decides the icon for release annotation "Deployment Uri": getDeploymentUri(), diff --git a/Tasks/AzureFunctionAppV1/package-lock.json b/Tasks/AzureFunctionAppV1/package-lock.json index ff5684541299..4ea5965bfb89 100644 --- a/Tasks/AzureFunctionAppV1/package-lock.json +++ b/Tasks/AzureFunctionAppV1/package-lock.json @@ -4,28 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@azure/msal-common": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-9.1.1.tgz", - "integrity": "sha512-we9xR8lvu47fF0h+J8KyXoRy9+G/fPzm3QEa2TrdR3jaVS3LKAyE2qyMuUkNdbVkvzl8Zr9f7l+IUSP22HeqXw==" - }, - "@azure/msal-node": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.14.5.tgz", - "integrity": "sha512-NcVdMfn8Z3ogN+9RjOSF7uwf2Gki5DEJl0BdDSL83KUAgVAobtkZi5W8EqxbJLrTO/ET0jv5DregrcR5qg2pEA==", - "requires": { - "@azure/msal-common": "^9.0.1", - "jsonwebtoken": "^8.5.1", - "uuid": "^8.3.0" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } - } - }, "@types/concat-stream": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", @@ -42,14 +20,6 @@ "@types/node": "*" } }, - "@types/jsonwebtoken": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", - "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", - "requires": { - "@types/node": "*" - } - }, "@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", @@ -70,20 +40,11 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, - "@xmldom/xmldom": { - "version": "git+https://github.com/xmldom/xmldom.git#238b1ea8431fae8817812c68d55b4933248af07e", - "from": "git+https://github.com/xmldom/xmldom.git#0.8.6" - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -95,6 +56,21 @@ "uri-js": "^4.2.2" } }, + "archiver": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", + "integrity": "sha512-01psM0DMD3YItvhnAXZODfsViaeDidrJwfne3lsoVrbyYa/xFQwTbVjY+2WlEBm7qH1fCsyxAA1SgNr/XenTlQ==", + "requires": { + "archiver-utils": "^1.3.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "zip-stream": "^1.2.0" + } + }, "archiver-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", @@ -134,14 +110,6 @@ "lodash": "^4.17.14" } }, - "async-mutex": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", - "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", - "requires": { - "tslib": "^2.4.0" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -157,15 +125,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, - "azure-devops-node-api": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", - "integrity": "sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==", - "requires": { - "tunnel": "0.0.6", - "typed-rest-client": "^1.8.4" - } - }, "azure-pipelines-task-lib": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-3.4.0.tgz", @@ -187,126 +146,47 @@ } } }, - "azure-pipelines-tasks-azure-arm-rest-v2": { - "version": "3.221.3", - "resolved": "https://registry.npmjs.org/azure-pipelines-tasks-azure-arm-rest-v2/-/azure-pipelines-tasks-azure-arm-rest-v2-3.221.3.tgz", - "integrity": "sha512-EBBphV/VwqJRjhiWSsb+163DVge3VPwGubKO7MPRFZ0BgLo2d0Zo217x1690D4u8GqpksGR/SytHdcKlDzTbBQ==", + "azure-pipelines-tasks-azurermdeploycommon": { + "version": "3.207.0", + "resolved": "https://registry.npmjs.org/azure-pipelines-tasks-azurermdeploycommon/-/azure-pipelines-tasks-azurermdeploycommon-3.207.0.tgz", + "integrity": "sha512-eGq/1x0yOkMw6bAZxJSN+PaIjmJgJbnEySljXdQsyQ8oBU5u442EGAC6PNa7tHCYcNf5el5cm2rhyhKeY2AQFQ==", "requires": { - "@azure/msal-node": "1.14.5", - "@types/jsonwebtoken": "^8.5.8", "@types/mocha": "^5.2.7", "@types/node": "^10.17.0", - "@types/q": "1.5.4", - "async-mutex": "^0.4.0", - "azure-devops-node-api": "^12.0.0", - "azure-pipelines-task-lib": "^3.4.0", - "https-proxy-agent": "^4.0.0", - "jsonwebtoken": "^8.5.1", - "node-fetch": "^2.6.7", + "@types/q": "1.0.7", + "archiver": "2.1.1", + "azure-pipelines-task-lib": "^3.1.0", + "decompress-zip": "^0.3.3", + "jsonwebtoken": "7.3.0", + "ltx": "2.6.2", + "node-stream-zip": "1.7.0", "q": "1.5.1", - "typed-rest-client": "1.8.4", + "typed-rest-client": "^1.8.4", + "winreg": "1.2.2", "xml2js": "0.4.13" }, "dependencies": { - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" - }, - "azure-devops-node-api": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.0.0.tgz", - "integrity": "sha512-S6Il++7dQeMlZDokBDWw7YVoPeb90tWF10pYxnoauRMnkuL91jq9M7SOYRVhtO3FUC5URPkB/qzGa7jTLft0Xw==", + "jsonwebtoken": { + "version": "7.3.0", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-7.3.0.tgz", + "integrity": "sha512-JPSYVU9RM5nObFojvXtjMsSSDhliKQhk61KPwsAyUpD+4plk5CJ2XEYdBy6zIuNYwKcV3CTbCE7Azd0KcymIlw==", "requires": { - "tunnel": "0.0.6", - "typed-rest-client": "^1.8.4" + "joi": "^6.10.1", + "jws": "^3.1.4", + "lodash.once": "^4.0.0", + "ms": "^0.7.1", + "xtend": "^4.0.1" } }, + "ms": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-0.7.3.tgz", + "integrity": "sha512-lrKNzMWqQZgwJahtrtrM+9NgOoDUveDrVmm5aGXrf3BdtL0mq7X6IVzoZaw+TfNti29eHd1/8GI+h45K5cQ6/w==" + }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" - }, - "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "typed-rest-client": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", - "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", - "requires": { - "qs": "^6.9.1", - "tunnel": "0.0.6", - "underscore": "^1.12.1" - } - } - } - }, - "azure-pipelines-tasks-webdeployment-common": { - "version": "4.219.1", - "resolved": "https://registry.npmjs.org/azure-pipelines-tasks-webdeployment-common/-/azure-pipelines-tasks-webdeployment-common-4.219.1.tgz", - "integrity": "sha512-Uf9b0u33Wwff2EBBxFcvPaL8LetSBT2q8l28SVTOVcDgnQVMKJPR7pMK+DBzLiDlLe164MOcGrlOK+Oi/0+5LA==", - "requires": { - "@types/mocha": "^5.2.7", - "@types/node": "^10.17.0", - "@xmldom/xmldom": "git+https://github.com/xmldom/xmldom.git#0.8.6", - "archiver": "1.2.0", - "azure-pipelines-task-lib": "^4.2.0", - "decompress-zip": "^0.3.3", - "ltx": "2.8.0", - "q": "1.4.1", - "winreg": "1.2.2", - "xml2js": "0.4.13" - }, - "dependencies": { - "archiver": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.2.0.tgz", - "integrity": "sha512-5GQRAgpHGPwWIiMzL9lthd+t75fLi8BpRBYtflomSYv2i6+EO9trtwWAm2+zGjIuwKmVmBRknAZFFBSqxYxiJw==", - "requires": { - "archiver-utils": "^1.3.0", - "async": "^2.0.0", - "buffer-crc32": "^0.2.1", - "glob": "^7.0.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0", - "tar-stream": "^1.5.0", - "zip-stream": "^1.1.0" - } - }, - "azure-pipelines-task-lib": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.3.1.tgz", - "integrity": "sha512-AEwz0+Sofv80UviCYsS6fzyX5zzsLapmNCMNUoaRePZQVN+oQBStix1DGg4fdZf9zJ6acNd9xEBZQWbWuZu5Zg==", - "requires": { - "minimatch": "3.0.5", - "mockery": "^2.1.0", - "q": "^1.5.1", - "semver": "^5.1.0", - "shelljs": "^0.8.5", - "sync-request": "6.1.0", - "uuid": "^3.0.1" - }, - "dependencies": { - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" - } - } - }, - "ltx": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.8.0.tgz", - "integrity": "sha512-SJJUrmDgXP0gkUzgErfkaeD+pugM8GYxerTALQa1gTUb5W1wrC4k07GZU+QNZd7MpFqJSYWXTQSUy8Ps03hx5Q==", - "requires": { - "inherits": "^2.0.1" - } } } }, @@ -534,21 +414,6 @@ "assert-plus": "^1.0.0" } }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "decompress-zip": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.3.tgz", @@ -779,6 +644,11 @@ } } }, + "hoek": { + "version": "2.16.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-2.16.3.tgz", + "integrity": "sha512-V6Yw1rIcYV/4JsnggjBU0l4Kr+EXhpwqXRusENU1Xx6ro00IHPHYNynCuBTOZAPlr3AAmLvchH9I7N/VUdvOwQ==" + }, "http-basic": { "version": "8.1.3", "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", @@ -808,15 +678,6 @@ "sshpk": "^1.7.0" } }, - "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "requires": { - "agent-base": "5", - "debug": "4" - } - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -859,11 +720,27 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "isemail": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-1.2.0.tgz", + "integrity": "sha512-pZMb1rDrWRAPtVY92VCxWtF+1gExWrCnao+GL1EKHx6z19ovW+xNcnC1iNB7WkbSYWlyl3uwlaH5eaBx2s2crw==" + }, "isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=" }, + "joi": { + "version": "6.10.1", + "resolved": "https://registry.npmjs.org/joi/-/joi-6.10.1.tgz", + "integrity": "sha512-K6+OwGaWM1sBEu+XMbgC4zDmg6hnddS2DWiCVtjnhkcrzv+ejSfh7HGUsoxmWQkv6kHEsVFAywttfkpmIE2QwQ==", + "requires": { + "hoek": "2.x.x", + "isemail": "1.x.x", + "moment": "2.x.x", + "topo": "1.x.x" + } + }, "jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", @@ -897,23 +774,6 @@ "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.2.0.tgz", "integrity": "sha1-XAxWhRBxYOcv50ib3eoLRMK8Z70=" }, - "jsonwebtoken": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-8.5.1.tgz", - "integrity": "sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==", - "requires": { - "jws": "^3.2.2", - "lodash.includes": "^4.3.0", - "lodash.isboolean": "^3.0.3", - "lodash.isinteger": "^4.0.4", - "lodash.isnumber": "^3.0.3", - "lodash.isplainobject": "^4.0.6", - "lodash.isstring": "^4.0.1", - "lodash.once": "^4.0.0", - "ms": "^2.1.1", - "semver": "^5.6.0" - } - }, "jsprim": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.2.tgz", @@ -957,41 +817,19 @@ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, - "lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==" - }, - "lodash.isboolean": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", - "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==" - }, - "lodash.isinteger": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", - "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==" - }, - "lodash.isnumber": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", - "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==" - }, - "lodash.isplainobject": { - "version": "4.0.6", - "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", - "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==" - }, - "lodash.isstring": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", - "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==" - }, "lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, + "ltx": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.6.2.tgz", + "integrity": "sha512-xg03AeMzTVozL1K/fuzxkzvnvx+yjY6/uJHN6dmQYP8Jm26GJjEr5EV3Sj5Yi/XYDJ1k9xVPZWvdQzJG7o+C5w==", + "requires": { + "inherits": "^2.0.1" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -1038,18 +876,10 @@ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==" }, - "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" - }, - "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "requires": { - "whatwg-url": "^5.0.0" - } + "node-stream-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.7.0.tgz", + "integrity": "sha512-kYVtF3lK++53Bg6hZNplYVMrR7Lt0IYdLWehgoHUJLJcSwg/xd2Rm2Z7kJ5W8ZA7pdeg/DiUQDatbYwL3C7qSw==" }, "nopt": { "version": "3.0.6", @@ -1249,16 +1079,6 @@ "rechoir": "^0.6.2" } }, - "side-channel": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", - "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", - "requires": { - "call-bind": "^1.0.0", - "get-intrinsic": "^1.0.2", - "object-inspect": "^1.9.0" - } - }, "sshpk": { "version": "1.17.0", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.17.0.tgz", @@ -1347,6 +1167,14 @@ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.1.1.tgz", "integrity": "sha512-lx9B5iv7msuFYE3dytT+KE5tap+rNYw+K4jVkb9R/asAb+pbBSM17jtunHplhBe6RRJdZx3Pn2Jph24O32mOVg==" }, + "topo": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/topo/-/topo-1.1.0.tgz", + "integrity": "sha512-vpmONxdZoD0R3hzH0lovwv8QmsqZmGCDE1wXW9YGD/reiDOAbPKEgRDlBCAt8u8nJhav/s/I+r+1gvdpA11x7Q==", + "requires": { + "hoek": "2.x.x" + } + }, "touch": { "version": "0.0.3", "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz", @@ -1374,21 +1202,11 @@ "punycode": "^2.1.1" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==" }, - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, "tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -1488,20 +1306,6 @@ "extsprintf": "^1.2.0" } }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "winreg": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.2.tgz", diff --git a/Tasks/AzureFunctionAppV1/package.json b/Tasks/AzureFunctionAppV1/package.json index 50ba654b2f92..6514f36b6962 100644 --- a/Tasks/AzureFunctionAppV1/package.json +++ b/Tasks/AzureFunctionAppV1/package.json @@ -20,10 +20,7 @@ "@types/mocha": "^5.2.7", "@types/node": "^10.17.0", "@types/q": "1.0.7", - "azure-devops-node-api": "11.2.0", - "azure-pipelines-task-lib": "^3.4.0", - "azure-pipelines-tasks-azure-arm-rest-v2": "^3.221.3", - "azure-pipelines-tasks-webdeployment-common": "^4.219.1", + "azure-pipelines-tasks-azurermdeploycommon": "3.207.0", "azure-storage": "2.10.7", "moment": "^2.29.3", "q": "1.4.1", diff --git a/Tasks/AzureFunctionAppV1/task.json b/Tasks/AzureFunctionAppV1/task.json index 65f5743bb935..ca989ca4c206 100644 --- a/Tasks/AzureFunctionAppV1/task.json +++ b/Tasks/AzureFunctionAppV1/task.json @@ -18,7 +18,7 @@ "version": { "Major": 1, "Minor": 221, - "Patch": 104 + "Patch": 105 }, "minimumAgentVersion": "2.104.1", "groups": [ diff --git a/Tasks/AzureFunctionAppV1/task.loc.json b/Tasks/AzureFunctionAppV1/task.loc.json index 1e75a7714fba..29ca42e87922 100644 --- a/Tasks/AzureFunctionAppV1/task.loc.json +++ b/Tasks/AzureFunctionAppV1/task.loc.json @@ -18,7 +18,7 @@ "version": { "Major": 1, "Minor": 221, - "Patch": 104 + "Patch": 105 }, "minimumAgentVersion": "2.104.1", "groups": [ diff --git a/Tasks/AzureFunctionAppV1/taskparameters.ts b/Tasks/AzureFunctionAppV1/taskparameters.ts index 62c4f4528fc5..166325901955 100644 --- a/Tasks/AzureFunctionAppV1/taskparameters.ts +++ b/Tasks/AzureFunctionAppV1/taskparameters.ts @@ -1,10 +1,10 @@ import tl = require('azure-pipelines-task-lib/task'); -var webCommonUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { AzureRMEndpoint } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-endpoint'; -import { Resources } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-resource'; -import { AzureEndpoint } from 'azure-pipelines-tasks-azure-arm-rest-v2/azureModels'; -import { Package, PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; +var webCommonUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +import { AzureAppService } from './azure-arm-rest/azure-arm-app-service'; +import { AzureRMEndpoint } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azure-arm-endpoint'; +import { AzureEndpoint } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; +import { Package, PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import { AzureResourceFilterUtility } from 'azure-pipelines-tasks-azurermdeploycommon/operations/AzureResourceFilterUtility'; const skuDynamicValue: string = 'dynamic'; const skuElasticPremiumValue: string = 'elasticpremium'; @@ -27,7 +27,7 @@ export class TaskParametersUtility { StartupCommand: tl.getInput('startUpCommand', false), ConfigurationSettings: tl.getInput('configurationStrings', false), WebAppName: tl.getInput('appName', true) - } + } //Clear input if deploytoslot is disabled taskParameters.ResourceGroupName = (!!taskParameters.DeployToSlotOrASEFlag) ? tl.getInput('resourceGroupName', false) : null; @@ -41,18 +41,18 @@ export class TaskParametersUtility { { taskParameters.AppSettings = taskParameters.AppSettings.replace('\n',' '); } - + var appDetails = await this.getWebAppKind(taskParameters); taskParameters.ResourceGroupName = appDetails["resourceGroupName"]; taskParameters.WebAppKind = appDetails["webAppKind"]; taskParameters.isConsumption = appDetails["sku"].toLowerCase() == skuDynamicValue; taskParameters.isPremium = appDetails["sku"].toLowerCase() == skuElasticPremiumValue; - + taskParameters.isLinuxApp = taskParameters.WebAppKind && taskParameters.WebAppKind.indexOf("Linux") !=-1; var endpointTelemetry = '{"endpointId":"' + taskParameters.connectedServiceName + '"}'; console.log("##vso[telemetry.publish area=TaskEndpointId;feature=AzureRmWebAppDeployment]" + endpointTelemetry); - + taskParameters.Package = new Package(tl.getPathInput('package', true)); taskParameters.WebConfigParameters = this.updateWebConfigParameters(taskParameters); @@ -70,8 +70,7 @@ export class TaskParametersUtility { var kind = taskParameters.WebAppKind; var sku; if (!resourceGroupName) { - var azureResources: Resources = new Resources(taskParameters.azureEndpoint); - var appDetails = await azureResources.getAppDetails(taskParameters.WebAppName); + var appDetails = await AzureResourceFilterUtility.getAppDetails(taskParameters.azureEndpoint, taskParameters.WebAppName); resourceGroupName = appDetails["resourceGroupName"]; if(!kind) { kind = webAppKindMap.get(appDetails["kind"]) ? webAppKindMap.get(appDetails["kind"]) : appDetails["kind"]; @@ -112,7 +111,7 @@ export class TaskParametersUtility { webConfigParameters += " -JAR_PATH " + jarPath; } if(webConfigParameters.indexOf("-Dserver.port=%HTTP_PLATFORM_PORT%") > 0) { - webConfigParameters = webConfigParameters.replace("-Dserver.port=%HTTP_PLATFORM_PORT%", ""); + webConfigParameters = webConfigParameters.replace("-Dserver.port=%HTTP_PLATFORM_PORT%", ""); } tl.debug("web config parameters :" + webConfigParameters); } diff --git a/Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service-kudu.ts b/Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service-kudu.ts new file mode 100644 index 000000000000..74f0dc58c6e6 --- /dev/null +++ b/Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service-kudu.ts @@ -0,0 +1,712 @@ +import tl = require('azure-pipelines-task-lib/task'); +import fs = require('fs'); +import util = require('util'); +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); +import { WebJob, SiteExtension } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; +import { KUDU_DEPLOYMENT_CONSTANTS } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/constants'; + + +export class KuduServiceManagementClient{ + private _scmUri; + private _cookie: string[]; + private _accessToken; + private _scmAccessCheck : boolean; + + + constructor(scmUri: string , accessToken: string , scmAccessCheck: boolean) { + this._accessToken = accessToken; + this._scmUri = scmUri; + this._scmAccessCheck = scmAccessCheck; + } + + public async beginRequest(request: webClient.WebRequest, reqOptions?: webClient.WebRequestOptions, contentType?: string): Promise { + request.headers = request.headers || {}; + if(this._scmAccessCheck === false || this._scmAccessCheck == null) { + request.headers["Authorization"] = "Bearer " + this._accessToken; + tl.debug('Using Bearer Authentication'); + let authMethodtelemetry = '{"authMethod":"Bearer"}'; + console.log("##vso[telemetry.publish area=TaskDeploymentMethod;feature=AzureFunctionAppDeployment]" + authMethodtelemetry); + } + else{ + request.headers["Authorization"] = "Basic " + this._accessToken; + tl.debug('Using Basic Authentication'); + let authMethodtelemetry = '{"authMethod":"Basic"}'; + console.log("##vso[telemetry.publish area=TaskDeploymentMethod;feature=AzureFunctionAppDeployment]" + authMethodtelemetry); + } + + request.headers['Content-Type'] = contentType || 'application/json; charset=utf-8'; + + + if(!!this._cookie) { + tl.debug(`setting affinity cookie ${JSON.stringify(this._cookie)}`); + request.headers['Cookie'] = this._cookie; + } + + let retryCount = reqOptions && util.isNumber(reqOptions.retryCount) ? reqOptions.retryCount : 5; + + while(retryCount >= 0) { + try { + let httpResponse = await webClient.sendRequest(request, reqOptions); + if(httpResponse.headers['set-cookie'] && !this._cookie) { + this._cookie = httpResponse.headers['set-cookie']; + tl.debug(`loaded affinity cookie ${JSON.stringify(this._cookie)}`); + } + + return httpResponse; + } + catch(exception) { + let exceptionString: string = exception.toString(); + if(exceptionString.indexOf("Hostname/IP doesn't match certificates's altnames") != -1 + || exceptionString.indexOf("unable to verify the first certificate") != -1 + || exceptionString.indexOf("unable to get local issuer certificate") != -1) { + tl.warning(tl.loc('ASE_SSLIssueRecommendation')); + } + + if(retryCount > 0 && exceptionString.indexOf('Request timeout') != -1 && (!reqOptions || reqOptions.retryRequestTimedout)) { + tl.debug('encountered request timedout issue in Kudu. Retrying again'); + retryCount -= 1; + continue; + } + + throw new Error(exceptionString); + } + } + + } + + public getRequestUri(uriFormat: string, queryParameters?: Array) { + uriFormat = uriFormat[0] == "/" ? uriFormat : "/" + uriFormat; + + if(queryParameters && queryParameters.length > 0) { + uriFormat = uriFormat + '?' + queryParameters.join('&'); + } + + return this._scmUri + uriFormat; + } + + public getScmUri(): string { + return this._scmUri; + } +} + +export class Kudu { + private _client: KuduServiceManagementClient; + + constructor(scmUri: string, accessToken: string, scmPolicyCheck: boolean) { + this._client = new KuduServiceManagementClient(scmUri, accessToken, scmPolicyCheck); + } + + public async updateDeployment(requestBody: any): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(requestBody); + httpRequest.uri = this._client.getRequestUri(`/api/deployments/${requestBody.id}`); + + try { + let webRequestOptions: webClient.WebRequestOptions = {retriableErrorCodes: [], retriableStatusCodes: null, retryCount: 5, retryIntervalInSeconds: 5, retryRequestTimedout: true}; + var response = await this._client.beginRequest(httpRequest, webRequestOptions); + tl.debug(`updateDeployment. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc("Successfullyupdateddeploymenthistory", response.body.url)); + return response.body.id; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('Failedtoupdatedeploymenthistory', this._getFormattedError(error))); + } + } + + + public async getContinuousJobs(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/continuouswebjobs`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getContinuousJobs. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body as Array; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetContinuousWebJobs', this._getFormattedError(error))) + } + } + + public async startContinuousWebJob(jobName: string): Promise { + console.log(tl.loc('StartingWebJob', jobName)); + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/continuouswebjobs/${jobName}/start`); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`startContinuousWebJob. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc('StartedWebJob', jobName)); + return response.body as WebJob; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToStartContinuousWebJob', jobName, this._getFormattedError(error))); + } + } + + public async stopContinuousWebJob(jobName: string): Promise { + console.log(tl.loc('StoppingWebJob', jobName)); + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/continuouswebjobs/${jobName}/stop`); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`stopContinuousWebJob. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc('StoppedWebJob', jobName)); + return response.body as WebJob; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToStopContinuousWebJob', jobName, this._getFormattedError(error))); + } + } + + public async installSiteExtension(extensionID: string): Promise { + console.log(tl.loc("InstallingSiteExtension", extensionID)); + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/siteextensions/${extensionID}`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`installSiteExtension. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + console.log(tl.loc("SiteExtensionInstalled", extensionID)); + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToInstallSiteExtension', extensionID, this._getFormattedError(error))) + } + } + + public async getSiteExtensions(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/siteextensions`, ['checkLatest=false']); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getSiteExtensions. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body as Array; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetSiteExtensions', this._getFormattedError(error))) + } + } + + public async getAllSiteExtensions(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/extensionfeed`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getAllSiteExtensions. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body as Array; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetAllSiteExtensions', this._getFormattedError(error))) + } + } + + public async getProcess(processID: number): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/processes/${processID}`); + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getProcess. status code: ${response.statusCode} - ${response.statusMessage}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetProcess', this._getFormattedError(error))) + } + } + + public async killProcess(processID: number): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'DELETE'; + httpRequest.uri = this._client.getRequestUri(`/api/processes/${processID}`); + var reqOptions: webClient.WebRequestOptions = { + retriableErrorCodes: ["ETIMEDOUT"], + retriableStatusCodes: [503], + retryCount: 1, + retryIntervalInSeconds: 5, + retryRequestTimedout: true + }; + try { + var response = await this._client.beginRequest(httpRequest, reqOptions); + tl.debug(`killProcess. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 502) { + tl.debug(`Killed Process ${processID}`); + return; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToKillProcess', this._getFormattedError(error))) + } + } + + public async getAppSettings(): Promise> { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/settings`); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getAppSettings. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToFetchKuduAppSettings', this._getFormattedError(error))); + } + } + + public async listDir(physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`listFiles. Data: ${JSON.stringify(response)}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + else if(response.statusCode === 404) { + return null; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToListPath', physicalPath, this._getFormattedError(error))); + } + } + + public async getFileContent(physicalPath: string, fileName: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/${fileName}`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getFileContent. Status code: ${response.statusCode} - ${response.statusMessage}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + else if(response.statusCode === 404) { + return null; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToGetFileContent', physicalPath, fileName, this._getFormattedError(error))); + } + } + + public async uploadFile(physicalPath: string, fileName: string, filePath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + if(!tl.exist(filePath)) { + throw new Error(tl.loc('FilePathInvalid', filePath)); + } + + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/${fileName}`); + httpRequest.headers = { + 'If-Match': '*' + }; + httpRequest.body = fs.createReadStream(filePath); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`uploadFile. Data: ${JSON.stringify(response)}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToUploadFile', physicalPath, fileName, this._getFormattedError(error))); + } + } + + public async createPath(physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`createPath. Data: ${JSON.stringify(response)}`); + if([200, 201, 204].indexOf(response.statusCode) != -1) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToCreatePath', physicalPath, this._getFormattedError(error))); + } + } + + public async runCommand(physicalPath: string, command: string): Promise { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/command`); + httpRequest.headers = { + 'Content-Type': 'multipart/form-data', + 'If-Match': '*' + }; + httpRequest.body = JSON.stringify({ + 'command': command, + 'dir': physicalPath + }); + + try { + tl.debug('Executing Script on Kudu. Command: ' + command); + let webRequestOptions: webClient.WebRequestOptions = {retriableErrorCodes: null, retriableStatusCodes: null, retryCount: 5, retryIntervalInSeconds: 5, retryRequestTimedout: false}; + var response = await this._client.beginRequest(httpRequest, webRequestOptions); + tl.debug(`runCommand. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(error.toString()); + } + } + + public async extractZIP(webPackage: string, physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.uri = this._client.getRequestUri(`/api/zip/${physicalPath}/`); + httpRequest.headers = { + 'Content-Type': 'multipart/form-data', + 'If-Match': '*' + }; + httpRequest.body = fs.createReadStream(webPackage); + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`extractZIP. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('Failedtodeploywebapppackageusingkuduservice', this._getFormattedError(error))); + } + } + + public getKuduStackTrace() { + let stackTraceUrl = this._client.getRequestUri(`/api/vfs/LogFiles/kudu/trace`); + return stackTraceUrl; + } + + public async zipDeploy(webPackage: string, queryParameters?: Array): Promise { + let httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/zipdeploy`, queryParameters); + httpRequest.body = fs.createReadStream(webPackage); + let requestOptions = new webClient.WebRequestOptions(); + //Bydefault webclient.sendRequest retries for [408, 409, 500, 502, 503, 504] as suggested by appservice team for zipdeploy api + //408 and 409 should not be retried as it will never turn into success + requestOptions.retriableStatusCodes = [500, 502, 503, 504]; + requestOptions.retryIntervalInSeconds = 5; + try { + let response = await this._client.beginRequest(httpRequest, requestOptions, 'application/octet-stream'); + tl.debug(`ZIP Deploy response: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + tl.debug('Deployment passed'); + return null; + } + else if(response.statusCode == 202) { + let pollableURL: string = response.headers.location; + if(!!pollableURL) { + tl.debug(`Polling for ZIP Deploy URL: ${pollableURL}`); + return await this._getDeploymentDetailsFromPollURL(pollableURL); + } + else { + tl.debug('zip deploy returned 202 without pollable URL.'); + return null; + } + } + else { + throw response; + } + } + catch(error) { + throw new Error(tl.loc('PackageDeploymentFailed', this._getFormattedError(error))); + } + } + + public async warDeploy(webPackage: string, queryParameters?: Array): Promise { + let httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.uri = this._client.getRequestUri(`/api/wardeploy`, queryParameters); + httpRequest.body = fs.createReadStream(webPackage); + + try { + let response = await this._client.beginRequest(httpRequest, null, 'application/octet-stream'); + tl.debug(`War Deploy response: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + tl.debug('Deployment passed'); + return null; + } + else if(response.statusCode == 202) { + let pollableURL: string = response.headers.location; + if(!!pollableURL) { + tl.debug(`Polling for War Deploy URL: ${pollableURL}`); + return await this._getDeploymentDetailsFromPollURL(pollableURL); + } + else { + tl.debug('war deploy returned 202 without pollable URL.'); + return null; + } + } + else { + throw response; + } + } + catch(error) { + throw new Error(tl.loc('PackageDeploymentFailed', this._getFormattedError(error))); + } + } + + + public async getDeploymentDetails(deploymentID: string): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`/api/deployments/${deploymentID}`); ; + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getDeploymentDetails. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetDeploymentLogs', this._getFormattedError(error))) + } + } + + public async getDeploymentLogs(log_url: string): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = log_url; + var response = await this._client.beginRequest(httpRequest); + tl.debug(`getDeploymentLogs. Data: ${JSON.stringify(response)}`); + if(response.statusCode == 200) { + return response.body; + } + + throw response; + } + catch(error) { + throw Error(tl.loc('FailedToGetDeploymentLogs', this._getFormattedError(error))) + } + } + + public async deleteFile(physicalPath: string, fileName: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'DELETE'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}/${fileName}`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`deleteFile. Data: ${JSON.stringify(response)}`); + if([200, 201, 204, 404].indexOf(response.statusCode) != -1) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToDeleteFile', physicalPath, fileName, this._getFormattedError(error))); + } + } + + public async deleteFolder(physicalPath: string): Promise { + physicalPath = physicalPath.replace(/[\\]/g, "/"); + physicalPath = physicalPath[0] == "/" ? physicalPath.slice(1): physicalPath; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'DELETE'; + httpRequest.uri = this._client.getRequestUri(`/api/vfs/${physicalPath}`); + httpRequest.headers = { + 'If-Match': '*' + }; + + try { + var response = await this._client.beginRequest(httpRequest); + tl.debug(`deleteFolder. Data: ${JSON.stringify(response)}`); + if([200, 201, 204, 404].indexOf(response.statusCode) != -1) { + return ; + } + else { + throw response; + } + } + catch(error) { + throw Error(tl.loc('FailedToDeleteFolder', physicalPath, this._getFormattedError(error))); + } + } + + private async _getDeploymentDetailsFromPollURL(pollURL: string):Promise { + let httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = pollURL; + httpRequest.headers = {}; + + while(true) { + let response = await this._client.beginRequest(httpRequest); + if(response.statusCode == 200 || response.statusCode == 202) { + var result = response.body; + tl.debug(`POLL URL RESULT: ${JSON.stringify(response)}`); + if(result.status == KUDU_DEPLOYMENT_CONSTANTS.SUCCESS || result.status == KUDU_DEPLOYMENT_CONSTANTS.FAILED) { + return result; + } + else { + tl.debug(`Deployment status: ${result.status} '${result.status_text}'. retry after 5 seconds`); + await webClient.sleepFor(5); + continue; + } + } + else { + throw response; + } + } + } + + private _getFormattedError(error: any) { + if(error && error.message) { + if(error.statusCode) { + error.message = `${typeof error.message.valueOf() == 'string' ? error.message : error.message.Code + " - " + error.message.Message } (CODE: ${error.statusCode})` + } + + return error.message; + } + if(error && error.statusCode) { + return `${error.statusMessage} (CODE: ${error.statusCode})`; + } + return error; + } + + public async validateZipDeploy(webPackage: string, queryParameters?: Array): Promise { + try { + var stats = fs.statSync(webPackage); + var fileSizeInBytes = stats.size; + let httpRequest: webClient.WebRequest = { + method: 'POST', + uri: this._client.getRequestUri(`/api/zipdeploy/validate`, queryParameters), + body: fs.createReadStream(webPackage), + headers: { + 'Content-Length': fileSizeInBytes + }, + }; + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retriableStatusCodes = [500, 502, 503, 504]; + requestOptions.retryIntervalInSeconds = 5; + + let response = await this._client.beginRequest(httpRequest, requestOptions, 'application/octet-stream'); + if(response.statusCode == 200) { + tl.debug(`Validation passed response: ${JSON.stringify(response)}`); + if (response.body && response.body.result){ + tl.warning(`${JSON.stringify(response.body.result)}`); + } + return null; + } + else if(response.statusCode == 400) { + tl.debug(`Validation failed response: ${JSON.stringify(response)}`); + throw response; + } + else { + tl.debug(`Skipping validation with status: ${response.statusCode}`); + return null; + } + } + catch(error) { + if (error && error.body && error.body.result && typeof error.body.result.valueOf() == 'string' && error.body.result.includes('ZipDeploy Validation ERROR')) { + throw Error(JSON.stringify(error.body.result)); + } + else { + tl.debug(`Skipping validation with error: ${error}`); + return null; + } + } + } +} diff --git a/Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service.ts b/Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service.ts new file mode 100644 index 000000000000..883e4f488982 --- /dev/null +++ b/Tasks/AzureFunctionAppV2/azure-arm-rest/azure-arm-app-service.ts @@ -0,0 +1,824 @@ +import tl = require('azure-pipelines-task-lib/task'); +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); +import { + AzureEndpoint, + AzureAppServiceConfigurationDetails +} from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; + +import { + ServiceClient, + ToError +} from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/AzureServiceClient'; +import constants = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/constants'); +const CorrelationIdInResponse = "x-ms-correlation-request-id"; + +export class ServiceClient_1 extends ServiceClient{ + public async beginRequest(request: webClient.WebRequest, reqOptions?: webClient.WebRequestOptions): Promise { + var token = await this.getCredentials().getToken(); + + request.headers = request.headers || {}; + request.headers["Authorization"] = "Bearer " + token; + if (this.acceptLanguage) { + request.headers['accept-language'] = this.acceptLanguage; + } + request.headers['Content-Type'] = 'application/json; charset=utf-8'; + + var httpResponse = null; + + try + { + httpResponse = await webClient.sendRequest(request, reqOptions); + if (httpResponse.statusCode === 401 && httpResponse.body && httpResponse.body.error && httpResponse.body.error.code === "ExpiredAuthenticationToken") { + // The access token might have expire. Re-issue the request after refreshing the token. + token = await this.getCredentials().getToken(true); + request.headers["Authorization"] = "Bearer " + token; + httpResponse = await webClient.sendRequest(request, reqOptions); + } + + if(!!httpResponse.headers[CorrelationIdInResponse]) { + tl.debug(`Correlation ID from ARM api call response : ${httpResponse.headers[CorrelationIdInResponse]}`); + } + } catch(exception) { + let exceptionString: string = exception.toString(); + if(exceptionString.indexOf("Hostname/IP doesn't match certificates's altnames") != -1 + || exceptionString.indexOf("unable to verify the first certificate") != -1 + || exceptionString.indexOf("unable to get local issuer certificate") != -1) { + tl.warning(tl.loc('ASE_SSLIssueRecommendation')); + } + + throw exception; + } + + return httpResponse; + } +} + +export class AzureAppService { + private _resourceGroup: string; + private _name: string; + private _slot: string; + private _appKind: string; + private _isConsumptionApp: boolean; + public _client: ServiceClient_1; + private _appServiceConfigurationDetails: AzureAppServiceConfigurationDetails; + private _appServicePublishingProfile: any; + private _appServiceApplicationSetings: AzureAppServiceConfigurationDetails; + private _appServiceConfigurationSettings: AzureAppServiceConfigurationDetails; + private _appServiceConnectionString: AzureAppServiceConfigurationDetails; + + constructor(endpoint: AzureEndpoint, resourceGroup: string, name: string, slot?: string, appKind?: string, isConsumptionApp?: boolean) { + this._client = new ServiceClient_1(endpoint.applicationTokenCredentials, endpoint.subscriptionID, 30); + this._resourceGroup = resourceGroup; + this._name = name; + this._slot = (slot && slot.toLowerCase() == constants.productionSlot) ? null : slot; + this._appKind = appKind; + this._isConsumptionApp = isConsumptionApp; + } + + public async start(): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/start`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name + }, null, '2016-08-01'); + + console.log(tl.loc('StartingAppService', this._getFormattedName())); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('StartedAppService', this._getFormattedName())); + } + catch(error) { + throw Error(tl.loc('FailedToStartAppService', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async stop(): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/stop`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name + }, null, '2016-08-01'); + + console.log(tl.loc('StoppingAppService', this._getFormattedName())); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('StoppedAppService', this._getFormattedName())); + } + catch(error) { + throw Error(tl.loc('FailedToStopAppService', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async restart(): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/restart`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name + }, null, '2016-08-01'); + + console.log(tl.loc('RestartingAppService', this._getFormattedName())); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('RestartedAppService', this._getFormattedName())); + } + catch(error) { + throw Error(tl.loc('FailedToRestartAppService', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async swap(slotName: string, preserveVNet?: boolean): Promise { + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'POST'; + webRequest.body = JSON.stringify({ + targetSlot: slotName, + preserveVnet: preserveVNet + }); + + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + webRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{ResourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/slotsswap`, { + '{ResourceGroupName}': this._resourceGroup, + '{name}': this._name, + '{slotUrl}': slotUrl + }, null, '2016-08-01'); + + console.log(tl.loc('SwappingAppServiceSlotSlots', this._name, this.getSlot(), slotName)); + var response = await this._client.beginRequest(webRequest); + if(response.statusCode == 202) { + response= await this._client.getLongRunningOperationResult(response); + } + + if(response.statusCode != 200) { + throw ToError(response); + } + + console.log(tl.loc('SwappedAppServiceSlotSlots', this._name, this.getSlot(), slotName)); + } + catch(error) { + throw Error(tl.loc('FailedToSwapAppServiceSlotSlots', this._name, this.getSlot(), slotName, this._client.getFormattedError(error))); + } + } + + public async get(force?: boolean): Promise { + if(force || !this._appServiceConfigurationDetails) { + this._appServiceConfigurationDetails = await this._get(); + } + + return this._appServiceConfigurationDetails; + } + + public async getPublishingProfileWithSecrets(force?: boolean): Promise{ + if(force || !this._appServicePublishingProfile) { + this._appServicePublishingProfile = await this._getPublishingProfileWithSecrets(); + } + + return this._appServicePublishingProfile; + } + + public async getPublishingCredentials(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/publishingcredentials/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServicePublishingCredentials', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async getApplicationSettings(force?: boolean): Promise { + if(force || !this._appServiceApplicationSetings) { + this._appServiceApplicationSetings = await this._getApplicationSettings(); + } + + return this._appServiceApplicationSetings; + } + + public async updateApplicationSettings(applicationSettings): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(applicationSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/appsettings`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceApplicationSettings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchApplicationSettings(addProperties: any, deleteProperties?: any, formatJSON?: boolean): Promise { + var applicationSettings = await this.getApplicationSettings(); + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(formatJSON) { + if(JSON.stringify(applicationSettings.properties[key]) != JSON.stringify(addProperties[key])) { + tl.debug(`Value of ${key} has been changed to ${JSON.stringify(addProperties[key])}`); + isNewValueUpdated = true; + } + else { + tl.debug(`${key} is already present.`); + } + } + else { + if(applicationSettings.properties[key] != addProperties[key]) { + tl.debug(`Value of ${key} has been changed to ${addProperties[key]}`); + isNewValueUpdated = true; + } + else { + tl.debug(`${key} is already present.`); + } + } + + applicationSettings.properties[key] = addProperties[key]; + } + for(var key in deleteProperties) { + if(key in applicationSettings.properties) { + delete applicationSettings.properties[key]; + tl.debug(`Removing app setting : ${key}`); + isNewValueUpdated = true; + } + } + + if(isNewValueUpdated) { + applicationSettings.properties[constants.WebsiteEnableSyncUpdateSiteKey] = this._isConsumptionApp ? 'false' : 'true'; + await this.updateApplicationSettings(applicationSettings); + } + + return isNewValueUpdated; + } + + public async patchApplicationSettingsSlot(addProperties: any): Promise { + var appSettingsSlotSettings = await this.getSlotConfigurationNames(); + let appSettingNames = appSettingsSlotSettings.properties.appSettingNames; + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(!appSettingNames) { + appSettingsSlotSettings.properties.appSettingNames = []; + appSettingNames = appSettingsSlotSettings.properties.appSettingNames; + } + if(addProperties[key].slotSetting == true) { + if((appSettingNames.length == 0) || (!appSettingNames.includes(addProperties[key].name))) { + appSettingNames.push(addProperties[key].name); + } + tl.debug(`Slot setting updated for key : ${addProperties[key].name}`); + isNewValueUpdated = true; + } + else if ((addProperties[key].slotSetting == false || (addProperties[key].slotSetting == null)) && appSettingNames != null ) { + const index = appSettingNames.indexOf(addProperties[key].name, 0); + if (index > -1) { + appSettingNames.splice(index, 1); + } + isNewValueUpdated = true; + } + } + + if(isNewValueUpdated) { + await this.updateSlotConfigSettings(appSettingsSlotSettings); + } + + } + + public async syncFunctionTriggers(): Promise { + try { + let i = 0; + let retryCount = 5; + let retryIntervalInSeconds = 2; + let timeToWait: number = retryIntervalInSeconds; + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/syncfunctiontriggers`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + while(true) { + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode == 200) { + return response.body; + } + else if(response.statusCode == 400) { + if (++i < retryCount) { + await webClient.sleepFor(timeToWait); + timeToWait = timeToWait * retryIntervalInSeconds + retryIntervalInSeconds; + continue; + } + else { + throw ToError(response); + } + } + else { + throw ToError(response); + } + } + } + catch(error) { + throw Error(tl.loc('FailedToSyncTriggers', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async getConfiguration(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/web`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2018-02-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceConfiguration', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async updateConfiguration(applicationSettings): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(applicationSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/web`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2018-02-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceConfiguration', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchConfiguration(properties: any): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PATCH'; + httpRequest.body = JSON.stringify(properties); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/web`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2018-02-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToPatchAppServiceConfiguration', this._getFormattedName(), this._client.getFormattedError(error))); + } + + } + + public async getConnectionStrings(force?: boolean): Promise { + if(force || !this._appServiceConnectionString) { + this._appServiceConnectionString = await this._getConnectionStrings(); + } + + return this._appServiceConnectionString; + } + + public async getSlotConfigurationNames(force?: boolean): Promise { + if(force || !this._appServiceConfigurationSettings) { + this._appServiceConfigurationSettings = await this._getSlotConfigurationNames(); + } + + return this._appServiceConfigurationSettings; + } + + public async patchConnectionString(addProperties: any): Promise { + var connectionStringSettings = await this.getConnectionStrings(); + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(JSON.stringify(connectionStringSettings.properties[key]) != JSON.stringify(addProperties[key])) { + tl.debug(`Value of ${key} has been changed to ${JSON.stringify(addProperties[key])}`); + isNewValueUpdated = true; + } + else { + tl.debug(`${key} is already present.`); + } + connectionStringSettings.properties[key] = addProperties[key]; + } + + if(isNewValueUpdated) { + await this.updateConnectionStrings(connectionStringSettings); + } + } + + public async updateConnectionStrings(connectionStringSettings: any): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(connectionStringSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/connectionstrings`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceConnectionStrings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchConnectionStringSlot(addProperties: any): Promise { + var connectionStringSlotSettings = await this.getSlotConfigurationNames(); + let connectionStringNames = connectionStringSlotSettings.properties.connectionStringNames; + var isNewValueUpdated: boolean = false; + for(var key in addProperties) { + if(!connectionStringNames) { + connectionStringSlotSettings.properties.connectionStringNames = []; + connectionStringNames = connectionStringSlotSettings.properties.connectionStringNames; + } + if(addProperties[key].slotSetting == true) { + if((connectionStringNames.length == 0) || (!connectionStringNames.includes(key))) { + connectionStringNames.push(key); + } + tl.debug(`Slot setting updated for key : ${key}`); + isNewValueUpdated = true; + } + } + + if(isNewValueUpdated) { + await this.updateSlotConfigSettings(connectionStringSlotSettings); + } + } + + public async updateSlotConfigSettings(SlotConfigSettings: any): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(SlotConfigSettings); + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/slotConfigNames`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceConfigSlotSettings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async getMetadata(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/metadata/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceMetadata', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async updateMetadata(applicationSettings): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'PUT'; + httpRequest.body = JSON.stringify(applicationSettings); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/metadata`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToUpdateAppServiceMetadata', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + public async patchMetadata(properties): Promise { + var applicationSettings = await this.getMetadata(); + for(var key in properties) { + applicationSettings.properties[key] = properties[key]; + } + + await this.updateMetadata(applicationSettings); + } + + public getSlot(): string { + return this._slot ? this._slot : "production"; + } + + private async _getPublishingProfileWithSecrets(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/publishxml`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + var publishingProfile = response.body; + return publishingProfile; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServicePublishingProfile', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _getApplicationSettings(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/appsettings/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceApplicationSettings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _getConnectionStrings(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/config/connectionstrings/list`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceConnectionStrings', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _getSlotConfigurationNames(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/config/slotConfigNames`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceSlotConfigurationNames', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private async _get(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2016-08-01'); + + var response = await this._client.beginRequest(httpRequest); + if(response.statusCode != 200) { + throw ToError(response); + } + + var appDetails = response.body; + return appDetails as AzureAppServiceConfigurationDetails; + } + catch(error) { + throw Error(tl.loc('FailedToGetAppServiceDetails', this._getFormattedName(), this._client.getFormattedError(error))); + } + } + + private _getFormattedName(): string { + return this._slot ? `${this._name}-${this._slot}` : this._name; + } + + public getName(): string { + return this._name; + } + + public async getSitePublishingCredentialPolicies(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/basicPublishingCredentialsPolicies/scm`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get SitePublishingCredentialPolicies. Error: ${this._client.getFormattedError(error)}`); + } + } + + public async getSiteVirtualNetworkConnections(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/virtualNetworkConnections`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get Virtual Network Connections. Error: ${this._client.getFormattedError(error)}`); + } + } + + public async getSitePrivateEndpointConnections(): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'GET'; + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/privateEndpointConnections`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get Private Endpoint Connections. Error: ${this._client.getFormattedError(error)}`); + } + } + + public async getConnectionStringValidation(connectionDetails): Promise { + try { + var httpRequest = new webClient.WebRequest(); + httpRequest.method = 'POST'; + httpRequest.body = JSON.stringify(connectionDetails); + var slotUrl: string = !!this._slot ? `/slots/${this._slot}` : ''; + httpRequest.uri = this._client.getRequestUri(`//subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Web/sites/{name}/${slotUrl}/extensions/DaaS/api/connectionstringvalidation/validate/`, + { + '{resourceGroupName}': this._resourceGroup, + '{name}': this._name, + }, null, '2022-03-01'); + let requestOptions = new webClient.WebRequestOptions(); + requestOptions.retryCount = 1; + + var response = await this._client.beginRequest(httpRequest, requestOptions); + if(response.statusCode != 200) { + throw ToError(response); + } + + return response.body; + } + catch(error) { + throw Error(`Failed to get Connection String Validation. Error: ${this._client.getFormattedError(error)}`); + } + } + } \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV2/azurermwebappdeployment.ts b/Tasks/AzureFunctionAppV2/azurermwebappdeployment.ts index e4f11d2c0053..6e8ba2756ef7 100644 --- a/Tasks/AzureFunctionAppV2/azurermwebappdeployment.ts +++ b/Tasks/AzureFunctionAppV2/azurermwebappdeployment.ts @@ -1,6 +1,6 @@ import tl = require('azure-pipelines-task-lib/task'); import path = require('path'); -import * as Endpoint from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-endpoint'; +import * as Endpoint from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azure-arm-endpoint'; import { TaskParameters, TaskParametersUtility } from './taskparameters'; import { DeploymentFactory } from './deploymentProvider/DeploymentFactory'; @@ -9,7 +9,7 @@ async function main() { try { tl.setResourcePath(path.join( __dirname, 'task.json')); - tl.setResourcePath(path.join( __dirname, 'node_modules/azure-pipelines-tasks-azure-arm-rest-v2/module.json')); + tl.setResourcePath(path.join( __dirname, 'node_modules/azure-pipelines-tasks-azurermdeploycommon/module.json')); var taskParams: TaskParameters = await TaskParametersUtility.getParameters(); var deploymentFactory: DeploymentFactory = new DeploymentFactory(taskParams); var deploymentProvider = await deploymentFactory.GetDeploymentProvider(); diff --git a/Tasks/AzureFunctionAppV2/deploymentProvider/AzureRmWebAppDeploymentProvider.ts b/Tasks/AzureFunctionAppV2/deploymentProvider/AzureRmWebAppDeploymentProvider.ts index 681425fa3cf3..c778ae9c83f8 100644 --- a/Tasks/AzureFunctionAppV2/deploymentProvider/AzureRmWebAppDeploymentProvider.ts +++ b/Tasks/AzureFunctionAppV2/deploymentProvider/AzureRmWebAppDeploymentProvider.ts @@ -1,12 +1,11 @@ import tl = require('azure-pipelines-task-lib/task'); -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { Kudu } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service-kudu'; -import { AzureAppServiceUtility } from 'azure-pipelines-tasks-azure-arm-rest-v2/azureAppServiceUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' -import { PackageUtility } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azure-arm-rest-v2/constants'; +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; +import { Kudu } from '../azure-arm-rest/azure-arm-app-service-kudu'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' +import { PackageUtility } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azurermdeploycommon/Constants'; import { TaskParameters } from '../taskparameters'; -import { AzureAppServiceUtilityExt } from '../operations/AzureAppServiceUtilityExt'; +import { AzureAppServiceUtility } from '../operations/AzureAppServiceUtility'; import { KuduServiceUtility } from '../operations/KuduServiceUtility'; import { addReleaseAnnotation } from '../operations/ReleaseAnnotationUtility'; import { IWebAppDeploymentProvider } from './IWebAppDeploymentProvider'; @@ -16,7 +15,6 @@ export class AzureRmWebAppDeploymentProvider implements IWebAppDeploymentProvide protected appService: AzureAppService; protected kuduService: Kudu; protected appServiceUtility: AzureAppServiceUtility; - protected appServiceUtilityExt: AzureAppServiceUtilityExt; protected kuduServiceUtility: KuduServiceUtility; protected virtualApplicationPath: string = ""; protected activeDeploymentID; @@ -31,12 +29,11 @@ export class AzureRmWebAppDeploymentProvider implements IWebAppDeploymentProvide this.appService = new AzureAppService(this.taskParams.azureEndpoint, this.taskParams.ResourceGroupName, this.taskParams.WebAppName, this.taskParams.SlotName, this.taskParams.WebAppKind); this.appServiceUtility = new AzureAppServiceUtility(this.appService); - this.appServiceUtilityExt = new AzureAppServiceUtilityExt(this.appService); this.kuduService = await this.appServiceUtility.getKuduService(); this.kuduServiceUtility = new KuduServiceUtility(this.kuduService); - await this.appServiceUtilityExt.getFuntionAppNetworkingCheck(this.taskParams.isLinuxApp); + await this.appServiceUtility.getFuntionAppNetworkingCheck(this.taskParams.isLinuxApp); } public async DeployWebAppStep() {} @@ -61,6 +58,6 @@ export class AzureRmWebAppDeploymentProvider implements IWebAppDeploymentProvide await this.appServiceUtility.updateAndMonitorAppSettings(customApplicationSettings); } - await this.appServiceUtilityExt.updateScmTypeAndConfigurationDetails(); + await this.appServiceUtility.updateScmTypeAndConfigurationDetails(); } } \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV2/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts b/Tasks/AzureFunctionAppV2/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts index 6f6dfec78390..5bcde8fcc52f 100644 --- a/Tasks/AzureFunctionAppV2/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts +++ b/Tasks/AzureFunctionAppV2/deploymentProvider/BuiltInLinuxWebAppDeploymentProvider.ts @@ -1,9 +1,9 @@ import tl = require('azure-pipelines-task-lib/task'); import path = require('path'); -var webCommonUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' +var webCommonUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' import { TaskParameters, DeploymentType } from '../taskparameters'; import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; @@ -45,7 +45,7 @@ export class BuiltInLinuxWebAppDeploymentProvider extends AzureRmWebAppDeploymen tl.debug('Performing Linux built-in package deployment'); var isNewValueUpdated: boolean = false; - + var linuxFunctionRuntimeSetting = ""; if(this.taskParams.RuntimeStack && linuxFunctionRuntimeSettingValue.get(this.taskParams.RuntimeStack)) { linuxFunctionRuntimeSetting = linuxFunctionRuntimeSettingName + linuxFunctionRuntimeSettingValue.get(this.taskParams.RuntimeStack); @@ -59,11 +59,11 @@ export class BuiltInLinuxWebAppDeploymentProvider extends AzureRmWebAppDeploymen } var customApplicationSetting = ParameterParser.parse(linuxFunctionAppSetting); isNewValueUpdated = await this.appServiceUtility.updateAndMonitorAppSettings(customApplicationSetting); - + if(!isNewValueUpdated) { await this.kuduServiceUtility.warmpUp(); } - + switch(packageType){ case PackageType.folder: let tempPackagePath = webCommonUtility.generateTemporaryFolderOrZipPath(tl.getVariable('AGENT.TEMPDIRECTORY'), false); diff --git a/Tasks/AzureFunctionAppV2/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts b/Tasks/AzureFunctionAppV2/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts index e7f44655eb4f..2792fc7cbd87 100644 --- a/Tasks/AzureFunctionAppV2/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts +++ b/Tasks/AzureFunctionAppV2/deploymentProvider/ConsumptionWebAppDeploymentProvider.ts @@ -1,13 +1,13 @@ import tl = require('azure-pipelines-task-lib/task'); import Q = require('q'); -var webCommonUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); +var webCommonUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); var azureStorage = require('azure-storage'); -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { sleepFor } from 'azure-pipelines-tasks-azure-arm-rest-v2/webClient'; -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility'; -import { AzureAppServiceUtilityExt } from '../operations/AzureAppServiceUtilityExt'; +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; +import { sleepFor } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'; +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility'; +import { AzureAppServiceUtility } from '../operations/AzureAppServiceUtility'; import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeploymentProvider { @@ -15,7 +15,7 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment public async PreDeploymentStep() { this.appService = new AzureAppService(this.taskParams.azureEndpoint, this.taskParams.ResourceGroupName, this.taskParams.WebAppName, this.taskParams.SlotName, this.taskParams.WebAppKind, true); - this.appServiceUtilityExt = new AzureAppServiceUtilityExt(this.appService); + this.appServiceUtility = new AzureAppServiceUtility(this.appService); } public async DeployWebAppStep() { @@ -95,7 +95,7 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment let expiryDate = new Date(startDate); expiryDate.setFullYear(startDate.getUTCFullYear() + 1); startDate.setMinutes(startDate.getMinutes()-5); - + let sharedAccessPolicy = { AccessPolicy: { Permissions: azureStorage.BlobUtilities.SharedAccessPermissions.READ, @@ -103,7 +103,7 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment Expiry: expiryDate } }; - + let token = blobService.generateSharedAccessSignature(containerName, blobName, sharedAccessPolicy); let sasUrl = blobService.getUrl(containerName, blobName, token); let index = sasUrl.indexOf("?"); @@ -134,10 +134,10 @@ export class ConsumptionWebAppDeploymentProvider extends AzureRmWebAppDeployment protected async PostDeploymentStep() { if(this.taskParams.ConfigurationSettings) { var customApplicationSettings = ParameterParser.parse(this.taskParams.ConfigurationSettings); - await this.appService.updateConfigurationSettings(customApplicationSettings); + await this.appServiceUtility.updateConfigurationSettings(customApplicationSettings); } - await this.appServiceUtilityExt.updateScmTypeAndConfigurationDetails(); + await this.appServiceUtility.updateScmTypeAndConfigurationDetails(); } private _getUserDefinedAppSettings() { diff --git a/Tasks/AzureFunctionAppV2/deploymentProvider/DeploymentFactory.ts b/Tasks/AzureFunctionAppV2/deploymentProvider/DeploymentFactory.ts index 7711ee67393e..80c4f1e81d51 100644 --- a/Tasks/AzureFunctionAppV2/deploymentProvider/DeploymentFactory.ts +++ b/Tasks/AzureFunctionAppV2/deploymentProvider/DeploymentFactory.ts @@ -1,5 +1,5 @@ import tl = require('azure-pipelines-task-lib/task'); -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; import { TaskParameters, DeploymentType } from '../taskparameters'; import { BuiltInLinuxWebAppDeploymentProvider } from './BuiltInLinuxWebAppDeploymentProvider'; import { IWebAppDeploymentProvider } from './IWebAppDeploymentProvider'; @@ -22,7 +22,7 @@ export class DeploymentFactory { return new ConsumptionWebAppDeploymentProvider(this._taskParams); } else { return new BuiltInLinuxWebAppDeploymentProvider(this._taskParams); - } + } } else { tl.debug("Deployment started for windows app service"); return await this._getWindowsDeploymentProvider() @@ -42,11 +42,11 @@ export class DeploymentFactory { private async _getWindowsDeploymentProviderForZipAndFolderPackageType(): Promise { if(this._taskParams.DeploymentType != DeploymentType.auto) { return await this._getUserSelectedDeploymentProviderForWindow(); - } else { - var _isMSBuildPackage = await this._taskParams.Package.isMSBuildPackage(); + } else { + var _isMSBuildPackage = await this._taskParams.Package.isMSBuildPackage(); if(_isMSBuildPackage) { throw new Error(tl.loc('MsBuildPackageNotSupported', this._taskParams.Package.getPath())); - } else { + } else { return new WindowsWebAppRunFromZipProvider(this._taskParams); } } diff --git a/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppRunFromZipProvider.ts b/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppRunFromZipProvider.ts index df9bb547e8d0..ef502d013d2a 100644 --- a/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppRunFromZipProvider.ts +++ b/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppRunFromZipProvider.ts @@ -1,10 +1,10 @@ import tl = require('azure-pipelines-task-lib/task'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; +var deployUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; import { DeploymentType } from '../taskparameters'; -import { FileTransformsUtility } from '../operations/FileTransformsUtility'; +import { FileTransformsUtility } from 'azure-pipelines-tasks-azurermdeploycommon/operations/FileTransformsUtility.js'; import { addReleaseAnnotation } from '../operations/ReleaseAnnotationUtility'; import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; @@ -36,7 +36,7 @@ export class WindowsWebAppRunFromZipProvider extends AzureRmWebAppDeploymentProv } tl.debug("Initiated deployment via kudu service for webapp package : "); - + var addCustomApplicationSetting = ParameterParser.parse(runFromZipAppSetting); var deleteCustomApplicationSetting = ParameterParser.parse(oldRunFromZipAppSetting); var isNewValueUpdated: boolean = await this.appServiceUtility.updateAndMonitorAppSettings(addCustomApplicationSetting, deleteCustomApplicationSetting); @@ -45,16 +45,16 @@ export class WindowsWebAppRunFromZipProvider extends AzureRmWebAppDeploymentProv await this.kuduServiceUtility.warmpUp(); } - await this.kuduServiceUtility.getZipDeployValidation(webPackage); - await this.kuduServiceUtility.deployUsingRunFromZip(webPackage, + await this.kuduServiceUtility.getZipDeployValidation(webPackage); + await this.kuduServiceUtility.deployUsingRunFromZip(webPackage, { slotName: this.appService.getSlot() }); await this.PostDeploymentStep(); } - + public async UpdateDeploymentStatus(isDeploymentSuccess: boolean) { await addReleaseAnnotation(this.taskParams.azureEndpoint, this.appService, isDeploymentSuccess); - + let appServiceApplicationUrl: string = await this.appServiceUtility.getApplicationURL(); console.log(tl.loc('AppServiceApplicationURL', appServiceApplicationUrl)); tl.setVariable('AppServiceApplicationUrl', appServiceApplicationUrl); diff --git a/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppZipDeployProvider.ts b/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppZipDeployProvider.ts index 9201fefe6a25..398d7a345048 100644 --- a/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppZipDeployProvider.ts +++ b/Tasks/AzureFunctionAppV2/deploymentProvider/WindowsWebAppZipDeployProvider.ts @@ -1,10 +1,10 @@ import tl = require('azure-pipelines-task-lib/task'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import * as ParameterParser from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility' -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; +var deployUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import * as ParameterParser from 'azure-pipelines-tasks-azurermdeploycommon/operations/ParameterParserUtility' +import { PackageType } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; import { DeploymentType } from '../taskparameters'; -import { FileTransformsUtility } from '../operations/FileTransformsUtility'; +import { FileTransformsUtility } from 'azure-pipelines-tasks-azurermdeploycommon/operations/FileTransformsUtility.js'; import { AzureRmWebAppDeploymentProvider } from './AzureRmWebAppDeploymentProvider'; const removeRunFromZipAppSetting: string = '-WEBSITE_RUN_FROM_ZIP -WEBSITE_RUN_FROM_PACKAGE'; @@ -36,7 +36,7 @@ export class WindowsWebAppZipDeployProvider extends AzureRmWebAppDeploymentProvi } tl.debug("Initiated deployment via kudu service for webapp package : "); - + var deleteApplicationSetting = ParameterParser.parse(removeRunFromZipAppSetting) var isNewValueUpdated: boolean = await this.appServiceUtility.updateAndMonitorAppSettings(null, deleteApplicationSetting); @@ -44,12 +44,12 @@ export class WindowsWebAppZipDeployProvider extends AzureRmWebAppDeploymentProvi await this.kuduServiceUtility.warmpUp(); } - await this.kuduServiceUtility.getZipDeployValidation(webPackage); + await this.kuduServiceUtility.getZipDeployValidation(webPackage); this.zipDeploymentID = await this.kuduServiceUtility.deployUsingZipDeploy(webPackage); await this.PostDeploymentStep(); } - + public async UpdateDeploymentStatus(isDeploymentSuccess: boolean) { if(this.kuduServiceUtility) { await super.UpdateDeploymentStatus(isDeploymentSuccess); diff --git a/Tasks/AzureFunctionAppV2/make.json b/Tasks/AzureFunctionAppV2/make.json index 8733fe153102..65aef0088694 100644 --- a/Tasks/AzureFunctionAppV2/make.json +++ b/Tasks/AzureFunctionAppV2/make.json @@ -1,12 +1,4 @@ { - "rm": [ - { - "items": [ - "node_modules/azure-pipelines-tasks-azure-arm-rest-v2/node_modules/azure-pipelines-task-lib" - ], - "options": "-Rf" - } - ], "externals": { "archivePackages": [ { diff --git a/Tasks/AzureFunctionAppV2/operations/AzureAppServiceUtilityExt.ts b/Tasks/AzureFunctionAppV2/operations/AzureAppServiceUtility.ts similarity index 55% rename from Tasks/AzureFunctionAppV2/operations/AzureAppServiceUtilityExt.ts rename to Tasks/AzureFunctionAppV2/operations/AzureAppServiceUtility.ts index 4235502d30a0..8402ce108aa1 100644 --- a/Tasks/AzureFunctionAppV2/operations/AzureAppServiceUtilityExt.ts +++ b/Tasks/AzureFunctionAppV2/operations/AzureAppServiceUtility.ts @@ -1,10 +1,14 @@ import tl = require('azure-pipelines-task-lib/task'); -var glob = require("glob"); +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); +var parseString = require('xml2js').parseString; +import Q = require('q'); +import { Kudu } from '../azure-arm-rest/azure-arm-app-service-kudu'; +import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azurermdeploycommon/Constants'; import * as os from "os"; -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azure-arm-rest-v2/constants'; +var glob = require("glob"); -export class AzureAppServiceUtilityExt { +export class AzureAppServiceUtility { private _appService: AzureAppService; constructor(appService: AzureAppService) { this._appService = appService; @@ -57,6 +61,169 @@ export class AzureAppServiceUtilityExt { } } + public async getWebDeployPublishingProfile(): Promise { + var publishingProfile = await this._appService.getPublishingProfileWithSecrets(); + var defer = Q.defer(); + parseString(publishingProfile, (error, result) => { + if(!!error) { + defer.reject(error); + } + var publishProfile = result && result.publishData && result.publishData.publishProfile ? result.publishData.publishProfile : null; + if(publishProfile) { + for (var index in publishProfile) { + if (publishProfile[index].$ && publishProfile[index].$.publishMethod === "MSDeploy") { + defer.resolve(result.publishData.publishProfile[index].$); + } + } + } + + defer.reject(tl.loc('ErrorNoSuchDeployingMethodExists')); + }); + + return defer.promise; + } + + public async getApplicationURL(virtualApplication?: string): Promise { + let webDeployProfile: any = await this.getWebDeployPublishingProfile(); + return await webDeployProfile.destinationAppUrl + ( virtualApplication ? "/" + virtualApplication : "" ); + } + + public async pingApplication(): Promise { + try { + var applicationUrl: string = await this.getApplicationURL(); + + if(!applicationUrl) { + tl.debug("Application Url not found."); + return; + } + await AzureAppServiceUtility.pingApplication(applicationUrl); + } catch(error) { + tl.debug("Unable to ping App Service. Error: ${error}"); + } + } + + public static async pingApplication(applicationUrl: string) { + if(!applicationUrl) { + tl.debug('Application Url empty.'); + return; + } + try { + var webRequest = new webClient.WebRequest(); + webRequest.method = 'GET'; + webRequest.uri = applicationUrl; + let webRequestOptions: webClient.WebRequestOptions = {retriableErrorCodes: [], retriableStatusCodes: [], retryCount: 1, retryIntervalInSeconds: 5, retryRequestTimedout: true}; + var response = await webClient.sendRequest(webRequest, webRequestOptions); + tl.debug(`App Service status Code: '${response.statusCode}'. Status Message: '${response.statusMessage}'`); + } + catch(error) { + tl.debug(`Unable to ping App Service. Error: ${error}`); + } + } + + public async getKuduService(): Promise { + + var publishingCredentials = await this._appService.getPublishingCredentials(); + var scmPolicyCheck = await this.isSitePublishingCredentialsEnabled(); + + if(publishingCredentials.properties["scmUri"]) { + if(scmPolicyCheck === false) { + tl.debug('Gettting Bearer token'); + var accessToken = await this._appService._client.getCredentials().getToken(); + } + else{ + tl.setVariable(`AZURE_APP_SERVICE_KUDU_${this._appService.getSlot()}_PASSWORD`, publishingCredentials.properties["publishingPassword"], true); + var accessToken = (new Buffer(publishingCredentials.properties["publishingUserName"] + ':' + publishingCredentials.properties["publishingPassword"]).toString('base64')); + } + return new Kudu(publishingCredentials.properties["scmUri"], accessToken, scmPolicyCheck); + } + + throw Error(tl.loc('KuduSCMDetailsAreEmpty')); + } + + public async getPhysicalPath(virtualApplication: string): Promise { + + if(!virtualApplication) { + return '/site/wwwroot'; + } + + virtualApplication = (virtualApplication.startsWith("/")) ? virtualApplication.substr(1) : virtualApplication; + + var physicalToVirtualPathMap = await this._getPhysicalToVirtualPathMap(virtualApplication); + + if(!physicalToVirtualPathMap) { + throw Error(tl.loc("VirtualApplicationDoesNotExist", virtualApplication)); + } + + tl.debug(`Virtual Application Map: Physical path: '${physicalToVirtualPathMap.physicalPath}'. Virtual path: '${physicalToVirtualPathMap.virtualPath}'.`); + return physicalToVirtualPathMap.physicalPath; + } + + public async updateConfigurationSettings(properties: any, formatJSON?: boolean) : Promise { + if(formatJSON) { + var configurationSettingsProperties = properties[0]; + console.log(tl.loc('UpdatingAppServiceConfigurationSettings', JSON.stringify(configurationSettingsProperties))); + await this._appService.patchConfiguration({'properties': configurationSettingsProperties}); + } + else + { + for(var property in properties) { + if(!!properties[property] && properties[property].value !== undefined) { + properties[property] = properties[property].value; + } + } + + console.log(tl.loc('UpdatingAppServiceConfigurationSettings', JSON.stringify(properties))); + await this._appService.patchConfiguration({'properties': properties}); + } + console.log(tl.loc('UpdatedAppServiceConfigurationSettings')); + } + + public async updateAndMonitorAppSettings(addProperties?: any, deleteProperties?: any, formatJSON?: boolean): Promise { + if(formatJSON) { + var appSettingsProperties = {}; + for(var property in addProperties) { + appSettingsProperties[addProperties[property].name] = addProperties[property].value; + } + + if(!!addProperties) { + console.log(tl.loc('UpdatingAppServiceApplicationSettings', JSON.stringify(appSettingsProperties))); + } + + if(!!deleteProperties) { + console.log(tl.loc('DeletingAppServiceApplicationSettings', JSON.stringify(Object.keys(deleteProperties)))); + } + + var isNewValueUpdated: boolean = await this._appService.patchApplicationSettings(appSettingsProperties, deleteProperties, true); + } + else { + for(var property in addProperties) { + if(!!addProperties[property] && addProperties[property].value !== undefined) { + addProperties[property] = addProperties[property].value; + } + } + + if(!!addProperties) { + console.log(tl.loc('UpdatingAppServiceApplicationSettings', JSON.stringify(addProperties))); + } + + if(!!deleteProperties) { + console.log(tl.loc('DeletingAppServiceApplicationSettings', JSON.stringify(Object.keys(deleteProperties)))); + } + + var isNewValueUpdated: boolean = await this._appService.patchApplicationSettings(addProperties, deleteProperties); + } + + if(!!isNewValueUpdated) { + console.log(tl.loc('UpdatedAppServiceApplicationSettings')); + } + else { + console.log(tl.loc('AppServiceApplicationSettingsAlreadyPresent')); + } + + await this._appService.patchApplicationSettingsSlot(addProperties); + return isNewValueUpdated; + } + public async updateConnectionStrings(addProperties: any): Promise { var connectionStringProperties = {}; for(var property in addProperties) { @@ -84,13 +251,69 @@ export class AzureAppServiceUtilityExt { return isNewValueUpdated; } + public async enableRenameLockedFiles(): Promise { + try { + var webAppSettings = await this._appService.getApplicationSettings(); + if(webAppSettings && webAppSettings.properties) { + if(webAppSettings.properties.MSDEPLOY_RENAME_LOCKED_FILES !== '1') { + tl.debug(`Rename locked files value found to be ${webAppSettings.properties.MSDEPLOY_RENAME_LOCKED_FILES}. Updating the value to 1`); + await this.updateAndMonitorAppSettings({ 'MSDEPLOY_RENAME_LOCKED_FILES' : '1' }); + console.log(tl.loc('RenameLockedFilesEnabled')); + } + else { + tl.debug('Rename locked files is already enabled in App Service'); + } + } + } + catch(error) { + throw new Error(tl.loc('FailedToEnableRenameLockedFiles', error)); + } + } + + public async updateStartupCommandAndRuntimeStack(runtimeStack: string, startupCommand?: string): Promise { + var configDetails = await this._appService.getConfiguration(); + var appCommandLine: string = configDetails.properties.appCommandLine; + startupCommand = (!!startupCommand) ? startupCommand : appCommandLine; + var linuxFxVersion: string = configDetails.properties.linuxFxVersion; + runtimeStack = (!!runtimeStack) ? runtimeStack : linuxFxVersion; + + if (startupCommand != appCommandLine || runtimeStack != linuxFxVersion) { + await this.updateConfigurationSettings({linuxFxVersion: runtimeStack, appCommandLine: startupCommand}); + } + else { + tl.debug(`Skipped updating the values. linuxFxVersion: ${linuxFxVersion} : appCommandLine: ${appCommandLine}`) + } + } + + private async _getPhysicalToVirtualPathMap(virtualApplication: string): Promise { + // construct URL depending on virtualApplication or root of webapplication + var physicalPath = null; + var virtualPath = "/" + virtualApplication; + var appConfigSettings = await this._appService.getConfiguration(); + var virtualApplicationMappings = appConfigSettings.properties && appConfigSettings.properties.virtualApplications; + + if(virtualApplicationMappings) { + for( var mapping of virtualApplicationMappings ) { + if(mapping.virtualPath.toLowerCase() == virtualPath.toLowerCase()) { + physicalPath = mapping.physicalPath; + break; + } + } + } + + return physicalPath ? { + 'virtualPath': virtualPath, + 'physicalPath': physicalPath + }: null; + } + private _getNewMetadata(): any { var collectionUri = tl.getVariable("system.teamfoundationCollectionUri"); var projectId = tl.getVariable("system.teamprojectId"); var releaseDefinitionId = tl.getVariable("release.definitionId"); // Log metadata properties based on whether task is running in build OR release. - + let newProperties = { VSTSRM_ProjectId: projectId, VSTSRM_AccountId: tl.getVariable("system.collectionId") @@ -100,7 +323,7 @@ export class AzureAppServiceUtilityExt { // Task is running in Release var artifactAlias = tl.getVariable(AzureDeployPackageArtifactAlias); tl.debug("Artifact Source Alias is: "+ artifactAlias); - + let buildDefinitionUrl = ""; let buildDefintionId = ""; @@ -122,7 +345,7 @@ export class AzureAppServiceUtilityExt { buildDefinitionUrl = collectionUri + buildProjectId + "/_build?_a=simple-process&definitionId=" + buildDefintionId; } } - + newProperties["VSTSRM_BuildDefinitionId"] = buildDefintionId; newProperties["VSTSRM_ReleaseDefinitionId"] = releaseDefinitionId; newProperties["VSTSRM_BuildDefinitionWebAccessUrl"] = buildDefinitionUrl; @@ -142,32 +365,32 @@ export class AzureAppServiceUtilityExt { public async getFuntionAppNetworkingCheck(isLinuxApp): Promise { let success = true; - + try{ let isFuncPrivate = await this.isFuncWithPrivateEndpoint(); let isMicrosoftHostedAgent = await this.isMicrosoftHostedAgent(); - + if (isFuncPrivate == "true" && isMicrosoftHostedAgent == "true"){ //will NOT be able to reach kudu site if isFuncPrivate and isMicrosoftHostedAgent tl.error("Function app has private endpoint(s). But you are not running this pipeline from a self-hosted agent that has access to the Functions App. Relevant documentation: " + "https://learn.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops&tabs=browser#install"); success = false; } - else if (isFuncPrivate == "true"){ + else if (isFuncPrivate == "true"){ //just FYI console.log("NOTE: Function app has private endpoint(s). Therefore, make sure that you are running this pipeline from a self-hosted agent that has access to the Functions App."); } - + let isFuncVNet = await this.isFuncVnetIntegrated(); if (isFuncVNet == "true"){ //just FYI console.log("NOTE: Function app is VNet integrated."); } - + //network validation only avaliable for Windows (NOT Linux) if (!isLinuxApp) { let errormessage = await this.isAzureWebJobsStorageAccessible(); - + if (errormessage){ //AzureWebJobsStorage connection string is NOT accessible from Kudu tl.error(errormessage); @@ -177,13 +400,13 @@ export class AzureAppServiceUtilityExt { if (isFuncVNet == "false"){ console.log("NOTE: Function app is NOT VNet integrated."); } - } - } + } + } } catch(error){ tl.debug(`Skipping networking check with error: ${error}`); - } - + } + if (!success){ throw Error("Networking validation for the Function app and Storage account has failed. Please review all error messages."); } @@ -196,35 +419,58 @@ export class AzureAppServiceUtilityExt { if (pe && pe.value && pe.value.length && pe.value.length > 0){ tl.debug("Function app has Private Endpoints."); return "true"; + } + else { + tl.debug("Function app has NO Private Endpoints."); + return "false"; + } + } + catch(error){ + tl.debug(`Skipping private endpoint check: ${error}`); + return null; + + } + + } + + + public async isSitePublishingCredentialsEnabled(): Promise{ + try{ + let scmAuthPolicy: any = await this._appService.getSitePublishingCredentialPolicies(); + tl.debug(`Site Publishing Policy check: ${JSON.stringify(scmAuthPolicy)}`); + if(scmAuthPolicy && scmAuthPolicy.properties.allow) { + tl.debug("Function App does allow SCM access"); + return true; } else { - tl.debug("Function app has NO Private Endpoints."); - return "false"; + tl.debug("Function App does not allow SCM Access"); + return false; } } catch(error){ - tl.debug(`Skipping private endpoint check: ${error}`); - return null; + tl.debug(`Call to get SCM Policy check failed: ${error}`); + return false; } } - + + public async isFuncVnetIntegrated(): Promise{ try{ let vnet: any = await this._appService.getSiteVirtualNetworkConnections(); tl.debug(`VNET check: ${JSON.stringify(vnet)}`); - if (vnet && vnet.length && vnet.length > 0){ + if (vnet && vnet.length && vnet.length > 0){ tl.debug("Function app is VNet integrated."); return "true"; } - else { + else { tl.debug("Function app is NOT VNet integrated."); return "false"; - } + } } catch(error){ tl.debug(`Skipping VNET check: ${error}`); return null; - } + } } public async isMicrosoftHostedAgent(): Promise{ @@ -234,10 +480,10 @@ export class AzureAppServiceUtilityExt { if (agentos.match(/^Window/)){ tl.debug(`Windows Agent`); - dir = "C:\\agents\\*\\.setup_info"; + dir = "C:\\agents\\*\\.setup_info"; } - else if (agentos.match(/^Linux/)){ - tl.debug(`Linux Agent`); + else if (agentos.match(/^Linux/)){ + tl.debug(`Linux Agent`); dir = `${process.env.HOME}/agents/*/.setup_info`; } else if (agentos.match(/^Darwin/)){ @@ -245,7 +491,7 @@ export class AzureAppServiceUtilityExt { dir = `${process.env.HOME}/runners/*/.setup_info`; } - var files = glob.sync(dir); + var files = glob.sync(dir); if (files && files.length && files.length > 0) { tl.debug(`Running on Microsoft-hosted agent.`); return "true"; @@ -258,14 +504,14 @@ export class AzureAppServiceUtilityExt { } } - public async isAzureWebJobsStorageAccessible(): Promise{ + public async isAzureWebJobsStorageAccessible(): Promise{ let errormessage = ""; let propertyName = "AzureWebJobsStorage"; let appSettings = await this._appService.getApplicationSettings(); if(appSettings && appSettings.properties && appSettings.properties.AzureWebJobsStorage) { let connectionDetails = {}; - connectionDetails['ConnectionString'] = appSettings.properties.AzureWebJobsStorage; + connectionDetails['ConnectionString'] = appSettings.properties.AzureWebJobsStorage; connectionDetails['Type'] = 'StorageAccount'; if(connectionDetails['ConnectionString'].includes('@Microsoft.KeyVault')){ @@ -289,7 +535,7 @@ export class AzureAppServiceUtilityExt { * MalformedConnectionString, * UnknownError */ - if (validation && validation.StatusText && validation.StatusText != "Success"){ + if (validation && validation.StatusText && validation.StatusText != "Success"){ switch (validation.StatusText) { case "MalformedConnectionString": @@ -306,8 +552,8 @@ export class AzureAppServiceUtilityExt { break; case "Forbidden": // Some authentication failures come through as Forbidden so check the exception data - if(validation.Exception != undefined && - validation.Exception.RequestInformation != undefined && + if(validation.Exception != undefined && + validation.Exception.RequestInformation != undefined && JSON.stringify(validation.Exception.RequestInformation).includes("AuthenticationFailed")) { errormessage = `Authentication failure - The credentials in the "${propertyName}" connection string are either invalid or expired. Please update the app setting "${propertyName}" with a valid connection string.`; } else { @@ -322,8 +568,8 @@ export class AzureAppServiceUtilityExt { // Show the exception message as it contains useful information to fix the issue. Don't show it unless its accompanied with other explanations. errormessage += (errormessage != "" && validation.Exception ? `\r\n\r\nException encountered while connecting: ${validation.Exception.Message}` : undefined); } - } - } - return errormessage; + } + } + return errormessage; } } \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV2/operations/FileTransformsUtility.ts b/Tasks/AzureFunctionAppV2/operations/FileTransformsUtility.ts deleted file mode 100644 index fdf1fef18a03..000000000000 --- a/Tasks/AzureFunctionAppV2/operations/FileTransformsUtility.ts +++ /dev/null @@ -1,29 +0,0 @@ -import tl = require('azure-pipelines-task-lib/task'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var generateWebConfigUtil = require('azure-pipelines-tasks-webdeployment-common/webconfigutil'); -import { parse } from 'azure-pipelines-tasks-webdeployment-common/ParameterParserUtility'; -import { PackageType } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; - -export class FileTransformsUtility { - private static rootDirectoryPath: string = "D:\\home\\site\\wwwroot"; - public static async applyTransformations(webPackage: string, parameters: string, packageType: PackageType): Promise { - tl.debug("WebConfigParameters is "+ parameters); - if (parameters) { - var isFolderBasedDeployment: boolean = tl.stats(webPackage).isDirectory(); - var folderPath = await deployUtility.generateTemporaryFolderForDeployment(isFolderBasedDeployment, webPackage, packageType); - if (parameters) { - tl.debug('parsing web.config parameters'); - var webConfigParameters = parse(parameters); - generateWebConfigUtil.addWebConfigFile(folderPath, webConfigParameters, this.rootDirectoryPath); - } - - var output = await deployUtility.archiveFolderForDeployment(isFolderBasedDeployment, folderPath); - webPackage = output.webDeployPkg; - } - else { - tl.debug('File Tranformation not enabled'); - } - - return webPackage; - } -} \ No newline at end of file diff --git a/Tasks/AzureFunctionAppV2/operations/KuduServiceUtility.ts b/Tasks/AzureFunctionAppV2/operations/KuduServiceUtility.ts index e1236eec2cda..e4517c7eb61f 100644 --- a/Tasks/AzureFunctionAppV2/operations/KuduServiceUtility.ts +++ b/Tasks/AzureFunctionAppV2/operations/KuduServiceUtility.ts @@ -1,10 +1,11 @@ import tl = require('azure-pipelines-task-lib/task'); import path = require('path'); -var deployUtility = require('azure-pipelines-tasks-webdeployment-common/utility'); -var zipUtility = require('azure-pipelines-tasks-webdeployment-common/ziputility'); -import { Kudu } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service-kudu'; -import { AzureDeployPackageArtifactAlias, KUDU_DEPLOYMENT_CONSTANTS } from 'azure-pipelines-tasks-azure-arm-rest-v2/constants'; -import webClient = require('azure-pipelines-tasks-azure-arm-rest-v2/webClient'); +var deployUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/utility.js'); +var zipUtility = require('azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/ziputility.js'); +import { Kudu } from '../azure-arm-rest/azure-arm-app-service-kudu'; +import { AzureDeployPackageArtifactAlias } from 'azure-pipelines-tasks-azurermdeploycommon/Constants'; +import { KUDU_DEPLOYMENT_CONSTANTS } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/constants'; +import webClient = require('azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/webClient'); const physicalRootPath: string = '/site/wwwroot'; const deploymentFolder: string = 'site/deployments'; @@ -90,7 +91,7 @@ export class KuduServiceUtility { return deploymentDetails.id; } catch(error) { - let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTraceUrl(); + let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTrace(); tl.error(tl.loc('PackageDeploymentFailed')); tl.error(tl.loc('KuduStackTraceURL', stackTraceUrl)); throw Error(error); @@ -113,7 +114,7 @@ export class KuduServiceUtility { console.log("NOTE: Run From Package makes wwwroot read-only, so you will receive an error when writing files to this directory."); } catch(error) { - let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTraceUrl(); + let stackTraceUrl:string = this._appServiceKuduService.getKuduStackTrace(); tl.error(tl.loc('PackageDeploymentFailed')); tl.error(tl.loc('KuduStackTraceURL', stackTraceUrl)); throw Error(error); @@ -127,7 +128,7 @@ export class KuduServiceUtility { let queryParameters: Array = [ 'isAsync=true' ]; - + if(targetFolderName) { queryParameters.push('name=' + encodeURIComponent(targetFolderName)); } @@ -284,9 +285,9 @@ export class KuduServiceUtility { repositoryUrl = tl.getVariable("build.repository.uri") || ""; branch = tl.getVariable("build.sourcebranchname") || tl.getVariable("build.sourcebranch"); } - + deploymentID = !!deploymentID ? deploymentID : this.getDeploymentID(); - + var message = { type : "deployment", commitId : commitId, @@ -309,7 +310,7 @@ export class KuduServiceUtility { for(var attribute in customMessage) { message[attribute] = customMessage[attribute]; } - + } var deploymentLogType: string = message['type']; var active: boolean = false; @@ -328,7 +329,7 @@ export class KuduServiceUtility { } public async getZipDeployValidation(packagePath: string, zipLanguage?: string, zipIs64Bit?: string): Promise { - try { + try { console.log("Validating deployment package for functions app before Zip Deploy"); let queryParameters: Array = [ 'zipLanguage=' + !!zipLanguage ? zipLanguage : '', diff --git a/Tasks/AzureFunctionAppV2/operations/ReleaseAnnotationUtility.ts b/Tasks/AzureFunctionAppV2/operations/ReleaseAnnotationUtility.ts index db004fa4389b..3e2c7bcefd5d 100644 --- a/Tasks/AzureFunctionAppV2/operations/ReleaseAnnotationUtility.ts +++ b/Tasks/AzureFunctionAppV2/operations/ReleaseAnnotationUtility.ts @@ -1,7 +1,7 @@ import tl = require('azure-pipelines-task-lib/task'); -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { AzureApplicationInsights, ApplicationInsightsResources} from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-appinsights'; -import { AzureEndpoint } from 'azure-pipelines-tasks-azure-arm-rest-v2/azureModels'; +import { AzureAppService } from '../azure-arm-rest/azure-arm-app-service'; +import { AzureApplicationInsights, ApplicationInsightsResources} from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azure-arm-appinsights'; +import { AzureEndpoint } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; var uuidV4 = require("uuid/v4"); @@ -42,7 +42,7 @@ function getReleaseAnnotation(isDeploymentSuccess: boolean): {[key: string]: any else if (!!buildUri) { annotationName = `${tl.getVariable("Build.DefinitionName")} - ${tl.getVariable("Build.BuildNumber")}`; } - + let releaseAnnotationProperties = { "Label": isDeploymentSuccess ? "Success" : "Error", // Label decides the icon for release annotation "Deployment Uri": getDeploymentUri(), diff --git a/Tasks/AzureFunctionAppV2/package-lock.json b/Tasks/AzureFunctionAppV2/package-lock.json index ae11a68615c4..a19736d27165 100644 --- a/Tasks/AzureFunctionAppV2/package-lock.json +++ b/Tasks/AzureFunctionAppV2/package-lock.json @@ -4,28 +4,6 @@ "lockfileVersion": 1, "requires": true, "dependencies": { - "@azure/msal-common": { - "version": "9.1.1", - "resolved": "https://registry.npmjs.org/@azure/msal-common/-/msal-common-9.1.1.tgz", - "integrity": "sha512-we9xR8lvu47fF0h+J8KyXoRy9+G/fPzm3QEa2TrdR3jaVS3LKAyE2qyMuUkNdbVkvzl8Zr9f7l+IUSP22HeqXw==" - }, - "@azure/msal-node": { - "version": "1.14.5", - "resolved": "https://registry.npmjs.org/@azure/msal-node/-/msal-node-1.14.5.tgz", - "integrity": "sha512-NcVdMfn8Z3ogN+9RjOSF7uwf2Gki5DEJl0BdDSL83KUAgVAobtkZi5W8EqxbJLrTO/ET0jv5DregrcR5qg2pEA==", - "requires": { - "@azure/msal-common": "^9.0.1", - "jsonwebtoken": "^8.5.1", - "uuid": "^8.3.0" - }, - "dependencies": { - "uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==" - } - } - }, "@types/concat-stream": { "version": "1.6.1", "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", @@ -42,14 +20,6 @@ "@types/node": "*" } }, - "@types/jsonwebtoken": { - "version": "8.5.9", - "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-8.5.9.tgz", - "integrity": "sha512-272FMnFGzAVMGtu9tkr29hRL6bZj4Zs1KZNeHLnKqAvp06tAIcarTMwOh8/8bz4FmKRcMxZhZNeUAQsNLoiPhg==", - "requires": { - "@types/node": "*" - } - }, "@types/mocha": { "version": "5.2.7", "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-5.2.7.tgz", @@ -70,20 +40,11 @@ "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.9.7.tgz", "integrity": "sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==" }, - "@xmldom/xmldom": { - "version": "git+https://github.com/xmldom/xmldom.git#238b1ea8431fae8817812c68d55b4933248af07e", - "from": "git+https://github.com/xmldom/xmldom.git#0.8.6" - }, "abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, - "agent-base": { - "version": "5.1.1", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz", - "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g==" - }, "ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", @@ -95,6 +56,21 @@ "uri-js": "^4.2.2" } }, + "archiver": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/archiver/-/archiver-2.1.1.tgz", + "integrity": "sha512-01psM0DMD3YItvhnAXZODfsViaeDidrJwfne3lsoVrbyYa/xFQwTbVjY+2WlEBm7qH1fCsyxAA1SgNr/XenTlQ==", + "requires": { + "archiver-utils": "^1.3.0", + "async": "^2.0.0", + "buffer-crc32": "^0.2.1", + "glob": "^7.0.0", + "lodash": "^4.8.0", + "readable-stream": "^2.0.0", + "tar-stream": "^1.5.0", + "zip-stream": "^1.2.0" + } + }, "archiver-utils": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-1.3.0.tgz", @@ -134,14 +110,6 @@ "lodash": "^4.17.14" } }, - "async-mutex": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.4.0.tgz", - "integrity": "sha512-eJFZ1YhRR8UN8eBLoNzcDPcy/jqjsg6I1AP+KvWQX80BqOSW1oJPJXDylPUEeMr2ZQvHgnQ//Lp6f3RQ1zI7HA==", - "requires": { - "tslib": "^2.4.0" - } - }, "asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -157,15 +125,6 @@ "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==" }, - "azure-devops-node-api": { - "version": "11.2.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-11.2.0.tgz", - "integrity": "sha512-XdiGPhrpaT5J8wdERRKs5g8E0Zy1pvOYTli7z9E8nmOn3YGp4FhtjhrOyFmX/8veWCwdI69mCHKJw6l+4J/bHA==", - "requires": { - "tunnel": "0.0.6", - "typed-rest-client": "^1.8.4" - } - }, "azure-pipelines-task-lib": { "version": "3.4.0", "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-3.4.0.tgz", @@ -187,126 +146,30 @@ } } }, - "azure-pipelines-tasks-azure-arm-rest-v2": { + "azure-pipelines-tasks-azurermdeploycommon": { "version": "3.221.3", - "resolved": "https://registry.npmjs.org/azure-pipelines-tasks-azure-arm-rest-v2/-/azure-pipelines-tasks-azure-arm-rest-v2-3.221.3.tgz", - "integrity": "sha512-EBBphV/VwqJRjhiWSsb+163DVge3VPwGubKO7MPRFZ0BgLo2d0Zo217x1690D4u8GqpksGR/SytHdcKlDzTbBQ==", + "resolved": "https://registry.npmjs.org/azure-pipelines-tasks-azurermdeploycommon/-/azure-pipelines-tasks-azurermdeploycommon-3.221.3.tgz", + "integrity": "sha512-PgSfdeV2SvKvjznAshoEJPHDFTf5w8AsAcMNeHp2PP4/0LNYccq7JxcAxy6FJJWBvMVz7fhWDX5wauhV5XpTug==", "requires": { - "@azure/msal-node": "1.14.5", - "@types/jsonwebtoken": "^8.5.8", "@types/mocha": "^5.2.7", "@types/node": "^10.17.0", - "@types/q": "1.5.4", - "async-mutex": "^0.4.0", - "azure-devops-node-api": "^12.0.0", - "azure-pipelines-task-lib": "^3.4.0", - "https-proxy-agent": "^4.0.0", + "@types/q": "1.0.7", + "archiver": "2.1.1", + "azure-pipelines-task-lib": "^3.1.0", + "decompress-zip": "^0.3.3", "jsonwebtoken": "^8.5.1", - "node-fetch": "^2.6.7", + "ltx": "2.6.2", + "node-stream-zip": "1.7.0", "q": "1.5.1", - "typed-rest-client": "1.8.4", + "typed-rest-client": "^1.8.4", + "winreg": "1.2.2", "xml2js": "0.4.13" }, "dependencies": { - "@types/q": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@types/q/-/q-1.5.4.tgz", - "integrity": "sha512-1HcDas8SEj4z1Wc696tH56G8OlRaH/sqZOynNNB+HF0WOeXPaxTtbYzJY2oEfiUxjSKjhCKr+MvR7dCHcEelug==" - }, - "azure-devops-node-api": { - "version": "12.0.0", - "resolved": "https://registry.npmjs.org/azure-devops-node-api/-/azure-devops-node-api-12.0.0.tgz", - "integrity": "sha512-S6Il++7dQeMlZDokBDWw7YVoPeb90tWF10pYxnoauRMnkuL91jq9M7SOYRVhtO3FUC5URPkB/qzGa7jTLft0Xw==", - "requires": { - "tunnel": "0.0.6", - "typed-rest-client": "^1.8.4" - } - }, "q": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" - }, - "qs": { - "version": "6.11.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.11.1.tgz", - "integrity": "sha512-0wsrzgTz/kAVIeuxSjnpGC56rzYtr6JT/2BwEvMaPhFIoYa1aGO8LbzuU1R0uUYQkLpWBTOj0l/CLAJB64J6nQ==", - "requires": { - "side-channel": "^1.0.4" - } - }, - "typed-rest-client": { - "version": "1.8.4", - "resolved": "https://registry.npmjs.org/typed-rest-client/-/typed-rest-client-1.8.4.tgz", - "integrity": "sha512-MyfKKYzk3I6/QQp6e1T50py4qg+c+9BzOEl2rBmQIpStwNUoqQ73An+Tkfy9YuV7O+o2mpVVJpe+fH//POZkbg==", - "requires": { - "qs": "^6.9.1", - "tunnel": "0.0.6", - "underscore": "^1.12.1" - } - } - } - }, - "azure-pipelines-tasks-webdeployment-common": { - "version": "4.219.1", - "resolved": "https://registry.npmjs.org/azure-pipelines-tasks-webdeployment-common/-/azure-pipelines-tasks-webdeployment-common-4.219.1.tgz", - "integrity": "sha512-Uf9b0u33Wwff2EBBxFcvPaL8LetSBT2q8l28SVTOVcDgnQVMKJPR7pMK+DBzLiDlLe164MOcGrlOK+Oi/0+5LA==", - "requires": { - "@types/mocha": "^5.2.7", - "@types/node": "^10.17.0", - "@xmldom/xmldom": "git+https://github.com/xmldom/xmldom.git#0.8.6", - "archiver": "1.2.0", - "azure-pipelines-task-lib": "^4.2.0", - "decompress-zip": "^0.3.3", - "ltx": "2.8.0", - "q": "1.4.1", - "winreg": "1.2.2", - "xml2js": "0.4.13" - }, - "dependencies": { - "archiver": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/archiver/-/archiver-1.2.0.tgz", - "integrity": "sha512-5GQRAgpHGPwWIiMzL9lthd+t75fLi8BpRBYtflomSYv2i6+EO9trtwWAm2+zGjIuwKmVmBRknAZFFBSqxYxiJw==", - "requires": { - "archiver-utils": "^1.3.0", - "async": "^2.0.0", - "buffer-crc32": "^0.2.1", - "glob": "^7.0.0", - "lodash": "^4.8.0", - "readable-stream": "^2.0.0", - "tar-stream": "^1.5.0", - "zip-stream": "^1.1.0" - } - }, - "azure-pipelines-task-lib": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/azure-pipelines-task-lib/-/azure-pipelines-task-lib-4.3.1.tgz", - "integrity": "sha512-AEwz0+Sofv80UviCYsS6fzyX5zzsLapmNCMNUoaRePZQVN+oQBStix1DGg4fdZf9zJ6acNd9xEBZQWbWuZu5Zg==", - "requires": { - "minimatch": "3.0.5", - "mockery": "^2.1.0", - "q": "^1.5.1", - "semver": "^5.1.0", - "shelljs": "^0.8.5", - "sync-request": "6.1.0", - "uuid": "^3.0.1" - }, - "dependencies": { - "q": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", - "integrity": "sha512-kV/CThkXo6xyFEZUugw/+pIOywXcDbFYgSct5cT3gqlbkBE1SJdwy6UQoZvodiWF/ckQLZyDE/Bu1M6gVu5lVw==" - } - } - }, - "ltx": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.8.0.tgz", - "integrity": "sha512-SJJUrmDgXP0gkUzgErfkaeD+pugM8GYxerTALQa1gTUb5W1wrC4k07GZU+QNZd7MpFqJSYWXTQSUy8Ps03hx5Q==", - "requires": { - "inherits": "^2.0.1" - } } } }, @@ -534,21 +397,6 @@ "assert-plus": "^1.0.0" } }, - "debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "requires": { - "ms": "2.1.2" - }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" - } - } - }, "decompress-zip": { "version": "0.3.3", "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.3.tgz", @@ -808,15 +656,6 @@ "sshpk": "^1.7.0" } }, - "https-proxy-agent": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz", - "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==", - "requires": { - "agent-base": "5", - "debug": "4" - } - }, "ieee754": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", @@ -992,6 +831,14 @@ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" }, + "ltx": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/ltx/-/ltx-2.6.2.tgz", + "integrity": "sha512-xg03AeMzTVozL1K/fuzxkzvnvx+yjY6/uJHN6dmQYP8Jm26GJjEr5EV3Sj5Yi/XYDJ1k9xVPZWvdQzJG7o+C5w==", + "requires": { + "inherits": "^2.0.1" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -1043,13 +890,10 @@ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" }, - "node-fetch": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", - "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", - "requires": { - "whatwg-url": "^5.0.0" - } + "node-stream-zip": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/node-stream-zip/-/node-stream-zip-1.7.0.tgz", + "integrity": "sha512-kYVtF3lK++53Bg6hZNplYVMrR7Lt0IYdLWehgoHUJLJcSwg/xd2Rm2Z7kJ5W8ZA7pdeg/DiUQDatbYwL3C7qSw==" }, "nopt": { "version": "3.0.6", @@ -1374,21 +1218,11 @@ "punycode": "^2.1.1" } }, - "tr46": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - }, "traverse": { "version": "0.3.9", "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz", "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==" }, - "tslib": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz", - "integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg==" - }, "tunnel": { "version": "0.0.6", "resolved": "https://registry.npmjs.org/tunnel/-/tunnel-0.0.6.tgz", @@ -1476,20 +1310,6 @@ "extsprintf": "^1.2.0" } }, - "webidl-conversions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - }, - "whatwg-url": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", - "requires": { - "tr46": "~0.0.3", - "webidl-conversions": "^3.0.0" - } - }, "winreg": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/winreg/-/winreg-1.2.2.tgz", diff --git a/Tasks/AzureFunctionAppV2/package.json b/Tasks/AzureFunctionAppV2/package.json index dfab4f3fc2b5..5537730c6f29 100644 --- a/Tasks/AzureFunctionAppV2/package.json +++ b/Tasks/AzureFunctionAppV2/package.json @@ -20,10 +20,7 @@ "@types/mocha": "^5.2.7", "@types/node": "^10.17.0", "@types/q": "1.0.7", - "azure-devops-node-api": "11.2.0", - "azure-pipelines-task-lib": "^3.4.0", - "azure-pipelines-tasks-azure-arm-rest-v2": "^3.221.3", - "azure-pipelines-tasks-webdeployment-common": "^4.219.1", + "azure-pipelines-tasks-azurermdeploycommon": "^3.208.0", "azure-storage": "2.10.7", "moment": "^2.29.4", "q": "1.4.1", diff --git a/Tasks/AzureFunctionAppV2/task.json b/Tasks/AzureFunctionAppV2/task.json index da43b778138c..8953fdce5900 100644 --- a/Tasks/AzureFunctionAppV2/task.json +++ b/Tasks/AzureFunctionAppV2/task.json @@ -18,7 +18,7 @@ "version": { "Major": 2, "Minor": 221, - "Patch": 104 + "Patch": 105 }, "releaseNotes": "What's new in version 2.*
Removed WAR Deploy Support, setting Web.Config options, and the option for the Startup command for Linux Azure Function Apps.", "minimumAgentVersion": "2.104.1", diff --git a/Tasks/AzureFunctionAppV2/task.loc.json b/Tasks/AzureFunctionAppV2/task.loc.json index f65e6f873e06..13b8d85d6dfc 100644 --- a/Tasks/AzureFunctionAppV2/task.loc.json +++ b/Tasks/AzureFunctionAppV2/task.loc.json @@ -18,7 +18,7 @@ "version": { "Major": 2, "Minor": 221, - "Patch": 104 + "Patch": 105 }, "releaseNotes": "ms-resource:loc.releaseNotes", "minimumAgentVersion": "2.104.1", diff --git a/Tasks/AzureFunctionAppV2/taskparameters.ts b/Tasks/AzureFunctionAppV2/taskparameters.ts index 5f3355e9540b..f843c3913844 100644 --- a/Tasks/AzureFunctionAppV2/taskparameters.ts +++ b/Tasks/AzureFunctionAppV2/taskparameters.ts @@ -1,9 +1,9 @@ import tl = require('azure-pipelines-task-lib/task'); -import { AzureEndpoint } from 'azure-pipelines-tasks-azure-arm-rest-v2/azureModels'; -import { AzureRMEndpoint } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-endpoint'; -import { AzureAppService } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-app-service'; -import { Resources } from 'azure-pipelines-tasks-azure-arm-rest-v2/azure-arm-resource'; -import { Package } from 'azure-pipelines-tasks-webdeployment-common/packageUtility'; +import { AzureEndpoint } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azureModels'; +import { AzureRMEndpoint } from 'azure-pipelines-tasks-azurermdeploycommon/azure-arm-rest/azure-arm-endpoint'; +import { AzureAppService } from './azure-arm-rest/azure-arm-app-service'; +import { AzureResourceFilterUtility } from 'azure-pipelines-tasks-azurermdeploycommon/operations/AzureResourceFilterUtility'; +import { Package } from 'azure-pipelines-tasks-azurermdeploycommon/webdeployment-common/packageUtility'; const skuDynamicValue: string = 'dynamic'; const skuElasticPremiumValue: string = 'elasticpremium'; @@ -24,7 +24,7 @@ export class TaskParametersUtility { AppSettings: tl.getInput('appSettings', false), ConfigurationSettings: tl.getInput('configurationStrings', false), WebAppName: tl.getInput('appName', true) - } + } //Clear input if deploytoslot is disabled taskParameters.ResourceGroupName = (!!taskParameters.DeployToSlotOrASEFlag) ? tl.getInput('resourceGroupName', false) : null; @@ -38,19 +38,19 @@ export class TaskParametersUtility { { taskParameters.AppSettings = taskParameters.AppSettings.replace('\n',' '); } - + var appDetails = await this.getWebAppKind(taskParameters); taskParameters.ResourceGroupName = appDetails["resourceGroupName"]; taskParameters.WebAppKind = appDetails["webAppKind"]; taskParameters.isConsumption = appDetails["sku"].toLowerCase() == skuDynamicValue; taskParameters.isPremium = appDetails["sku"].toLowerCase() == skuElasticPremiumValue; - + taskParameters.isLinuxApp = taskParameters.WebAppKind && taskParameters.WebAppKind.indexOf("Linux") !=-1; var endpointTelemetry = '{"endpointId":"' + taskParameters.connectedServiceName + '"}'; console.log("##vso[telemetry.publish area=TaskEndpointId;feature=AzureFunctionAppDeployment]" + endpointTelemetry); - - taskParameters.Package = new Package(tl.getPathInput('package', true)); + + taskParameters.Package = new Package(tl.getPathInput('package', true)); if(taskParameters.isLinuxApp) { taskParameters.RuntimeStack = tl.getInput('runtimeStack', false); @@ -66,8 +66,7 @@ export class TaskParametersUtility { var kind = taskParameters.WebAppKind; var sku; if (!resourceGroupName) { - var azureResources: Resources = new Resources(taskParameters.azureEndpoint); - var appDetails = await azureResources.getAppDetails(taskParameters.WebAppName); + var appDetails = await AzureResourceFilterUtility.getAppDetails(taskParameters.azureEndpoint, taskParameters.WebAppName); resourceGroupName = appDetails["resourceGroupName"]; if(!kind) { kind = webAppKindMap.get(appDetails["kind"]) ? webAppKindMap.get(appDetails["kind"]) : appDetails["kind"]; @@ -94,7 +93,7 @@ export class TaskParametersUtility { export enum DeploymentType { auto, zipDeploy, - runFromPackage + runFromPackage }