Skip to content

Command for generation of EOL annotation data file#1358

Merged
mthalman merged 17 commits intodotnet:mainfrom
NikolaMilosavljevic:generate.annotations
Jul 22, 2024
Merged

Command for generation of EOL annotation data file#1358
mthalman merged 17 commits intodotnet:mainfrom
NikolaMilosavljevic:generate.annotations

Conversation

@NikolaMilosavljevic
Copy link
Member

@NikolaMilosavljevic NikolaMilosavljevic commented Jul 1, 2024

Contributes to #1201

Opening this PR as a draft. There are some areas that could use a little more cleanup and refactoring.

This work adds a new command, with related tests, for generation of EOL annotation data file.

The command is doing the following:

  • Compares old and new image-info files to determine which digests should be marked for EOL
  • Utilizes Microsoft.Deployment.DotNet.Releases library to determine EOL dates of .NET products and use them in annotations of images and platforms
  • Queries Azure event log telemetry to detect any 'dangling' digests - i.e. those that are present in ACR but were never added to image-info files

Pipeline work, to enable annotation of EOL digests is not present in this PR. That work isn't complex, however it is incomplete and untested. Shared in https://github.com/NikolaMilosavljevic/docker-tools/pull/new/pipeline.eol.annotations

@NikolaMilosavljevic NikolaMilosavljevic added the do-not-merge A PR that should not be merged yet but needs to remain open label Jul 1, 2024
@NikolaMilosavljevic NikolaMilosavljevic requested a review from a team as a code owner July 1, 2024 23:29
@ghost ghost added the untriaged label Jul 1, 2024
@ghost
Copy link

ghost commented Jul 1, 2024

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

1 similar comment
@ghost
Copy link

ghost commented Jul 1, 2024

I couldn't figure out the best area label to add to this PR. If you have write-permissions please help me learn by adding exactly one area label.

Copy link
Member

@mthalman mthalman left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't looked at the tests yet but this to be on track with what I was expecting.

@mthalman
Copy link
Member

mthalman commented Jul 2, 2024

@dagood - Be aware that we're adding a dependency on Azure Logs Workspace here that contains the ACR events. That would be necessary to configure in order to support EOL annotations.

NikolaMilosavljevic and others added 3 commits July 2, 2024 14:00
Co-authored-by: Matt Thalman <mthalman@microsoft.com>
…tationDataCommand.cs

