Command for generation of EOL annotation data file#1358
Command for generation of EOL annotation data file#1358mthalman merged 17 commits intodotnet:mainfrom
Conversation
|
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
|
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. |
mthalman
left a comment
There was a problem hiding this comment.
I haven't looked at the tests yet but this to be on track with what I was expecting.
src/Microsoft.DotNet.ImageBuilder/src/Commands/GenerateEolAnnotationDataCommand.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.ImageBuilder/src/Commands/GenerateEolAnnotationDataCommand.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.ImageBuilder/src/Commands/GenerateEolAnnotationDataCommand.cs
Outdated
Show resolved
Hide resolved
src/Microsoft.DotNet.ImageBuilder/src/Commands/GenerateEolAnnotationDataCommand.cs
Outdated
Show resolved
Hide resolved
|
@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. |
Co-authored-by: Matt Thalman <mthalman@microsoft.com>
…tationDataCommand.cs Co-authored-by: Matt Thalman <mthalman@microsoft.com>
| 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() | ||
| { | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
| 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); |
| using System.Linq; | ||
| using Microsoft.Deployment.DotNet.Releases; | ||
| using System.Threading.Tasks; | ||
| using Microsoft.DotNet.ImageBuilder.Models.Annotations; | ||
| using Newtonsoft.Json; |
There was a problem hiding this comment.
| 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
| ProductCollection col = await ProductCollection.GetAsync(); | ||
|
|
||
| foreach (Product product in col) |
There was a problem hiding this comment.
| 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.
| 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; } |
There was a problem hiding this comment.
| 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; |
There was a problem hiding this comment.
| 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() |
There was a problem hiding this comment.
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), |
There was a problem hiding this comment.
| 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) |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
| .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) |
There was a problem hiding this comment.
I have nothing to say besides I tried to speed up this method and I couldn't :)
| 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 []; |
There was a problem hiding this comment.
Consider inverting these conditions and returning early?
| 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 }); |
| 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)); | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
For .NET major version EOL. The last patch ships on the EOL date.
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:
Microsoft.Deployment.DotNet.Releaseslibrary to determine EOL dates of .NET products and use them in annotations of images and platformsPipeline 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