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 #2688

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,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>
6 changes: 4 additions & 2 deletions sdk/Sdk/Tasks/ZipDeploy/DeployStatus.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.

// IMPORTANT: Do not modify this file directly with major changes
Expand All @@ -14,6 +14,8 @@ public enum DeployStatus
Building = 1,
Deploying = 2,
Failed = 3,
Success = 4
Success = 4,
Conflict = 5,
PartialSuccess = 6
}
}
1 change: 1 addition & 0 deletions sdk/Sdk/Tasks/ZipDeploy/StringMessages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
public static class StringMessages
{
public const string DeploymentStatus = "Deployment status is {0}.";
public const string DeploymentStatusWithText = "Deployment status is {0}: {1}";
public const string DeploymentStatusPolling = "Polling for deployment status...";
public const string NeitherSiteNameNorPublishUrlGivenError = "Neither SiteName nor PublishUrl was given a value.";
public const string PublishingZipViaZipDeploy = "Publishing {0} to {1}...";
Expand Down
26 changes: 19 additions & 7 deletions sdk/Sdk/Tasks/ZipDeploy/ZipDeployTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,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 @@ -52,13 +53,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, 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 @@ -73,11 +79,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 @@ -89,13 +95,19 @@ 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?RemoteBuild=false" : "zipdeploy?isAsync=true";

// "<publishUrl>/api/zipdeploy?isAsync=true" or "<publishUrl>/api/publish?RemoteBuild=false"
zipDeployPublishUrl = $"{zipDeployPublishUrl}/{publishUriPath}";

if (logMessages)
{
Log.LogMessage(MessageImportance.High, String.Format(StringMessages.PublishingZipViaZipDeploy, zipToPublishPath, zipDeployPublishUrl));
}

// use the async version of the api
Uri uri = new Uri($"{zipDeployPublishUrl}?isAsync=true", UriKind.Absolute);
Uri uri = new Uri($"{zipDeployPublishUrl}", UriKind.Absolute);
string userAgent = $"{UserAgentName}/{userAgentVersion}";
FileStream stream = File.OpenRead(zipToPublishPath);
IHttpResponse response = await client.PostRequestAsync(uri, userName, password, "application/zip", userAgent, Encoding.UTF8, stream);
Expand All @@ -120,12 +132,12 @@ internal async System.Threading.Tasks.Task<bool> ZipDeployAsync(string zipToPubl
{
ZipDeploymentStatus deploymentStatus = new ZipDeploymentStatus(client, userAgent, Log, logMessages);
DeployStatus status = await deploymentStatus.PollDeploymentStatusAsync(deploymentUrl, userName, password);
if (status == DeployStatus.Success)
if (status == DeployStatus.Success || status == DeployStatus.PartialSuccess)
{
Log.LogMessage(MessageImportance.High, StringMessages.ZipDeploymentSucceeded);
return true;
}
else if (status == DeployStatus.Failed || status == DeployStatus.Unknown)
else if (status == DeployStatus.Failed || status == DeployStatus.Conflict || status == DeployStatus.Unknown)
{
Log.LogError(String.Format(StringMessages.ZipDeployFailureErrorMessage, zipDeployPublishUrl, status));
return false;
Expand Down
57 changes: 47 additions & 10 deletions sdk/Sdk/Tasks/ZipDeploy/ZipDeploymentStatus.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@

using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
Expand Down Expand Up @@ -43,21 +41,33 @@ 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(StringMessages.DeploymentStatusPolling);
}
while (!tokenSource.IsCancellationRequested && deployStatus != DeployStatus.Success && deployStatus != DeployStatus.Failed && deployStatus != DeployStatus.Unknown)
while (!tokenSource.IsCancellationRequested
&& deployStatus != DeployStatus.Success
&& deployStatus != DeployStatus.PartialSuccess
&& deployStatus != DeployStatus.Failed
&& deployStatus != DeployStatus.Conflict
&& deployStatus != DeployStatus.Unknown)
{
anvillan marked this conversation as resolved.
Show resolved Hide resolved
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(StringMessages.DeploymentStatus, Enum.GetName(typeof(DeployStatus), deployStatus)));
var deployStatusName = Enum.GetName(typeof(DeployStatus), deployStatus);

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

_log.LogMessage(message);
}
}
catch (HttpRequestException)
Expand All @@ -71,16 +81,29 @@ 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 status = DeployStatus.Unknown;
var statusText = string.Empty;

IDictionary<string, object>? json = await InvokeGetRequestWithRetryAsync<Dictionary<string, object>>(deploymentUrl, userName, password, retryCount, retryDelay, cts);

if (json != null && TryParseDeploymentStatus(json, out DeployStatus result))
if (json is not null)
{
return result;
// status
if (TryParseDeploymentStatus(json, out DeployStatus result))
{
status = result;
}

// status text message
if (TryParseDeploymentStatusText(json, out string text))
{
statusText = text;
}
}

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

private static bool TryParseDeploymentStatus(IDictionary<string, object> json, out DeployStatus status)
Expand All @@ -97,6 +120,20 @@ private static bool TryParseDeploymentStatus(IDictionary<string, object> json, o
return false;
}

private static bool TryParseDeploymentStatusText(IDictionary<string, object> json, out string statusText)
{
statusText = string.Empty;

if (json.TryGetValue("status_text", out var textObj)
&& textObj is not null)
{
statusText = textObj.ToString();
return true;
}

return false;
}

private async Task<T?> InvokeGetRequestWithRetryAsync<T>(string url, string userName, string password, int retryCount, TimeSpan retryDelay, CancellationTokenSource cts)
{
IHttpResponse? response = null;
Expand Down
5 changes: 4 additions & 1 deletion sdk/release_notes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,10 @@

- Fix incorrect function version in build message (#2606)
- Fix inner build failures when central package management is enabled (#2689)
- Add support to publish a Function App (Flex Consumption) with `ZipDeploy` (#2688)
- Add `'UseBlobContainerDeploy'` property to identify when to use `OneDeploy` publish API endpoint (`"<publish_url>/api/publish"`)
- Enhance `ZipDeploy` deployment status logging by appending the `'status_message'` (when defined) to the output messages

### Microsoft.Azure.Functions.Worker.Sdk.Generators <version>

- <entry>
- <entry>
Binary file not shown.
5 changes: 5 additions & 0 deletions test/FunctionMetadataGeneratorTests/SdkTests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -44,4 +44,9 @@
<ProjectReference Include="..\TestUtility\TestUtility.csproj" />
</ItemGroup>

<ItemGroup>
<None Update="Resources\TestPublishContents.zip">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</None>
</ItemGroup>
</Project>
Loading
Loading