diff --git a/action.yml b/action.yml index cedea42e0..243ca5969 100644 --- a/action.yml +++ b/action.yml @@ -28,6 +28,9 @@ inputs: resource-group-name: description: 'Enter the resource group name of the web app' required: false + sitecontainers-config: + description: 'Applies to Sitecontainers, containes a list of siteContainer specs' + required: false outputs: webapp-url: diff --git a/lib/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.js b/lib/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.js index eadc8d5d9..3b9213c69 100644 --- a/lib/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.js +++ b/lib/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.js @@ -19,6 +19,7 @@ class PublishProfileContainerWebAppValidator { (0, Validations_1.packageNotAllowed)(actionParams.packageInput); (0, Validations_1.multiContainerNotAllowed)(actionParams.multiContainerConfigFile); (0, Validations_1.startupCommandNotAllowed)(actionParams.startupCommand); + (0, Validations_1.siteContainersConfigNotAllowed)(actionParams.siteContainers); (0, Validations_1.validateAppDetails)(); (0, Validations_1.validateSingleContainerInputs)(); }); diff --git a/lib/ActionInputValidator/ActionValidators/PublishProfileWebAppSiteContainersValidator.js b/lib/ActionInputValidator/ActionValidators/PublishProfileWebAppSiteContainersValidator.js new file mode 100644 index 000000000..ff511bddd --- /dev/null +++ b/lib/ActionInputValidator/ActionValidators/PublishProfileWebAppSiteContainersValidator.js @@ -0,0 +1,20 @@ +"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 }); +exports.PublishProfileWebAppSiteContainersValidator = void 0; +class PublishProfileWebAppSiteContainersValidator { + validate() { + return __awaiter(this, void 0, void 0, function* () { + throw new Error("publish-profile is not supported for Site Containers scenario"); + }); + } +} +exports.PublishProfileWebAppSiteContainersValidator = PublishProfileWebAppSiteContainersValidator; diff --git a/lib/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.js b/lib/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.js index 77174b4d8..37531c529 100644 --- a/lib/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.js +++ b/lib/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.js @@ -19,6 +19,7 @@ class PublishProfileWebAppValidator { (0, Validations_1.containerInputsNotAllowed)(actionParams.images, actionParams.multiContainerConfigFile, true); (0, Validations_1.validateAppDetails)(); (0, Validations_1.startupCommandNotAllowed)(actionParams.startupCommand); + (0, Validations_1.siteContainersConfigNotAllowed)(actionParams.siteContainers); yield (0, Validations_1.validatePackageInput)(); }); } diff --git a/lib/ActionInputValidator/ActionValidators/SpnWebAppSiteContainersValidator.js b/lib/ActionInputValidator/ActionValidators/SpnWebAppSiteContainersValidator.js new file mode 100644 index 000000000..639e16254 --- /dev/null +++ b/lib/ActionInputValidator/ActionValidators/SpnWebAppSiteContainersValidator.js @@ -0,0 +1,30 @@ +"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 }); +exports.SpnWebAppSiteContainersValidator = void 0; +const Validations_1 = require("../Validations"); +const SpnLinuxWebAppValidator_1 = require("./SpnLinuxWebAppValidator"); +const actionparameters_1 = require("../../actionparameters"); +class SpnWebAppSiteContainersValidator extends SpnLinuxWebAppValidator_1.SpnLinuxWebAppValidator { + validate() { + const _super = Object.create(null, { + validate: { get: () => super.validate } + }); + return __awaiter(this, void 0, void 0, function* () { + let actionParams = actionparameters_1.ActionParameters.getActionParams(); + if (!!actionParams.blessedAppSitecontainers) { + yield _super.validate.call(this); + } + (0, Validations_1.validateSiteContainersInputs)(); + }); + } +} +exports.SpnWebAppSiteContainersValidator = SpnWebAppSiteContainersValidator; diff --git a/lib/ActionInputValidator/Validations.js b/lib/ActionInputValidator/Validations.js index 65f4b7a36..6cc546713 100644 --- a/lib/ActionInputValidator/Validations.js +++ b/lib/ActionInputValidator/Validations.js @@ -51,6 +51,8 @@ exports.multiContainerNotAllowed = multiContainerNotAllowed; exports.validateSingleContainerInputs = validateSingleContainerInputs; exports.validateContainerInputs = validateContainerInputs; exports.validatePackageInput = validatePackageInput; +exports.siteContainersConfigNotAllowed = siteContainersConfigNotAllowed; +exports.validateSiteContainersInputs = validateSiteContainersInputs; const core = __importStar(require("@actions/core")); const packageUtility_1 = require("azure-actions-utility/packageUtility"); const PublishProfile_1 = require("../Utilities/PublishProfile"); @@ -146,3 +148,16 @@ function validatePackageInput() { } }); } +// Error if Sitecontainers configuration is provided +function siteContainersConfigNotAllowed(siteContainers) { + if (!!siteContainers) { + throw new Error("SiteContainers not valid input for this web app."); + } +} +// validate Sitecontainers inputs +function validateSiteContainersInputs() { + const actionParams = actionparameters_1.ActionParameters.getActionParams(); + if (!actionParams.siteContainers || actionParams.siteContainers.length === 0) { + throw new Error("Site containers not provided."); + } +} diff --git a/lib/ActionInputValidator/ValidatorFactory.js b/lib/ActionInputValidator/ValidatorFactory.js index d15b01453..f23d67b05 100644 --- a/lib/ActionInputValidator/ValidatorFactory.js +++ b/lib/ActionInputValidator/ValidatorFactory.js @@ -59,11 +59,17 @@ const SpnWindowsWebAppValidator_1 = require("./ActionValidators/SpnWindowsWebApp const Validations_1 = require("./Validations"); const PublishProfile_1 = require("../Utilities/PublishProfile"); const RuntimeConstants_1 = __importDefault(require("../RuntimeConstants")); +const SpnWebAppSiteContainersValidator_1 = require("./ActionValidators/SpnWebAppSiteContainersValidator"); +const PublishProfileWebAppSiteContainersValidator_1 = require("./ActionValidators/PublishProfileWebAppSiteContainersValidator"); +const azure_app_service_1 = require("azure-actions-appservice-rest/Arm/azure-app-service"); 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) { + if (!!actionParams.blessedAppSitecontainers || !!actionParams.siteContainers) { + return new PublishProfileWebAppSiteContainersValidator_1.PublishProfileWebAppSiteContainersValidator(); + } if (!!actionParams.images) { yield this.setResourceDetails(actionParams); return new PublishProfileContainerWebAppValidator_1.PublishProfileContainerWebAppValidator(); @@ -83,7 +89,11 @@ class ValidatorFactory { (0, Validations_1.appNameIsRequired)(actionParams.appName); yield this.getResourceDetails(actionParams); if (!!actionParams.isLinux) { - if (!!actionParams.images || !!actionParams.multiContainerConfigFile) { + if (!!actionParams.siteContainers) { + yield this.setBlessedSitecontainerApp(actionParams); + return new SpnWebAppSiteContainersValidator_1.SpnWebAppSiteContainersValidator(); + } + else if (!!actionParams.images || !!actionParams.multiContainerConfigFile) { return new SpnLinuxContainerWebAppValidator_1.SpnLinuxContainerWebAppValidator(); } else { @@ -121,5 +131,18 @@ class ValidatorFactory { actionParams.isLinux = appOS.includes(RuntimeConstants_1.default.Unix) || appOS.includes(RuntimeConstants_1.default.Unix.toLowerCase()); }); } + static setBlessedSitecontainerApp(actionParams) { + return __awaiter(this, void 0, void 0, function* () { + var _a; + const appService = new azure_app_service_1.AzureAppService(actionParams.endpoint, actionParams.resourceGroupName, actionParams.appName, actionParams.slotName); + let config = yield appService.getConfiguration(); + core.debug(`LinuxFxVersion of app is: ${config.properties.linuxFxVersion}`); + const linuxFxVersion = ((_a = config.properties.linuxFxVersion) === null || _a === void 0 ? void 0 : _a.toUpperCase()) || ""; + actionParams.blessedAppSitecontainers = (!linuxFxVersion.startsWith("DOCKER|") + && !linuxFxVersion.startsWith("COMPOSE|") + && linuxFxVersion !== "SITECONTAINERS"); + core.debug(`Is blessed app sitecontainers: ${actionParams.blessedAppSitecontainers}`); + }); + } } exports.ValidatorFactory = ValidatorFactory; diff --git a/lib/DeploymentProvider/DeploymentProviderFactory.js b/lib/DeploymentProvider/DeploymentProviderFactory.js index b41de98ae..cef6c59c7 100644 --- a/lib/DeploymentProvider/DeploymentProviderFactory.js +++ b/lib/DeploymentProvider/DeploymentProviderFactory.js @@ -6,6 +6,7 @@ const BaseWebAppDeploymentProvider_1 = require("./Providers/BaseWebAppDeployment const WebAppContainerDeployment_1 = require("./Providers/WebAppContainerDeployment"); const WebAppDeploymentProvider_1 = require("./Providers/WebAppDeploymentProvider"); const PublishProfileWebAppContainerDeploymentProvider_1 = require("./Providers/PublishProfileWebAppContainerDeploymentProvider"); +const WebAppSiteContainersDeploymentProvider_1 = require("./Providers/WebAppSiteContainersDeploymentProvider"); class DeploymentProviderFactory { static getDeploymentProvider(type) { if (type === BaseWebAppDeploymentProvider_1.DEPLOYMENT_PROVIDER_TYPES.PUBLISHPROFILE) { @@ -17,6 +18,9 @@ class DeploymentProviderFactory { } } else if (type == BaseWebAppDeploymentProvider_1.DEPLOYMENT_PROVIDER_TYPES.SPN) { + if (!!actionparameters_1.ActionParameters.getActionParams().blessedAppSitecontainers || !!actionparameters_1.ActionParameters.getActionParams().siteContainers) { + return new WebAppSiteContainersDeploymentProvider_1.WebAppSiteContainersDeploymentProvider(type); + } if (!!actionparameters_1.ActionParameters.getActionParams().images || (!!actionparameters_1.ActionParameters.getActionParams().isLinux && !!actionparameters_1.ActionParameters.getActionParams().multiContainerConfigFile)) { return new WebAppContainerDeployment_1.WebAppContainerDeploymentProvider(type); } diff --git a/lib/DeploymentProvider/Providers/WebAppSiteContainersDeploymentProvider.js b/lib/DeploymentProvider/Providers/WebAppSiteContainersDeploymentProvider.js new file mode 100644 index 000000000..355ed258b --- /dev/null +++ b/lib/DeploymentProvider/Providers/WebAppSiteContainersDeploymentProvider.js @@ -0,0 +1,70 @@ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); +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 }); +exports.WebAppSiteContainersDeploymentProvider = void 0; +const SiteContainerDeploymentUtility_1 = require("azure-actions-appservice-rest/Utilities/SiteContainerDeploymentUtility"); +const core = __importStar(require("@actions/core")); +const WebAppDeploymentProvider_1 = require("./WebAppDeploymentProvider"); +class WebAppSiteContainersDeploymentProvider extends WebAppDeploymentProvider_1.WebAppDeploymentProvider { + DeployWebAppStep() { + const _super = Object.create(null, { + DeployWebAppStep: { get: () => super.DeployWebAppStep } + }); + return __awaiter(this, void 0, void 0, function* () { + if (!!this.actionParams.blessedAppSitecontainers) { + core.debug("Blessed site containers detected, using WebAppDeploymentProvider for deployment."); + yield _super.DeployWebAppStep.call(this); + } + let siteContainerDeploymentUtility = new SiteContainerDeploymentUtility_1.SiteContainerDeploymentUtility(this.appService); + let siteContainers = this.actionParams.siteContainers; + core.debug("Updating site containers"); + for (let i = 0; i < siteContainers.length; i++) { + let siteContainer = siteContainers[i]; + core.debug("updating site container: " + siteContainer.getName()); + yield siteContainerDeploymentUtility.updateSiteContainer(siteContainer); + } + }); + } +} +exports.WebAppSiteContainersDeploymentProvider = WebAppSiteContainersDeploymentProvider; diff --git a/lib/actionparameters.js b/lib/actionparameters.js index ffdedfdf0..290673c40 100644 --- a/lib/actionparameters.js +++ b/lib/actionparameters.js @@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () { Object.defineProperty(exports, "__esModule", { value: true }); exports.ActionParameters = exports.appKindMap = exports.WebAppKind = void 0; const core = __importStar(require("@actions/core")); +const SiteContainer_1 = require("azure-actions-appservice-rest/Arm/SiteContainer"); const github = require('@actions/github'); var WebAppKind; (function (WebAppKind) { @@ -66,6 +67,16 @@ class ActionParameters { */ this._commitMessage = github.context.eventName === 'push' ? github.context.payload.head_commit.message.slice(0, 1000) : ""; this._endpoint = endpoint; + // Used for Sitecontainers app. + const siteContainersConfigInput = core.getInput('sitecontainers-config'); + if (siteContainersConfigInput) { + const raw = JSON.parse(siteContainersConfigInput); + this._siteContainers = raw.map(SiteContainer_1.SiteContainer.fromJson); + } + else { + this._siteContainers = null; + } + this._blessedAppSitecontainers = false; } static getActionParams(endpoint) { if (!this.actionparams) { @@ -139,5 +150,17 @@ class ActionParameters { get multiContainerConfigFile() { return this._multiContainerConfigFile; } + get siteContainers() { + return this._siteContainers; + } + set siteContainers(siteContainers) { + this._siteContainers = siteContainers; + } + get blessedAppSitecontainers() { + return this._blessedAppSitecontainers; + } + set blessedAppSitecontainers(blessedAppSitecontainers) { + this._blessedAppSitecontainers = blessedAppSitecontainers; + } } exports.ActionParameters = ActionParameters; diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 4a363b634..82dda911f 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1394,7 +1394,9 @@ } }, "node_modules/azure-actions-appservice-rest": { - "version": "1.3.29", + "version": "1.3.34", + "resolved": "https://registry.npmjs.org/azure-actions-appservice-rest/-/azure-actions-appservice-rest-1.3.34.tgz", + "integrity": "sha512-Wy48/F+gPvM6UiRuJkUxfL7aPbb7vXwAoDPrI18cKS1XVWYoQOHm5cJu8gPWc8WVgO7FFs4Jd0ZqfzGv440fzw==", "license": "MIT", "dependencies": { "@actions/core": "^1.1.10", diff --git a/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.d.ts b/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.d.ts index 421b983a3..375802ca1 100644 --- a/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.d.ts +++ b/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.d.ts @@ -1,7 +1,7 @@ export declare enum AUTH_TYPE { ANONYMOUS = "Anonymous", USERCREDENTIALS = "UserCredentials", - SYSTEM_ASSIGNED = "SystemAssigned", + SYSTEM_IDENTITY = "SystemIdentity", USER_ASSIGNED = "UserAssigned" } export declare class EnvironmentVariable { @@ -17,58 +17,55 @@ export declare class EnvironmentVariable { } export declare class VolumeMount { private containerMountPath; - private data; - private readOnly; private volumeSubPath; - constructor(containerMountPath: string, data: string, readOnly: boolean, volumeSubPath: string); + private readOnly?; + constructor(containerMountPath: string, volumeSubPath: string, readOnly?: boolean); getContainerMountPath(): string; - getData(): string; - getReadOnly(): boolean; getVolumeSubPath(): string; + getReadOnly(): boolean | undefined; setContainerMountPath(containerMountPath: string): void; - setData(data: string): void; - setReadOnly(readOnly: boolean): void; setVolumeSubPath(volumeSubPath: string): void; + setReadOnly(readOnly: boolean): void; static fromJson(item: any): VolumeMount; static toJson(volumeMount: VolumeMount): any; } export declare class SiteContainer { private name; private image; + private isMain; private targetPort?; - private isMain?; private startupCommand?; private authType?; private userName?; private passwordSecret?; private userManagedIdentityClientId?; - private type?; private environmentVariables?; private volumeMounts?; - constructor(name: string, image: string, targetPort?: string, isMain?: boolean, startupCommand?: string, authType?: AUTH_TYPE, userName?: string, passwordSecret?: string, userManagedIdentityClientId?: string, type?: string, environmentVariables?: EnvironmentVariable[], volumeMounts?: VolumeMount[]); + private inheritAppSettingsAndConnectionStrings?; + constructor(name: string, image: string, isMain: boolean, targetPort?: string, startupCommand?: string, authType?: AUTH_TYPE, userName?: string, passwordSecret?: string, userManagedIdentityClientId?: string, environmentVariables?: EnvironmentVariable[], volumeMounts?: VolumeMount[], inheritAppSettingsAndConnectionStrings?: boolean); getName(): string; getImage(): string; - getTargetPort(): string | undefined; getIsMain(): boolean; + getTargetPort(): string | undefined; getStartupCommand(): string | undefined; getAuthType(): AUTH_TYPE | undefined; getUserName(): string | undefined; getPasswordSecret(): string | undefined; getUserManagedIdentityClientId(): string | undefined; - getType(): string | undefined; getEnvironmentVariables(): EnvironmentVariable[] | undefined; getVolumeMounts(): VolumeMount[] | undefined; + getInheritAppSettingsAndConnectionStrings(): boolean | undefined; setName(name: string): void; setImage(image: string): void; - setTargetPort(targetPort: string): void; setIsMain(isMain: boolean): void; + setTargetPort(targetPort: string): void; setStartupCommand(startupCommand: string): void; setAuthType(authType: AUTH_TYPE): void; setUserName(userName: string): void; setPasswordSecret(passwordSecret: string): void; setUserManagedIdentityClientId(userManagedIdentityClientId: string): void; - setType(type: string): void; setEnvironmentVariables(environmentVariables: EnvironmentVariable[]): void; setVolumeMounts(volumeMounts: VolumeMount[]): void; + setInheritAppSettingsAndConnectionStrings(inheritAppSettingsAndConnectionStrings: boolean): void; static fromJson(item: any): SiteContainer; } diff --git a/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.js b/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.js index 198d18515..eb4224d80 100644 --- a/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.js +++ b/node_modules/azure-actions-appservice-rest/Arm/SiteContainer.js @@ -5,7 +5,7 @@ var AUTH_TYPE; (function (AUTH_TYPE) { AUTH_TYPE["ANONYMOUS"] = "Anonymous"; AUTH_TYPE["USERCREDENTIALS"] = "UserCredentials"; - AUTH_TYPE["SYSTEM_ASSIGNED"] = "SystemAssigned"; + AUTH_TYPE["SYSTEM_IDENTITY"] = "SystemIdentity"; AUTH_TYPE["USER_ASSIGNED"] = "UserAssigned"; })(AUTH_TYPE = exports.AUTH_TYPE || (exports.AUTH_TYPE = {})); class EnvironmentVariable { @@ -37,63 +37,55 @@ class EnvironmentVariable { } exports.EnvironmentVariable = EnvironmentVariable; class VolumeMount { - constructor(containerMountPath, data, readOnly, volumeSubPath) { + constructor(containerMountPath, volumeSubPath, readOnly) { this.containerMountPath = containerMountPath; - this.data = data; - this.readOnly = readOnly; this.volumeSubPath = volumeSubPath; + this.readOnly = readOnly; } getContainerMountPath() { return this.containerMountPath; } - getData() { - return this.data; + getVolumeSubPath() { + return this.volumeSubPath; } getReadOnly() { return this.readOnly; } - getVolumeSubPath() { - return this.volumeSubPath; - } setContainerMountPath(containerMountPath) { this.containerMountPath = containerMountPath; } - setData(data) { - this.data = data; + setVolumeSubPath(volumeSubPath) { + this.volumeSubPath = volumeSubPath; } setReadOnly(readOnly) { this.readOnly = readOnly; } - setVolumeSubPath(volumeSubPath) { - this.volumeSubPath = volumeSubPath; - } static fromJson(item) { - return new VolumeMount(item.containerMountPath, item.data, item.readOnly, item.volumeSubPath); + return new VolumeMount(item.containerMountPath, item.volumeSubPath, item.readOnly); } static toJson(volumeMount) { return { containerMountPath: volumeMount.getContainerMountPath(), - data: volumeMount.getData(), - readOnly: volumeMount.getReadOnly(), - volumeSubPath: volumeMount.getVolumeSubPath() + volumeSubPath: volumeMount.getVolumeSubPath(), + readOnly: volumeMount.getReadOnly() }; } } exports.VolumeMount = VolumeMount; class SiteContainer { - constructor(name, image, targetPort, isMain, startupCommand, authType, userName, passwordSecret, userManagedIdentityClientId, type, environmentVariables, volumeMounts) { + constructor(name, image, isMain, targetPort, startupCommand, authType, userName, passwordSecret, userManagedIdentityClientId, environmentVariables, volumeMounts, inheritAppSettingsAndConnectionStrings) { this.name = name; this.image = image; - this.targetPort = targetPort; this.isMain = isMain; + this.targetPort = targetPort; this.startupCommand = startupCommand; this.authType = authType; this.userName = userName; this.passwordSecret = passwordSecret; this.userManagedIdentityClientId = userManagedIdentityClientId; - this.type = type; this.environmentVariables = environmentVariables; this.volumeMounts = volumeMounts; + this.inheritAppSettingsAndConnectionStrings = inheritAppSettingsAndConnectionStrings; } getName() { return this.name; @@ -101,13 +93,12 @@ class SiteContainer { getImage() { return this.image; } + getIsMain() { + return this.isMain; + } getTargetPort() { return this.targetPort; } - getIsMain() { - var _a; - return (_a = this.isMain) !== null && _a !== void 0 ? _a : false; - } getStartupCommand() { return this.startupCommand; } @@ -123,27 +114,27 @@ class SiteContainer { getUserManagedIdentityClientId() { return this.userManagedIdentityClientId; } - getType() { - return this.type; - } getEnvironmentVariables() { return this.environmentVariables; } getVolumeMounts() { return this.volumeMounts; } + getInheritAppSettingsAndConnectionStrings() { + return this.inheritAppSettingsAndConnectionStrings; + } setName(name) { this.name = name; } setImage(image) { this.image = image; } - setTargetPort(targetPort) { - this.targetPort = targetPort; - } setIsMain(isMain) { this.isMain = isMain; } + setTargetPort(targetPort) { + this.targetPort = targetPort; + } setStartupCommand(startupCommand) { this.startupCommand = startupCommand; } @@ -159,18 +150,18 @@ class SiteContainer { setUserManagedIdentityClientId(userManagedIdentityClientId) { this.userManagedIdentityClientId = userManagedIdentityClientId; } - setType(type) { - this.type = type; - } setEnvironmentVariables(environmentVariables) { this.environmentVariables = environmentVariables; } setVolumeMounts(volumeMounts) { this.volumeMounts = volumeMounts; } + setInheritAppSettingsAndConnectionStrings(inheritAppSettingsAndConnectionStrings) { + this.inheritAppSettingsAndConnectionStrings = inheritAppSettingsAndConnectionStrings; + } static fromJson(item) { - var _a, _b, _c, _d; - return new SiteContainer(item.name, item.image, (_a = item.targetPort) === null || _a === void 0 ? void 0 : _a.toString(), (_b = item.isMain) !== null && _b !== void 0 ? _b : false, item.startupCommand, item.authType, item.userName, item.passwordSecret, item.userManagedIdentityClientId, item.type, (_c = item.environmentVariables) === null || _c === void 0 ? void 0 : _c.map((env) => new EnvironmentVariable(env.name, env.value)), (_d = item.volumeMounts) === null || _d === void 0 ? void 0 : _d.map((mount) => new VolumeMount(mount.containerMountPath, mount.data, mount.readOnly, mount.volumeSubPath))); + var _a, _b, _c; + return new SiteContainer(item.name, item.image, item.isMain, (_a = item.targetPort) === null || _a === void 0 ? void 0 : _a.toString(), item.startupCommand, item.authType, item.userName, item.passwordSecret, item.userManagedIdentityClientId, (_b = item.environmentVariables) === null || _b === void 0 ? void 0 : _b.map((env) => new EnvironmentVariable(env.name, env.value)), (_c = item.volumeMounts) === null || _c === void 0 ? void 0 : _c.map((mount) => new VolumeMount(mount.containerMountPath, mount.volumeSubPath, mount.readOnly)), item.inheritAppSettingsAndConnectionStrings); } } exports.SiteContainer = SiteContainer; diff --git a/node_modules/azure-actions-appservice-rest/Utilities/SiteContainerDeploymentUtility.js b/node_modules/azure-actions-appservice-rest/Utilities/SiteContainerDeploymentUtility.js index 349a7d195..caab285d3 100644 --- a/node_modules/azure-actions-appservice-rest/Utilities/SiteContainerDeploymentUtility.js +++ b/node_modules/azure-actions-appservice-rest/Utilities/SiteContainerDeploymentUtility.js @@ -51,7 +51,7 @@ class SiteContainerDeploymentUtility { userName: siteContainer.getUserName(), passwordSecret: siteContainer.getPasswordSecret(), userManagedIdentityClientId: siteContainer.getUserManagedIdentityClientId(), - type: siteContainer.getType() + inheritAppSettingsAndConnectionStrings: siteContainer.getInheritAppSettingsAndConnectionStrings() }; for (const key in allProperties) { const value = allProperties[key]; diff --git a/node_modules/azure-actions-appservice-rest/package.json b/node_modules/azure-actions-appservice-rest/package.json index a52953c80..3000634dc 100644 --- a/node_modules/azure-actions-appservice-rest/package.json +++ b/node_modules/azure-actions-appservice-rest/package.json @@ -1,6 +1,6 @@ { "name": "azure-actions-appservice-rest", - "version": "1.3.29", + "version": "1.3.34", "description": "Azure resource manager and kudu node rest module", "keywords": [ "appservice", diff --git a/package-lock.json b/package-lock.json index b01895dbb..fa92c1882 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,7 +12,7 @@ "@actions/core": "^1.10.0", "@actions/github": "^4.0.0", "actions-secret-parser": "^1.0.4", - "azure-actions-appservice-rest": "^1.3.16", + "azure-actions-appservice-rest": "^1.3.34", "azure-actions-utility": "1.0.3", "azure-actions-webclient": "^1.1.1" }, @@ -1415,7 +1415,9 @@ } }, "node_modules/azure-actions-appservice-rest": { - "version": "1.3.29", + "version": "1.3.34", + "resolved": "https://registry.npmjs.org/azure-actions-appservice-rest/-/azure-actions-appservice-rest-1.3.34.tgz", + "integrity": "sha512-Wy48/F+gPvM6UiRuJkUxfL7aPbb7vXwAoDPrI18cKS1XVWYoQOHm5cJu8gPWc8WVgO7FFs4Jd0ZqfzGv440fzw==", "license": "MIT", "dependencies": { "@actions/core": "^1.1.10", diff --git a/package.json b/package.json index 10de476f8..f70548213 100644 --- a/package.json +++ b/package.json @@ -34,7 +34,7 @@ "@actions/core": "^1.10.0", "@actions/github": "^4.0.0", "actions-secret-parser": "^1.0.4", - "azure-actions-appservice-rest": "^1.3.16", + "azure-actions-appservice-rest": "^1.3.34", "azure-actions-utility": "1.0.3", "azure-actions-webclient": "^1.1.1" } diff --git a/src/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.ts b/src/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.ts index 89751daab..42b8b378d 100644 --- a/src/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.ts +++ b/src/ActionInputValidator/ActionValidators/PublishProfileContainerWebAppValidator.ts @@ -1,4 +1,4 @@ -import { packageNotAllowed, multiContainerNotAllowed, startupCommandNotAllowed, validateSingleContainerInputs, validateAppDetails } from "../Validations"; +import { packageNotAllowed, multiContainerNotAllowed, startupCommandNotAllowed, validateSingleContainerInputs, validateAppDetails, siteContainersConfigNotAllowed } from "../Validations"; import { ActionParameters } from "../../actionparameters"; import { IValidator } from "./IValidator"; @@ -12,6 +12,8 @@ export class PublishProfileContainerWebAppValidator implements IValidator { startupCommandNotAllowed(actionParams.startupCommand); + siteContainersConfigNotAllowed(actionParams.siteContainers); + validateAppDetails(); validateSingleContainerInputs(); diff --git a/src/ActionInputValidator/ActionValidators/PublishProfileWebAppSiteContainersValidator.ts b/src/ActionInputValidator/ActionValidators/PublishProfileWebAppSiteContainersValidator.ts new file mode 100644 index 000000000..096988b1d --- /dev/null +++ b/src/ActionInputValidator/ActionValidators/PublishProfileWebAppSiteContainersValidator.ts @@ -0,0 +1,7 @@ +import { IValidator } from "./IValidator"; + +export class PublishProfileWebAppSiteContainersValidator implements IValidator { + async validate(): Promise { + throw new Error("publish-profile is not supported for Site Containers scenario"); + } +} \ No newline at end of file diff --git a/src/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.ts b/src/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.ts index 5ead09b61..fdf153aec 100644 --- a/src/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.ts +++ b/src/ActionInputValidator/ActionValidators/PublishProfileWebAppValidator.ts @@ -1,4 +1,4 @@ -import { containerInputsNotAllowed, startupCommandNotAllowed, validateAppDetails, validatePackageInput } from "../Validations"; +import { containerInputsNotAllowed, siteContainersConfigNotAllowed, startupCommandNotAllowed, validateAppDetails, validatePackageInput } from "../Validations"; import { ActionParameters } from "../../actionparameters"; import { IValidator } from "./IValidator"; @@ -15,6 +15,8 @@ export class PublishProfileWebAppValidator implements IValidator { startupCommandNotAllowed(actionParams.startupCommand); + siteContainersConfigNotAllowed(actionParams.siteContainers); + await validatePackageInput(); } diff --git a/src/ActionInputValidator/ActionValidators/SpnWebAppSiteContainersValidator.ts b/src/ActionInputValidator/ActionValidators/SpnWebAppSiteContainersValidator.ts new file mode 100644 index 000000000..1ef5f65ee --- /dev/null +++ b/src/ActionInputValidator/ActionValidators/SpnWebAppSiteContainersValidator.ts @@ -0,0 +1,15 @@ +import { validateSiteContainersInputs } from "../Validations"; +import { SpnLinuxWebAppValidator } from "./SpnLinuxWebAppValidator"; +import { ActionParameters } from "../../actionparameters"; + +export class SpnWebAppSiteContainersValidator extends SpnLinuxWebAppValidator { + async validate(): Promise { + + let actionParams: ActionParameters = ActionParameters.getActionParams(); + if (!!actionParams.blessedAppSitecontainers) { + await super.validate(); + } + + validateSiteContainersInputs(); + } +} \ No newline at end of file diff --git a/src/ActionInputValidator/Validations.ts b/src/ActionInputValidator/Validations.ts index 750385674..a9bd90f16 100644 --- a/src/ActionInputValidator/Validations.ts +++ b/src/ActionInputValidator/Validations.ts @@ -4,6 +4,7 @@ import { Package, exist } from "azure-actions-utility/packageUtility"; import { PublishProfile, ScmCredentials } from "../Utilities/PublishProfile"; import RuntimeConstants from '../RuntimeConstants'; import { ActionParameters } from "../actionparameters"; +import { SiteContainer } from 'azure-actions-appservice-rest/Arm/SiteContainer'; import fs = require('fs'); @@ -109,4 +110,20 @@ export async function validatePackageInput() { if(isMSBuildPackage) { throw new Error(`Deployment of msBuild generated package is not supported. Please change package format.`); } +} + +// Error if Sitecontainers configuration is provided +export function siteContainersConfigNotAllowed(siteContainers: SiteContainer[]) { + if(!!siteContainers) { + throw new Error("SiteContainers not valid input for this web app."); + } +} + +// validate Sitecontainers inputs +export function validateSiteContainersInputs() { + const actionParams: ActionParameters = ActionParameters.getActionParams(); + + if (!actionParams.siteContainers || actionParams.siteContainers.length === 0) { + throw new Error("Site containers not provided."); + } } \ No newline at end of file diff --git a/src/ActionInputValidator/ValidatorFactory.ts b/src/ActionInputValidator/ValidatorFactory.ts index ccf344b2c..100f6a722 100644 --- a/src/ActionInputValidator/ValidatorFactory.ts +++ b/src/ActionInputValidator/ValidatorFactory.ts @@ -13,12 +13,17 @@ import { SpnWindowsWebAppValidator } from "./ActionValidators/SpnWindowsWebAppVa import { appNameIsRequired } from "./Validations"; import { PublishProfile } from "../Utilities/PublishProfile"; import RuntimeConstants from "../RuntimeConstants"; +import { SpnWebAppSiteContainersValidator } from "./ActionValidators/SpnWebAppSiteContainersValidator"; +import { PublishProfileWebAppSiteContainersValidator } from "./ActionValidators/PublishProfileWebAppSiteContainersValidator" +import { AzureAppService } from "azure-actions-appservice-rest/Arm/azure-app-service"; export class ValidatorFactory { public static async getValidator(type: DEPLOYMENT_PROVIDER_TYPES) : Promise { let actionParams: ActionParameters = ActionParameters.getActionParams(); if(type === DEPLOYMENT_PROVIDER_TYPES.PUBLISHPROFILE) { - if (!!actionParams.images) { + if (!!actionParams.blessedAppSitecontainers || !!actionParams.siteContainers) { + return new PublishProfileWebAppSiteContainersValidator(); + } if (!!actionParams.images) { await this.setResourceDetails(actionParams); return new PublishProfileContainerWebAppValidator(); } @@ -36,18 +41,21 @@ export class ValidatorFactory { appNameIsRequired(actionParams.appName); await this.getResourceDetails(actionParams); if (!!actionParams.isLinux) { - if (!!actionParams.images || !!actionParams.multiContainerConfigFile) { + if (!!actionParams.siteContainers) { + + await this.setBlessedSitecontainerApp(actionParams); + + return new SpnWebAppSiteContainersValidator(); + } else if (!!actionParams.images || !!actionParams.multiContainerConfigFile) { return new SpnLinuxContainerWebAppValidator(); - } - else { + } else { return new SpnLinuxWebAppValidator(); } } else { if (!!actionParams.images) { return new SpnWindowsContainerWebAppValidator(); - } - else { + } else { return new SpnWindowsWebAppValidator(); } } @@ -71,4 +79,19 @@ export class ValidatorFactory { const appOS: string = await publishProfile.getAppOS(); actionParams.isLinux = appOS.includes(RuntimeConstants.Unix) || appOS.includes(RuntimeConstants.Unix.toLowerCase()); } + + private static async setBlessedSitecontainerApp(actionParams: ActionParameters): Promise { + const appService = new AzureAppService(actionParams.endpoint, actionParams.resourceGroupName, actionParams.appName, actionParams.slotName); + + let config = await appService.getConfiguration(); + + core.debug(`LinuxFxVersion of app is: ${config.properties.linuxFxVersion}`); + + const linuxFxVersion = config.properties.linuxFxVersion?.toUpperCase() || ""; + actionParams.blessedAppSitecontainers = (!linuxFxVersion.startsWith("DOCKER|") + && !linuxFxVersion.startsWith("COMPOSE|") + && linuxFxVersion !== "SITECONTAINERS"); + + core.debug(`Is blessed app sitecontainers: ${actionParams.blessedAppSitecontainers}`); + } } diff --git a/src/DeploymentProvider/DeploymentProviderFactory.ts b/src/DeploymentProvider/DeploymentProviderFactory.ts index b225abc4b..dc3345f04 100644 --- a/src/DeploymentProvider/DeploymentProviderFactory.ts +++ b/src/DeploymentProvider/DeploymentProviderFactory.ts @@ -5,6 +5,7 @@ import { IWebAppDeploymentProvider } from "./Providers/IWebAppDeploymentProvider import { WebAppContainerDeploymentProvider } from "./Providers/WebAppContainerDeployment"; import { WebAppDeploymentProvider } from "./Providers/WebAppDeploymentProvider"; import { PublishProfileWebAppContainerDeploymentProvider } from "./Providers/PublishProfileWebAppContainerDeploymentProvider"; +import { WebAppSiteContainersDeploymentProvider } from "./Providers/WebAppSiteContainersDeploymentProvider"; export class DeploymentProviderFactory { @@ -18,6 +19,9 @@ export class DeploymentProviderFactory { } } else if(type == DEPLOYMENT_PROVIDER_TYPES.SPN) { + if (!!ActionParameters.getActionParams().blessedAppSitecontainers || !!ActionParameters.getActionParams().siteContainers) { + return new WebAppSiteContainersDeploymentProvider(type); + } if(!!ActionParameters.getActionParams().images || (!!ActionParameters.getActionParams().isLinux && !!ActionParameters.getActionParams().multiContainerConfigFile)) { return new WebAppContainerDeploymentProvider(type); } diff --git a/src/DeploymentProvider/Providers/WebAppSiteContainersDeploymentProvider.ts b/src/DeploymentProvider/Providers/WebAppSiteContainersDeploymentProvider.ts new file mode 100644 index 000000000..39401e2cd --- /dev/null +++ b/src/DeploymentProvider/Providers/WebAppSiteContainersDeploymentProvider.ts @@ -0,0 +1,25 @@ +import { BaseWebAppDeploymentProvider } from './BaseWebAppDeploymentProvider'; +import { SiteContainerDeploymentUtility } from 'azure-actions-appservice-rest/Utilities/SiteContainerDeploymentUtility'; +import * as core from '@actions/core'; +import { WebAppDeploymentProvider } from './WebAppDeploymentProvider'; + +export class WebAppSiteContainersDeploymentProvider extends WebAppDeploymentProvider { + public async DeployWebAppStep() { + + if(!!this.actionParams.blessedAppSitecontainers){ + core.debug("Blessed site containers detected, using WebAppDeploymentProvider for deployment."); + await super.DeployWebAppStep(); + } + + let siteContainerDeploymentUtility = new SiteContainerDeploymentUtility(this.appService); + let siteContainers = this.actionParams.siteContainers; + + core.debug("Updating site containers"); + + for (let i = 0; i < siteContainers.length; i++) { + let siteContainer = siteContainers[i]; + core.debug("updating site container: " + siteContainer.getName()); + await siteContainerDeploymentUtility.updateSiteContainer(siteContainer); + } + } +} \ No newline at end of file diff --git a/src/actionparameters.ts b/src/actionparameters.ts index a21e3bbd4..917c90cff 100644 --- a/src/actionparameters.ts +++ b/src/actionparameters.ts @@ -1,6 +1,7 @@ import * as core from '@actions/core'; import { IAuthorizer } from "azure-actions-webclient/Authorizer/IAuthorizer"; import { Package } from 'azure-actions-utility/packageUtility'; +import { SiteContainer } from 'azure-actions-appservice-rest/Arm/SiteContainer'; const github = require('@actions/github'); export enum WebAppKind { @@ -36,6 +37,10 @@ export class ActionParameters { private _isLinux: boolean; private _commitMessage: string; + // Used for Sitecontainers app. + private _siteContainers: SiteContainer[]; + private _blessedAppSitecontainers: boolean; + private constructor(endpoint: IAuthorizer) { this._publishProfileContent = core.getInput('publish-profile'); this._appName = core.getInput('app-name'); @@ -50,6 +55,17 @@ export class ActionParameters { */ this._commitMessage = github.context.eventName === 'push' ? github.context.payload.head_commit.message.slice(0, 1000) : ""; this._endpoint = endpoint; + + // Used for Sitecontainers app. + const siteContainersConfigInput = core.getInput('sitecontainers-config'); + if (siteContainersConfigInput) { + const raw = JSON.parse(siteContainersConfigInput); + this._siteContainers = raw.map(SiteContainer.fromJson); + } else { + this._siteContainers = null; + } + + this._blessedAppSitecontainers = false; } public static getActionParams(endpoint?: IAuthorizer) { @@ -143,4 +159,19 @@ export class ActionParameters { return this._multiContainerConfigFile; } + public get siteContainers(): SiteContainer[] { + return this._siteContainers; + } + + public set siteContainers(siteContainers: SiteContainer[]) { + this._siteContainers = siteContainers; + } + + public get blessedAppSitecontainers() { + return this._blessedAppSitecontainers; + } + + public set blessedAppSitecontainers(blessedAppSitecontainers: boolean) { + this._blessedAppSitecontainers = blessedAppSitecontainers; + } }