Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
569 changes: 437 additions & 132 deletions .editorconfig

Large diffs are not rendered by default.

16 changes: 12 additions & 4 deletions .github/workflows/update-dependencies-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,13 @@ name: Update Dependencies Tests
on:
pull_request:
paths:
- '.editorconfig'
- 'Directory.Build.props'
- 'Microsoft.DotNet.Docker.slnx'
- 'eng/update-dependencies/**'
- 'tests/UpdateDependencies.Tests/**'
- 'eng/shared/**'
- 'tests/**'
- '!tests/Microsoft.DotNet.Docker.Tests/TestAppArtifacts/**'
- '.github/workflows/update-dependencies-tests.yml'

permissions: {}
Expand All @@ -28,13 +33,16 @@ jobs:
- name: Restore dependencies
run: >
dotnet restore
tests/UpdateDependencies.Tests/UpdateDependencies.Tests.csproj
Microsoft.DotNet.Docker.slnx
--verbosity normal

- name: Build tests
- name: Verify formatting
run: dotnet format Microsoft.DotNet.Docker.slnx --no-restore --verify-no-changes

- name: Build solution
run: >
dotnet build
tests/UpdateDependencies.Tests/UpdateDependencies.Tests.csproj
Microsoft.DotNet.Docker.slnx
--no-restore

- name: Run tests
Expand Down
9 changes: 9 additions & 0 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<Project>
<PropertyGroup>
<AnalysisMode>Recommended</AnalysisMode>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<!-- IDE0005 (remove unnecessary using directives) requires XML documentation processing. -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
</Project>
5 changes: 0 additions & 5 deletions eng/shared/.editorconfig

This file was deleted.

12 changes: 7 additions & 5 deletions eng/shared/DotNetVersion.cs
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System.Globalization;
using System.Text.RegularExpressions;
using NuGet.Versioning;

#pragma warning disable CA1716 // Avoid language keywords in namespaces: this shared code is consumed only by C# projects.
namespace Microsoft.DotNet.Docker.Shared;
#pragma warning restore CA1716

/// <summary>
/// Represents the version of a .NET build artifact.
Expand All @@ -31,7 +33,7 @@ public sealed partial class DotNetVersion(SemanticVersion version) : SemanticVer
// SemanticVersion only handles full, strict semantic versions.
// If we can parse it as a full semantic version, use that. It resolves
// full build versions correctly including prerelease suffixes (e.g. '-preview.7')
if (SemanticVersion.TryParse(versionString, out var fullSemanticVersion))
if (TryParse(versionString, out SemanticVersion? fullSemanticVersion))
{
return new DotNetVersion(fullSemanticVersion);
}
Expand All @@ -42,9 +44,9 @@ public sealed partial class DotNetVersion(SemanticVersion version) : SemanticVer
// we don't need to account for that in our parsing.
// Don't use System.Version.Parse here, because it defaults unmatched
// minor/patch versions to -1, whereas we want them to default to 0.
var matchGroups = MajorMinorVersionRegex.Match(versionString).Groups;
int major = int.Parse(matchGroups["major"].Value);
int minor = int.TryParse(matchGroups["minor"].Value.TrimStart('.'), out int minorValue) ? minorValue : 0;
GroupCollection matchGroups = MajorMinorVersionRegex.Match(versionString).Groups;
int major = int.Parse(matchGroups["major"].Value, CultureInfo.InvariantCulture);
int minor = int.TryParse(matchGroups["minor"].Value.TrimStart('.'), NumberStyles.Integer, CultureInfo.InvariantCulture, out int minorValue) ? minorValue : 0;
return new DotNetVersion(new SemanticVersion(major, minor, 0));
}

Expand Down
4 changes: 0 additions & 4 deletions eng/shared/Microsoft.DotNet.Docker.Shared.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,7 @@
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<RootNamespace>Microsoft.DotNet.Docker.Shared</RootNamespace>

<!-- Enables CS1591, which lets us know when public members aren't documented -->
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>

