Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Azure App service Manage & Deploy Task Spec changes #4125

Merged
merged 18 commits into from
May 4, 2017
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@
"loc.input.label.Slot": "Slot",
"loc.input.label.ExtensionsList": "Install Extensions",
"loc.input.help.ExtensionsList": "Site Extensions run on Microsoft Azure App Service. You can install set of tools as site extension and better manage your Azure App Service. The App Service will be restarted to make sure latest changes take effect.",
"loc.input.label.OutputVariable": "Output variable",
"loc.input.help.OutputVariable": "Provide the variable name for the local installation path for the selected extension.<br />Note: In case of multiple extensions selected for installation, provide comma separated list of variables that saves the local path for each of the selected extension in the order it appears in the Install Extension field",
"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",
Expand Down
2 changes: 1 addition & 1 deletion Tasks/AzureAppServiceManage/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ describe('Azure App Service Manage Suite', function() {
assert(tr.succeeded, 'task should have succeeded');
assert(tr.stdOutContained('Retrieved list of extensions already available in Azure App Service.'), 'Should have retrieved extensions already avaliable in Azure App Service.');
assert(tr.stdOutContained('InstallingSiteExtension python2713x86'), 'Should have tried to Install extension.');
assert(tr.stdOutContained('ExtensionInstallSuccess python2713x86'), 'Should have installed extension successfully.');
assert(tr.stdOutContained('ExtensionInstallSuccess Python 2.7.13 x86'), 'Should have installed extension successfully.');
done();
});
it('Return error when List Extension fails', (done:MochaDone) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@ nock('https://mytestappKuduUrl.scm.azurewebsites.net:443')
.intercept('/api/siteextensions/python2713x86', 'PUT')
.reply(200, extensionManageUtility.installExtensionSuccess);

extensionManage.installExtensions(extensionManageUtility.mockPublishProfile, ['ComposerExtension', 'python2713x86']);
extensionManage.installExtensions(extensionManageUtility.mockPublishProfile, ['ComposerExtension', 'python2713x86'], []);
33 changes: 24 additions & 9 deletions Tasks/AzureAppServiceManage/azureappservicemanage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,26 @@ async function updateKuduDeploymentLog(endPoint, webAppName, resourceGroupName,
}
}

async function waitForAppServiceToStart(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName) {

while(true) {
var appServiceDetails = await azureRmUtil.getAppServiceDetails(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName);
if(appServiceDetails.hasOwnProperty("properties") && appServiceDetails.properties.hasOwnProperty("state")) {
tl.debug('App Service State : ' + appServiceDetails.properties.state);
if(appServiceDetails.properties.state == "Running" || appServiceDetails.properties.state == "running") {
tl.debug('App Service is in Running State');
break;
}
else {
tl.debug('App Service State : ' + appServiceDetails.properties.state);
continue;
}
}
tl.debug('Unable to find state of the App Service.');
break;
}
}

