diff --git a/Kudu.Contracts/Deployment/BuildMetadata.cs b/Kudu.Contracts/Deployment/BuildMetadata.cs new file mode 100644 index 00000000..32fedb0e --- /dev/null +++ b/Kudu.Contracts/Deployment/BuildMetadata.cs @@ -0,0 +1,19 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Kudu.Contracts.Deployment +{ + public class BuildMetadata + { + [JsonProperty(PropertyName = "appName")] + public string AppName; + + [JsonProperty(PropertyName = "buildVersion")] + public string BuildVersion; + + [JsonProperty(PropertyName = "appSubPath")] + public string AppSubPath; + } +} diff --git a/Kudu.Contracts/Deployment/DeploymentInfoBase.cs b/Kudu.Contracts/Deployment/DeploymentInfoBase.cs index fc8b5730..f64c02ec 100644 --- a/Kudu.Contracts/Deployment/DeploymentInfoBase.cs +++ b/Kudu.Contracts/Deployment/DeploymentInfoBase.cs @@ -72,5 +72,8 @@ public bool IsValid() // won't update until after a process restart. Therefore, we copy the needed // files into a separate folders and run sync triggers from there. public string SyncFunctionsTriggersPath { get; set; } = null; + + // Used to set Publish Endpoint context + public bool ShouldBuildArtifact { get; set; } } } \ No newline at end of file diff --git a/Kudu.Core/Deployment/DeploymentManager.cs b/Kudu.Core/Deployment/DeploymentManager.cs index 49a178ad..d0046174 100644 --- a/Kudu.Core/Deployment/DeploymentManager.cs +++ b/Kudu.Core/Deployment/DeploymentManager.cs @@ -9,7 +9,6 @@ using Kudu.Contracts.Infrastructure; using Kudu.Contracts.Settings; using Kudu.Contracts.Tracing; -using Kudu.Core.Functions; using Kudu.Core.Helpers; using Kudu.Core.Hooks; using Kudu.Core.Infrastructure; @@ -286,7 +285,7 @@ public async Task DeployAsync( } string appName = _environment.SiteRootPath.Replace("/home/apps/", "").Split("/")[0]; - DockerContainerRestartTrigger.RequestContainerRestart(_environment, RestartTriggerReason, deploymentInfo.RepositoryUrl); + DockerContainerRestartTrigger.RequestContainerRestart(_environment, RestartTriggerReason, deploymentInfo.RepositoryUrl, deploymentInfo.TargetPath); logger.Log($"Deployment Pod Rollout Started! Use kubectl watch deplotment {appName} to monitor the rollout status"); } } @@ -645,7 +644,7 @@ private async Task Build( { using (tracer.Step("Determining deployment builder")) { - builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, repository); + builder = _builderFactory.CreateBuilder(tracer, innerLogger, perDeploymentSettings, repository, deploymentInfo); deploymentAnalytics.ProjectType = builder.ProjectType; tracer.Trace("Builder is {0}", builder.GetType().Name); } diff --git a/Kudu.Core/Deployment/Generator/OryxBuilder.cs b/Kudu.Core/Deployment/Generator/OryxBuilder.cs index 1204502d..ce3fc9a6 100644 --- a/Kudu.Core/Deployment/Generator/OryxBuilder.cs +++ b/Kudu.Core/Deployment/Generator/OryxBuilder.cs @@ -56,11 +56,15 @@ public override Task Build(DeploymentContext context) { PreOryxBuild(context); + args.Flags = BuildOptimizationsFlags.UseExpressBuild; + string buildCommand = args.GenerateOryxBuildCommand(context, environment); RunCommand(context, buildCommand, false, "Running oryx build..."); // // Run express build setups if needed + // + if (args.Flags == BuildOptimizationsFlags.UseExpressBuild) { if (FunctionAppHelper.LooksLikeFunctionApp()) @@ -73,6 +77,10 @@ public override Task Build(DeploymentContext context) appServiceExpressBuilder.SetupExpressBuilderArtifacts(context.OutputPath, context, args); } } + else + { + Console.WriteLine("No Express :("); + } } return Task.CompletedTask; } diff --git a/Kudu.Core/Deployment/Generator/SiteBuilderFactory.cs b/Kudu.Core/Deployment/Generator/SiteBuilderFactory.cs index 41ea76d2..1528655d 100644 --- a/Kudu.Core/Deployment/Generator/SiteBuilderFactory.cs +++ b/Kudu.Core/Deployment/Generator/SiteBuilderFactory.cs @@ -40,9 +40,14 @@ private IEnvironment GetEnvironment(IHttpContextAccessor accessor, IEnvironment return _environment; } - public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository repository) + public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository repository, DeploymentInfoBase deploymentInfo) { + string repositoryRoot = _environment.RepositoryPath; + if (!string.IsNullOrEmpty(repository.RepositoryPath)) + { + repositoryRoot = repository.RepositoryPath; + } // Use the cached vs projects file finder for: a. better performance, b. ignoring solutions/projects under node_modules var fileFinder = new CachedVsProjectsFileFinder(repository); @@ -75,14 +80,14 @@ public ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSet return new RunFromZipSiteBuilder(); } - if (!settings.DoBuildDuringDeployment() && repository.RepositoryType != RepositoryType.Git) + if (!deploymentInfo.ShouldBuildArtifact && !settings.DoBuildDuringDeployment() && repository.RepositoryType != RepositoryType.Git) { var projectPath = !String.IsNullOrEmpty(targetProjectPath) ? targetProjectPath : repositoryRoot; return new BasicBuilder(_environment, settings, _propertyProvider, repositoryRoot, projectPath); } string enableOryxBuild = System.Environment.GetEnvironmentVariable("ENABLE_ORYX_BUILD"); - if (!string.IsNullOrEmpty(enableOryxBuild)) + if (!string.IsNullOrEmpty(enableOryxBuild) && (deploymentInfo.ShouldBuildArtifact || settings.DoBuildDuringDeployment())) { if (StringUtils.IsTrueLike(enableOryxBuild)) { diff --git a/Kudu.Core/Deployment/ISiteBuilderFactory.cs b/Kudu.Core/Deployment/ISiteBuilderFactory.cs index 3c421819..609849c1 100644 --- a/Kudu.Core/Deployment/ISiteBuilderFactory.cs +++ b/Kudu.Core/Deployment/ISiteBuilderFactory.cs @@ -6,6 +6,6 @@ namespace Kudu.Core.Deployment { public interface ISiteBuilderFactory { - ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository fileFinder); + ISiteBuilder CreateBuilder(ITracer tracer, ILogger logger, IDeploymentSettingsManager settings, IRepository fileFinder, DeploymentInfoBase deploymentInfo); } } diff --git a/Kudu.Core/Deployment/Oryx/AppServiceOryxArguments.cs b/Kudu.Core/Deployment/Oryx/AppServiceOryxArguments.cs index e55ae34d..616b26cd 100644 --- a/Kudu.Core/Deployment/Oryx/AppServiceOryxArguments.cs +++ b/Kudu.Core/Deployment/Oryx/AppServiceOryxArguments.cs @@ -34,7 +34,6 @@ public AppServiceOryxArguments(IEnvironment environment) if (K8SEDeploymentHelper.IsK8SEEnvironment()) { - Console.WriteLine("Oryx App Name : " + environment.K8SEAppName); this.AppName = environment.K8SEAppName; // K8SE TODO: Inject Environment @@ -184,12 +183,13 @@ public string GenerateOryxBuildCommand(DeploymentContext context, IEnvironment e { // 10-LTS, 12-LTS should use versions 10, 12 etc // Oryx Builder uses lts for major versions - Version = Version.Replace("LTS", "").Replace("-", ""); + Version = Version.Replace("LTS", "").Replace("lts", "").Replace("-", ""); if (string.IsNullOrEmpty(Version)) { // Current LTS Version = "10"; } + OryxArgumentsHelper.AddLanguageVersion(args, Version); } break; case Framework.Python: diff --git a/Kudu.Core/Infrastructure/DockerContainerRestartTrigger.cs b/Kudu.Core/Infrastructure/DockerContainerRestartTrigger.cs index 1e40d2ae..cc80f643 100644 --- a/Kudu.Core/Infrastructure/DockerContainerRestartTrigger.cs +++ b/Kudu.Core/Infrastructure/DockerContainerRestartTrigger.cs @@ -4,9 +4,11 @@ using System.Globalization; using System.IO; using System.Linq; +using Kudu.Contracts.Deployment; using Kudu.Core.Functions; using Kudu.Core.Helpers; using Kudu.Core.K8SE; +using Newtonsoft.Json; namespace Kudu.Core.Infrastructure { @@ -25,22 +27,28 @@ public static class DockerContainerRestartTrigger "The last modification Kudu made to this file was at {0}, for the following reason: {1}.", System.Environment.NewLine); - public static void RequestContainerRestart(IEnvironment environment, string reason, string repositoryUrl = null) + public static void RequestContainerRestart(IEnvironment environment, string reason, string repositoryUrl = null, string appSubPath = "") { if (K8SEDeploymentHelper.IsK8SEEnvironment()) { string appName = environment.SiteRootPath.Replace("/home/apps/", "").Split("/")[0]; string buildNumber = environment.CurrId; var functionTriggers = FunctionTriggerProvider.GetFunctionTriggers>("keda", repositoryUrl); + var buildMetadata = new BuildMetadata() + { + AppName = appName, + BuildVersion = buildNumber, + AppSubPath = appSubPath + }; //Only for function apps functionTriggers will be non-null/non-empty if (functionTriggers?.Any() == true) { - K8SEDeploymentHelper.UpdateFunctionAppTriggers(appName, functionTriggers, $"{buildNumber}|{appName}"); + K8SEDeploymentHelper.UpdateFunctionAppTriggers(appName, functionTriggers, buildMetadata); } else { - K8SEDeploymentHelper.UpdateBuildNumber(appName, buildNumber); + K8SEDeploymentHelper.UpdateBuildNumber(appName, buildMetadata); } return; diff --git a/Kudu.Core/K8SE/K8SEDeploymentHelper.cs b/Kudu.Core/K8SE/K8SEDeploymentHelper.cs index 6954c6f3..73a78c63 100644 --- a/Kudu.Core/K8SE/K8SEDeploymentHelper.cs +++ b/Kudu.Core/K8SE/K8SEDeploymentHelper.cs @@ -1,4 +1,5 @@ -using Kudu.Contracts.Tracing; +using Kudu.Contracts.Deployment; +using Kudu.Contracts.Tracing; using Kudu.Core.Deployment; using Kudu.Core.Functions; using Microsoft.AspNetCore.Http; @@ -8,6 +9,7 @@ using System.Diagnostics; using System.Linq; using System.Text; +using System.Web; namespace Kudu.Core.K8SE { @@ -44,25 +46,43 @@ public static string GetLinuxFxVersion(string appName) /// /// /// - public static void UpdateBuildNumber(string appName, string buildNumber) + public static void UpdateBuildNumber(string appName, BuildMetadata buildMetadata) { + var buildPatchJson = $"\"{HttpUtility.JavaScriptStringEncode(JsonConvert.SerializeObject(buildMetadata)).Replace("\\","\\\\")}\""; + var cmd = new StringBuilder(); BuildCtlArgumentsHelper.AddBuildCtlCommand(cmd, "update"); BuildCtlArgumentsHelper.AddAppNameArgument(cmd, appName); - BuildCtlArgumentsHelper.AddAppPropertyArgument(cmd, "buildVersion"); - BuildCtlArgumentsHelper.AddAppPropertyValueArgument(cmd, buildNumber); + BuildCtlArgumentsHelper.AddAppPropertyArgument(cmd, "buildMetadata"); + BuildCtlArgumentsHelper.AddAppPropertyValueArgument(cmd, buildPatchJson); RunBuildCtlCommand(cmd.ToString(), "Updating build version..."); } + /// + /// Updates the Image Tag of the K8SE custom container app + /// + /// + /// container image tag of the format registry/: + /// + public static void UpdateImageTag(string appName, string imageTag) + { + var cmd = new StringBuilder(); + BuildCtlArgumentsHelper.AddBuildCtlCommand(cmd, "update"); + BuildCtlArgumentsHelper.AddAppNameArgument(cmd, appName); + BuildCtlArgumentsHelper.AddAppPropertyArgument(cmd, "appImage"); + BuildCtlArgumentsHelper.AddAppPropertyValueArgument(cmd, imageTag); + RunBuildCtlCommand(cmd.ToString(), "Updating image tag..."); + } + /// /// Updates the triggers for the function apps /// /// The app name to update /// The IEnumerable /// Build number to update - public static void UpdateFunctionAppTriggers(string appName, IEnumerable functionTriggers, string buildNumber) + public static void UpdateFunctionAppTriggers(string appName, IEnumerable functionTriggers, BuildMetadata buildMetadata) { - var functionAppPatchJson = GetFunctionAppPatchJson(functionTriggers, buildNumber); + var functionAppPatchJson = GetFunctionAppPatchJson(functionTriggers, buildMetadata); if (string.IsNullOrEmpty(functionAppPatchJson)) { return; @@ -118,19 +138,24 @@ public static string GetAppName(HttpContext context) // K8SE TODO: move this to resource map throw new InvalidOperationException("Couldn't recognize AppName"); } - - Console.WriteLine("AppName :::::::: " + appName); - return appName; } - private static string GetFunctionAppPatchJson(IEnumerable functionTriggers, string buildNumber) + private static string GetFunctionAppPatchJson(IEnumerable functionTriggers, BuildMetadata buildMetadata) { if (functionTriggers == null || !functionTriggers.Any()) { return null; } + if (buildMetadata == null ) + { + return null; + } + + var buildPatchJson = JsonConvert.SerializeObject(buildMetadata); + var buildmetaJsonString = JsonConvert.ToString(buildPatchJson); + var patchAppJson = new PatchAppJson { PatchSpec = new PatchSpec @@ -143,7 +168,7 @@ private static string GetFunctionAppPatchJson(IEnumerable function { PackageRef = new PackageReference { - BuildVersion = buildNumber + BuildVersion = buildmetaJsonString } } } diff --git a/Kudu.Services.Web/Startup.cs b/Kudu.Services.Web/Startup.cs index 413a6f0a..918839f5 100644 --- a/Kudu.Services.Web/Startup.cs +++ b/Kudu.Services.Web/Startup.cs @@ -84,8 +84,8 @@ public void ConfigureServices(IServiceCollection services) services.Configure(options => { options.MultipartBodyLengthLimit = 52428800; - options.ValueCountLimit = 500000; - options.KeyLengthLimit = 500000; + options.ValueCountLimit = 1000000; + options.KeyLengthLimit = 1000000; }); services.AddRouteAnalyzer(); @@ -518,6 +518,8 @@ public void Configure(IApplicationBuilder app, new {controller = "Deployment", action = "GetLogEntry"}); routes.MapHttpRouteDual("one-deployment-log-details", "deployments/{id}/log/{logId}", new {controller = "Deployment", action = "GetLogEntryDetails"}); + routes.MapHttpRouteDual("update-container-tag", "app/update", + new { controller = "Deployment", action = "UpdateContainerTag" }); // Deployment script routes.MapRoute("get-deployment-script", "api/deploymentscript", diff --git a/Kudu.Services/Deployment/DeploymentController.cs b/Kudu.Services/Deployment/DeploymentController.cs index a55a957e..dacc3870 100644 --- a/Kudu.Services/Deployment/DeploymentController.cs +++ b/Kudu.Services/Deployment/DeploymentController.cs @@ -30,6 +30,7 @@ using Kudu.Services.Zip; using System.IO.Compression; using Kudu.Core.K8SE; +using Org.BouncyCastle.Ocsp; namespace Kudu.Services.Deployment { @@ -139,6 +140,11 @@ public IActionResult IsDeploying() [HttpPut] public async Task Deploy(string id = null) { + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } + JObject jsonContent = GetJsonContent(); // Just block here to read the json payload from the body @@ -402,6 +408,11 @@ public IActionResult GetDeployResults() _tracer.Trace("Current Etag: {0}, Cached Etag: {1}", currentEtag, cachedDeployments.Etag); } + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } + // Avoid Caching when on K8 if (EtagEquals(Request, currentEtag) && !K8SEDeploymentHelper.IsK8SEEnvironment()) { @@ -442,6 +453,11 @@ public IActionResult GetDeployResults() [HttpGet] public IActionResult GetLogEntry(string id) { + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } + using (_tracer.Step("DeploymentService.GetLogEntry")) { try @@ -472,6 +488,11 @@ public IActionResult GetLogEntry(string id) [HttpGet] public IActionResult GetLogEntryDetails(string id, string logId) { + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } + using (_tracer.Step("DeploymentService.GetLogEntryDetails")) { try @@ -499,6 +520,11 @@ public IActionResult GetLogEntryDetails(string id, string logId) [HttpGet] public IActionResult GetResult(string id) { + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } + using (_tracer.Step("DeploymentService.GetResult")) { DeployResult pending; @@ -558,6 +584,25 @@ private bool IsLatestPendingDeployment(ref string id, out DeployResult pending) return false; } + + /// + /// Updates Image tag of a custom container app when running in the K8SE Environment + /// + /// + [HttpPost] + public IActionResult UpdateContainerTag() + { + if(!K8SEDeploymentHelper.IsK8SEEnvironment() + || !Request.Headers.ContainsKey("LINUXFXVERSION") + || !Request.Headers["LINUXFXVERSION"].First().StartsWith("DOCKER|")) + { + return StatusCode(Microsoft.AspNetCore.Http.StatusCodes.Status405MethodNotAllowed); + } + string linuxFxVersion = Request.Headers["LINUXFXVERSION"].First().Replace("DOCKER|", ""); + K8SEDeploymentHelper.UpdateImageTag(K8SEDeploymentHelper.GetAppName(Request.HttpContext), linuxFxVersion); + return Ok(); + } + /// /// Gets a zip with containing deploy.cmd and .deployment /// diff --git a/Kudu.Services/Deployment/PushDeploymentController.cs b/Kudu.Services/Deployment/PushDeploymentController.cs index 9b3bf8e1..c461584c 100644 --- a/Kudu.Services/Deployment/PushDeploymentController.cs +++ b/Kudu.Services/Deployment/PushDeploymentController.cs @@ -79,8 +79,20 @@ public async Task ZipPushDeploy( [FromQuery] string deployer = DefaultDeployer, [FromQuery] string message = DefaultMessage) { + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } using (_tracer.Step("ZipPushDeploy")) { + var buildHeader = false; + + if(HttpContext.Request.Headers.ContainsKey("SCM_DO_BUILD_DURING_DEPLOYMENT")) + { + string header = HttpContext.Request.Headers["SCM_DO_BUILD_DURING_DEPLOYMENT"]; + buildHeader = !String.IsNullOrEmpty(header) && (header == "1" || header.Equals(Boolean.TrueString, StringComparison.OrdinalIgnoreCase)); + } + var deploymentInfo = new ZipDeploymentInfo(_environment, _traceFactory) { AllowDeploymentWhileScmDisabled = true, @@ -97,7 +109,8 @@ public async Task ZipPushDeploy( Author = author, AuthorEmail = authorEmail, Message = message, - ZipURL = null + ZipURL = null, + ShouldBuildArtifact = buildHeader }; if (_settings.RunFromLocalZip()) @@ -125,6 +138,11 @@ public async Task ZipPushDeployViaUrl( [FromQuery] string deployer = DefaultDeployer, [FromQuery] string message = DefaultMessage) { + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } + using (_tracer.Step("ZipPushDeployViaUrl")) { string zipUrl = GetZipURLFromJSON(requestJson); @@ -162,6 +180,11 @@ public async Task WarPushDeploy( [FromQuery] string deployer = DefaultDeployer, [FromQuery] string message = DefaultMessage) { + if (K8SEDeploymentHelper.IsK8SEEnvironment()) + { + Request.Scheme = "https"; + } + using (_tracer.Step("WarPushDeploy")) { var appName = HttpContext.Request.Query["name"].ToString(); @@ -235,6 +258,7 @@ private async Task PushDeployAsync(ZipDeploymentInfo deploymentIn } var zipFilePath = Path.Combine(_environment.ZipTempPath, Guid.NewGuid() + ".zip"); + if (_settings.RunFromLocalZip()) { await WriteSitePackageZip(deploymentInfo, _tracer);