Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fuction App Flex Consumption publish support #656

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
Expand Up @@ -144,4 +144,8 @@
<data name="ZipFileUploaded" xml:space="preserve">
<value>Uploaded the Zip file to the target.</value>
</data>
<data name="DeploymentStatusWithText" xml:space="preserve">
<value>Deployment status is {0}: {1}</value>
<comment>0 - name, 1 - text message</comment>
</data>
</root>
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,8 @@ WARNING: DO NOT MODIFY this file unless you are knowledgeable about MSBuild and
DeploymentPassword="$(Password)"
SiteName="$(DeployIisAppPath)"
PublishUrl="$(PublishUrl)"
UserAgentVersion="$(ZipDeployUserAgent)"/>
UserAgentVersion="$(ZipDeployUserAgent)"
UseBlobContainerDeploy="$(UseBlobContainerDeploy)"/>
</Target>

</Project>
21 changes: 16 additions & 5 deletions src/Microsoft.NET.Sdk.Functions.MSBuild/Tasks/ZipDeployTask.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net;
Expand Down Expand Up @@ -30,6 +29,7 @@ public class ZipDeployTask : Task

public string PublishUrl { get; set; }

public bool UseBlobContainerDeploy { get; set; }

/// <summary>
/// Our fallback if PublishUrl is not given, which is the case for ZIP Deploy profiles created prior to 15.8 Preview 4.
Expand All @@ -41,13 +41,18 @@ public override bool Execute()
{
using(DefaultHttpClient client = new DefaultHttpClient())
{
System.Threading.Tasks.Task<bool> t = ZipDeployAsync(ZipToPublishPath, DeploymentUsername, DeploymentPassword, PublishUrl, SiteName, UserAgentVersion, client, true);
System.Threading.Tasks.Task<bool> t = ZipDeployAsync(ZipToPublishPath, DeploymentUsername, DeploymentPassword, PublishUrl, SiteName, UserAgentVersion, UseBlobContainerDeploy, client, logMessages: true);
t.Wait();
return t.Result;
}
}

internal async System.Threading.Tasks.Task<bool> ZipDeployAsync(string zipToPublishPath, string userName, string password, string publishUrl, string siteName, string userAgentVersion, IHttpClient client, bool logMessages)
internal System.Threading.Tasks.Task<bool> ZipDeployAsync(string zipToPublishPath, string userName, string password, string publishUrl, string siteName, string userAgentVersion, IHttpClient client, bool logMessages)
{
return ZipDeployAsync(zipToPublishPath, userName, password, publishUrl, siteName, userAgentVersion, useBlobContainerDeploy: false, client, logMessages);
}

