Skip to content

Commit

Permalink
Allow parsing nested JSON responses V3 (#3015)
Browse files Browse the repository at this point in the history
* Modify helpers so JSON responses can be deserialized correctly

* Added explicit contract for responses

* Enforce the expectation that status is of type DeployStatus?
  • Loading branch information
michaelpeng36 committed Apr 12, 2022
1 parent 45ee637 commit f181eda
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 30 deletions.
49 changes: 19 additions & 30 deletions src/Azure.Functions.Cli/Helpers/KuduLiteDeploymentHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Azure.Functions.Cli.Arm.Models;
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Models;
using Colors.Net;
using Newtonsoft.Json;
using System;
Expand Down Expand Up @@ -51,61 +52,58 @@ public static async Task<DeployStatus> WaitForRemoteBuild(HttpClient client, Sit

private static async Task<string> GetLatestDeploymentId(HttpClient client, Site functionApp)
{
var json = await InvokeRequest<List<Dictionary<string, string>>>(client, HttpMethod.Get, "/deployments");
var deployments = await InvokeRequest<List<DeploymentResponse>>(client, HttpMethod.Get, "/deployments");

// Automatically ordered by received time
var latestDeployment = json.First();
if (latestDeployment.TryGetValue("status", out string statusString))
var latestDeployment = deployments.First();
DeployStatus? status = latestDeployment.Status;
if (status == DeployStatus.Building || status == DeployStatus.Deploying
|| status == DeployStatus.Success || status == DeployStatus.Failed)
{
DeployStatus status = ConvertToDeployementStatus(statusString);
if (status == DeployStatus.Building || status == DeployStatus.Deploying
|| status == DeployStatus.Success || status == DeployStatus.Failed)
{
return latestDeployment["id"];
}
return latestDeployment.Id;
}
return null;
}

private static async Task<DeployStatus> GetDeploymentStatusById(HttpClient client, Site functionApp, string id)
{
Dictionary<string, string> json = await InvokeRequest<Dictionary<string, string>>(client, HttpMethod.Get, $"/deployments/{id}");
if (!json.TryGetValue("status", out string statusString))
var deploymentInfo = await InvokeRequest<DeploymentResponse>(client, HttpMethod.Get, $"/deployments/{id}");
DeployStatus? status = deploymentInfo.Status;
if (status == null)
{
return DeployStatus.Unknown;
}

return ConvertToDeployementStatus(statusString);
return status.Value;
}

private static async Task<DateTime> DisplayDeploymentLog(HttpClient client, Site functionApp, string id, DateTime lastUpdate, Uri innerUrl = null, StringBuilder innerLogger = null)
{
string logUrl = innerUrl != null ? innerUrl.ToString() : $"/deployments/{id}/log";
StringBuilder sbLogger = innerLogger != null ? innerLogger : new StringBuilder();

var json = await InvokeRequest<List<Dictionary<string, string>>>(client, HttpMethod.Get, logUrl);
var logs = json.Where(dict => DateTime.Parse(dict["log_time"]) > lastUpdate || dict["details_url"] != null);
var deploymentLogs = await InvokeRequest<List<DeploymentLogResponse>>(client, HttpMethod.Get, logUrl);
var newLogs = deploymentLogs.Where(deploymentLog => deploymentLog.LogTime > lastUpdate || !string.IsNullOrEmpty(deploymentLog.DetailsUrlString));
DateTime currentLogDatetime = lastUpdate;

foreach (var log in logs)
foreach (var log in newLogs)
{
// Filter out details_url log
if (DateTime.Parse(log["log_time"]) > lastUpdate)
if (log.LogTime > lastUpdate)
{
sbLogger.AppendLine(log["message"]);
sbLogger.AppendLine(log.Message);
}

// Recursively log details_url from scm/api/deployments/xxx/log endpoint
if (log["details_url"] != null && Uri.TryCreate(log["details_url"], UriKind.Absolute, out Uri detailsUrl))
if (!string.IsNullOrEmpty(log.DetailsUrlString) && Uri.TryCreate(log.DetailsUrlString, UriKind.Absolute, out Uri detailsUrl))
{
DateTime innerLogDatetime = await DisplayDeploymentLog(client, functionApp, id, currentLogDatetime, detailsUrl, sbLogger);
currentLogDatetime = innerLogDatetime > currentLogDatetime ? innerLogDatetime : currentLogDatetime;
}
}

if (logs.LastOrDefault() != null)
if (newLogs.LastOrDefault() != null)
{
DateTime lastLogDatetime = DateTime.Parse(logs.Last()["log_time"]);
DateTime lastLogDatetime = newLogs.Last().LogTime;
currentLogDatetime = lastLogDatetime > currentLogDatetime ? lastLogDatetime : currentLogDatetime;
}

Expand Down Expand Up @@ -139,14 +137,5 @@ await RetryHelper.Retry(async () =>
return default(T);
}
}

private static DeployStatus ConvertToDeployementStatus(string statusString)
{
if (Enum.TryParse(statusString, out DeployStatus result))
{
return result;
}
return DeployStatus.Unknown;
}
}
}
22 changes: 22 additions & 0 deletions src/Azure.Functions.Cli/Models/DeploymentLogResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@

// 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 Newtonsoft.Json;

namespace Azure.Functions.Cli.Models
{
[JsonObject]
public class DeploymentLogResponse
{
[JsonProperty("log_time")]
public DateTime LogTime { get; set; }

[JsonProperty("message")]
public string Message { get; set; }

[JsonProperty("details_url")]
public string DetailsUrlString { get; set; }
}
}
20 changes: 20 additions & 0 deletions src/Azure.Functions.Cli/Models/DeploymentResponse.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

// 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 Newtonsoft.Json;
using Azure.Functions.Cli.Common;

namespace Azure.Functions.Cli.Models
{
[JsonObject]
public class DeploymentResponse
{
[JsonProperty("id")]
public string Id { get; set; }

[JsonProperty("status")]
public DeployStatus? Status { get; set; }
}
}

0 comments on commit f181eda

Please sign in to comment.