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 ec002bfa..9ddbf698 100644
--- a/Kudu.Contracts/Settings/SettingsKeys.cs
+++ b/Kudu.Contracts/Settings/SettingsKeys.cs
@@ -49,6 +49,7 @@ 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 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..ba9beb3c 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
{
@@ -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,6 +67,12 @@ public override Task Build(DeploymentContext context)
}
}
+ // Detect if package upload is necessary for server side build
+ if (FunctionAppHelper.HasScmRunFromPackage() && FunctionAppHelper.LooksLikeFunctionApp())
+ {
+ SetupLinuxConsumptionFunctionAppDeployment(context).Wait();
+ }
+
return Task.CompletedTask;
}
@@ -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 = System.Environment.GetEnvironmentVariable(Constants.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..915db022 100644
--- a/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs
+++ b/Kudu.Core/Deployment/Oryx/FunctionAppOryxArguments.cs
@@ -4,13 +4,13 @@
namespace Kudu.Core.Deployment.Oryx
{
- class FunctionAppOryxArguments : IOryxArguments
+ public class FunctionAppOryxArguments : IOryxArguments
{
public bool RunOryxBuild { get; set; }
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/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
new file mode 100644
index 00000000..929aca5a
--- /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
+{
+ public 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..4d4d5cca 100644
--- a/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs
+++ b/Kudu.Core/Deployment/Oryx/OryxArgumentsFactory.cs
@@ -2,13 +2,18 @@
namespace Kudu.Core.Deployment.Oryx
{
- class OryxArgumentsFactory
+ public class OryxArgumentsFactory
{
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..3a4fe65e 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(Constants.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.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))
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/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs b/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs
new file mode 100644
index 00000000..82ca3d31
--- /dev/null
+++ b/Kudu.Tests/Core/Deployment/Oryx/OryxArgumentsFactoryTests.cs
@@ -0,0 +1,147 @@
+using Kudu.Core.Deployment;
+using Kudu.Core.Deployment.Oryx;
+using System;
+using System.Collections.Generic;
+using Xunit;
+
+namespace Kudu.Tests.Core.Deployment.Oryx
+{
+ public class OryxArgumentsFactoryTests
+ {
+ [Fact]
+ 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/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
{
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);
}
}
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
{