diff --git a/Tasks/AzureAppServiceManage/Strings/resources.resjson/en-US/resources.resjson b/Tasks/AzureAppServiceManage/Strings/resources.resjson/en-US/resources.resjson index 5b7b33c2adc9..77092970fb4e 100644 --- a/Tasks/AzureAppServiceManage/Strings/resources.resjson/en-US/resources.resjson +++ b/Tasks/AzureAppServiceManage/Strings/resources.resjson/en-US/resources.resjson @@ -9,6 +9,7 @@ "loc.input.help.Action": "Action to be performed on the App Service. You can Start/Stop/Restart an App Service or Manage a slot swap.", "loc.input.label.WebAppName": "App Service name", "loc.input.help.WebAppName": "Enter or select the name of an existing Azure App Service", + "loc.input.label.SpecifySlot": "Specify Slot", "loc.input.label.ResourceGroupName": "Resource group", "loc.input.help.ResourceGroupName": "Enter or Select the Azure Resource Group that contains the Azure App Service specified above", "loc.input.label.SourceSlot": "Source Slot", @@ -19,6 +20,7 @@ "loc.input.help.TargetSlot": "The swap action directs destination slot's traffic to the source slot", "loc.input.label.PreserveVnet": "Preserve Vnet", "loc.input.help.PreserveVnet": "Preserve the Virtual network settings", + "loc.input.label.Slot": "Slot", "loc.messages.ErrorNoSuchDeployingMethodExists": "Error : Deploy method MSDeploy does not exists for Azure Web App: %s", "loc.messages.Successfullyupdatedslotswaphistory": "Successfully updated slot swap history at %s for Azure Web App : %s", "loc.messages.Failedtoupdateslotswaphistory": "Failed to update slot swap history for Azure Web App : %s", diff --git a/Tasks/AzureAppServiceManage/azureappservicemanage.ts b/Tasks/AzureAppServiceManage/azureappservicemanage.ts index 96d2de59c9a6..0ab4819b09eb 100644 --- a/Tasks/AzureAppServiceManage/azureappservicemanage.ts +++ b/Tasks/AzureAppServiceManage/azureappservicemanage.ts @@ -31,6 +31,8 @@ async function run() { var action = tl.getInput('Action', true); var webAppName: string = tl.getInput('WebAppName', true); var resourceGroupName: string = tl.getInput('ResourceGroupName', false); + var specifySlotFlag: boolean = tl.getBoolInput('SpecifySlot', false); + var slotName: string = tl.getInput('Slot', false); var sourceSlot: string = tl.getInput('SourceSlot', false); var swapWithProduction = tl.getBoolInput('SwapWithProduction', false); var targetSlot: string = tl.getInput('TargetSlot', false); @@ -51,15 +53,15 @@ async function run() { } switch(action) { case "Start Azure App Service": { - tl._writeLine(await azureRmUtil.startAppService(endPoint, resourceGroupName, webAppName)); + tl._writeLine(await azureRmUtil.startAppService(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName)); break; } case "Stop Azure App Service": { - tl._writeLine(await azureRmUtil.stopAppService(endPoint, resourceGroupName, webAppName)); + tl._writeLine(await azureRmUtil.stopAppService(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName)); break; } case "Restart Azure App Service": { - tl._writeLine(await azureRmUtil.restartAppService(endPoint, resourceGroupName, webAppName)); + tl._writeLine(await azureRmUtil.restartAppService(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName)); break; } case "Swap Slots": { @@ -89,8 +91,8 @@ async function run() { await updateKuduDeploymentLog(endPoint, webAppName, resourceGroupName, !(swapWithProduction), targetSlot, taskResult, customMessage, deploymentId); } else { - customMessage['slotName'] = 'Production'; - await updateKuduDeploymentLog(endPoint, webAppName, resourceGroupName, false, null, taskResult, customMessage, deploymentId); + customMessage['slotName'] = (specifySlotFlag) ? slotName : 'Production'; + await updateKuduDeploymentLog(endPoint, webAppName, resourceGroupName, specifySlotFlag, slotName, taskResult, customMessage, deploymentId); } } diff --git a/Tasks/AzureAppServiceManage/kududeploymentlog.ts b/Tasks/AzureAppServiceManage/kududeploymentlog.ts deleted file mode 100644 index d504e28b1bec..000000000000 --- a/Tasks/AzureAppServiceManage/kududeploymentlog.ts +++ /dev/null @@ -1,103 +0,0 @@ -import tl = require('vsts-task-lib/task'); - -export function generateDeploymentId(): string{ - var buildUrl = tl.getVariable('build.buildUri'); - var releaseUrl = tl.getVariable('release.releaseUri'); - - var buildId = tl.getVariable('build.buildId'); - var releaseId = tl.getVariable('release.releaseId'); - - if(releaseUrl !== undefined) { - return releaseId + Date.now(); - } - else if(buildUrl !== undefined) { - return buildId + Date.now(); - } - else { - throw new Error(tl.loc('CannotupdatedeploymentstatusuniquedeploymentIdCannotBeRetrieved')); - } -} - -export function getUpdateHistoryRequest(webAppPublishKuduUrl: string, deploymentId: string, isSlotSwapSuccess: boolean, sourceSlot: string, targetSlot: string): any { - - var status = isSlotSwapSuccess ? 4 : 3; - var status_text = (status == 4) ? "success" : "failed"; - var author = getDeploymentAuthor(); - - var buildUrl = tl.getVariable('build.buildUri'); - var releaseUrl = tl.getVariable('release.releaseUri'); - - var buildId = tl.getVariable('build.buildId'); - var releaseId = tl.getVariable('release.releaseId'); - - var buildNumber = tl.getVariable('build.buildNumber'); - var releaseName = tl.getVariable('release.releaseName'); - - var collectionUrl = tl.getVariable('system.TeamFoundationCollectionUri'); - var teamProject = tl.getVariable('system.teamProject'); - - var type = "SlotSwap"; - var commitId = tl.getVariable('build.sourceVersion'); - var repoName = tl.getVariable('build.repository.name'); - var repoProvider = tl.getVariable('build.repository.provider'); - - var buildOrReleaseUrl = "" ; - - if(releaseUrl !== undefined) { - buildOrReleaseUrl = collectionUrl + teamProject + "/_apps/hub/ms.vss-releaseManagement-web.hub-explorer?releaseId=" + releaseId + "&_a=release-summary"; - } - else if(buildUrl !== undefined) { - buildOrReleaseUrl = collectionUrl + teamProject + "/_build?buildId=" + buildId + "&_a=summary"; - } - - var message = JSON.stringify({ - type : type, - sourceSlot : sourceSlot, - targetSlot : targetSlot, - commitId : commitId, - buildId : buildId, - releaseId : releaseId, - buildNumber : buildNumber, - releaseName : releaseName, - repoProvider : repoProvider, - repoName : repoName, - collectionUrl : collectionUrl, - teamProject : teamProject - }); - - var requestBody = { - status : status, - status_text : status_text, - message : message, - author : author, - deployer : 'VSTS', - active : false, - details : buildOrReleaseUrl - }; - - var webAppHostUrl = webAppPublishKuduUrl.split(':')[0]; - var requestUrl = "https://" + encodeURIComponent(webAppHostUrl) + "/deployments/" + encodeURIComponent(deploymentId); - - var requestDetails = new Array(); - requestDetails["requestBody"] = requestBody; - requestDetails["requestUrl"] = requestUrl; - return requestDetails; -} - -function getDeploymentAuthor(): string { - var author = tl.getVariable('build.sourceVersionAuthor'); - - if(author === undefined) { - author = tl.getVariable('build.requestedfor'); - } - - if(author === undefined) { - author = tl.getVariable('release.requestedfor'); - } - - if(author === undefined) { - author = tl.getVariable('agent.name'); - } - - return author; -} \ No newline at end of file diff --git a/Tasks/AzureAppServiceManage/task.json b/Tasks/AzureAppServiceManage/task.json index 6c731ed390ba..40f485f920e9 100644 --- a/Tasks/AzureAppServiceManage/task.json +++ b/Tasks/AzureAppServiceManage/task.json @@ -18,8 +18,7 @@ }, "minimumAgentVersion": "1.102.0", "instanceNameFormat": "$(Action): $(WebAppName)", - "groups": [ - + "groups": [ ], "inputs": [ { @@ -55,6 +54,13 @@ }, "helpMarkDown": "Enter or select the name of an existing Azure App Service" }, + { + "name": "SpecifySlot", + "type": "boolean", + "label": "Specify Slot", + "required": false, + "visibleRule": "Action != Swap Slots" + }, { "name": "ResourceGroupName", "type": "pickList", @@ -65,7 +71,7 @@ "EditableOptions": "True" }, "helpMarkDown": "Enter or Select the Azure Resource Group that contains the Azure App Service specified above", - "visibleRule":"Action = Swap Slots" + "visibleRule": "Action = Swap Slots || SpecifySlot = true" }, { "name": "SourceSlot", @@ -108,6 +114,17 @@ "required": false, "helpMarkDown": "Preserve the Virtual network settings", "visibleRule": "Action = Swap Slots" + }, + { + "name": "Slot", + "type": "pickList", + "label": "Slot", + "defaultValue": "", + "properties": { + "EditableOptions": "True" + }, + "required": true, + "visibleRule": "Action != Swap Slots && SpecifySlot = true" } ], "dataSourceBindings": [ diff --git a/Tasks/AzureAppServiceManage/task.loc.json b/Tasks/AzureAppServiceManage/task.loc.json index 3e2a88bc15cc..b566683b60ba 100644 --- a/Tasks/AzureAppServiceManage/task.loc.json +++ b/Tasks/AzureAppServiceManage/task.loc.json @@ -53,6 +53,13 @@ }, "helpMarkDown": "ms-resource:loc.input.help.WebAppName" }, + { + "name": "SpecifySlot", + "type": "boolean", + "label": "ms-resource:loc.input.label.SpecifySlot", + "required": false, + "visibleRule": "Action != Swap Slots" + }, { "name": "ResourceGroupName", "type": "pickList", @@ -63,7 +70,7 @@ "EditableOptions": "True" }, "helpMarkDown": "ms-resource:loc.input.help.ResourceGroupName", - "visibleRule": "Action = Swap Slots" + "visibleRule": "Action = Swap Slots || SpecifySlot = true" }, { "name": "SourceSlot", @@ -106,6 +113,17 @@ "required": false, "helpMarkDown": "ms-resource:loc.input.help.PreserveVnet", "visibleRule": "Action = Swap Slots" + }, + { + "name": "Slot", + "type": "pickList", + "label": "ms-resource:loc.input.label.Slot", + "defaultValue": "", + "properties": { + "EditableOptions": "True" + }, + "required": true, + "visibleRule": "Action != Swap Slots && SpecifySlot = true" } ], "dataSourceBindings": [ diff --git a/Tasks/Common/azurerest-common/azurerestutility.ts b/Tasks/Common/azurerest-common/azurerestutility.ts index d503dec7af4d..3b2dd2450606 100644 --- a/Tasks/Common/azurerest-common/azurerestutility.ts +++ b/Tasks/Common/azurerest-common/azurerestutility.ts @@ -18,17 +18,17 @@ var azureApiVersion = 'api-version=2016-08-01'; /** * gets the name of the ResourceGroup that contains the webApp * - * @param SPN Service Principal Name + * @param endpoint Service Principal Name * @param webAppName Name of the web App */ -export async function getResourceGroupName(SPN, webAppName: string) +export async function getResourceGroupName(endpoint, webAppName: string) { - var requestURL = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resources?$filter=resourceType EQ \'Microsoft.Web/Sites\' AND name EQ \'' + webAppName + '\'&api-version=2016-07-01'; - var accessToken = await getAuthorizationToken(SPN); + var requestURL = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resources?$filter=resourceType EQ \'Microsoft.Web/Sites\' AND name EQ \'' + webAppName + '\'&api-version=2016-07-01'; + var accessToken = await getAuthorizationToken(endpoint); var headers = { authorization: 'Bearer '+ accessToken }; - var webAppID = await getAzureRMWebAppID(SPN, webAppName, requestURL, headers); + var webAppID = await getAzureRMWebAppID(endpoint, webAppName, requestURL, headers); tl.debug('Web App details : ' + webAppID.id); var resourceGroupName = webAppID.id.split ('/')[4]; @@ -77,9 +77,9 @@ export function updateDeploymentStatus(publishingProfile, isDeploymentSuccess: b } /** - * Gets the Azure RM Web App Connections details from SPN + * Gets the Azure RM Web App Connections details from endpoint * - * @param SPN Service Principal Name + * @param endpoint Service Principal Name * @param webAppName Name of the web App * @param resourceGroupName Resource Group Name * @param deployToSlotFlag Flag to check slot deployment @@ -153,7 +153,7 @@ function getAuthorizationToken(endPoint): Q.Promise { return deferred.promise; } -async function getAzureRMWebAppID(SPN, webAppName: string, url: string, headers) { +async function getAzureRMWebAppID(endpoint, webAppName: string, url: string, headers) { var deferred = Q.defer(); tl.debug('Requesting Azure App Service ID: ' + url); @@ -167,7 +167,7 @@ async function getAzureRMWebAppID(SPN, webAppName: string, url: string, headers) if(webAppIDDetails.value.length === 0) { if(webAppIDDetails.nextLink) { tl.debug("Requesting nextLink to accesss webappId for webapp " + webAppName); - deferred.resolve(await getAzureRMWebAppID(SPN, webAppName, webAppIDDetails.nextLink, headers)); + deferred.resolve(await getAzureRMWebAppID(endpoint, webAppName, webAppIDDetails.nextLink, headers)); } deferred.reject(tl.loc("WebAppDoesntExist", webAppName)); } @@ -184,21 +184,21 @@ async function getAzureRMWebAppID(SPN, webAppName: string, url: string, headers) /** * REST request for azure webapp config details. Config details contains virtual application mappings. * - * @param SPN Subscription details + * @param endpoint Subscription details * @param webAppName Web application name * @param deployToSlotFlag Should deploy to slot * @param slotName Slot for deployment */ -export async function getAzureRMWebAppConfigDetails(SPN, webAppName: string, resourceGroupName: string, deployToSlotFlag: boolean, slotName: string) { +export async function getAzureRMWebAppConfigDetails(endpoint, webAppName: string, resourceGroupName: string, deployToSlotFlag: boolean, slotName: string) { var deferred = Q.defer(); - var accessToken = await getAuthorizationToken(SPN); + var accessToken = await getAuthorizationToken(endpoint); var headers = { authorization: 'Bearer '+ accessToken }; var slotUrl = deployToSlotFlag ? "/slots/" + slotName : ""; - var configUrl = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resourceGroups/' + resourceGroupName + + var configUrl = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resourceGroups/' + resourceGroupName + '/providers/Microsoft.Web/sites/' + webAppName + slotUrl + '/config/web?' + azureApiVersion; tl.debug('Requesting Azure App Service Config Details: ' + configUrl); @@ -251,13 +251,13 @@ export async function updateAzureRMWebAppConfigDetails(SPN, webAppName: string, export async function getWebAppAppSettings(SPN, webAppName: string, resourceGroupName: string, deployToSlotFlag: boolean, slotName: string/*, appSettings: Object*/) { var deferred = Q.defer(); - var accessToken = await getAuthorizationToken(SPN); + var accessToken = await getAuthorizationToken(endpoint); var headers = { authorization: 'Bearer '+ accessToken }; var slotUrl = deployToSlotFlag ? "/slots/" + slotName : ""; - var configUrl = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resourceGroups/' + resourceGroupName + + var configUrl = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resourceGroups/' + resourceGroupName + '/providers/Microsoft.Web/sites/' + webAppName + slotUrl + '/config/appsettings/list?' + azureApiVersion; tl.debug('Requesting for the Current List of App Settings: ' + configUrl); @@ -278,16 +278,16 @@ export async function getWebAppAppSettings(SPN, webAppName: string, resourceGrou return deferred.promise; } -export async function updateWebAppAppSettings(SPN, webAppName: string, resourceGroupName: string, deployToSlotFlag: boolean, slotName: string, appSettings: Object) { +export async function updateWebAppAppSettings(endpoint, webAppName: string, resourceGroupName: string, deployToSlotFlag: boolean, slotName: string, appSettings: Object) { var deferred = Q.defer(); - var accessToken = await getAuthorizationToken(SPN); + var accessToken = await getAuthorizationToken(endpoint); var headers = { authorization: 'Bearer '+ accessToken }; var slotUrl = deployToSlotFlag ? "/slots/" + slotName : ""; - var configUrl = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resourceGroups/' + resourceGroupName + + var configUrl = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resourceGroups/' + resourceGroupName + '/providers/Microsoft.Web/sites/' + webAppName + slotUrl + '/config/appsettings?' + azureApiVersion; tl.debug('Updating the Current List of App Settings: ' + configUrl); @@ -308,13 +308,13 @@ export async function updateWebAppAppSettings(SPN, webAppName: string, resourceG return deferred.promise; } -export async function swapWebAppSlot(SPN, resourceGroupName: string, webAppName: string, sourceSlot: string, targetSlot: string,preserveVnet: boolean) { +export async function swapWebAppSlot(endpoint, resourceGroupName: string, webAppName: string, sourceSlot: string, targetSlot: string,preserveVnet: boolean) { var deferred = Q.defer(); - var url = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resourceGroups/' + resourceGroupName + + var url = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resourceGroups/' + resourceGroupName + '/providers/Microsoft.Web/sites/' + webAppName + "/slots/" + sourceSlot + '/slotsswap?' + azureApiVersion; - var accessToken = await getAuthorizationToken(SPN); + var accessToken = await getAuthorizationToken(endpoint); var headers = { 'Authorization': 'Bearer '+ accessToken, 'Content-Type': 'application/json' @@ -343,86 +343,89 @@ export async function swapWebAppSlot(SPN, resourceGroupName: string, webAppName: return deferred.promise; } -export async function startAppService(SPN, resourceGroupName: string, webAppName: string) { +export async function startAppService(endpoint, resourceGroupName: string, webAppName: string, specifySlotFlag: boolean, slotName: string) { var deferred = Q.defer(); - var url = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resourceGroups/' + resourceGroupName + - '/providers/Microsoft.Web/sites/' + webAppName + "/start?" + azureApiVersion; + var slotUrl = (specifySlotFlag) ? "/slots/" + slotName : ""; + var url = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resourceGroups/' + resourceGroupName + + '/providers/Microsoft.Web/sites/' + webAppName + slotUrl + "/start?" + azureApiVersion; - var accessToken = await getAuthorizationToken(SPN); + var accessToken = await getAuthorizationToken(endpoint); var headers = { 'Authorization': 'Bearer '+ accessToken }; - - tl._writeLine(tl.loc('StartingAppService', webAppName)); + var webAppNameWithSlot = (specifySlotFlag) ? webAppName + '-' + slotName : webAppName; + tl._writeLine(tl.loc('StartingAppService', webAppNameWithSlot)); httpObj.send('POST', url, null, headers, (error, response, body) => { if(error) { deferred.reject(error); } if(response.statusCode === 200 || response.statusCode === 204) { - deferred.resolve(tl.loc('AppServicestartedsuccessfully', webAppName)); + deferred.resolve(tl.loc('AppServicestartedsuccessfully', webAppNameWithSlot)); } else { tl.error(response.statusMessage); - deferred.reject(tl.loc("FailedtoStartAppService",webAppName, response.statusCode, response.statusMessage)); + deferred.reject(tl.loc("FailedtoStartAppService", webAppNameWithSlot, response.statusCode, response.statusMessage)); } }); return deferred.promise; } -export async function stopAppService(SPN, resourceGroupName: string, webAppName: string) { +export async function stopAppService(endpoint, resourceGroupName: string, webAppName: string, specifySlotFlag: boolean, slotName: string) { var deferred = Q.defer(); - var url = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resourceGroups/' + resourceGroupName + - '/providers/Microsoft.Web/sites/' + webAppName + "/stop?" + azureApiVersion; + var slotUrl = (specifySlotFlag) ? "/slots/" + slotName : ""; + var url = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resourceGroups/' + resourceGroupName + + '/providers/Microsoft.Web/sites/' + webAppName + slotUrl + "/stop?" + azureApiVersion; - var accessToken = await getAuthorizationToken(SPN); + var accessToken = await getAuthorizationToken(endpoint); var headers = { 'Authorization': 'Bearer '+ accessToken }; - - tl._writeLine(tl.loc('StoppingAppService', webAppName)); + var webAppNameWithSlot = (specifySlotFlag) ? webAppName + '-' + slotName : webAppName; + tl._writeLine(tl.loc('StoppingAppService', webAppNameWithSlot)); httpObj.send('POST', url, null, headers, (error, response, body) => { if(error) { deferred.reject(error); } if(response.statusCode === 200 || response.statusCode === 204) { - deferred.resolve(tl.loc('AppServicestoppedsuccessfully', webAppName)); + deferred.resolve(tl.loc('AppServicestoppedsuccessfully', webAppNameWithSlot)); } else { tl.error(response.statusMessage); - deferred.reject(tl.loc("FailedtoStopAppService",webAppName, response.statusCode, response.statusMessage)); + deferred.reject(tl.loc("FailedtoStopAppService",webAppNameWithSlot, response.statusCode, response.statusMessage)); } }); return deferred.promise; } -export async function restartAppService(SPN, resourceGroupName: string, webAppName: string) { +export async function restartAppService(endpoint, resourceGroupName: string, webAppName: string, specifySlotFlag: boolean, slotName: string) { var deferred = Q.defer(); - var url = armUrl + 'subscriptions/' + SPN.subscriptionId + '/resourceGroups/' + resourceGroupName + - '/providers/Microsoft.Web/sites/' + webAppName + "/restart?" + azureApiVersion + '&synchronous=true'; + var slotUrl = (specifySlotFlag) ? "/slots/" + slotName : ""; + var url = armUrl + 'subscriptions/' + endpoint.subscriptionId + '/resourceGroups/' + resourceGroupName + + '/providers/Microsoft.Web/sites/' + webAppName + slotUrl + "/restart?" + azureApiVersion + '&synchronous=true'; - var accessToken = await getAuthorizationToken(SPN); + var accessToken = await getAuthorizationToken(endpoint); var headers = { 'Authorization': 'Bearer '+ accessToken }; - - tl._writeLine(tl.loc('RestartingAppService', webAppName)); + var webAppNameWithSlot = (specifySlotFlag) ? webAppName + '-' + slotName : webAppName; + tl._writeLine(tl.loc('RestartingAppService', webAppNameWithSlot)); httpObj.send('POST', url, null, headers, (error, response, body) => { if(error) { deferred.reject(error); } if(response.statusCode === 200 || response.statusCode === 204) { - deferred.resolve(tl.loc('AppServiceRestartedSuccessfully', webAppName)); + deferred.resolve(tl.loc('AppServiceRestartedSuccessfully', webAppNameWithSlot)); } else if(response.statusCode === 202) { tl.warning(tl.loc('RestartAppServiceAccepted')); - deferred.resolve(tl.loc('RestartAppServiceAccepted', webAppName)); + deferred.resolve(tl.loc('RestartAppServiceAccepted', webAppNameWithSlot)); } else { tl.error(response.statusMessage); - deferred.reject(tl.loc("FailedtoRestartAppService",webAppName, response.statusCode, response.statusMessage)); + deferred.reject(tl.loc("FailedtoRestartAppService",webAppNameWithSlot, response.statusCode, response.statusMessage)); } }); return deferred.promise;