sdk/Sdk/Tasks/ZipDeploy/ZipDeploymentStatus.cs (148 lines of code) (raw):
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text.Json;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Build.Utilities;
using Microsoft.NET.Sdk.Functions.Http;
using Microsoft.NET.Sdk.Functions.Tasks;
using Task = System.Threading.Tasks.Task;
// IMPORTANT: Do not modify this file directly with major changes
// This file is a copy from this project (with minor updates) -- https://github.com/Azure/azure-functions-vs-build-sdk/blob/b0e54a832a92119e00a2b1796258fcf88e0d6109/src/Microsoft.NET.Sdk.Functions.MSBuild/Microsoft.NET.Sdk.Functions.MSBuild.csproj
// Please make any changes upstream first.
namespace Microsoft.NET.Sdk.Functions.MSBuild.Tasks
{
internal class ZipDeploymentStatus
{
private const int MaxMinutesToWait = 3;
private const int StatusRefreshDelaySeconds = 3;
private const int RetryCount = 3;
private const int RetryDelaySeconds = 1;
private readonly IHttpClient _client;
private readonly string _userAgent;
private readonly TaskLoggingHelper _log;
private readonly bool _logMessages;
public ZipDeploymentStatus(IHttpClient client, string userAgent, TaskLoggingHelper log, bool logMessages)
{
_client = client;
_userAgent = userAgent;
_log = log;
_logMessages = logMessages;
}
public async Task<DeployStatus> PollDeploymentStatusAsync(string deploymentUrl, string userName, string password)
{
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.PartialSuccess
&& deployStatus != DeployStatus.Failed
&& deployStatus != DeployStatus.Conflict
&& deployStatus != DeployStatus.Unknown)
{
try
{
(deployStatus, deployStatusText) = await GetDeploymentStatusAsync(deploymentUrl, userName, password, RetryCount, TimeSpan.FromSeconds(RetryDelaySeconds), tokenSource);
if (_logMessages)
{
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)
{
return DeployStatus.Unknown;
}
await Task.Delay(TimeSpan.FromSeconds(StatusRefreshDelaySeconds), tokenSource.Token);
}
return deployStatus;
}
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 is not null)
{
// status
if (TryParseDeploymentStatus(json, out DeployStatus result))
{
status = result;
}
// status text message
if (TryParseDeploymentStatusText(json, out string text))
{
statusText = text;
}
}
return (status, statusText);
}
private static bool TryParseDeploymentStatus(IDictionary<string, object> json, out DeployStatus status)
{
status = DeployStatus.Unknown;
if (json.TryGetValue("status", out object statusObj)
&& int.TryParse(statusObj.ToString(), out int statusInt)
&& Enum.TryParse(statusInt.ToString(), out status))
{
return true;
}
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;
await RetryAsync(async () =>
{
response = await _client.GetRequestAsync(new Uri(url, UriKind.RelativeOrAbsolute), userName, password, _userAgent, cts.Token);
}, retryCount, retryDelay);
if (response!.StatusCode != HttpStatusCode.OK && response.StatusCode != HttpStatusCode.Accepted)
{
return default;
}
else
{
using (var stream = await response.GetResponseBodyAsync())
{
return await JsonSerializer.DeserializeAsync<T>(stream);
}
}
}
private async Task RetryAsync(Func<Task> func, int retryCount, TimeSpan retryDelay)
{
while (true)
{
try
{
await func();
return;
}
catch (Exception e)
{
if (retryCount <= 0)
{
throw e;
}
retryCount--;
}
await Task.Delay(retryDelay);
}
}
}
}