Skip to content

Commit

Permalink
Publish profile support for container web apps (#39)
Browse files Browse the repository at this point in the history
* adding publish profile support for container apps

* added node modules

* removed unit test file

* Update action.yml

* Update README.md

* Update README.md
  • Loading branch information
aksm-ms authored Jun 30, 2020
1 parent 4c9203b commit 6c17269
Show file tree
Hide file tree
Showing 26 changed files with 444 additions and 81 deletions.
34 changes: 33 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ The definition of this Github Action is in [action.yml](https://github.com/Azure
## Dependencies on other Github Actions

* [Checkout](https://github.com/actions/checkout) Checkout your Git repository content into Github Actions agent.
* Authenticate using [Azure Web App Publish Profile](https://github.com/projectkudu/kudu/wiki/Deployment-credentials#site-credentials-aka-publish-profile-credentials) or using [Azure Login](https://github.com/Azure/login)
* Authenticate using [Azure Web App Publish Profile](https://github.com/projectkudu/kudu/wiki/Deployment-credentials#site-credentials-aka-publish-profile-credentials) or using [Azure Login](https://github.com/Azure/login). Action supports publish profile for [Azure Web Apps](https://azure.microsoft.com/en-us/services/app-service/web/) (both Windows and Linux) and [Azure Web Apps for Containers](https://azure.microsoft.com/en-us/services/app-service/containers/) (Linux only). Action does not support multi-container scenario with publish profile.
* Environment setup actions
* [Setup DotNet](https://github.com/actions/setup-dotnet) Sets up a dotnet environment by optionally downloading and caching a version of dotnet by SDK version and adding to PATH .
* [Setup Node](https://github.com/actions/setup-node) sets up a node environment by optionally downloading and caching a version of node - npm by version spec and add to PATH
Expand Down Expand Up @@ -70,7 +70,39 @@ jobs:
app-name: node-rn
publish-profile: ${{ secrets.azureWebAppPublishProfile }}

```
### Sample workflow to build and deploy a Node.js app to Containerized WebApp using publish profile
```yaml

on: [push]

name: Linux_Container_Node_Workflow

jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
# checkout the repo
- name: 'Checkout Github Action'
uses: actions/checkout@master

- uses: azure/docker-login@v1
with:
login-server: contoso.azurecr.io
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_PASSWORD }}

- run: |
docker build . -t contoso.azurecr.io/nodejssampleapp:${{ github.sha }}
docker push contoso.azurecr.io/nodejssampleapp:${{ github.sha }}
- uses: azure/webapps-deploy@v2
with:
app-name: 'node-rnc'
publish-profile: ${{ secrets.azureWebAppPublishProfile }}
images: 'contoso.azurecr.io/nodejssampleapp:${{ github.sha }}'

```
#### Configure deployment credentials:
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ inputs:
description: 'Name of the Azure Web App'
required: true
publish-profile:
description: 'Applies to Web App only: Publish profile (*.publishsettings) file contents with Web Deploy secrets'
description: 'Applies to Web Apps(Windows and Linux) and Web App Containers(linux). Multi container scenario not supported. Publish profile (*.publishsettings) file contents with Web Deploy secrets'
required: false
slot-name:
description: 'Enter an existing Slot other than the Production slot'
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const Validations_1 = require("../Validations");
const actionparameters_1 = require("../../actionparameters");
class PublishProfileContainerWebAppValidator {
validate() {
return __awaiter(this, void 0, void 0, function* () {
const actionParams = actionparameters_1.ActionParameters.getActionParams();
Validations_1.packageNotAllowed(actionParams.packageInput);
yield Validations_1.windowsContainerAppNotAllowedForPublishProfile();
Validations_1.multiContainerNotAllowed(actionParams.multiContainerConfigFile);
Validations_1.startupCommandNotAllowed(actionParams.startupCommand);
Validations_1.validateAppDetails();
Validations_1.validateSingleContainerInputs();
});
}
}
exports.PublishProfileContainerWebAppValidator = PublishProfileContainerWebAppValidator;
31 changes: 25 additions & 6 deletions lib/ActionInputValidator/Validations.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,14 @@ var __importStar = (this && this.__importStar) || function (mod) {
result["default"] = mod;
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const core = __importStar(require("@actions/core"));
const packageUtility_1 = require("azure-actions-utility/packageUtility");
const PublishProfile_1 = require("../Utilities/PublishProfile");
const RuntimeConstants_1 = __importDefault(require("../RuntimeConstants"));
const actionparameters_1 = require("../actionparameters");
const fs = require("fs");
// Error is app-name is not provided
Expand All @@ -31,12 +35,7 @@ exports.appNameIsRequired = appNameIsRequired;
// Error if image info is provided
function containerInputsNotAllowed(images, configFile, isPublishProfile = false) {
if (!!images || !!configFile) {
if (!!isPublishProfile) {
throw new Error("Container Deployment is not supported with publish profile credentails. Instead add an Azure login action before this action. For more details refer https://github.com/azure/login");
}
else {
throw new Error(`This is not a container web app. Please remove inputs like images and configuration-file which are only relevant for container deployment.`);
}
throw new Error(`This is not a container web app. Please remove inputs like images and configuration-file which are only relevant for container deployment.`);
}
}
exports.containerInputsNotAllowed = containerInputsNotAllowed;
Expand Down Expand Up @@ -75,6 +74,14 @@ function multiContainerNotAllowed(configFile) {
}
}
exports.multiContainerNotAllowed = multiContainerNotAllowed;
// Error if image name is not provided
function validateSingleContainerInputs() {
const actionParams = actionparameters_1.ActionParameters.getActionParams();
if (!actionParams.images) {
throw new Error("Image name not provided for container. Provide a valid image name");
}
}
exports.validateSingleContainerInputs = validateSingleContainerInputs;
// Validate container inputs
function validateContainerInputs() {
let actionParams = actionparameters_1.ActionParameters.getActionParams();
Expand Down Expand Up @@ -116,3 +123,15 @@ function validatePackageInput() {
});
}
exports.validatePackageInput = validatePackageInput;
// windows container app not allowed for publish profile auth scheme
function windowsContainerAppNotAllowedForPublishProfile() {
return __awaiter(this, void 0, void 0, function* () {
const actionParams = actionparameters_1.ActionParameters.getActionParams();
const publishProfile = PublishProfile_1.PublishProfile.getPublishProfile(actionParams.publishProfileContent);
const appOS = yield publishProfile.getAppOS();
if (appOS.includes(RuntimeConstants_1.default.Windows) || appOS.includes(RuntimeConstants_1.default.Windows.toLowerCase())) {
throw new Error("Publish profile auth scheme is not supported for Windows container Apps.");
}
});
}
exports.windowsContainerAppNotAllowedForPublishProfile = windowsContainerAppNotAllowedForPublishProfile;
23 changes: 21 additions & 2 deletions lib/ActionInputValidator/ValidatorFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,34 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const actionparameters_1 = require("../actionparameters");
const AzureResourceFilterUtility_1 = require("azure-actions-appservice-rest/Utilities/AzureResourceFilterUtility");
const BaseWebAppDeploymentProvider_1 = require("../DeploymentProvider/Providers/BaseWebAppDeploymentProvider");
const PublishProfileWebAppValidator_1 = require("./ActionValidators/PublishProfileWebAppValidator");
const PublishProfileContainerWebAppValidator_1 = require("./ActionValidators/PublishProfileContainerWebAppValidator");
const SpnLinuxContainerWebAppValidator_1 = require("./ActionValidators/SpnLinuxContainerWebAppValidator");
const SpnLinuxWebAppValidator_1 = require("./ActionValidators/SpnLinuxWebAppValidator");
const SpnWindowsContainerWebAppValidator_1 = require("./ActionValidators/SpnWindowsContainerWebAppValidator");
const SpnWindowsWebAppValidator_1 = require("./ActionValidators/SpnWindowsWebAppValidator");
const Validations_1 = require("./Validations");
const PublishProfile_1 = require("../Utilities/PublishProfile");
const RuntimeConstants_1 = __importDefault(require("../RuntimeConstants"));
class ValidatorFactory {
static getValidator(type) {
return __awaiter(this, void 0, void 0, function* () {
let actionParams = actionparameters_1.ActionParameters.getActionParams();
if (type == BaseWebAppDeploymentProvider_1.DEPLOYMENT_PROVIDER_TYPES.PUBLISHPROFILE) {
return new PublishProfileWebAppValidator_1.PublishProfileWebAppValidator();
if (type === BaseWebAppDeploymentProvider_1.DEPLOYMENT_PROVIDER_TYPES.PUBLISHPROFILE) {
yield this.setResourceDetails(actionParams);
if (!!actionParams.images) {
return new PublishProfileContainerWebAppValidator_1.PublishProfileContainerWebAppValidator();
}
else {
return new PublishProfileWebAppValidator_1.PublishProfileWebAppValidator();
}
}
else if (type == BaseWebAppDeploymentProvider_1.DEPLOYMENT_PROVIDER_TYPES.SPN) {
// app-name is required to get resource details
Expand Down Expand Up @@ -56,5 +68,12 @@ class ValidatorFactory {
params.isLinux = params.realKind.indexOf("linux") > -1;
});
}
static setResourceDetails(actionParams) {
return __awaiter(this, void 0, void 0, function* () {
const publishProfile = PublishProfile_1.PublishProfile.getPublishProfile(actionParams.publishProfileContent);
const appOS = yield publishProfile.getAppOS();
actionParams.isLinux = appOS.includes(RuntimeConstants_1.default.Unix) || appOS.includes(RuntimeConstants_1.default.Unix.toLowerCase());
});
}
}
exports.ValidatorFactory = ValidatorFactory;
9 changes: 7 additions & 2 deletions lib/DeploymentProvider/DeploymentProviderFactory.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,16 @@ const actionparameters_1 = require("../actionparameters");
const BaseWebAppDeploymentProvider_1 = require("./Providers/BaseWebAppDeploymentProvider");
const WebAppContainerDeployment_1 = require("./Providers/WebAppContainerDeployment");
const WebAppDeploymentProvider_1 = require("./Providers/WebAppDeploymentProvider");
const PublishProfileWebAppContainerDeploymentProvider_1 = require("./Providers/PublishProfileWebAppContainerDeploymentProvider");
class DeploymentProviderFactory {
static getDeploymentProvider(type) {
// For publish profile type app kind is not available so we directly return WebAppDeploymentProvider
if (type === BaseWebAppDeploymentProvider_1.DEPLOYMENT_PROVIDER_TYPES.PUBLISHPROFILE) {
return new WebAppDeploymentProvider_1.WebAppDeploymentProvider(type);
if (!!actionparameters_1.ActionParameters.getActionParams().images) {
return new PublishProfileWebAppContainerDeploymentProvider_1.PublishProfileWebAppContainerDeploymentProvider(type);
}
else {
return new WebAppDeploymentProvider_1.WebAppDeploymentProvider(type);
}
}
else if (type == BaseWebAppDeploymentProvider_1.DEPLOYMENT_PROVIDER_TYPES.SPN) {
let kind = actionparameters_1.ActionParameters.getActionParams().kind;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ const PublishProfile_1 = require("../../Utilities/PublishProfile");
const actionparameters_1 = require("../../actionparameters");
const azure_app_service_1 = require("azure-actions-appservice-rest/Arm/azure-app-service");
const AzureAppServiceUtility_1 = require("azure-actions-appservice-rest/Utilities/AzureAppServiceUtility");
const azure_app_kudu_service_1 = require("azure-actions-appservice-rest/Kudu/azure-app-kudu-service");
const KuduServiceUtility_1 = require("azure-actions-appservice-rest/Utilities/KuduServiceUtility");
const AnnotationUtility_1 = require("azure-actions-appservice-rest/Utilities/AnnotationUtility");
class BaseWebAppDeploymentProvider {
Expand Down Expand Up @@ -68,9 +67,8 @@ class BaseWebAppDeploymentProvider {
}
initializeForPublishProfile() {
return __awaiter(this, void 0, void 0, function* () {
let publishProfile = PublishProfile_1.PublishProfile.getPublishProfile(this.actionParams.publishProfileContent);
let scmCreds = publishProfile.creds;
this.kuduService = new azure_app_kudu_service_1.Kudu(scmCreds.uri, scmCreds.username, scmCreds.password);
const publishProfile = PublishProfile_1.PublishProfile.getPublishProfile(this.actionParams.publishProfileContent);
this.kuduService = publishProfile.kuduService;
this.kuduServiceUtility = new KuduServiceUtility_1.KuduServiceUtility(this.kuduService);
this.applicationURL = publishProfile.appUrl;
});
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const BaseWebAppDeploymentProvider_1 = require("./BaseWebAppDeploymentProvider");
class PublishProfileWebAppContainerDeploymentProvider extends BaseWebAppDeploymentProvider_1.BaseWebAppDeploymentProvider {
DeployWebAppStep() {
return __awaiter(this, void 0, void 0, function* () {
const appName = this.actionParams.appName;
const images = this.actionParams.images;
const isLinux = this.actionParams.isLinux;
yield this.kuduServiceUtility.deployWebAppImage(appName, images, isLinux);
});
}
}
exports.PublishProfileWebAppContainerDeploymentProvider = PublishProfileWebAppContainerDeploymentProvider;
9 changes: 9 additions & 0 deletions lib/RuntimeConstants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
class RuntimeConstants {
}
exports.default = RuntimeConstants;
RuntimeConstants.system = "system";
RuntimeConstants.osName = "os_name";
RuntimeConstants.Windows = "Windows";
RuntimeConstants.Unix = "Unix";
33 changes: 33 additions & 0 deletions lib/Utilities/PublishProfile.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,21 @@
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var core = require("@actions/core");
const actions_secret_parser_1 = require("actions-secret-parser");
const azure_app_kudu_service_1 = require("azure-actions-appservice-rest/Kudu/azure-app-kudu-service");
const RuntimeConstants_1 = __importDefault(require("../RuntimeConstants"));
class PublishProfile {
constructor(publishProfileContent) {
try {
Expand All @@ -16,6 +30,7 @@ class PublishProfile {
throw new Error("Publish profile does not contain kudu URL");
}
this._creds.uri = `https://${this._creds.uri}`;
this._kuduService = new azure_app_kudu_service_1.Kudu(this._creds.uri, this._creds.username, this._creds.password);
}
catch (error) {
core.error("Failed to fetch credentials from Publish Profile. For more details on how to set publish profile credentials refer https://aka.ms/create-secrets-for-GitHub-workflows");
Expand All @@ -34,5 +49,23 @@ class PublishProfile {
get appUrl() {
return this._appUrl;
}
get kuduService() {
return this._kuduService;
}
getAppOS() {
return __awaiter(this, void 0, void 0, function* () {
try {
if (!this._appOS) {
const appRuntimeDetails = yield this._kuduService.getAppRuntime();
this._appOS = appRuntimeDetails[RuntimeConstants_1.default.system][RuntimeConstants_1.default.osName];
core.debug(`App Runtime OS: ${this._appOS}`);
}
}
catch (error) {
throw Error("Internal Server Error. Please try again\n" + error);
}
return this._appOS;
});
}
}
exports.PublishProfile = PublishProfile;
Loading

0 comments on commit 6c17269

Please sign in to comment.