Skip to content

Commit

Permalink
WIP - create GH issue listing packages to deprecate
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr committed Aug 4, 2023
1 parent 0c3c3ee commit c9e42b3
Show file tree
Hide file tree
Showing 8 changed files with 214 additions and 59 deletions.
11 changes: 11 additions & 0 deletions build/NugetVersionDeprecator/AgentRelease.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using Newtonsoft.Json;

namespace NugetVersionDeprecator;

internal class AgentRelease
{
[JsonProperty("eolDate")]
public DateTime EolDate { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
}
11 changes: 11 additions & 0 deletions build/NugetVersionDeprecator/Configuration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// Copyright 2023 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

namespace NugetVersionDeprecator
{
public class Configuration
{
[YamlDotNet.Serialization.YamlMember(Alias = "nuget-packages")]
public List<string> Packages { get; set; }
}
}
12 changes: 12 additions & 0 deletions build/NugetVersionDeprecator/ExitCode.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// Copyright 2023 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

namespace NugetVersionDeprecator;

public enum ExitCode : int
{
Success = 0,
Error = 1,
FileNotFound = 2,
BadArguments = 160
}
12 changes: 12 additions & 0 deletions build/NugetVersionDeprecator/NugetVersionDeprecator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,21 @@
<LangVersion>default</LangVersion>
</PropertyGroup>

<ItemGroup>
<None Remove="config.yml" />
</ItemGroup>

<ItemGroup>
<Content Include="config.yml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>

<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.9.1" />
<PackageReference Include="NuGet.Protocol" Version="6.6.1" />
<PackageReference Include="Octokit" Version="7.1.0" />
<PackageReference Include="YamlDotNet" Version="13.1.1" />
</ItemGroup>

</Project>
18 changes: 18 additions & 0 deletions build/NugetVersionDeprecator/Options.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
// Copyright 2023 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using CommandLine;

namespace NugetVersionDeprecator;

public class Options
{
[Option('t', "test-mode", Required = true, HelpText = "Test mode, report deprecated packages but don't create GH Issue")]
public bool TestMode { get; set; }

[Option('c', "config", Default = "config.yml", Required = false, HelpText = "Path to the configuration file.")]
public required string ConfigurationPath { get; set; }

[Option('g', "github-token", Required = true, HelpText = "The Github token to use when creating new issues")]
public string GithubToken { get; set; }
}
5 changes: 5 additions & 0 deletions build/NugetVersionDeprecator/PackageDeprecationInfo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
public class PackageDeprecationInfo
{
public string PackageName { get; set; }
public string PackageVersion { get; set; }
}
192 changes: 133 additions & 59 deletions build/NugetVersionDeprecator/Program.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Net;
using System.Text;
using CommandLine;
using Newtonsoft.Json.Linq;
using NuGet.Common;
using NuGet.Protocol;
using NuGet.Protocol.Core.Types;
using NuGet.Versioning;
using Octokit;
using Repository = NuGet.Protocol.Core.Types.Repository;

namespace NugetVersionDeprecator
namespace NugetVersionDeprecator;

internal class Program
{
internal class Program
{
private const string RepoUrl = "https://api.nuget.org/v3/index.json";
private const string RepoUrl = "https://api.nuget.org/v3/index.json";

private const string NerdGraphQuery = @"
private const string NerdGraphQuery = @"
{
docs {
agentReleases(agentName: DOTNET) {
Expand All @@ -21,7 +26,7 @@ internal class Program
}
}";

private const string NerdGraphSample = @"
private const string NerdGraphSample = @"
{
""data"": {
""docs"": {
Expand Down Expand Up @@ -828,85 +833,154 @@ internal class Program
}
}";

// Need: A list of package names to query
// NewRelic.Agent
// NewRelic.Agent.API
// NewRelicWindowsAzure (?)
// NewRelic.Azure.Websites
// NewRelic.Azure.Websites.x64
static async Task Main(string[] args)

static async Task Main(string[] args)
{
var options = Parser.Default.ParseArguments<Options>(args)
.WithParsed(ValidateOptions)
.WithNotParsed(HandleParseError)
.Value;

{
DateTime releaseDate = DateTime.Today; // TODO: pass as a parameter
if (options.TestMode)
Console.WriteLine("**** TEST MODE *** No Github Issues will be created.");

var configuration = LoadConfiguration(options.ConfigurationPath);

Console.WriteLine("Hello, World!");
var deprecatedReleases = await QueryNerdGraphAsync(DateTime.UtcNow);

// query the docs API to get a current list of .NET Agent versions
// TODO: figure out how to query NerdGraph Api from code
//
if (deprecatedReleases.Any())
{
List<PackageDeprecationInfo> packagesToDeprecate = new();

// parse the NerdGraph response -- we want to deserialize data.docs.agentReleases into an array of AgentRelease
var parsedResponse = JObject.Parse(NerdGraphSample);
var allAgentReleases = parsedResponse.SelectToken("data.docs.agentReleases")?.ToObject<List<AgentRelease>>();
if (allAgentReleases == null)
foreach (var package in configuration.Packages)
{
Console.WriteLine($"Unable to parse NerdGraph response: {Environment.NewLine}{NerdGraphSample}");
return;
packagesToDeprecate.AddRange(await GetPackagesToDeprecateAsync(package, deprecatedReleases, DateTime.UtcNow.Date));
}
// parse off the 3rd dot and anything after it so we have a semantic version

var deprecatedReleases = allAgentReleases.Where(ar => ar.EolDate <= releaseDate).ToList();
if (deprecatedReleases.Any())
{
await CheckAndDeprecateAsync("NewRelic.Agent.API", deprecatedReleases, DateTime.UtcNow.Date);
}
else
if (packagesToDeprecate.Any())
{
Console.WriteLine("No eligible deprecated releases found.");
return;
var message = ReportPackagesToDeprecate(packagesToDeprecate, deprecatedReleases);
Console.WriteLine(message);

if (!options.TestMode)
await CreateGhIssueAsync(message, options.GithubToken);
}
}
else
{
Console.WriteLine("No eligible deprecated Agent released found.");
}
}

private static async Task<List<AgentRelease>> QueryNerdGraphAsync(DateTime releaseDate)
{
// query the docs API to get a list of all .NET Agent versions

static async Task CheckAndDeprecateAsync(string packageName, List<AgentRelease> versionList, DateTime releaseDate)
// TODO: figure out how to query NerdGraph Api from code
// for now, use a static sample response
var nerdGraphResponse = NerdGraphSample;

// parse the NerdGraph response -- we want to deserialize data.docs.agentReleases into an array of AgentRelease
var parsedResponse = JObject.Parse(nerdGraphResponse);
var allAgentReleases = parsedResponse.SelectToken("data.docs.agentReleases", false)?.ToObject<List<AgentRelease>>();
if (allAgentReleases == null)
{
throw new Exception($"Unable to parse NerdGraph response: {Environment.NewLine}{nerdGraphResponse}");
}

var deprecatedReleases = allAgentReleases.Where(ar => ar.EolDate <= releaseDate).ToList();

// TODO: refactor when doing real async, or make method non-async
return await Task.FromResult(deprecatedReleases);
}

private static string ReportPackagesToDeprecate(List<PackageDeprecationInfo> packagesToDeprecate, List<AgentRelease> deprecatedReleases)
{
var sb = new StringBuilder();

sb.AppendLine("The following NuGet packages should be deprecated:");
foreach (var package in packagesToDeprecate)
{
var eolRelease = deprecatedReleases.Single(ar => ar.Version.StartsWith(package.PackageVersion));

sb.AppendLine($" * {package.PackageName} v{package.PackageVersion} (EOL as of {eolRelease.EolDate.ToShortDateString()})");
}

return sb.ToString();
}

// query NuGet for a current list of non-deprecated versions of all .NET Agent packages
SourceCacheContext cache = new SourceCacheContext();
SourceRepository repository = Repository.Factory.GetCoreV3(RepoUrl);
PackageMetadataResource resource = await repository.GetResourceAsync<PackageMetadataResource>();
var packages = (await resource.GetMetadataAsync(
static async Task<IEnumerable<PackageDeprecationInfo>> GetPackagesToDeprecateAsync(string packageName, List<AgentRelease> versionList, DateTime releaseDate)
{
// query NuGet for a current list of non-deprecated versions of all .NET Agent packages
SourceCacheContext cache = new SourceCacheContext();
SourceRepository repository = Repository.Factory.GetCoreV3(RepoUrl);
PackageMetadataResource resource = await repository.GetResourceAsync<PackageMetadataResource>();
var packages = (await resource.GetMetadataAsync(
packageName,
includePrerelease: false,
includeUnlisted: false,
cache,
NullLogger.Instance,
CancellationToken.None)).Cast<PackageSearchMetadata>()
.Where(p =>
p.DeprecationMetadata is null
&& string.Equals(p.Identity.Id, packageName, StringComparison.CurrentCultureIgnoreCase)).ToList();
.Where(p =>
p.DeprecationMetadata is null
&& string.Equals(p.Identity.Id, packageName, StringComparison.CurrentCultureIgnoreCase)).ToList();


// intersect the two lists and build a list of NuGet versions that should be deprecated
var deprecatedVersions = versionList.Select(ar => NuGetVersion.Parse(ar.Version)).ToList();
var packagesToDeprecate = packages.Where(p => deprecatedVersions.Contains(p.Version)).ToList();

// iterate the list of versions to deprecate and call the NuGet API to deprecate them
var pkgUpdateResource = await repository.GetResourceAsync<PackageUpdateResource>();
// intersect the two lists and build a list of NuGet versions that should be deprecated
var deprecatedVersions = versionList.Select(ar => NuGetVersion.Parse(ar.Version)).ToList();
var packagesToDeprecate = packages.Where(p => deprecatedVersions.Contains(p.Version)).ToList();

foreach (var package in packagesToDeprecate)
{
// call Nuget API to deprecate the package
}
return packagesToDeprecate.Select(p =>
new PackageDeprecationInfo() { PackageName = p.PackageId, PackageVersion = p.Version.ToString() });
}

static async Task CreateGhIssueAsync(string message, string githubToken)
{
var ghClient = new GitHubClient(new Octokit.ProductHeaderValue("NugetVersionDeprecator"));
var tokenAuth = new Credentials(githubToken);
ghClient.Credentials = tokenAuth;


var newIssue = new NewIssue($"chore(NugetDeprecator): NuGet packages need to be deprecated.")
{
Body = message
};

newIssue.Labels.Add("Deprecation");
newIssue.Labels.Add("NuGet");

await ghClient.Issue.Create("newrelic", "newrelic-dotnet-agent", newIssue);
}

static Configuration LoadConfiguration(string path)
{
var input = File.ReadAllText(path);
var deserializer = new YamlDotNet.Serialization.Deserializer();
return deserializer.Deserialize<Configuration>(input);
}

static void ValidateOptions(Options opts)
{
if (string.IsNullOrWhiteSpace(opts.ConfigurationPath) || string.IsNullOrWhiteSpace(opts.GithubToken))
{
ExitWithError(ExitCode.BadArguments, "One or more required arguments were not supplied.");
}

if (!File.Exists(opts.ConfigurationPath))
{
ExitWithError(ExitCode.FileNotFound, $"Configuration file did not exist at {opts.ConfigurationPath}.");
}
}

static void HandleParseError(IEnumerable<Error> errs)
{
ExitWithError(ExitCode.BadArguments, "Error occurred while parsing command line arguments.");
}

internal class AgentRelease
static void ExitWithError(ExitCode exitCode, string message)
{
[JsonProperty("eolDate")]
public DateTime EolDate { get; set; }
[JsonProperty("version")]
public string Version { get; set; }
Console.WriteLine(message);
Environment.Exit((int)exitCode);
}
}
12 changes: 12 additions & 0 deletions build/NugetVersionDeprecator/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright 2023 New Relic, Inc. All rights reserved.
# SPDX-License-Identifier: Apache-2.0

# NuGetPackages
# A list of NuGet package names to validate
nuget-packages:
- NewRelic.Agent
- NewRelic.Agent.Api
- NewRelic.Azure.Websites
- NewRelic.Azure.Websites.x64
- NewRelicWindowsAzure

0 comments on commit c9e42b3

Please sign in to comment.