async function run() {
try {
tl.setResourcePath(path.join( __dirname, 'task.json'));
Expand All @@ -42,6 +62,7 @@ async function run() {
var targetSlot: string = tl.getInput('TargetSlot', false);
var preserveVnet: boolean = tl.getBoolInput('PreserveVnet', false);
var extensionList = tl.getInput('ExtensionsList', false);
var extensionOutputVariables = tl.getInput('OutputVariable').split(",");
var endPointAuthCreds = tl.getEndpointAuthorization(connectedServiceName, true);
var subscriptionId = tl.getEndpointDataParameter(connectedServiceName, 'subscriptionid', true);
var taskResult = true;
Expand All @@ -62,14 +83,7 @@ async function run() {
switch(action) {
case "Start Azure App Service": {
console.log(await azureRmUtil.startAppService(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName));
var appServiceDetails = await azureRmUtil.getAppServiceDetails(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName);
if(appServiceDetails.hasOwnProperty("properties") && appServiceDetails.properties.hasOwnProperty("state"))
{
while(!(appServiceDetails.properties.state == "Running" || appServiceDetails.properties.state == "running"))
{
appServiceDetails = await azureRmUtil.getAppServiceDetails(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName);
}
}
await waitForAppServiceToStart(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName);
break;
}
case "Stop Azure App Service": {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we wait for Stop as well?

Copy link
Contributor Author

@vincent1173 vincent1173 Apr 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stop actually stops the App Service to accept any request. We should be good here.

Expand All @@ -80,14 +94,15 @@ async function run() {
resourceGroupName = (specifySlotFlag ? resourceGroupName : await azureRmUtil.getResourceGroupName(endPoint, webAppName));
var publishingProfile = await azureRmUtil.getAzureRMWebAppPublishProfile(endPoint, webAppName, resourceGroupName, specifySlotFlag, slotName);
tl.debug('Retrieved publishing Profile');
var anyExtensionInstalled = await extensionManage.installExtensions(publishingProfile, extensionList.split(','));
var anyExtensionInstalled = await extensionManage.installExtensions(publishingProfile, extensionList.split(','), extensionOutputVariables);
if(!anyExtensionInstalled) {
tl.debug('No new extension installed. Skipping Restart App Service.');
break;
}
}
case "Restart Azure App Service": {
console.log(await azureRmUtil.restartAppService(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName));
await waitForAppServiceToStart(endPoint, resourceGroupName, webAppName, specifySlotFlag, slotName);
break;
}
case "Swap Slots": {
Expand Down
42 changes: 36 additions & 6 deletions Tasks/AzureAppServiceManage/extensionmanage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export async function getInstalledExtensions(publishingProfile) {
var extensionsList = JSON.parse(body);
for(var extension of extensionsList) {
tl.debug('* ' + extension['id']);
installedExtensionsList[extension['id']] = extension['title'];
installedExtensionsList[extension['id']] = extension;
}
defer.resolve(installedExtensionsList);
}
Expand Down Expand Up @@ -52,8 +52,9 @@ export async function installExtension(publishingProfile, extension: string) {
}
else if(response.statusCode === 200) {
tl.debug(body);
console.log(tl.loc('ExtensionInstallSuccess', extension));
defer.resolve(JSON.parse(body));
var responseBody = JSON.parse(body);
console.log(tl.loc('ExtensionInstallSuccess', responseBody['title']));
defer.resolve(responseBody);
}
else {
console.log(body);
Expand All @@ -64,19 +65,48 @@ export async function installExtension(publishingProfile, extension: string) {
return defer.promise;
}

export async function installExtensions(publishingProfile, extensions: Array<string>) {
export async function installExtensions(publishingProfile, extensions: Array<string>, extensionOutputVariables: Array<string>) {

var outputVariableCount = 0;
var outputVariableSize = extensionOutputVariables.length;
var InstalledExtensions = await getInstalledExtensions(publishingProfile);
var extensionInfo = null;
var anyExtensionInstalled = false;
for(var extension of extensions) {
extension = extension.trim();
if(InstalledExtensions[extension]) {
console.log(tl.loc('ExtensionAlreadyAvaiable', InstalledExtensions[extension]));
extensionInfo = InstalledExtensions[extension];
console.log(tl.loc('ExtensionAlreadyAvaiable', extensionInfo['title']));
}
else {
tl.debug("Extension '" + extension + "' not installed. Installing...");
await installExtension(publishingProfile, extension);
extensionInfo = await installExtension(publishingProfile, extension);
anyExtensionInstalled = true;
}
if(outputVariableCount < outputVariableSize) {
var extensionLocalPath: string = getExtensionLocalPath(extensionInfo);
tl.debug('Set Variable ' + extensionOutputVariables[outputVariableCount] + ' to value: ' + extensionLocalPath);
tl.setVariable(extensionOutputVariables[outputVariableCount], extensionLocalPath);
outputVariableCount += 1;
}
}
return anyExtensionInstalled;
}

function getExtensionLocalPath(extensionInfo: JSON): string {
var extensionId: string = extensionInfo['id'];
var homeDir = "D:\\home\\";

if(extensionId.startsWith('python2')) {
return homeDir + "Python27";
}
else if(extensionId.startsWith('python351') || extensionId.startsWith('python352')) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this may not be required as we are not showing option to install 3.5 < python version < 3.5.3 ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Additional check, if we add them.

return homeDir + "Python35";
}
else if(extensionId.startsWith('python3')) {
return homeDir + extensionId;
}
else {
return extensionInfo['local_path'];
}
}
10 changes: 9 additions & 1 deletion Tasks/AzureAppServiceManage/task.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"version": {
"Major": 0,
"Minor": 2,
"Patch": 7
"Patch": 8
},
"minimumAgentVersion": "1.102.0",
"instanceNameFormat": "$(Action): $(WebAppName)",
Expand Down Expand Up @@ -155,6 +155,14 @@
"required": "True",
"visibleRule": "Action = Install Extensions",
"helpMarkDown": "Site Extensions run on Microsoft Azure App Service. You can install set of tools as site extension and better manage your Azure App Service. The App Service will be restarted to make sure latest changes take effect."
},
{
"name": "OutputVariable",
"type": "string",
"label": "Output variable",
"defaultValue": "",
"visibleRule": "Action = Install Extensions",
"helpMarkDown": "Provide the variable name for the local installation path for the selected extension.<br />Note: In case of multiple extensions selected for installation, provide comma separated list of variables that saves the local path for each of the selected extension in the order it appears in the Install Extension field"
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Give an example. Should the name be just varName or $(varName)?

}
],
"dataSourceBindings": [
Expand Down
10 changes: 9 additions & 1 deletion Tasks/AzureAppServiceManage/task.loc.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"version": {
"Major": 0,
"Minor": 2,
"Patch": 7
"Patch": 8
},
"minimumAgentVersion": "1.102.0",
"instanceNameFormat": "ms-resource:loc.instanceNameFormat",
Expand Down Expand Up @@ -155,6 +155,14 @@
"required": "True",
"visibleRule": "Action = Install Extensions",
"helpMarkDown": "ms-resource:loc.input.help.ExtensionsList"
},
{
"name": "OutputVariable",
"type": "string",
"label": "ms-resource:loc.input.label.OutputVariable",
"defaultValue": "",
"visibleRule": "Action = Install Extensions",
"helpMarkDown": "ms-resource:loc.input.help.OutputVariable"
}
],
"dataSourceBindings": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,5 +140,9 @@
"loc.messages.ScriptStatusTimeout": "Unable to fetch script status due to timeout.",
"loc.messages.PollingForFileTimeOut": "Unable to fetch script status due to timeout. You can increase the timeout limit by setting 'appservicedeploy.retrytimeout' variable to number of minutes required.",
"loc.messages.InvalidPollOption": "Invalid polling option provided: %s.",
"loc.messages.MissingWebConfigParameters": "Some web.config parameters are missing"
"loc.messages.MissingAppTypeWebConfigParameters": "Attribute '-appType' is missing in the Web.config parameters. Valid values for '-appType' are: 'python_Bottle', 'python_Django', 'python_Flask' and 'node'.<br />For example, '-appType python_Bottle' (sans-quotes) in case of Python Bottle framework..",
"loc.messages.AutoDetectDjangoSettingsFailed": "Unable to detect DJANGO_SETTINGS_MODULE 'settings.py' file path. Ensure that the 'settings.py' file exists or provide the correct path in Web.config parameter input in the following format '-DJANGO_SETTINGS_MODULE <folder_name>.settings'",
"loc.messages.FailedToApplyTransformation": "Unable to apply transformation for the given package. Verify the following. \n1. Whether the Transformation is already applied for the MSBuild generated package during build. If yes, remove the <DependentUpon> tag for each config in the csproj file and rebuild. \n2. Ensure that the config file and transformation files are present in the same folder inside the package.",
"loc.messages.AutoParameterizationMessage": "ConnectionString attributes in Web.config is parameterized by default. Note that the transformation has no effect on connectionString attributes as the value is overridden during deployment by 'Parameters.xml or 'SetParameters.xml' files. You can disable the auto-parameterization by setting /p:AutoParameterizationWebConfigConnectionStrings=False during MSBuild package generation.",
"loc.messages.UnsupportedAppType": "App type '%s' not supported in Web.config generation. Valid values for '-appType' are: 'python_Bottle', 'python_Django', 'python_Flask' and 'node'"
}
13 changes: 13 additions & 0 deletions Tasks/AzureRmWebAppDeployment/Tests/L0.ts
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,7 @@ describe('AzureRmWebAppDeployment Suite', function() {
expectedOut = 'Successfully updated scmType to VSTSRM';
assert(tr.stdout.search(expectedOut) > 0, 'should have said: ' + expectedOut);
assert(tr.succeeded, 'task should have succeeded');
assert(tr.stdout.search('loc_mock_AutoParameterizationMessage'), 'Should have provided message for MSBuild package');
done();
});

Expand All @@ -291,6 +292,18 @@ describe('AzureRmWebAppDeployment Suite', function() {
assert(tr.failed, 'task should have failed');
done();
});

it('Fails if XML Transformation throws error (Mock) for MSBuild package', (done) => {
this.timeout(1000);
let tp = path.join(__dirname, 'L0XdtTransformationFailMSBuildPackage.js');
let tr : ttm.MockTestRunner = new ttm.MockTestRunner(tp);
tr.run();

assert(tr.failed, 'task should have failed');
assert(tr.stderr.search('loc_mock_FailedToApplyTransformation'), 'Should have provided proper errror message for MSBuild package');
assert(tr.stdout.search('loc_mock_AutoParameterizationMessage'), 'Should have provided message for MSBuild package');
done();
});
}
else {
it('Runs KuduDeploy successfully with default inputs on non-windows agent', (done) => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,9 @@ tr.registerMock('./msdeployutility.js', {
});

tr.registerMock('azurerest-common/azurerestutility.js', {
testAzureWebAppAvailability: function() {
console.log('App Service availability check.');
},
getAzureRMWebAppPublishProfile: function(SPN, webAppName, resourceGroupName, deployToSlotFlag, slotName) {
var mockPublishProfile = {
profileName: 'mytestapp - Web Deploy',
Expand Down Expand Up @@ -222,12 +225,6 @@ tr.registerMock('webdeployment-common/utility.js', {
}
});

tr.registerMock('webdeployment-common/generatewebconfig.js', {
generateWebConfigFile: function(filePath, templatePath, data) {
return;
}
});

tr.registerMock('./parameterparser', {
parse: function (data) {
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ tr.registerMock('webdeployment-common/utility.js', {
"webDeployPkg": "DefaultWorkingDirectory\\temp_web_package.zip",
"tempPackagePath": "DefaultWorkingDirectory\\temp_web_package.zip"
};
},
isMSDeployPackage: function() {
return true;
}
});

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,9 @@ tr.registerMock('webdeployment-common/utility.js', {
"webDeployPkg": "DefaultWorkingDirectory\\temp_web_package.zip",
"tempPackagePath": "DefaultWorkingDirectory\\temp_web_package.zip"
};
},
isMSDeployPackage: function() {
return false;
}
});

Expand Down
Loading