Co-authored-by: Matt Thalman <mthalman@microsoft.com>
Comment on lines 9 to 26
namespace Microsoft.DotNet.ImageBuilder.Models.Annotations
{
public class AcrEventEntry
{
// TODO: at the moment we only use 'TimeGenerated' and 'Digest'
// We should likely delete the other properties.

public DateTime TimeGenerated { get; set; }
public string OperationName { get; set; }
public string Repository { get; set; }
public string Tag { get; set; }
public string Digest { get; set; }

public AcrEventEntry()
{
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
namespace Microsoft.DotNet.ImageBuilder.Models.Annotations
{
public class AcrEventEntry
{
// TODO: at the moment we only use 'TimeGenerated' and 'Digest'
// We should likely delete the other properties.
public DateTime TimeGenerated { get; set; }
public string OperationName { get; set; }
public string Repository { get; set; }
public string Tag { get; set; }
public string Digest { get; set; }
public AcrEventEntry()
{
}
}
}
namespace Microsoft.DotNet.ImageBuilder.Models.Annotations;
public record AcrEventEntry(DateTime TimeGenerated, string Digest);

@mthalman mthalman requested a review from lbussell July 18, 2024 13:03
@mthalman mthalman changed the title [DRAFT] Command for generation of EOL annotation data file Command for generation of EOL annotation data file Jul 18, 2024
@mthalman mthalman removed the do-not-merge A PR that should not be merged yet but needs to remain open label Jul 18, 2024
Comment on lines 8 to 12
using System.Linq;
using Microsoft.Deployment.DotNet.Releases;
using System.Threading.Tasks;
using Microsoft.DotNet.ImageBuilder.Models.Annotations;
using Newtonsoft.Json;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
using System.Linq;
using Microsoft.Deployment.DotNet.Releases;
using System.Threading.Tasks;
using Microsoft.DotNet.ImageBuilder.Models.Annotations;
using Newtonsoft.Json;
using Microsoft.Deployment.DotNet.Releases;
using System.Threading.Tasks;

Unused imports

Comment on lines 24 to 26
ProductCollection col = await ProductCollection.GetAsync();

foreach (Product product in col)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
ProductCollection col = await ProductCollection.GetAsync();
foreach (Product product in col)
ProductCollection dotnetProducts = await ProductCollection.GetAsync();
foreach (Product product in dotnetProducts)

Naming could be more clear here.

Comment on lines 11 to 19
public class EolDigestData
{
[JsonProperty(Required = Required.Always)]
public string Digest { get; set; }
public string Digest { get; set; } = string.Empty;

// This isn't read from programmatically, but is useful for debugging
public string? Tag { get; set; }

public DateOnly? EolDate { get; set; }
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public class EolDigestData
{
[JsonProperty(Required = Required.Always)]
public string Digest { get; set; }
public string Digest { get; set; } = string.Empty;
// This isn't read from programmatically, but is useful for debugging
public string? Tag { get; set; }
public DateOnly? EolDate { get; set; }
public record EolDigestData
{
[JsonProperty(Required = Required.Always)]
public required string Digest { get; init; } = string.Empty;
// This isn't read from programmatically, but is useful for debugging
public string? Tag { get; init; }
public DateOnly? EolDate { get; init; }

These can be init-only

public const string DefaultAzureManagementScope = "https://management.azure.com" + ScopeSuffix;
public const string ContainerRegistryScope = "https://containerregistry.azure.net" + ScopeSuffix;
public const string McrStatusScope = "api://c00053c3-a979-4ee6-b94e-941881e62d8e" + ScopeSuffix;
public const string LogAnalyticsScope = "https://api.loganalytics.io" + ScopeSuffix;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
public const string LogAnalyticsScope = "https://api.loganalytics.io" + ScopeSuffix;

This is not referenced anywhere.

[Export(typeof(IDotNetReleasesService))]
public class DotNetReleasesService : IDotNetReleasesService
{
public async Task<Dictionary<string, DateOnly?>> GetProductEolDatesFromReleasesJson()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is DateOnly nullable here? Will DateOnly.FromDateTime below ever return null? Could this be non-nullable and missing dates be represented by being absent from the dictionary?

base.GetCliArguments()
.Concat(
[
new Argument<string>(nameof(AnnotateEolDigestsOptions.EolDigestsListOutputPath),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
new Argument<string>(nameof(AnnotateEolDigestsOptions.EolDigestsListOutputPath),
new Argument<string>(nameof(GenerateEolAnnotationDataOptions.EolDigestsListPath),

I think this one is a bug - it causes an exception later on when you run the code.

/// <summary>
/// Finds all the digests that are in the registry but not in the supported digests list.
/// </summary>
private static IEnumerable<EolDigestData> GetUnsupportedDigests(IEnumerable<(string Digest, string? Tag)> registryDigests, IEnumerable<string> supportedDigests)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method can just be:

registryDigests
    .Where(registryDigest => !supportedDigests.Contains(registryDigest.Digest))
    .Select(registryDigest => new EolDigestData(registryDigest.Digest) { Tag = registryDigest.Tag });

private IEnumerable<string> GetSupportedDigests() =>
_newImageArtifactDetails.Repos
.SelectMany(repo => repo.Images)
.SelectMany(image => GetImageDigests(image))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.SelectMany(image => GetImageDigests(image))
.SelectMany(GetImageDigests)

private static string? GetLongestTag(IEnumerable<string> tags) =>
tags.OrderByDescending(tag => tag.Length).FirstOrDefault();

private async Task<IEnumerable<(string Digest, string? Tag)>> GetAllDigestsFromRegistry(IEnumerable<string> repoNames)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have nothing to say besides I tried to speed up this method and I couldn't :)

Comment on lines 215 to 224
if (image.ProductVersion != null)
{
string dotnetVersion = Version.Parse(image.ProductVersion).ToString(2);
if (_productEolDates != null && _productEolDates.TryGetValue(dotnetVersion, out DateOnly? date))
{
return GetImageDigests(image).Select(val => new EolDigestData(val.Digest) { Tag = val.Tag, EolDate = date });
}
}

return [];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Consider inverting these conditions and returning early?

Suggested change
if (image.ProductVersion != null)
{
string dotnetVersion = Version.Parse(image.ProductVersion).ToString(2);
if (_productEolDates != null && _productEolDates.TryGetValue(dotnetVersion, out DateOnly? date))
{
return GetImageDigests(image).Select(val => new EolDigestData(val.Digest) { Tag = val.Tag, EolDate = date });
}
}
return [];
if (image.ProductVersion == null)
{
return [];
}
string dotnetVersion = Version.Parse(image.ProductVersion).ToString(2);
if (_productEolDates == null || !_productEolDates.TryGetValue(dotnetVersion, out DateOnly? date))
{
return [];
}
return GetImageDigests(image).Select(val => new EolDigestData(val.Digest) { Tag = val.Tag, EolDate = date });

@mthalman mthalman requested a review from lbussell July 18, 2024 20:25
Copy link
Member

@lbussell lbussell left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one question.

Comment on lines +101 to +109
if (Options.AnnotateEolProducts)
{
// Annotate images for eol products in new image info
foreach (ImageData image in newImageArtifactDetails.Repos.SelectMany(repo => repo.Images))
{
digestDataList.AddRange(GetProductEolDigests(image, productEolDates));
}
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry if this has already been discussed. But in what case would we decide to build and ship an image for a product that is already EOL, and mark it as EOL immediately?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For .NET major version EOL. The last patch ships on the EOL date.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants