diff --git a/src/Cli.Tests/VersionCheckTests.cs b/src/Cli.Tests/VersionCheckTests.cs new file mode 100644 index 0000000000..2d14fe36c2 --- /dev/null +++ b/src/Cli.Tests/VersionCheckTests.cs @@ -0,0 +1,31 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +using Azure.DataApiBuilder.Core; + +namespace Cli.Tests; + +[TestClass] +public class VersionCheckTests +{ + [TestMethod] + public void GetVersions_LatestVersionNotNull() + { + VersionChecker.IsCurrentVersion(out string? nugetVersion, out string? _); + Assert.IsNotNull(nugetVersion, "Nuget version should not be null."); + } + + [TestMethod] + public void GetVersions_CurrentVersionNotNull() + { + VersionChecker.IsCurrentVersion(out string? _, out string? localVersion); + Assert.IsNotNull(localVersion, "Local version should not be null."); + } + + [TestMethod] + public void GetVersions_IsNotInNuGet() + { + bool result = VersionChecker.IsCurrentVersion(out string? nugetVersion, out string? localVersion); + Assert.IsFalse(result, $"Should not be in NuGet. {localVersion} -> {nugetVersion}"); + } +} diff --git a/src/Core/Configurations/RuntimeConfigValidator.cs b/src/Core/Configurations/RuntimeConfigValidator.cs index 5620ed352e..ddf8ef6340 100644 --- a/src/Core/Configurations/RuntimeConfigValidator.cs +++ b/src/Core/Configurations/RuntimeConfigValidator.cs @@ -141,6 +141,11 @@ public async Task TryValidateConfig( string configFilePath, ILoggerFactory loggerFactory) { + if (!VersionChecker.IsCurrentVersion(out string? nugetVersion, out string? localVersion)) + { + _logger.LogWarning("A newer version of Data API builder is available. Update {LocalVersion} -> {NugetVersion}.", localVersion, nugetVersion); + } + RuntimeConfig? runtimeConfig; if (!_runtimeConfigProvider.TryGetConfig(out runtimeConfig)) diff --git a/src/Core/VersionChecker.cs b/src/Core/VersionChecker.cs new file mode 100644 index 0000000000..28d25b513a --- /dev/null +++ b/src/Core/VersionChecker.cs @@ -0,0 +1,63 @@ +using System.Net.Http.Json; +using System.Text.Json.Serialization; +using Azure.DataApiBuilder.Product; + +namespace Azure.DataApiBuilder.Core; + +/// +/// A class to test the local version of the engine against the NuGet version. +/// +/// +/// This is used in startup to suggest upgrading the engine. +/// +public static class VersionChecker +{ + private const string NUGETURL = "https://api.nuget.org/v3-flatcontainer/microsoft.dataapibuilder/index.json"; + + /// + /// Checks if the current local version of the product matches the latest version available on NuGet. + /// + /// Outputs the latest version available on NuGet. + /// Outputs the current local version of the product. + /// + /// Returns true if the local version matches the latest NuGet version or if the NuGet version is not available; + /// otherwise, returns false. + /// + /// + // If the internet is unavailable or NuGet is down or the HTTP request fails for any reason + /// (there is a 2 second Timeout on the request), then the NuGet version will be null and + // this method will return true. This is mostly because this check is a user courtesy. + // + public static bool IsCurrentVersion(out string? nugetVersion, out string? localVersion) + { + nugetVersion = FetchLatestNuGetVersion(); + localVersion = ProductInfo.GetProductVersion(false); + return string.IsNullOrEmpty(nugetVersion) || nugetVersion == localVersion; + } + + private static string? FetchLatestNuGetVersion() + { + try + { + using HttpClient httpClient = new() { Timeout = TimeSpan.FromSeconds(2) }; + NuGetVersionResponse? versionData = httpClient + .GetFromJsonAsync(new Uri(NUGETURL).ToString()) + .GetAwaiter().GetResult(); + + return versionData?.Versions + ?.Where(version => !version.Contains("-rc")) // Filter out pre-release versions + .Select(version => new Version(version)) // Convert to Version objects + .Max()?.ToString(); // Get the latest + } + catch (Exception) + { + return null; // Assume no update available on failure + } + } + + private class NuGetVersionResponse + { + [JsonPropertyName("versions")] + public string[]? Versions { get; set; } + } +}