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

Update VersionHelper to utilize GH release tag for version check #4224

Merged
merged 6 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from 4 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
2 changes: 2 additions & 0 deletions src/Azure.Functions.Cli/Common/Constants.cs
liliankasem marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,8 @@ internal static class Constants
public const string InProcDotNet8EnabledSetting = "FUNCTIONS_INPROC_NET8_ENABLED";
public const string AzureDevSessionsRemoteHostName = "AzureDevSessionsRemoteHostName";
public const string AzureDevSessionsPortSuffixPlaceholder = "<port>";
public const string GitHubReleaseApiUrl = "https://api.github.com/repos/Azure/azure-functions-core-tools/releases/latest";

// Sample format https://n12abc3t-<port>.asse.devtunnels.ms/


Expand Down
124 changes: 17 additions & 107 deletions src/Azure.Functions.Cli/Helpers/VersionHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Extensions;
using Colors.Net;
using Newtonsoft.Json;
using Newtonsoft.Json.Serialization;
Expand All @@ -7,6 +8,7 @@
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
Expand All @@ -17,6 +19,9 @@ namespace Azure.Functions.Cli.Helpers
{
internal class VersionHelper
{
// Set the CliVersion for testing
internal static string CliVersion { get; set; } = Constants.CliVersion;

public static async Task<string> RunAsync(Task<bool> isRunningOlderVersion = null)
{
isRunningOlderVersion ??= IsRunningAnOlderVersion();
Expand All @@ -36,39 +41,25 @@ public static async Task<string> RunAsync(Task<bool> isRunningOlderVersion = nul
// Check that current core tools is the latest version.
// To ensure that it doesn't block other tasks. The HTTP Request timeout is only 1 second.
// We simply ingnore the exception if for any reason the check fails.
public static async Task<bool> IsRunningAnOlderVersion()
public static async Task<bool> IsRunningAnOlderVersion(HttpClient client = null)
VineethReyya marked this conversation as resolved.
Show resolved Hide resolved
{
try
{
var client = new System.Net.Http.HttpClient
{
Timeout = TimeSpan.FromSeconds(1)
};
var response = await client.GetAsync(Constants.CoreToolsVersionsFeedUrl);
var content = await response.Content.ReadAsStringAsync();
var data = JsonConvert.DeserializeObject<CliFeed>(content);
IEnumerable releases = ((IEnumerable) data.Releases);
var releaseList = new List<ReleaseSummary>();
foreach (var item in releases)
using (client ??= new HttpClient { Timeout = TimeSpan.FromSeconds(1) })
VineethReyya marked this conversation as resolved.
Show resolved Hide resolved
{
var jProperty = (Newtonsoft.Json.Linq.JProperty)item;
var releaseDetail = JsonConvert.DeserializeObject<ReleaseDetail>(jProperty.Value.ToString());
releaseList.Add(new ReleaseSummary() { Release = jProperty.Name, ReleaseDetail = releaseDetail.ReleaseList.FirstOrDefault() });
}
client.DefaultRequestHeaders.Add("User-Agent", "AzureFunctionsCoreToolsClient");

var latestCoreToolsReleaseVersion = releaseList.FirstOrDefault(x => x.Release == data.Tags.V4Release.ReleaseVersion)?.CoreToolsReleaseNumber;
var response = await client.GetAsync(Constants.GitHubReleaseApiUrl);
response.EnsureSuccessStatusCode();

if (!string.IsNullOrEmpty(latestCoreToolsReleaseVersion) &&
Constants.CliVersion != latestCoreToolsReleaseVersion)
{
return true;
}
var content = await response.Content.ReadAsStringAsync();
var release = JsonConvert.DeserializeObject<GitHubRelease>(content);

return false;
return !release.TagName.EqualsIgnoreCase(CliVersion);
}
}
catch (Exception)
{
// ignore exception and no warning when the check fails.
return false;
}
}
Expand Down Expand Up @@ -123,91 +114,10 @@ private static async Task<string> GetMultipleInstallationMessage(bool isRunningO
return string.Empty;
}

private class CliFeed
{
[JsonProperty("tags")]
public Tags Tags { get; set; }

[JsonProperty("releases")]
public Object Releases { get; set; }
}

private class Tags
{
[JsonProperty("v4")]
public Release V4Release { get; set; }

[JsonProperty("v4-prerelease")]
public Release V4PreRelease { get; set; }

[JsonProperty("v3")]
public Release V3Release { get; set; }

[JsonProperty("v3-prerelease")]
public Release V3PreRelease { get; set; }
}

private class Release
{
[JsonProperty("release")]
public string ReleaseVersion { get; set; }

[JsonProperty("releaseQuality")]
public string ReleaseQuality { get; set; }

[JsonProperty("hidden")]
public bool Hidden { get; set; }
}

private class ReleaseSummary
private class GitHubRelease
{
public string Release { get; set; }

public string CoreToolsReleaseNumber
{
get
{
var downloadLink = ReleaseDetail?.DownloadLink;
if (string.IsNullOrEmpty(ReleaseDetail?.DownloadLink))
{
return string.Empty;
}

Uri uri = new UriBuilder(ReleaseDetail?.DownloadLink).Uri;

if (uri.Segments.Length < 4)
{
return string.Empty;
}

return uri.Segments[2].Replace("/", string.Empty);
}
}
public CoreToolsRelease ReleaseDetail { get; set; }
}

private class ReleaseDetail
{
[JsonProperty("coreTools")]
public IList<CoreToolsRelease> ReleaseList { get; set; }
}

private class CoreToolsRelease
{
[JsonProperty("OS")]
public string Os { get; set; }

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

[JsonProperty("downloadLink")]
public string DownloadLink { get; set; }

[JsonProperty("size")]
public string Size { get; set; }

[JsonProperty("default")]
public bool Default { get; set; }
[JsonProperty("tag_name")]
public string TagName { get; set; }
}
}

Expand Down
61 changes: 61 additions & 0 deletions test/Azure.Functions.Cli.Tests/E2E/VersionTests.cs
Original file line number Diff line number Diff line change
@@ -1,9 +1,17 @@
using System;
using System.Net.Http;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using Azure.Functions.Cli.Common;
using Azure.Functions.Cli.Helpers;
using Azure.Functions.Cli.Tests.E2E.Helpers;
using Moq.Protected;
using Moq;
using Xunit;
using Xunit.Abstractions;
using static Azure.Functions.Cli.Helpers.VersionHelper;
using FluentAssertions;

namespace Azure.Functions.Cli.Tests.E2E
{
Expand All @@ -24,5 +32,58 @@ await CliTester.Run(new RunConfiguration
CommandTimeout = TimeSpan.FromSeconds(30)
}, _output);
}

[Fact]
public async Task IsRunningAnOlderVersion_ShouldReturnTrue_WhenVersionIsOlder()
{
// Create the mocked HttpClient with the mock response
var mockHttpClient = GetMockHttpClientWithResponse();

VersionHelper.CliVersion = "OlderVersion";
VineethReyya marked this conversation as resolved.
Show resolved Hide resolved
var result = await VersionHelper.IsRunningAnOlderVersion(mockHttpClient);

result.Should().Be(true);
}

[Fact]
public async Task IsRunningAnOlderVersion_ShouldReturnFalse_WhenVersionIsUpToDate()
{
// Create the mocked HttpClient with the mock response
var mockHttpClient = GetMockHttpClientWithResponse();

VersionHelper.CliVersion = "LatestVersion-4.0";
VineethReyya marked this conversation as resolved.
Show resolved Hide resolved
var result = await VersionHelper.IsRunningAnOlderVersion(mockHttpClient);

result.Should().Be(false);
}

// Method to return a mocked HttpClient
private HttpClient GetMockHttpClientWithResponse()
{
var mockJsonResponse = @"{
'tag_name':'LatestVersion-4.0',
VineethReyya marked this conversation as resolved.
Show resolved Hide resolved
'target_commitish': '48490a7ee744ed435fdce62f5e1f2f39c61c5309',
'name': '4.0.6610',
'draft': false,
'prerelease': false,
'created_at': '',
'published_at': '2024-11-13T22:08:49Z',
}";
var mockHandler = new Mock<HttpMessageHandler>();

// Mock the SendAsync method to return a mocked response
mockHandler.Protected()
.Setup<Task<HttpResponseMessage>>("SendAsync",
ItExpr.IsAny<HttpRequestMessage>(),
ItExpr.IsAny<CancellationToken>())
.ReturnsAsync(new HttpResponseMessage
{
StatusCode = HttpStatusCode.OK,
Content = new StringContent(mockJsonResponse)
});

// Return HttpClient with mocked handler
return new HttpClient(mockHandler.Object) { Timeout = TimeSpan.FromSeconds(1) };
}
}
}
Loading