<ItemGroup>
Expand Down
6 changes: 3 additions & 3 deletions eng/update-dependencies/AzdoAuthProvider.cs
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,9 @@ public string AccessToken
/// </returns>
public VssConnection GetVssConnection(string azdoOrg)
{
var baseUrl = new Uri(azdoOrg);
var credential = new VssBasicCredential(userName: string.Empty, password: AccessToken);
var connection = new VssConnection(baseUrl, credential);
Uri baseUrl = new Uri(azdoOrg);
VssBasicCredential credential = new VssBasicCredential(userName: string.Empty, password: AccessToken);
VssConnection connection = new VssConnection(baseUrl, credential);
return connection;
}
}
Expand Down
2 changes: 1 addition & 1 deletion eng/update-dependencies/AzdoHttpClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ internal class AzdoHttpClient(IAzdoAuthProvider azdoAuthProvider, HttpClient htt
{
public async Task<HttpResponseMessage> GetAsync(string requestUri, CancellationToken ct = default)
{
using var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
using HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Get, requestUri);
request.Headers.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(
Expand Down
25 changes: 11 additions & 14 deletions eng/update-dependencies/BuildAssetService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,37 +22,37 @@ public async Task<string> GetAssetTextContentsAsync(Asset asset)
string url = ResolveAssetUrl(asset);
try
{
_logger.LogInformation("Fetching contents from {url}", url);
var response = await _httpClient.GetAsync(url);
_logger.LogInformation("Fetching contents from {Url}", url);
HttpResponseMessage response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();
string content = await response.Content.ReadAsStringAsync();
_logger.LogInformation(
"""
Successfully fetched contents from {url}:
{content}
Successfully fetched contents from {Url}:
{Content}
""",
url, content);
// Remove trailing newlines
return content.Trim();
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to fetch contents from {url}", url);
_logger.LogError(ex, "Failed to fetch contents from {Url}", url);
throw;
}
}

private string ResolveAssetUrl(Asset asset)
{
var blobStorageLocations = asset.Locations.Where(l => l.Location.Contains("blob.core.windows.net"));
var azdoLocations = asset.Locations.Where(l => l.Location.Contains("dev.azure.com"));
IEnumerable<AssetLocation> blobStorageLocations = asset.Locations.Where(l => l.Location.Contains("blob.core.windows.net"));
IEnumerable<AssetLocation> azdoLocations = asset.Locations.Where(l => l.Location.Contains("dev.azure.com"));

string allLocations =
string.Join(Environment.NewLine, asset.Locations.Select(l => $"Type: {l.Type}; Url: {l.Location}"));
_logger.LogInformation(
"""
Asset {asset.Name} has {asset.Locations.Count} locations:
{allLocations}
Asset {AssetName} has {LocationCount} locations:
{AllLocations}
""",
asset.Name, asset.Locations.Count, allLocations);

Expand All @@ -63,12 +63,9 @@ private string ResolveAssetUrl(Asset asset)
?? throw new InvalidOperationException(FormatErrorMessage(asset, "does not have any valid locations"));

string url = $"{bestLocation.Location}/{asset.Name}";
_logger.LogInformation("Using location {url}", url);
_logger.LogInformation("Using location {Url}", url);
return url;
}

private static string FormatErrorMessage(Asset asset, string message)
{
return $"Build {asset.BuildId} Asset {asset.Name} (version {asset.Version}) {message}";
}
private static string FormatErrorMessage(Asset asset, string message) => $"Build {asset.BuildId} Asset {asset.Name} (version {asset.Version}) {message}";
}
2 changes: 1 addition & 1 deletion eng/update-dependencies/BuildLabelService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ internal sealed class BuildLabelService(TextWriter output) : IBuildLabelService
/// <inheritdoc />
public void AddBuildTags(params IEnumerable<string> tags)
{
foreach (var tag in tags)
foreach (string tag in tags)
{
output.WriteLine($"##vso[build.addbuildtag]{tag}");
}
Expand Down
23 changes: 13 additions & 10 deletions eng/update-dependencies/ChecksumHelper.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Diagnostics;
using System.Globalization;
using System.Security.Cryptography;
using System.Text;

Expand All @@ -17,18 +21,17 @@ internal static class ChecksumHelper
{
if (response.IsSuccessStatusCode)
{
using (Stream httpStream = await response.Content.ReadAsStreamAsync(cancellationToken))
using (SHA512 hash = SHA512.Create())
{
byte[] hashedInputBytes = hash.ComputeHash(httpStream);
using Stream httpStream = await response.Content.ReadAsStreamAsync(cancellationToken);
using SHA512 hash = SHA512.Create();
byte[] hashedInputBytes = hash.ComputeHash(httpStream);

StringBuilder stringBuilder = new(128);
foreach (byte b in hashedInputBytes)
{
stringBuilder.Append(b.ToString("X2"));
}
sha = stringBuilder.ToString();
StringBuilder stringBuilder = new(128);
foreach (byte b in hashedInputBytes)
{
stringBuilder.Append(b.ToString("X2", CultureInfo.InvariantCulture));
}

sha = stringBuilder.ToString();
}
else
{
Expand Down
28 changes: 14 additions & 14 deletions eng/update-dependencies/Commands/CreatePullRequestOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,33 @@ public record CreatePullRequestOptions
public string TargetBranch { get; set; } = "nightly";
public bool SubmitPullRequest { get; set; }

private static readonly Option<string> s_repoRoot = new("--repo-root")
private static readonly Option<string> RepoRootOption = new("--repo-root")
{
Description = "The root of the dotnet-docker repo to run against (defaults to current working directory)",
DefaultValueFactory = _ => Directory.GetCurrentDirectory(),
};
private static readonly Option<bool> s_submitPullRequest = new("--submit-pr")
private static readonly Option<bool> SubmitPullRequestOption = new("--submit-pr")
{
Description = "Apply updates in a temporary clone and submit a pull request with them."
+ " By default, updates are applied to the local repo and no git operations are run.",
};
private static readonly Option<string> s_sourceBranch = new("--source-branch")
private static readonly Option<string> SourceBranchOption = new("--source-branch")
{
Description = "Branch to pull updates from",
DefaultValueFactory = _ => "",
};
private static readonly Option<string> s_targetBranch = new("--target-branch")
private static readonly Option<string> TargetBranchOption = new("--target-branch")
{
Description = "Pull request will be submitted targeting this branch",
DefaultValueFactory = _ => "nightly",
};

public static void AddTo(Command command)
{
command.Options.Add(s_repoRoot);
command.Options.Add(s_submitPullRequest);
command.Options.Add(s_sourceBranch);
command.Options.Add(s_targetBranch);
command.Options.Add(RepoRootOption);
command.Options.Add(SubmitPullRequestOption);
command.Options.Add(SourceBranchOption);
command.Options.Add(TargetBranchOption);
}

public static CreatePullRequestOptions Bind(ParseResult result) =>
Expand All @@ -47,17 +47,17 @@ public static CreatePullRequestOptions Bind(ParseResult result) =>
protected static TOptions Bind<TOptions>(ParseResult result, TOptions options)
where TOptions : CreatePullRequestOptions
{
options.RepoRoot = result.GetRequiredValue(s_repoRoot);
options.SubmitPullRequest = result.GetValue(s_submitPullRequest);
options.SourceBranch = result.GetRequiredValue(s_sourceBranch);
options.TargetBranch = result.GetRequiredValue(s_targetBranch);
options.RepoRoot = result.GetRequiredValue(RepoRootOption);
options.SubmitPullRequest = result.GetValue(SubmitPullRequestOption);
options.SourceBranch = result.GetRequiredValue(SourceBranchOption);
options.TargetBranch = result.GetRequiredValue(TargetBranchOption);
return options;
}

public string CreatePullRequestBranchName(string name, string buildId = "")
{
var buildIdSuffix = string.IsNullOrWhiteSpace(buildId) ? string.Empty : $"-{buildId}";
var sanitizedTargetBranch = TargetBranch.Replace('/', '-');
string buildIdSuffix = string.IsNullOrWhiteSpace(buildId) ? string.Empty : $"-{buildId}";
string sanitizedTargetBranch = TargetBranch.Replace('/', '-');
return $"{sanitizedTargetBranch}/{name}{buildIdSuffix}";
}
}
10 changes: 9 additions & 1 deletion eng/update-dependencies/Commands/DependencyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,19 +10,27 @@ internal static class DependencyCommand
{
public static Command CreateCliCommand<TUpdater>(IServiceProvider services) where TUpdater : class, IUpdater
{
var command = new Command(TUpdater.Name, $"Update {TUpdater.Name}");
Command command = new Command(TUpdater.Name, $"Update {TUpdater.Name}");

if (typeof(IBarBuildUpdater).IsAssignableFrom(typeof(TUpdater)))
{
command.Subcommands.Add(FromBuildCommand.CreateCliCommand<TUpdater>(services));
}

if (typeof(IBarChannelUpdater).IsAssignableFrom(typeof(TUpdater)))
{
command.Subcommands.Add(FromChannelCommand.CreateCliCommand<TUpdater>(services));
}

if (typeof(IStagingPipelineUpdater).IsAssignableFrom(typeof(TUpdater)))
{
command.Subcommands.Add(FromStagingPipelineCommand.CreateCliCommand(services));
}

if (typeof(IGitHubReleaseUpdater).IsAssignableFrom(typeof(TUpdater)))
{
FromGitHubReleaseCommand.Configure<TUpdater>(command, services);
}

return command;
}
Expand Down
8 changes: 4 additions & 4 deletions eng/update-dependencies/Commands/FromBuildCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ internal static class FromBuildCommand
{
public static Command CreateCliCommand<TUpdater>(IServiceProvider services) where TUpdater : class, IUpdater
{
var command = new Command("build-id", "Update from a specific BAR build");
Command command = new Command("build-id", "Update from a specific BAR build");
FromBuildOptions.AddTo(command);

command.SetAction(async (result, cancellationToken) =>
{
FromBuildOptions options = FromBuildOptions.Bind(result);
var barClient = services.GetRequiredService<IBasicBarClient>();
var updater = (IBarBuildUpdater)services.GetRequiredService<TUpdater>();
var runner = services.GetRequiredService<DependencyUpdateRunner>();
IBasicBarClient barClient = services.GetRequiredService<IBasicBarClient>();
IBarBuildUpdater updater = (IBarBuildUpdater)services.GetRequiredService<TUpdater>();
DependencyUpdateRunner runner = services.GetRequiredService<DependencyUpdateRunner>();
Build build = await barClient.GetBuildAsync(options.Id).WaitAsync(cancellationToken);
DependencyUpdate update = await updater.ResolveFromBarBuildAsync(build, cancellationToken);

Expand Down
6 changes: 3 additions & 3 deletions eng/update-dependencies/Commands/FromBuildOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ internal record FromBuildOptions : CreatePullRequestOptions
{
public required int Id { get; init; }

private static readonly PositiveIdArgument s_id = new("The BAR build ID to use as the update source");
private static readonly PositiveIdArgument IdArgument = new("The BAR build ID to use as the update source");

public static new void AddTo(Command command)
{
CreatePullRequestOptions.AddTo(command);
command.Arguments.Add(s_id);
command.Arguments.Add(IdArgument);
}

public static new FromBuildOptions Bind(ParseResult result) =>
Bind(result, new FromBuildOptions { Id = result.GetValue(s_id) });
Bind(result, new FromBuildOptions { Id = result.GetValue(IdArgument) });
}
6 changes: 3 additions & 3 deletions eng/update-dependencies/Commands/FromChannelCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ internal static class FromChannelCommand
{
public static Command CreateCliCommand<TUpdater>(IServiceProvider services) where TUpdater : class, IUpdater
{
var command = new Command("channel", "Update from the latest build in a BAR channel");
Command command = new Command("channel", "Update from the latest build in a BAR channel");
FromChannelOptions.AddTo(command);

command.SetAction(async (result, cancellationToken) =>
{
FromChannelOptions options = FromChannelOptions.Bind(result);
var updater = (IBarChannelUpdater)services.GetRequiredService<TUpdater>();
var runner = services.GetRequiredService<DependencyUpdateRunner>();
IBarChannelUpdater updater = (IBarChannelUpdater)services.GetRequiredService<TUpdater>();
DependencyUpdateRunner runner = services.GetRequiredService<DependencyUpdateRunner>();
DependencyUpdate update = await updater.ResolveFromBarChannelAsync(options.Channel, cancellationToken);

await runner.RunAsync(options, TUpdater.VersionSourceName, update, cancellationToken);
Expand Down
6 changes: 3 additions & 3 deletions eng/update-dependencies/Commands/FromChannelOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,14 @@ internal record FromChannelOptions : CreatePullRequestOptions
{
public required int Channel { get; init; }

private static readonly PositiveIdArgument s_channel = new("The BAR channel ID to use as the update source");
private static readonly PositiveIdArgument ChannelArgument = new("The BAR channel ID to use as the update source");

public static new void AddTo(Command command)
{
CreatePullRequestOptions.AddTo(command);
command.Arguments.Add(s_channel);
command.Arguments.Add(ChannelArgument);
}

public static new FromChannelOptions Bind(ParseResult result) =>
Bind(result, new FromChannelOptions { Channel = result.GetValue(s_channel) });
Bind(result, new FromChannelOptions { Channel = result.GetValue(ChannelArgument) });
}
Loading
Loading