internal async System.Threading.Tasks.Task<bool> ZipDeployAsync(string zipToPublishPath, string userName, string password, string publishUrl, string siteName, string userAgentVersion, bool useBlobContainerDeploy, IHttpClient client, bool logMessages)
{
if (!File.Exists(zipToPublishPath) || client == null)
{
Expand All @@ -62,11 +67,11 @@ internal async System.Threading.Tasks.Task<bool> ZipDeployAsync(string zipToPubl
publishUrl += "/";
}

zipDeployPublishUrl = publishUrl + "api/zipdeploy";
zipDeployPublishUrl = publishUrl + "api";
}
else if(!string.IsNullOrEmpty(siteName))
{
zipDeployPublishUrl = $"https://{siteName}.scm.azurewebsites.net/api/zipdeploy";
zipDeployPublishUrl = $"https://{siteName}.scm.azurewebsites.net/api";
}
else
{
Expand All @@ -78,6 +83,12 @@ internal async System.Threading.Tasks.Task<bool> ZipDeployAsync(string zipToPubl
return false;
}

// publish endpoint differs when using a blob storage container
var publishUriPath = useBlobContainerDeploy ? "publish" : "zipdeploy";

// "<publishUrl>/api/zipdeploy" or "<publishUrl>/api/publish"
zipDeployPublishUrl = $"{zipDeployPublishUrl}/{publishUriPath}";

if (logMessages)
{
Log.LogMessage(MessageImportance.High, String.Format(Resources.PublishingZipViaZipDeploy, zipToPublishPath, zipDeployPublishUrl));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
Expand Down Expand Up @@ -37,8 +36,10 @@ public ZipDeploymentStatus(IHttpClient client, string userAgent, TaskLoggingHelp

public async Task<DeployStatus> PollDeploymentStatusAsync(string deploymentUrl, string userName, string password)
{
DeployStatus deployStatus = DeployStatus.Pending;
var deployStatus = DeployStatus.Pending;
var deployStatusText = string.Empty;
var tokenSource = new CancellationTokenSource(TimeSpan.FromMinutes(MaxMinutesToWait));

if (_logMessages)
{
_log.LogMessage(Resources.DeploymentStatusPolling);
Expand All @@ -47,10 +48,16 @@ public async Task<DeployStatus> PollDeploymentStatusAsync(string deploymentUrl,
{
try
{
deployStatus = await GetDeploymentStatusAsync(deploymentUrl, userName, password, RetryCount, TimeSpan.FromSeconds(RetryDelaySeconds), tokenSource);
(deployStatus, deployStatusText) = await GetDeploymentStatusAsync(deploymentUrl, userName, password, RetryCount, TimeSpan.FromSeconds(RetryDelaySeconds), tokenSource);
if (_logMessages)
{
_log.LogMessage(String.Format(Resources.DeploymentStatus, Enum.GetName(typeof(DeployStatus), deployStatus)));
var deployStatusName = Enum.GetName(typeof(DeployStatus), deployStatus);

var message = string.IsNullOrEmpty(deployStatusText)
? string.Format(Resources.DeploymentStatus, deployStatusName)
: string.Format(Resources.DeploymentStatusWithText, deployStatusName, deployStatusText);

_log.LogMessage(message);
}
}
catch (HttpRequestException)
Expand All @@ -64,17 +71,28 @@ public async Task<DeployStatus> PollDeploymentStatusAsync(string deploymentUrl,
return deployStatus;
}

private async Task<DeployStatus> GetDeploymentStatusAsync(string deploymentUrl, string userName, string password, int retryCount, TimeSpan retryDelay, CancellationTokenSource cts)
private async Task<(DeployStatus, string)> GetDeploymentStatusAsync(string deploymentUrl, string userName, string password, int retryCount, TimeSpan retryDelay, CancellationTokenSource cts)
{
var json = await InvokeGetRequestWithRetryAsync<JObject>(deploymentUrl, userName, password, retryCount, retryDelay, cts);
if (json != null
&& json.TryGetValue("status", out JToken statusString)
&& Enum.TryParse(statusString.Value<string>(), out DeployStatus result))
{
return result;
var status = DeployStatus.Unknown;
var statusText = string.Empty;

var json = await InvokeGetRequestWithRetryAsync<JObject>(deploymentUrl, userName, password, retryCount, retryDelay, cts);
if (json is not null)
{
if (json.TryGetValue("status", out JToken statusString)
&& Enum.TryParse(statusString.Value<string>(), out DeployStatus result))
{
status = result;
}

if (json.TryGetValue("status_text", out JToken textString)
&& textString is not null)
{
statusText = textString.ToString();
}
}

return DeployStatus.Unknown;
return (status, statusText);
}

private async Task<T> InvokeGetRequestWithRetryAsync<T>(string url, string userName, string password, int retryCount, TimeSpan retryDelay, CancellationTokenSource cts)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ public async Task ExecuteZipDeploy_InvalidZipFilePath()
Mock<IHttpClient> client = new Mock<IHttpClient>();
ZipDeployTask zipDeployer = new ZipDeployTask();

bool result = await zipDeployer.ZipDeployAsync(string.Empty, "username", "password", "publishUrl", null, "Foo", client.Object, false);
bool result = await zipDeployer.ZipDeployAsync(string.Empty, "username", "password", "publishUrl", null, "Foo", false, client.Object, false);

client.Verify(c => c.PostAsync(It.IsAny<Uri>(), It.IsAny<StreamContent>()), Times.Never);
Assert.False(result);
Expand All @@ -51,12 +51,17 @@ public async Task ExecuteZipDeploy_InvalidZipFilePath()
/// ZipDeploy should use PublishUrl if not null or empty, else use SiteName.
/// </summary>
[Theory]
[InlineData("https://sitename.scm.azurewebsites.net", null, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData("https://sitename.scm.azurewebsites.net", "", "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData("https://sitename.scm.azurewebsites.net", "shouldNotBeUsed", "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData(null, "sitename", "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData("", "sitename", "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
public async Task ExecuteZipDeploy_PublishUrlOrSiteNameGiven(string publishUrl, string siteName, string expectedZipDeployEndpoint)
[InlineData("https://sitename.scm.azurewebsites.net", null, false, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData("https://sitename.scm.azurewebsites.net", null, true, "https://sitename.scm.azurewebsites.net/api/publish?isAsync=true")]
[InlineData("https://sitename.scm.azurewebsites.net", "", false, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData("https://sitename.scm.azurewebsites.net", "", true, "https://sitename.scm.azurewebsites.net/api/publish?isAsync=true")]
[InlineData("https://sitename.scm.azurewebsites.net", "shouldNotBeUsed", false, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData("https://sitename.scm.azurewebsites.net", "shouldNotBeUsed", true, "https://sitename.scm.azurewebsites.net/api/publish?isAsync=true")]
[InlineData(null, "sitename", false, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData(null, "sitename", true, "https://sitename.scm.azurewebsites.net/api/publish?isAsync=true")]
[InlineData("", "sitename", false, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true")]
[InlineData("", "sitename", true, "https://sitename.scm.azurewebsites.net/api/publish?isAsync=true")]
public async Task ExecuteZipDeploy_PublishUrlOrSiteNameGiven(string publishUrl, string siteName, bool useBlobContainerDeploy, string expectedZipDeployEndpoint)
{
Action<Mock<IHttpClient>, bool> verifyStep = (client, result) =>
{
Expand All @@ -68,7 +73,7 @@ public async Task ExecuteZipDeploy_PublishUrlOrSiteNameGiven(string publishUrl,
Assert.True(result);
};

await RunZipDeployAsyncTest(publishUrl, siteName, UserAgentVersion, HttpStatusCode.OK, verifyStep);
await RunZipDeployAsyncTest(publishUrl, siteName, UserAgentVersion, useBlobContainerDeploy, HttpStatusCode.OK, verifyStep);
}

[Theory]
Expand All @@ -88,32 +93,43 @@ public async Task ExecuteZipDeploy_NeitherPublishUrlNorSiteNameGiven(string publ
Assert.False(result);
};

await RunZipDeployAsyncTest(publishUrl, siteName, UserAgentVersion, HttpStatusCode.OK, verifyStep);
await RunZipDeployAsyncTest(publishUrl, siteName, UserAgentVersion, useBlobContainerDeploy: false, HttpStatusCode.OK, verifyStep);
}

[Theory]
[InlineData(HttpStatusCode.OK, true)]
[InlineData(HttpStatusCode.Accepted, true)]
[InlineData(HttpStatusCode.Forbidden, false)]
[InlineData(HttpStatusCode.NotFound, false)]
[InlineData(HttpStatusCode.RequestTimeout, false)]
[InlineData(HttpStatusCode.InternalServerError, false)]
public async Task ExecuteZipDeploy_VaryingHttpResponseStatuses(HttpStatusCode responseStatusCode, bool expectedResult)
[InlineData(HttpStatusCode.OK, false, true)]
[InlineData(HttpStatusCode.OK, true, true)]
[InlineData(HttpStatusCode.Accepted, false, true)]
[InlineData(HttpStatusCode.Accepted, true, true)]
[InlineData(HttpStatusCode.Forbidden, false, false)]
[InlineData(HttpStatusCode.Forbidden, true, false)]
[InlineData(HttpStatusCode.NotFound, false, false)]
[InlineData(HttpStatusCode.NotFound, true, false)]
[InlineData(HttpStatusCode.RequestTimeout, false, false)]
[InlineData(HttpStatusCode.RequestTimeout, true, false)]
[InlineData(HttpStatusCode.InternalServerError, false, false)]
[InlineData(HttpStatusCode.InternalServerError, true, false)]
public async Task ExecuteZipDeploy_VaryingHttpResponseStatuses(
HttpStatusCode responseStatusCode, bool useBlobContainerDeploy, bool expectedResult)
{
var zipDeployPublishUrl = useBlobContainerDeploy
? "https://sitename.scm.azurewebsites.net/api/publish?isAsync=true"
: "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true";

Action<Mock<IHttpClient>, bool> verifyStep = (client, result) =>
{
client.Verify(c => c.PostAsync(
It.Is<Uri>(uri => string.Equals(uri.AbsoluteUri, "https://sitename.scm.azurewebsites.net/api/zipdeploy?isAsync=true", StringComparison.Ordinal)),
It.Is<Uri>(uri => string.Equals(uri.AbsoluteUri, zipDeployPublishUrl, StringComparison.Ordinal)),
It.Is<StreamContent>(streamContent => IsStreamContentEqualToFileContent(streamContent, TestZippedPublishContentsPath))),
Times.Once);
Assert.Equal($"{UserAgentName}/{UserAgentVersion}", client.Object.DefaultRequestHeaders.GetValues("User-Agent").FirstOrDefault());
Assert.Equal(expectedResult, result);
};

await RunZipDeployAsyncTest("https://sitename.scm.azurewebsites.net", null, UserAgentVersion, responseStatusCode, verifyStep);
await RunZipDeployAsyncTest("https://sitename.scm.azurewebsites.net", null, UserAgentVersion, useBlobContainerDeploy, responseStatusCode, verifyStep);
}

private async Task RunZipDeployAsyncTest(string publishUrl, string siteName, string userAgentVersion, HttpStatusCode responseStatusCode, Action<Mock<IHttpClient>, bool> verifyStep)
private async Task RunZipDeployAsyncTest(string publishUrl, string siteName, string userAgentVersion, bool useBlobContainerDeploy, HttpStatusCode responseStatusCode, Action<Mock<IHttpClient>, bool> verifyStep)
{
Mock<IHttpClient> client = new Mock<IHttpClient>();

Expand All @@ -138,7 +154,7 @@ private async Task RunZipDeployAsyncTest(string publishUrl, string siteName, str

ZipDeployTask zipDeployer = new ZipDeployTask();

bool result = await zipDeployer.ZipDeployAsync(TestZippedPublishContentsPath, "username", "password", publishUrl, siteName, userAgentVersion, client.Object, false);
bool result = await zipDeployer.ZipDeployAsync(TestZippedPublishContentsPath, "username", "password", publishUrl, siteName, userAgentVersion, useBlobContainerDeploy, client.Object, false);

verifyStep(client, result);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using System.Threading;
using System.Threading.Tasks;
using Microsoft.NET.Sdk.Functions.Http;
using Microsoft.NET.Sdk.Functions.MSBuild.Properties;
using Microsoft.NET.Sdk.Functions.MSBuild.Tasks;
using Moq;
using Newtonsoft.Json;
Expand Down Expand Up @@ -55,13 +54,13 @@ public async Task PollDeploymentStatusTest_ForErrorResponses(HttpStatusCode resp
}

[Theory]
[InlineData(HttpStatusCode.OK, DeployStatus.Success)]
[InlineData(HttpStatusCode.Accepted, DeployStatus.Success)]
[InlineData(HttpStatusCode.OK, DeployStatus.Failed)]
[InlineData(HttpStatusCode.Accepted, DeployStatus.Failed)]
[InlineData(HttpStatusCode.OK, DeployStatus.Unknown)]
[InlineData(HttpStatusCode.Accepted, DeployStatus.Unknown)]
public async Task PollDeploymentStatusTest_ForValidResponses(HttpStatusCode responseStatusCode, DeployStatus expectedDeployStatus)
[InlineData(HttpStatusCode.OK, "", DeployStatus.Success)]
[InlineData(HttpStatusCode.Accepted, null, DeployStatus.Success)]
[InlineData(HttpStatusCode.OK, "Instance configuration is not valid", DeployStatus.Failed)]
[InlineData(HttpStatusCode.Accepted, "", DeployStatus.Failed)]
[InlineData(HttpStatusCode.OK, null, DeployStatus.Unknown)]
[InlineData(HttpStatusCode.Accepted, null, DeployStatus.Unknown)]
public async Task PollDeploymentStatusTest_ForValidResponses(HttpStatusCode responseStatusCode, string statusMessage, DeployStatus expectedDeployStatus)
{
// Arrange
string deployUrl = "https://sitename.scm.azurewebsites.net/DeploymentStatus?Id=knownId";
Expand All @@ -80,7 +79,8 @@ public async Task PollDeploymentStatusTest_ForValidResponses(HttpStatusCode resp
{
string statusJson = JsonConvert.SerializeObject(new
{
status = Enum.GetName(typeof(DeployStatus), expectedDeployStatus)
status = Enum.GetName(typeof(DeployStatus), expectedDeployStatus),
status_text = statusMessage
}, Formatting.Indented);

HttpContent httpContent = new StringContent(statusJson, Encoding.UTF8, "application/json");
Expand Down