From 6fa8d6d005c4bc88d710b5aff4c32df12bc66faa Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Fri, 24 May 2019 13:57:31 -0700 Subject: [PATCH 1/8] Enable scm build in oryx builder Fix using directives Add TODO to remove all standard workers Address issues in comment Rename Linux consumption functionapp related function Internal call does not require https Update remove all worker URL Catch error when SCM_RUN_FROM_PACKAGE is not provided --- Kudu.Contracts/Settings/SettingsKeys.cs | 2 + .../Generator/ExternalCommandBuilder.cs | 1 + Kudu.Core/Deployment/Generator/OryxBuilder.cs | 122 ++++++++++++++++-- .../Oryx/FunctionAppOryxArguments.cs | 14 +- ...inuxConsumptionFunctionAppOryxArguments.cs | 28 ++++ .../Deployment/Oryx/OryxArgumentsFactory.cs | 7 +- Kudu.Core/Helpers/PostDeploymentHelper.cs | 36 ++++++ Kudu.Core/Infrastructure/FunctionAppHelper.cs | 8 +- Kudu.Core/Kudu.Core.csproj | 1 + .../Deployment/Generator/OryxBuilderTests.cs | 34 +++++ .../Oryx/OryxArgumentsFactoryTests.cs | 15 +++ 11 files changed, 246 insertions(+), 22 deletions(-) create mode 100644 Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs create mode 100644 Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs create mode 100644 Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs diff --git a/Kudu.Contracts/Settings/SettingsKeys.cs b/Kudu.Contracts/Settings/SettingsKeys.cs index ec002bfa..3f2b4bcf 100644 --- a/Kudu.Contracts/Settings/SettingsKeys.cs +++ b/Kudu.Contracts/Settings/SettingsKeys.cs @@ -49,6 +49,8 @@ public static class SettingsKeys // Antares container specific settings public const string PlaceholderMode = "WEBSITE_PLACEHOLDER_MODE"; public const string ContainerReady = "WEBSITE_CONTAINER_READY"; + public const string ScmRunFromPackage = "SCM_RUN_FROM_PACKAGE"; + public const string WebsiteHostname = "WEBSITE_HOSTNAME"; public const string AuthEncryptionKey = "WEBSITE_AUTH_ENCRYPTION_KEY"; public const string ContainerEncryptionKey = "CONTAINER_ENCRYPTION_KEY"; } diff --git a/Kudu.Core/Deployment/Generator/ExternalCommandBuilder.cs b/Kudu.Core/Deployment/Generator/ExternalCommandBuilder.cs index 3c426f15..c1ded864 100644 --- a/Kudu.Core/Deployment/Generator/ExternalCommandBuilder.cs +++ b/Kudu.Core/Deployment/Generator/ExternalCommandBuilder.cs @@ -17,6 +17,7 @@ namespace Kudu.Core.Deployment.Generator // // ExternalCommandBuilder // CustomBuilder + // OryxBuilder // GeneratorSiteBuilder // BaseBasicBuilder // BasicBuilder diff --git a/Kudu.Core/Deployment/Generator/OryxBuilder.cs b/Kudu.Core/Deployment/Generator/OryxBuilder.cs index 2d5e29b9..1617574e 100644 --- a/Kudu.Core/Deployment/Generator/OryxBuilder.cs +++ b/Kudu.Core/Deployment/Generator/OryxBuilder.cs @@ -1,11 +1,13 @@ -using System.Threading.Tasks; +using System; +using System.IO; +using System.Threading.Tasks; +using System.Net.Http; +using Microsoft.WindowsAzure.Storage.Blob; +using Microsoft.WindowsAzure.Storage; +using Kudu.Core.Infrastructure; using Kudu.Core.Helpers; using Kudu.Contracts.Settings; using Kudu.Core.Deployment.Oryx; -using Kudu.Core.Infrastructure; -using System.IO; -using System; -using Kudu.Core.Commands; namespace Kudu.Core.Deployment.Generator { @@ -18,7 +20,7 @@ public OryxBuilder(IEnvironment environment, IDeploymentSettingsManager settings { } - public override Task Build(DeploymentContext context) + public override async Task Build(DeploymentContext context) { FileLogHelper.Log("In oryx build..."); @@ -42,7 +44,7 @@ public override Task Build(DeploymentContext context) RunCommand(context, kuduSyncCommand, false, "Oryx-Build: Running kudu sync..."); } - + if (args.RunOryxBuild) { PreOryxBuild(context); @@ -65,7 +67,13 @@ public override Task Build(DeploymentContext context) } } - return Task.CompletedTask; + // Detect if package upload is necessary for server side build + if (FunctionAppHelper.HasScmRunFromPackage() && FunctionAppHelper.LooksLikeFunctionApp()) + { + await SetupLinuxConsumptionFunctionAppDeployment(context); + } + + return; } private static void PreOryxBuild(DeploymentContext context) @@ -91,10 +99,17 @@ private void SetupFunctionAppExpressArtifacts(DeploymentContext context) FileSystemHelpers.EnsureDirectory(sitePackages); string zipAppName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip"; - string zipFile = Path.Combine(sitePackages, zipAppName); + PackageArtifactFromFolder(context, OryxBuildConstants.FunctionAppBuildSettings.ExpressBuildSetup, sitePackages, zipAppName, zipQuota:3); + + File.WriteAllText(packageNameFile, zipAppName); + File.WriteAllText(packagePathFile, sitePackages); + } + private string PackageArtifactFromFolder(DeploymentContext context, string srcDirectory, string destDirectory, string destFilename, int zipQuota = 0) + { context.Logger.Log("Writing the artifacts to a zip file"); - var exe = ExternalCommandFactory.BuildExternalCommandExecutable(OryxBuildConstants.FunctionAppBuildSettings.ExpressBuildSetup, sitePackages, context.Logger); + string zipFile = Path.Combine(destDirectory, destFilename); + var exe = ExternalCommandFactory.BuildExternalCommandExecutable(srcDirectory, destDirectory, context.Logger); try { exe.ExecuteWithProgressWriter(context.Logger, context.Tracer, $"zip -r {zipFile} .", String.Empty); @@ -106,10 +121,33 @@ private void SetupFunctionAppExpressArtifacts(DeploymentContext context) } // Just to be sure that we don't keep adding zip files here - DeploymentHelper.PurgeZipsIfNecessary(sitePackages, context.Tracer, totalAllowedZips: 3); + if (zipQuota > 0) + { + DeploymentHelper.PurgeZipsIfNecessary(destDirectory, context.Tracer, totalAllowedZips: zipQuota); + } - File.WriteAllText(packageNameFile, zipAppName); - File.WriteAllText(packagePathFile, sitePackages); + return zipFile; + } + + /// + /// Specifically used for Linux Consumption to support Server Side build scenario + /// + /// + private async Task SetupLinuxConsumptionFunctionAppDeployment(DeploymentContext context) + { + string sas = DeploymentSettings.GetValue(SettingsKeys.ScmRunFromPackage); + string builtFolder = context.RepositoryPath; + string packageFolder = Environment.DeploymentsPath; + string packageFileName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip"; + + // Package built content from oryx build artifact + string filePath = PackageArtifactFromFolder(context, builtFolder, packageFolder, packageFileName); + + // Upload from DeploymentsPath + await UploadLinuxConsumptionFunctionAppBuiltContent(context, sas, filePath); + + // Remove Linux consumption plan functionapp workers for the site + await RemoveLinuxConsumptionFunctionAppWorkers(context); } //public override void PostBuild(DeploymentContext context) @@ -118,5 +156,63 @@ private void SetupFunctionAppExpressArtifacts(DeploymentContext context) // context.Logger.Log($"Skipping post build. Project type: {ProjectType}"); // FileLogHelper.Log("Completed PostBuild oryx...."); //} + + private async Task UploadLinuxConsumptionFunctionAppBuiltContent(DeploymentContext context, string sas, string filePath) + { + context.Logger.Log($"Uploading built content {filePath} -> {sas}"); + + // Check if SCM_RUN_FROM_PACKAGE does exist + if (string.IsNullOrEmpty(sas)) + { + context.Logger.Log($"Failed to upload because SCM_RUN_FROM_PACKAGE is not provided."); + throw new DeploymentFailedException(new ArgumentException("Failed to upload because SAS is empty.")); + } + + // Parse SAS + Uri sasUri = null; + if (!Uri.TryCreate(sas, UriKind.Absolute, out sasUri)) + { + context.Logger.Log($"Malformed SAS when uploading built content."); + throw new DeploymentFailedException(new ArgumentException("Failed to upload because SAS is malformed.")); + } + + // Upload blob to Azure Storage + CloudBlockBlob blob = new CloudBlockBlob(sasUri); + try + { + await blob.UploadFromFileAsync(filePath); + } catch (StorageException se) + { + context.Logger.Log($"Failed to upload because Azure Storage responds {se.RequestInformation.HttpStatusCode}."); + context.Logger.Log(se.Message); + throw new DeploymentFailedException(se); + } + } + + private async Task RemoveLinuxConsumptionFunctionAppWorkers(DeploymentContext context) + { + string webSiteHostName = System.Environment.GetEnvironmentVariable(SettingsKeys.WebsiteHostname); + string sitename = ServerConfiguration.GetApplicationName(); + + context.Logger.Log($"Reseting all workers for {webSiteHostName}"); + + try + { + await OperationManager.AttemptAsync(async () => + { + await PostDeploymentHelper.RemoveAllWorkersAsync(webSiteHostName, sitename); + }, retries: 3, delayBeforeRetry: 2000); + } + catch (ArgumentException ae) + { + context.Logger.Log($"Reset all workers has malformed webSiteHostName or sitename {ae.Message}"); + throw new DeploymentFailedException(ae); + } + catch (HttpRequestException hre) + { + context.Logger.Log($"Reset all workers endpoint responded with {hre.Message}"); + throw new DeploymentFailedException(hre); + } + } } } diff --git a/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs b/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs index 85339caf..11e53b5f 100644 --- a/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs +++ b/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs @@ -10,7 +10,7 @@ class FunctionAppOryxArguments : IOryxArguments public BuildOptimizationsFlags Flags { get; set; } - private readonly WorkerRuntime FunctionsWorkerRuntime; + protected readonly WorkerRuntime FunctionsWorkerRuntime; public bool SkipKuduSync { get; set; } public FunctionAppOryxArguments() @@ -23,7 +23,7 @@ public FunctionAppOryxArguments() SkipKuduSync = Flags == BuildOptimizationsFlags.UseExpressBuild; } - public string GenerateOryxBuildCommand(DeploymentContext context) + public virtual string GenerateOryxBuildCommand(DeploymentContext context) { StringBuilder args = new StringBuilder(); @@ -36,7 +36,7 @@ public string GenerateOryxBuildCommand(DeploymentContext context) return args.ToString(); } - private void AddOryxBuildCommand(StringBuilder args, DeploymentContext context, string source, string destination) + protected void AddOryxBuildCommand(StringBuilder args, DeploymentContext context, string source, string destination) { // If it is express build, we don't directly need to write to /home/site/wwwroot // So, we build into a different directory to avoid overlap @@ -55,7 +55,7 @@ private void AddOryxBuildCommand(StringBuilder args, DeploymentContext context, OryxArgumentsHelper.AddOryxBuildCommand(args, source, destination); } - private void AddLanguage(StringBuilder args, WorkerRuntime workerRuntime) + protected void AddLanguage(StringBuilder args, WorkerRuntime workerRuntime) { switch (workerRuntime) { @@ -73,7 +73,7 @@ private void AddLanguage(StringBuilder args, WorkerRuntime workerRuntime) } } - private void AddLanguageVersion(StringBuilder args, WorkerRuntime workerRuntime) + protected void AddLanguageVersion(StringBuilder args, WorkerRuntime workerRuntime) { var workerVersion = ResolveWorkerRuntimeVersion(FunctionsWorkerRuntime); if (!string.IsNullOrEmpty(workerVersion)) @@ -82,7 +82,7 @@ private void AddLanguageVersion(StringBuilder args, WorkerRuntime workerRuntime) } } - private void AddBuildOptimizationFlags(StringBuilder args, DeploymentContext context, BuildOptimizationsFlags optimizationFlags) + protected void AddBuildOptimizationFlags(StringBuilder args, DeploymentContext context, BuildOptimizationsFlags optimizationFlags) { switch (Flags) { @@ -99,7 +99,7 @@ private void AddBuildOptimizationFlags(StringBuilder args, DeploymentContext con } } - private void AddWorkerRuntimeArgs(StringBuilder args, WorkerRuntime workerRuntime) + protected void AddWorkerRuntimeArgs(StringBuilder args, WorkerRuntime workerRuntime) { switch (workerRuntime) { diff --git a/Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs b/Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs new file mode 100644 index 00000000..d3badeca --- /dev/null +++ b/Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kudu.Core.Deployment.Oryx +{ + class LinuxConsumptionFunctionAppOryxArguments : FunctionAppOryxArguments + { + public LinuxConsumptionFunctionAppOryxArguments() : base() + { + SkipKuduSync = true; + Flags = BuildOptimizationsFlags.Off; + } + + public override string GenerateOryxBuildCommand(DeploymentContext context) + { + StringBuilder args = new StringBuilder(); + + base.AddOryxBuildCommand(args, context, source: context.RepositoryPath, destination: context.RepositoryPath); + base.AddLanguage(args, base.FunctionsWorkerRuntime); + base.AddLanguageVersion(args, base.FunctionsWorkerRuntime); + base.AddBuildOptimizationFlags(args, context, Flags); + base.AddWorkerRuntimeArgs(args, base.FunctionsWorkerRuntime); + + return args.ToString(); + } + } +} diff --git a/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs b/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs index 1dca6f3e..6b9ea527 100644 --- a/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs +++ b/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs @@ -8,7 +8,12 @@ public static IOryxArguments CreateOryxArguments() { if (FunctionAppHelper.LooksLikeFunctionApp()) { - return new FunctionAppOryxArguments(); + if (FunctionAppHelper.HasScmRunFromPackage()) + { + return new LinuxConsumptionFunctionAppOryxArguments(); + } else { + return new FunctionAppOryxArguments(); + } } return new AppServiceOryxArguments(); } diff --git a/Kudu.Core/Helpers/PostDeploymentHelper.cs b/Kudu.Core/Helpers/PostDeploymentHelper.cs index 3143358f..c3e102e7 100644 --- a/Kudu.Core/Helpers/PostDeploymentHelper.cs +++ b/Kudu.Core/Helpers/PostDeploymentHelper.cs @@ -11,6 +11,7 @@ using System.Text; using System.Threading; using System.Threading.Tasks; +using System.Web; using Kudu.Contracts.Settings; using Kudu.Core.Deployment; using Kudu.Core.Infrastructure; @@ -348,6 +349,41 @@ public static async Task PerformAutoSwap(string requestId, TraceListener tracer) } } + /// + /// Remove all site workers after cloudbuilt content is uploaded + /// + /// WEBSITE_HOSTNAME + /// WEBSITE_SITE_NAME + /// Thrown when RemoveAllWorkers url is malformed. + /// Thrown when request to RemoveAllWorkers is not OK. + public static async Task RemoveAllWorkersAsync(string websiteHostname, string sitename) + { + // Generate URL encoded auth token + string websiteAuthEncryptionKey = System.Environment.GetEnvironmentVariable(SettingsKeys.AuthEncryptionKey); + DateTime expiry = DateTime.UtcNow.AddMinutes(5); + string authToken = SimpleWebTokenHelper.CreateToken(expiry, websiteAuthEncryptionKey.ToKeyBytes()); + string authTokenEncoded = HttpUtility.UrlEncode(authToken); + + // Generate RemoveAllWorker request URI + string baseUrl = $"http://{websiteHostname}/operations/removeworker/{sitename}/allStandard?token={authTokenEncoded}"; + Uri baseUri = null; + if (!Uri.TryCreate(baseUrl, UriKind.Absolute, out baseUri)) + { + throw new ArgumentException($"Malformed URI is used in RemoveAllWorkers"); + } + Trace(TraceEventType.Information, "Calling RemoveAllWorkers to refresh the function app"); + + // Initiate GET request + using (var client = HttpClientFactory()) + using (var response = await client.GetAsync(baseUri)) + { + response.EnsureSuccessStatusCode(); + Trace(TraceEventType.Information, "RemoveAllWorkers, statusCode = {0}", response.StatusCode); + } + + return; + } + private static void VerifyEnvironments() { if (string.IsNullOrEmpty(HttpHost)) diff --git a/Kudu.Core/Infrastructure/FunctionAppHelper.cs b/Kudu.Core/Infrastructure/FunctionAppHelper.cs index 67ee56ab..7feb33f4 100644 --- a/Kudu.Core/Infrastructure/FunctionAppHelper.cs +++ b/Kudu.Core/Infrastructure/FunctionAppHelper.cs @@ -1,4 +1,5 @@ -using System; +using Kudu.Contracts.Settings; +using System; using System.Linq; namespace Kudu.Core.Infrastructure @@ -10,6 +11,11 @@ public static bool LooksLikeFunctionApp() return !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(Constants.FunctionRunTimeVersion)); } + public static bool HasScmRunFromPackage() + { + return !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(SettingsKeys.ScmRunFromPackage)); + } + public static bool IsCSharpFunctionFromProjectFile(string projectPath) { return VsHelper.IncludesAnyReferencePackage(projectPath, "Microsoft.NET.Sdk.Functions"); diff --git a/Kudu.Core/Kudu.Core.csproj b/Kudu.Core/Kudu.Core.csproj index aaeeaedc..d75282c2 100644 --- a/Kudu.Core/Kudu.Core.csproj +++ b/Kudu.Core/Kudu.Core.csproj @@ -36,6 +36,7 @@ + diff --git a/Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs b/Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs new file mode 100644 index 00000000..5af9fc3e --- /dev/null +++ b/Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; +using Kudu.Core.Deployment.Generator; +using Kudu.Core; +using Kudu.Core.Deployment; +using Kudu.Core.Settings; +using Kudu.Contracts.Settings; +using Microsoft.AspNetCore.Http; + +namespace Kudu.Tests.Core.Deployment.Generator +{ + public class OryxBuilderTests + { + private static IEnvironment mockEnvironment = null; + private static IDeploymentSettingsManager mockDeploymentSettingsManager = null; + private static IBuildPropertyProvider mockBuildPropertyProvider = null; + private static string mockSourcePath = null; + + public OryxBuilderTests() + { + mockEnvironment = new Kudu.Core.Environment( + rootPath: "rootPath", + binPath: "binPath", + repositoryPath: "repositoryPath", + requestId: "requestId", + kuduConsoleFullPath: "kuduConsoleFullPath", + httpContextAccessor: new HttpContextAccessor()); + + mockDeploymentSettingsManager = new DeploymentSettingsManager(XmlSettings.Settings); + } + } +} diff --git a/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs b/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs new file mode 100644 index 00000000..45c2fd8d --- /dev/null +++ b/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Xunit; + +namespace Kudu.Tests.Core.Deployment.Oryx +{ + public class OryxArgumentsFactoryTests + { + [Fact] + public void UseWebAppOryxSettings() + { + } + } +} From e493079ba27462d22c4166405cefaef641c2c2b3 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Mon, 3 Jun 2019 19:32:24 -0700 Subject: [PATCH 2/8] Add unit tests for Oryx Factory --- .../Oryx/FunctionAppOryxArguments.cs | 2 +- .../Oryx/FunctionAppSupportedWorkerRuntime.cs | 6 +- Kudu.Core/Deployment/Oryx/IOryxArguments.cs | 2 +- ...inuxConsumptionFunctionAppOryxArguments.cs | 2 +- .../Deployment/Oryx/OryxArgumentsFactory.cs | 2 +- .../Deployment/Generator/OryxBuilderTests.cs | 34 ----- .../Oryx/OryxArgumentsFactoryTests.cs | 138 +++++++++++++++++- .../ArmAuthenticationHandlerTests.cs | 2 + 8 files changed, 146 insertions(+), 42 deletions(-) delete mode 100644 Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs diff --git a/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs b/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs index 11e53b5f..915db022 100644 --- a/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs +++ b/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs @@ -4,7 +4,7 @@ namespace Kudu.Core.Deployment.Oryx { - class FunctionAppOryxArguments : IOryxArguments + public class FunctionAppOryxArguments : IOryxArguments { public bool RunOryxBuild { get; set; } diff --git a/Kudu.Core/Deployment/Oryx/FunctionAppSupportedWorkerRuntime.cs b/Kudu.Core/Deployment/Oryx/FunctionAppSupportedWorkerRuntime.cs index c45d5c14..9375cfe8 100644 --- a/Kudu.Core/Deployment/Oryx/FunctionAppSupportedWorkerRuntime.cs +++ b/Kudu.Core/Deployment/Oryx/FunctionAppSupportedWorkerRuntime.cs @@ -14,7 +14,11 @@ public class FunctionAppSupportedWorkerRuntime { public static WorkerRuntime ParseWorkerRuntime(string value) { - if (value.StartsWith("NODE", StringComparison.OrdinalIgnoreCase)) + if (string.IsNullOrEmpty(value)) + { + return WorkerRuntime.None; + } + else if (value.StartsWith("NODE", StringComparison.OrdinalIgnoreCase)) { return WorkerRuntime.Node; } diff --git a/Kudu.Core/Deployment/Oryx/IOryxArguments.cs b/Kudu.Core/Deployment/Oryx/IOryxArguments.cs index 32c133f4..70c48826 100644 --- a/Kudu.Core/Deployment/Oryx/IOryxArguments.cs +++ b/Kudu.Core/Deployment/Oryx/IOryxArguments.cs @@ -4,7 +4,7 @@ namespace Kudu.Core.Deployment.Oryx { - interface IOryxArguments + public interface IOryxArguments { bool RunOryxBuild { get; set; } diff --git a/Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs b/Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs index d3badeca..929aca5a 100644 --- a/Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs +++ b/Kudu.Core/Deployment/Oryx/LinuxConsumptionFunctionAppOryxArguments.cs @@ -4,7 +4,7 @@ namespace Kudu.Core.Deployment.Oryx { - class LinuxConsumptionFunctionAppOryxArguments : FunctionAppOryxArguments + public class LinuxConsumptionFunctionAppOryxArguments : FunctionAppOryxArguments { public LinuxConsumptionFunctionAppOryxArguments() : base() { diff --git a/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs b/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs index 6b9ea527..4d4d5cca 100644 --- a/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs +++ b/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs @@ -2,7 +2,7 @@ namespace Kudu.Core.Deployment.Oryx { - class OryxArgumentsFactory + public class OryxArgumentsFactory { public static IOryxArguments CreateOryxArguments() { diff --git a/Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs b/Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs deleted file mode 100644 index 5af9fc3e..00000000 --- a/Kudu.Tests/Core/Deployment/Generator/OryxBuilderTests.cs +++ /dev/null @@ -1,34 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Text; -using Xunit; -using Kudu.Core.Deployment.Generator; -using Kudu.Core; -using Kudu.Core.Deployment; -using Kudu.Core.Settings; -using Kudu.Contracts.Settings; -using Microsoft.AspNetCore.Http; - -namespace Kudu.Tests.Core.Deployment.Generator -{ - public class OryxBuilderTests - { - private static IEnvironment mockEnvironment = null; - private static IDeploymentSettingsManager mockDeploymentSettingsManager = null; - private static IBuildPropertyProvider mockBuildPropertyProvider = null; - private static string mockSourcePath = null; - - public OryxBuilderTests() - { - mockEnvironment = new Kudu.Core.Environment( - rootPath: "rootPath", - binPath: "binPath", - repositoryPath: "repositoryPath", - requestId: "requestId", - kuduConsoleFullPath: "kuduConsoleFullPath", - httpContextAccessor: new HttpContextAccessor()); - - mockDeploymentSettingsManager = new DeploymentSettingsManager(XmlSettings.Settings); - } - } -} diff --git a/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs b/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs index 45c2fd8d..82ca3d31 100644 --- a/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs +++ b/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs @@ -1,6 +1,7 @@ -using System; +using Kudu.Core.Deployment; +using Kudu.Core.Deployment.Oryx; +using System; using System.Collections.Generic; -using System.Text; using Xunit; namespace Kudu.Tests.Core.Deployment.Oryx @@ -8,8 +9,139 @@ namespace Kudu.Tests.Core.Deployment.Oryx public class OryxArgumentsFactoryTests { [Fact] - public void UseWebAppOryxSettings() + public void OryxArgumentShouldBeAppService() { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + Assert.IsType(args); + } + + [Fact] + public void OryxArgumentShouldBeFunctionApp() + { + using (new TestScopedEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "PYTHON")) + using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2")) + { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + Assert.IsType(args); + } + } + + [Fact] + public void OryxArgumentShouldBeLinuxConsumption() + { + using (new TestScopedEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "PYTHON")) + using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2")) + using (new TestScopedEnvironmentVariable("SCM_RUN_FROM_PACKAGE", "http://microsoft.com")) + { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + Assert.IsType(args); + } + } + + [Theory] + [InlineData(false)] + [InlineData(false, "FUNCTIONS_EXTENSION_VERSION", "~2")] + [InlineData(false, "FUNCTIONS_EXTENSION_VERSION", "~2", "SCM_RUN_FROM_PACKAGE", "http://microsoft.com")] + [InlineData(true, "FUNCTIONS_EXTENSION_VERSION", "~2", "FUNCTIONS_WORKER_RUNTIME", "PYTHON")] + [InlineData(true, "FUNCTIONS_EXTENSION_VERSION", "~2", "SCM_RUN_FROM_PACKAGE", "http://microsoft.com", "FUNCTIONS_WORKER_RUNTIME", "PYTHON")] + public void OryxArgumentRunOryxBuild(bool expectedRunOryxBuild, params string[] varargs) + { + IDictionary env = new Dictionary(); + for (int i = 0; i < varargs.Length; i += 2) + { + env.Add(varargs[i], varargs[i + 1]); + } + + using (new TestScopedEnvironmentVariable(env)) + { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + Assert.Equal(expectedRunOryxBuild, args.RunOryxBuild); + } + } + + [Theory] + [InlineData(false)] + [InlineData(false, "FUNCTIONS_EXTENSION_VERSION", "~2")] + [InlineData(true, "FUNCTIONS_EXTENSION_VERSION", "~2", "SCM_RUN_FROM_PACKAGE", "http://microsoft.com")] + [InlineData(false, "FUNCTIONS_EXTENSION_VERSION", "~2", "FUNCTIONS_WORKER_RUNTIME", "PYTHON")] + [InlineData(true, "FUNCTIONS_EXTENSION_VERSION", "~2", "SCM_RUN_FROM_PACKAGE", "http://microsoft.com", "FUNCTIONS_WORKER_RUNTIME", "PYTHON")] + public void OryxArgumentSkipKuduSync(bool expectedSkipKuduSync, params string[] varargs) + { + IDictionary env = new Dictionary(); + for (int i = 0; i < varargs.Length; i += 2) + { + env.Add(varargs[i], varargs[i + 1]); + } + + using (new TestScopedEnvironmentVariable(env)) + { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + Assert.Equal(expectedSkipKuduSync, args.SkipKuduSync); + } + } + + [Fact] + public void BuildCommandForAppService() + { + DeploymentContext deploymentContext = new DeploymentContext() + { + OutputPath = "outputpath" + }; + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + string command = args.GenerateOryxBuildCommand(deploymentContext); + Assert.Equal(@"oryx build outputpath -o outputpath", command); + } + + [Fact] + public void BuildCommandForFunctionApp() + { + DeploymentContext deploymentContext = new DeploymentContext() + { + OutputPath = "outputpath", + BuildTempPath = "buildtemppath" + }; + + using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2")) + { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + string command = args.GenerateOryxBuildCommand(deploymentContext); + Assert.Equal(@"oryx build outputpath -o outputpath -i buildtemppath", command); + } + } + + [Fact] + public void BuildCommandForLinuxConsumptionFunctionApp() + { + DeploymentContext deploymentContext = new DeploymentContext() + { + RepositoryPath = "repositorypath" + }; + + using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2")) + using (new TestScopedEnvironmentVariable("SCM_RUN_FROM_PACKAGE", "http://microsoft.com")) + { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + string command = args.GenerateOryxBuildCommand(deploymentContext); + Assert.Equal(@"oryx build repositorypath -o repositorypath", command); + } + } + + [Fact] + public void BuildCommandForPythonFunctionApp() + { + DeploymentContext deploymentContext = new DeploymentContext() + { + OutputPath = "outputpath", + BuildTempPath = "buildtemppath" + }; + + using (new TestScopedEnvironmentVariable("FUNCTIONS_EXTENSION_VERSION", "~2")) + using (new TestScopedEnvironmentVariable("FUNCTIONS_WORKER_RUNTIME", "python")) + { + IOryxArguments args = OryxArgumentsFactory.CreateOryxArguments(); + string command = args.GenerateOryxBuildCommand(deploymentContext); + Assert.Equal(@"oryx build outputpath -o outputpath -l python --language-version 3.6 -i buildtemppath -p packagedir=.python_packages\lib\python3.6\site-packages", command); + } } } } diff --git a/Kudu.Tests/Services/Infrastructure/Authentication/ArmAuthenticationHandlerTests.cs b/Kudu.Tests/Services/Infrastructure/Authentication/ArmAuthenticationHandlerTests.cs index 598d82de..e3c0655a 100644 --- a/Kudu.Tests/Services/Infrastructure/Authentication/ArmAuthenticationHandlerTests.cs +++ b/Kudu.Tests/Services/Infrastructure/Authentication/ArmAuthenticationHandlerTests.cs @@ -13,6 +13,8 @@ namespace Kudu.Tests.Services.Infrastructure.Authentication { + // The following 'Collection' attribute is used for disabling parallel testing + // Some of the tests require changing the system environment variables, which is not thread safe. [Collection("MockedEnvironmentVariablesCollection")] public class ArmAuthenticationHandlerTests { From dc39e6692b11b4a412a9e2a7fe1d15bf1cf14f2f Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Wed, 5 Jun 2019 10:42:05 -0700 Subject: [PATCH 3/8] Make SetupLinuxConsumptionFunctionAppDeployment(context).Wait() synchronous --- Kudu.Core/Deployment/Generator/OryxBuilder.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Kudu.Core/Deployment/Generator/OryxBuilder.cs b/Kudu.Core/Deployment/Generator/OryxBuilder.cs index 1617574e..16c0395d 100644 --- a/Kudu.Core/Deployment/Generator/OryxBuilder.cs +++ b/Kudu.Core/Deployment/Generator/OryxBuilder.cs @@ -20,7 +20,7 @@ public OryxBuilder(IEnvironment environment, IDeploymentSettingsManager settings { } - public override async Task Build(DeploymentContext context) + public override Task Build(DeploymentContext context) { FileLogHelper.Log("In oryx build..."); @@ -70,10 +70,10 @@ public override async Task Build(DeploymentContext context) // Detect if package upload is necessary for server side build if (FunctionAppHelper.HasScmRunFromPackage() && FunctionAppHelper.LooksLikeFunctionApp()) { - await SetupLinuxConsumptionFunctionAppDeployment(context); + SetupLinuxConsumptionFunctionAppDeployment(context).Wait(); } - return; + return Task.CompletedTask; } private static void PreOryxBuild(DeploymentContext context) From a80e651ca34c4af0cb246c5c145e8f579bb358a0 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Wed, 5 Jun 2019 11:17:11 -0700 Subject: [PATCH 4/8] Remove assignment auth --- .../LinuxConsumptionInstanceAdminController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs b/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs index 4041ebf7..a249fad0 100644 --- a/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs +++ b/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs @@ -37,7 +37,7 @@ public LinuxConsumptionInstanceAdminController(ILinuxConsumptionInstanceManager /// /// Expect 200 when current service is up and running [HttpGet] - [Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] + //[Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] public IActionResult Info() { return Ok(_instanceManager.GetInstanceInfo()); @@ -50,7 +50,7 @@ public IActionResult Info() /// Encrypted content which contains HostAssignmentContext /// Expect 202 when receives the first call, otherwise, returns 409 [HttpPost] - [Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] + //[Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] public async Task AssignAsync([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext) { var containerKey = System.Environment.GetEnvironmentVariable(SettingsKeys.ContainerEncryptionKey); From 3689a9e2a28db8d6ad5eccfbc2fa62c113233aa0 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Wed, 5 Jun 2019 14:25:02 -0700 Subject: [PATCH 5/8] Move ScmRunFromPackage from AppSettings to Environment Variables --- Common/Constants.cs | 1 + Kudu.Contracts/Settings/SettingsKeys.cs | 1 - Kudu.Core/Deployment/Generator/OryxBuilder.cs | 2 +- Kudu.Core/Infrastructure/FunctionAppHelper.cs | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Common/Constants.cs b/Common/Constants.cs index d5680466..1c3154f2 100644 --- a/Common/Constants.cs +++ b/Common/Constants.cs @@ -118,6 +118,7 @@ public static TimeSpan MaxAllowedExecutionTime public const string FunctionsPortal = "FunctionsPortal"; public const string FunctionKeyNewFormat = "~0.7"; public const string FunctionRunTimeVersion = "FUNCTIONS_EXTENSION_VERSION"; + public const string ScmRunFromPackage = "SCM_RUN_FROM_PACKAGE"; public const string WebSiteSku = "WEBSITE_SKU"; public const string WebSiteElasticScaleEnabled = "WEBSITE_ELASTIC_SCALING_ENABLED"; public const string DynamicSku = "Dynamic"; diff --git a/Kudu.Contracts/Settings/SettingsKeys.cs b/Kudu.Contracts/Settings/SettingsKeys.cs index 3f2b4bcf..9ddbf698 100644 --- a/Kudu.Contracts/Settings/SettingsKeys.cs +++ b/Kudu.Contracts/Settings/SettingsKeys.cs @@ -49,7 +49,6 @@ public static class SettingsKeys // Antares container specific settings public const string PlaceholderMode = "WEBSITE_PLACEHOLDER_MODE"; public const string ContainerReady = "WEBSITE_CONTAINER_READY"; - public const string ScmRunFromPackage = "SCM_RUN_FROM_PACKAGE"; public const string WebsiteHostname = "WEBSITE_HOSTNAME"; public const string AuthEncryptionKey = "WEBSITE_AUTH_ENCRYPTION_KEY"; public const string ContainerEncryptionKey = "CONTAINER_ENCRYPTION_KEY"; diff --git a/Kudu.Core/Deployment/Generator/OryxBuilder.cs b/Kudu.Core/Deployment/Generator/OryxBuilder.cs index 16c0395d..ba9beb3c 100644 --- a/Kudu.Core/Deployment/Generator/OryxBuilder.cs +++ b/Kudu.Core/Deployment/Generator/OryxBuilder.cs @@ -135,7 +135,7 @@ private string PackageArtifactFromFolder(DeploymentContext context, string srcDi /// private async Task SetupLinuxConsumptionFunctionAppDeployment(DeploymentContext context) { - string sas = DeploymentSettings.GetValue(SettingsKeys.ScmRunFromPackage); + string sas = System.Environment.GetEnvironmentVariable(Constants.ScmRunFromPackage); string builtFolder = context.RepositoryPath; string packageFolder = Environment.DeploymentsPath; string packageFileName = $"{DateTime.UtcNow.ToString("yyyyMMddHHmmss")}.zip"; diff --git a/Kudu.Core/Infrastructure/FunctionAppHelper.cs b/Kudu.Core/Infrastructure/FunctionAppHelper.cs index 7feb33f4..3a4fe65e 100644 --- a/Kudu.Core/Infrastructure/FunctionAppHelper.cs +++ b/Kudu.Core/Infrastructure/FunctionAppHelper.cs @@ -13,7 +13,7 @@ public static bool LooksLikeFunctionApp() public static bool HasScmRunFromPackage() { - return !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(SettingsKeys.ScmRunFromPackage)); + return !string.IsNullOrEmpty(System.Environment.GetEnvironmentVariable(Constants.ScmRunFromPackage)); } public static bool IsCSharpFunctionFromProjectFile(string projectPath) From 95a9c733bf83711df021f0ef6678d2a1fe3b1064 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Mon, 10 Jun 2019 09:08:21 -0700 Subject: [PATCH 6/8] Null check for pushdeploymentcontroller.cs --- Kudu.Services/Deployment/PushDeploymentController.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Kudu.Services/Deployment/PushDeploymentController.cs b/Kudu.Services/Deployment/PushDeploymentController.cs index d503017c..82a916ca 100644 --- a/Kudu.Services/Deployment/PushDeploymentController.cs +++ b/Kudu.Services/Deployment/PushDeploymentController.cs @@ -214,8 +214,8 @@ private async Task PushDeployAsync(ZipDeploymentInfo deploymentIn { using (_tracer.Step("Writing zip file to {0}", zipFilePath)) { - if (context.Request.ContentType.Contains("multipart/form-data", - StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(context.Request.ContentType) && + context.Request.ContentType.Contains("multipart/form-data", StringComparison.OrdinalIgnoreCase)) { FormValueProvider formModel; using (_tracer.Step("Writing zip file to {0}", zipFilePath)) From 2ab259f7812360c6099c30a05dc80cfbcfce6801 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Thu, 13 Jun 2019 11:36:04 -0700 Subject: [PATCH 7/8] Reenable Authorize --- .../LinuxConsumptionInstanceAdminController.cs | 4 ++-- Kudu.Tests/Core/Helpers/SimpleWebTokenTests.cs | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs b/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs index a249fad0..4041ebf7 100644 --- a/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs +++ b/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionInstanceAdminController.cs @@ -37,7 +37,7 @@ public LinuxConsumptionInstanceAdminController(ILinuxConsumptionInstanceManager /// /// Expect 200 when current service is up and running [HttpGet] - //[Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] + [Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] public IActionResult Info() { return Ok(_instanceManager.GetInstanceInfo()); @@ -50,7 +50,7 @@ public IActionResult Info() /// Encrypted content which contains HostAssignmentContext /// Expect 202 when receives the first call, otherwise, returns 409 [HttpPost] - //[Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] + [Authorize(Policy = AuthPolicyNames.AdminAuthLevel)] public async Task AssignAsync([FromBody] EncryptedHostAssignmentContext encryptedAssignmentContext) { var containerKey = System.Environment.GetEnvironmentVariable(SettingsKeys.ContainerEncryptionKey); diff --git a/Kudu.Tests/Core/Helpers/SimpleWebTokenTests.cs b/Kudu.Tests/Core/Helpers/SimpleWebTokenTests.cs index 3b9520dc..4a117e03 100644 --- a/Kudu.Tests/Core/Helpers/SimpleWebTokenTests.cs +++ b/Kudu.Tests/Core/Helpers/SimpleWebTokenTests.cs @@ -6,6 +6,8 @@ namespace Kudu.Tests.Core.Helpers { + // The following 'Collection' attribute is used for disabling parallel testing + // Some of the tests require changing the system environment variables, which is not thread safe. [Collection("MockedEnvironmentVariablesCollection")] public class SimpleWebTokenTests { From e0697c559a051751791ac09cbaa9cb2f7d6bcc22 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Thu, 13 Jun 2019 12:09:50 -0700 Subject: [PATCH 8/8] Address unit tests --- .../LinuxConsumptionRouteMiddleware.cs | 18 +++++++++--------- .../LinuxConsumptionRouteMiddlewareTests.cs | 5 +++-- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddleware.cs b/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddleware.cs index 1099fef9..432a15d4 100644 --- a/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddleware.cs +++ b/Kudu.Services/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddleware.cs @@ -20,7 +20,6 @@ public class LinuxConsumptionRouteMiddleware { private static readonly HashSet Whitelist = new HashSet { - HomePageRoute, "/api/zipdeploy", "/admin/instance", "/deployments", @@ -66,7 +65,8 @@ public async Task Invoke(HttpContext context, IAuthorizationService authorizatio } else { - context.Request.Host = new HostString(SanitizeScmUrl(context.Request.Headers[HostHeader][0])); + context.Request.Host = new HostString(SanitizeScmUrl( + context.Request.Headers[HostHeader].FirstOrDefault())); } if (context.Request.Headers.TryGetValue(ForwardedProtocolHeader, out value)) @@ -74,17 +74,17 @@ public async Task Invoke(HttpContext context, IAuthorizationService authorizatio context.Request.Scheme = value; } - // Step 2: check if the request endpoint is enabled in Linux Consumption - if (!IsRouteWhitelisted(context.Request.Path)) + // Step 2: check if it is homepage route, always return 200 + if (IsHomePageRoute(context.Request.Path)) { - context.Response.StatusCode = 404; + context.Response.StatusCode = 200; return; } - // Step 3: check if it is homepage route, always return 200 - if (IsHomePageRoute(context.Request.Path)) + // Step 3: check if the request endpoint is enabled in Linux Consumption + if (!IsRouteWhitelisted(context.Request.Path)) { - context.Response.StatusCode = 200; + context.Response.StatusCode = 404; return; } @@ -116,7 +116,7 @@ private bool IsRouteWhitelisted(PathString routePath) private bool IsHomePageRoute(PathString routePath) { - return routePath.ToString() == "/"; + return routePath.ToString() == HomePageRoute; } private static string SanitizeScmUrl(string malformedUrl) diff --git a/Kudu.Tests/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddlewareTests.cs b/Kudu.Tests/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddlewareTests.cs index a4f07766..fec7c730 100644 --- a/Kudu.Tests/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddlewareTests.cs +++ b/Kudu.Tests/LinuxConsumptionInstanceAdmin/LinuxConsumptionRouteMiddlewareTests.cs @@ -30,6 +30,7 @@ public LinuxConsumptionRouteMiddlewareTests() _containerEncryptionKey = TestHelpers.GenerateKeyBytes(); _environmentVariables = new Dictionary { + { Constants.ContainerName, "linux_consumption_container_name" }, { SettingsKeys.AuthEncryptionKey, TestHelpers.GenerateKeyHexString(_websiteAuthEncryptionKey) }, { SettingsKeys.ContainerEncryptionKey, TestHelpers.GenerateKeyHexString(_containerEncryptionKey) } }; @@ -94,14 +95,14 @@ public void UnwhitelistedRouteNotFound() } [Fact] - public void HomepageRouteNotFound() + public void HomepageRouteFound() { using (new TestScopedEnvironmentVariable(_environmentVariables)) { HttpContext httpContext = GenerateHttpContext(DateTime.UtcNow.AddDays(1)); httpContext.Request.Path = "/"; _middleware.Invoke(httpContext).Wait(); - Assert.Equal(404, httpContext.Response.StatusCode); + Assert.Equal(200, httpContext.Response.StatusCode); } }