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..d503017c 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,34 @@ private async Task PushDeployAsync(ZipDeploymentInfo deploymentIn } } } + else if (deploymentInfo.ZipURL != null) + { + using (_tracer.Step("Writing zip file from packageUri to {0}", zipFilePath)) + { + using (var httpClient = new HttpClient()) + using (var fileStream = new FileStream(zipFilePath, + FileMode.Create, FileAccess.Write, FileShare.None, bufferSize: 4096, useAsync: true)) + { + 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()) + { + await content.CopyToAsync(fileStream); + } + } + } + } else { using (var file = System.IO.File.Create(zipFilePath))