From c77842ccbd9a2f3219b02dd8c504c96f46dec55b Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Thu, 16 May 2019 18:46:37 -0700 Subject: [PATCH 1/2] Add: Url support for ZipDeploy --- Kudu.Core/Deployment/ZipDeploymentInfo.cs | 3 + Kudu.Services.Web/Startup.cs | 3 + .../Deployment/PushDeploymentController.cs | 91 ++++++++++++++++++- 3 files changed, 94 insertions(+), 3 deletions(-) diff --git a/Kudu.Core/Deployment/ZipDeploymentInfo.cs b/Kudu.Core/Deployment/ZipDeploymentInfo.cs index d336ff35..e37eee8d 100644 --- a/Kudu.Core/Deployment/ZipDeploymentInfo.cs +++ b/Kudu.Core/Deployment/ZipDeploymentInfo.cs @@ -32,5 +32,8 @@ public override IRepository GetRepository() // This is used if the deployment is Run-From-Zip public string ZipName { get; set; } + + // This is used when getting the zipfile from the zipURL + public string ZipURL { get; set; } } } diff --git a/Kudu.Services.Web/Startup.cs b/Kudu.Services.Web/Startup.cs index 363cadf5..5f10cea4 100644 --- a/Kudu.Services.Web/Startup.cs +++ b/Kudu.Services.Web/Startup.cs @@ -443,6 +443,9 @@ public void Configure(IApplicationBuilder app, routes.MapRoute("zip-push-deploy", "api/zipdeploy", new {controller = "PushDeployment", action = "ZipPushDeploy"}, new {verb = new HttpMethodRouteConstraint("POST")}); + routes.MapRoute("zip-push-deploy-url", "api/zipdeploy", + new {controller = "PushDeployment", action = "ZipPushDeployViaUrl"}, + new {verb = new HttpMethodRouteConstraint("PUT")}); routes.MapRoute("zip-war-deploy", "api/wardeploy", new {controller = "PushDeployment", action = "WarPushDeploy"}, new {verb = new HttpMethodRouteConstraint("POST")}); diff --git a/Kudu.Services/Deployment/PushDeploymentController.cs b/Kudu.Services/Deployment/PushDeploymentController.cs index d7d18e4f..7c92f2ce 100644 --- a/Kudu.Services/Deployment/PushDeploymentController.cs +++ b/Kudu.Services/Deployment/PushDeploymentController.cs @@ -15,6 +15,8 @@ using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.ModelBinding; +using Newtonsoft.Json.Linq; +using System.Net.Http; namespace Kudu.Services.Deployment { @@ -71,7 +73,8 @@ public async Task ZipPushDeploy( DoFullBuildByDefault = false, Author = author, AuthorEmail = authorEmail, - Message = message + Message = message, + ZipURL = null }; if (_settings.RunFromLocalZip()) @@ -92,6 +95,41 @@ public async Task ZipPushDeploy( } } + [HttpPut] + public async Task ZipPushDeployViaUrl( + [FromBody] JObject requestJson, + [FromQuery] bool isAsync = false, + [FromQuery] string author = null, + [FromQuery] string authorEmail = null, + [FromQuery] string deployer = DefaultDeployer, + [FromQuery] string message = DefaultMessage) + { + using (_tracer.Step("ZipPushDeployViaUrl")) + { + string zipUrl = GetZipURLFromJSON(requestJson); + + var deploymentInfo = new ZipDeploymentInfo(_environment, _traceFactory) + { + AllowDeploymentWhileScmDisabled = true, + Deployer = deployer, + IsContinuous = false, + AllowDeferredDeployment = false, + IsReusable = false, + TargetChangeset = + DeploymentManager.CreateTemporaryChangeSet(message: "Deploying from pushed zip file"), + CommitId = null, + RepositoryType = RepositoryType.None, + Fetch = LocalZipHandler, + DoFullBuildByDefault = false, + Author = author, + AuthorEmail = authorEmail, + Message = message, + ZipURL = zipUrl, + }; + return await PushDeployAsync(deploymentInfo, isAsync, HttpContext); + } + } + [HttpPost] [DisableRequestSizeLimit] @@ -130,13 +168,39 @@ public async Task WarPushDeploy( DoFullBuildByDefault = false, Author = author, AuthorEmail = authorEmail, - Message = message + Message = message, + ZipURL = null }; - return await PushDeployAsync(deploymentInfo, isAsync, HttpContext); } } + private string GetZipURLFromJSON(JObject requestObject) + { + using (_tracer.Step("Reading the zip URL from the request JSON")) + { + try + { + string packageUri = requestObject.Value("packageUri"); + if (string.IsNullOrEmpty(packageUri)) + { + throw new ArgumentException("Request body does not contain packageUri"); + } + + Uri zipUri = null; + if (!Uri.TryCreate(packageUri, UriKind.Absolute, out zipUri)) + { + throw new ArgumentException("Malformed packageUri"); + } + return packageUri; + } + catch (Exception ex) + { + _tracer.TraceError(ex, "Error reading the URL from the JSON {0}", requestObject.ToString()); + throw; + } + } + } private async Task PushDeployAsync(ZipDeploymentInfo deploymentInfo, bool isAsync, HttpContext context) @@ -162,6 +226,27 @@ private async Task PushDeployAsync(ZipDeploymentInfo deploymentIn } } } + else if (deploymentInfo.ZipURL != null) + { + using (_tracer.Step("Writing zip file from packageUri to {0}", zipFilePath)) + { + using (var file = System.IO.File.Create(zipFilePath)) + { + using (var client = new HttpClient()) + { + var zipUrlResponse = await client.GetAsync(deploymentInfo.ZipURL); + if (zipUrlResponse.IsSuccessStatusCode) + { + await zipUrlResponse.Content.CopyToAsync(file); + } + else + { + _tracer.TraceError("Failed to get file from packageUri {0}", deploymentInfo.ZipURL); + } + } + } + } + } else { using (var file = System.IO.File.Create(zipFilePath)) From dab71f58b346f003485782821129372612a31e37 Mon Sep 17 00:00:00 2001 From: "Hanzhang Zeng (Roger)" Date: Fri, 17 May 2019 09:16:13 -0700 Subject: [PATCH 2/2] Use ReadAsStreamAsync to reduce thread blocking time --- .../Deployment/PushDeploymentController.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/Kudu.Services/Deployment/PushDeploymentController.cs b/Kudu.Services/Deployment/PushDeploymentController.cs index 7c92f2ce..d503017c 100644 --- a/Kudu.Services/Deployment/PushDeploymentController.cs +++ b/Kudu.Services/Deployment/PushDeploymentController.cs @@ -230,19 +230,26 @@ private async Task PushDeployAsync(ZipDeploymentInfo deploymentIn { using (_tracer.Step("Writing zip file from packageUri to {0}", zipFilePath)) { - using (var file = System.IO.File.Create(zipFilePath)) + using (var httpClient = new HttpClient()) + using (var fileStream = new FileStream(zipFilePath, + FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) { - using (var client = new HttpClient()) + var zipUrlRequest = new HttpRequestMessage(HttpMethod.Get, deploymentInfo.ZipURL); + var zipUrlResponse = await httpClient.SendAsync(zipUrlRequest); + + try + { + zipUrlResponse.EnsureSuccessStatusCode(); + } + catch (HttpRequestException hre) + { + _tracer.TraceError(hre, "Failed to get file from packageUri {0}", deploymentInfo.ZipURL); + throw; + } + + using (var content = await zipUrlResponse.Content.ReadAsStreamAsync()) { - var zipUrlResponse = await client.GetAsync(deploymentInfo.ZipURL); - if (zipUrlResponse.IsSuccessStatusCode) - { - await zipUrlResponse.Content.CopyToAsync(file); - } - else - { - _tracer.TraceError("Failed to get file from packageUri {0}", deploymentInfo.ZipURL); - } + await content.CopyToAsync(fileStream); } } }