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
9 changes: 5 additions & 4 deletions eng/dockerfile-templates/sdk/Dockerfile.linux
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@
cat(ARCH_VERSIONED, "/buildpack-deps:", osVersionBase, "-curl")) ^

set isPowerShellSupported to !(isAlpine && ARCH_SHORT != "x64") ^
set isPowerShellInternal to isPowerShellSupported &&
find(VARIABLES[cat("powershell|", dotnetVersion, "|base-url")], "pscoretestdata") >= 0 ^
set useAccessToken to isInternal || isPowerShellInternal ^
set includePowerShellVars to isPowerShellSupported || dotnetVersion = "8.0" || dotnetVersion = "9.0" ^
set powershellNupkgPlatform to when(isAlpine, "Linux.Alpine", cat("Linux.", ARCH_NUPKG)) ^
set powershellNupkgName to cat(
Expand Down Expand Up @@ -75,7 +78,7 @@

}}ARG REPO=mcr.microsoft.com/dotnet/aspnet
# Installer image
FROM {{internalInstallerBase}} AS installer{{if isInternal:
FROM {{internalInstallerBase}} AS installer{{if useAccessToken:

ARG ACCESSTOKEN}}
{{if isAzureLinux:
Expand All @@ -92,9 +95,7 @@ RUN {{InsertTemplate("../Dockerfile.download-dotnet", [
], " ")}}{{if isPowerShellSupported:

# Download PowerShell
RUN {{InsertTemplate("Dockerfile.linux.download-powershell", [
"dotnet-is-internal": isInternal
])}}}}
RUN {{InsertTemplate("Dockerfile.linux.download-powershell")}}}}


# .NET SDK image
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{{
_ ARGS:
dotnet-is-internal (optional): Whether the Dockerfile is targeting an internal build of the .NET product. ^

set dotnetVersion to join(slice(split(PRODUCT_VERSION, "."), 0, 2), ".") ^
set isAlpine to find(OS_VERSION, "alpine") >= 0 ^
set nupkgPlatform to when(isAlpine, "Linux.Alpine", cat("Linux.", ARCH_NUPKG)) ^
Expand All @@ -10,13 +7,16 @@
set shaFunction to VARIABLES[cat("powershell|", dotnetVersion, "|sha-function")] ^
set shaVarName to cat("powershell_sha", shaFunction) ^
set nupkgName to cat("PowerShell.", nupkgPlatform, ".$powershell_version.nupkg") ^
set url to when(
ARGS["dotnet-is-internal"],
cat("https://pscoretestdata.blob.core.windows.net/v$powershell_url_version-nuget/globaltool/", nupkgName),
cat("https://powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net/tool/$powershell_version/", nupkgName)
)
_ Refer to the version through shell variables so it is only written once. Internal storage
uses a dashed version (e.g. v7-5-11-nuget) in its directory names. ^
set replace(subject, old, new) to:{{ return join(split(subject, old), new) }} ^
set dashedVersion to replace(powershellVersion, ".", "-") ^
set baseUrl to replace(VARIABLES[cat("powershell|", dotnetVersion, "|base-url")], powershellVersion, "$powershell_version") ^
set useDashedVersion to find(baseUrl, dashedVersion) >= 0 ^
set baseUrl to replace(baseUrl, dashedVersion, "$powershell_url_version") ^
set url to cat(baseUrl, "/", nupkgName)
}}powershell_version={{powershellVersion}} \
{{if ARGS["dotnet-is-internal"]: && powershell_url_version=$(printf '%s' "$powershell_version" | tr '.' '-') \
{{if useDashedVersion: && powershell_url_version=$(printf '%s' "$powershell_version" | tr '.' '-') \
}} && {{InsertTemplate("../Dockerfile.linux.download-files", ["files": [[
"url": url,
"out-file": nupkgName
Expand Down
8 changes: 5 additions & 3 deletions eng/dockerfile-templates/sdk/Dockerfile.windows
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
set dotnetVersion to join(slice(split(PRODUCT_VERSION, "."), 0, 2), ".") ^
set baseUrl to VARIABLES[cat("sdk|", dotnetVersion, "|base-url|", VARIABLES["branch"])] ^
set isInternal to find(baseUrl, "dotnetstage") >= 0 ^
set isSingleStage to (find(OS_VERSION, "windowsservercore") >= 0 && !isInternal) ^
set isPowerShellInternal to find(VARIABLES[cat("powershell|", dotnetVersion, "|base-url")], "pscoretestdata") >= 0 ^
set useAccessToken to isInternal || isPowerShellInternal ^
set isSingleStage to (find(OS_VERSION, "windowsservercore") >= 0 && !useAccessToken) ^

set installerStageFromImage to cat("mcr.microsoft.com/windows/servercore:", OS_VERSION_NUMBER, "-amd64") ^

Expand All @@ -21,13 +23,13 @@ ARG REPO=mcr.microsoft.com/dotnet/aspnet

# Installer image
FROM {{installerStageFromImage}} AS installer
{{if isInternal:
{{if useAccessToken:
ARG ACCESSTOKEN
}}
{{InsertTemplate("Dockerfile.windows.install-components",
[
"use-local-version-var": "true",
"dotnet-is-internal": isInternal,
"use-access-token": useAccessToken,
])}}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
_ ARGS:
use-local-version-var (optional): Whether to define a local variable for the SDK version
instead of referencing the environment variable.
dotnet-is-internal (optional): Whether the Dockerfile is targeting an internal build of the .NET product. ^
use-access-token (optional): Whether any component is downloaded from internal storage using the
ACCESSTOKEN build arg. ^

set dotnetVersion to join(slice(split(PRODUCT_VERSION, "."), 0, 2), ".") ^
set isServerCore to find(OS_VERSION, "windowsservercore") >= 0 ^
Expand Down Expand Up @@ -59,7 +60,7 @@ RUN powershell -Command " `
], " ")}}; `
Remove-Item -Force mingit.zip"

{{if ARGS["dotnet-is-internal"]:SHELL ["powershell", "-command"]
{{if ARGS["use-access-token"]:SHELL ["powershell", "-command"]
RUN `^else:RUN powershell -Command " `}}
$ErrorActionPreference = 'Stop'; `
$ProgressPreference = 'SilentlyContinue'; `
Expand All @@ -70,11 +71,9 @@ RUN `^else:RUN powershell -Command " `}}
"extract-to": "dotnet"
], " ")}}; `
`
{{InsertTemplate("Dockerfile.windows.install-powershell", [
"dotnet-is-internal": ARGS["dotnet-is-internal"]
], " ")}}{{if !isSingleStage: `
{{InsertTemplate("Dockerfile.windows.install-powershell", [], " ")}}{{if !isSingleStage: `
`
# Delete everything in the dotnet folder that's not needed in the SDK layer but will instead be derived from base layers{{
for i, group in sdkExtractGroups:
Get-ChildItem -Exclude {{join(map(group.paths, getFormattedPath), ",")}} -Path {{group.dir}} `
| Remove-Item -Force -Recurse{{if i < len(group.paths) - 1:; `}}}}}}{{if !ARGS["dotnet-is-internal"]:"}}
| Remove-Item -Force -Recurse{{if i < len(group.paths) - 1:; `}}}}}}{{if !ARGS["use-access-token"]:"}}
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
{{
_ ARGS:
dotnet-is-internal (optional): Whether the Dockerfile is targeting an internal build of the .NET product. ^

set dotnetVersion to join(slice(split(PRODUCT_VERSION, "."), 0, 2), ".") ^
set rootDir to "\" ^
set nupkgFile to "PowerShell.Windows.x64.$powershell_version.nupkg" ^
set url to when(
ARGS["dotnet-is-internal"],
cat("https://pscoretestdata.blob.core.windows.net/v$powershell_url_version-nuget/globaltool/", nupkgFile),
cat("https://powershellinfraartifacts-gkhedzdeaghdezhr.z01.azurefd.net/tool/$powershell_version/", nupkgFile)
) ^
set powershellVersion to VARIABLES[cat("powershell|", dotnetVersion, "|build-version")] ^
_ Refer to the version through PowerShell variables so it is only written once. Internal storage
uses a dashed version (e.g. v7-5-11-nuget) in its directory names. ^
set replace(subject, old, new) to:{{ return join(split(subject, old), new) }} ^
set dashedVersion to replace(powershellVersion, ".", "-") ^
set baseUrl to replace(VARIABLES[cat("powershell|", dotnetVersion, "|base-url")], powershellVersion, "$powershell_version") ^
set useDashedVersion to find(baseUrl, dashedVersion) >= 0 ^
set baseUrl to replace(baseUrl, dashedVersion, "$powershell_url_version") ^
set url to cat(baseUrl, "/", nupkgFile) ^
set shaFunction to VARIABLES[cat("powershell|", dotnetVersion, "|sha-function")]
}}# Install PowerShell global tool
$powershell_version = '{{VARIABLES[cat("powershell|", dotnetVersion, "|build-version")]}}'; `
{{if ARGS["dotnet-is-internal"]:$powershell_url_version = $powershell_version.Replace('.', '-'); `
$powershell_version = '{{powershellVersion}}'; `
{{if useDashedVersion:$powershell_url_version = $powershell_version.Replace('.', '-'); `
}}{{InsertTemplate("../Dockerfile.windows.download-files", ["files": [[
"out-file": nupkgFile,
"url": url
Expand Down
20 changes: 12 additions & 8 deletions eng/update-dependencies/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,20 @@ For usage, see [README.md](./README.md).
## Design/Concepts

- An [Updater](./Updaters) contains all details needed to update one specific
product. It implements `IUpdater` and one or more of the other `I.*Updater`
interfaces.
product. Updaters with shared update sources implement `IUpdater` and one or
more of the other `I.*Updater` interfaces.
- [Commands](./Commands) map to the sources for dependency updates. These are
typically implemented as static `Command` factories that bind to command line
arguments. Commands know how to pass the correct values to the corresponding
`I.*Updater` interface.
- If the command fits within the standard updater/command split, it should be
registered in [DependencyCommands.cs](./Commands/DependencyCommand.cs).
The exception is if it's a one-off workflow type of command, like
[SyncInternalReleaseCommand](./Commands/SyncInternalReleaseCommand.cs).
- If the command fits within the standard updater/command split, it should be
registered in [DependencyCommands.cs](./Commands/DependencyCommand.cs).
The exception is if it's a one-off workflow type of command, like
[SyncInternalReleaseCommand](./Commands/SyncInternalReleaseCommand.cs).
- Updaters with sources that no other updater shares, like
[PowerShellCommand](./Commands/PowerShellCommand.cs) and
[MonitorCommand](./Commands/MonitorCommand.cs), define their own commands
instead of a single-use `I.*Updater` interface.

Example:

Expand All @@ -36,8 +40,8 @@ updater interfaces.

## Command line

Updaters and commands are automatically composed together in the CLI without any
extra work: `update-dependencies <updater> <command>`.
Updaters with shared update sources are composed together with their commands
in the CLI without any extra work: `update-dependencies <updater> <command>`.

## Inputs/Configuration

Expand Down
103 changes: 103 additions & 0 deletions eng/update-dependencies/AzureCredentialProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Text.Json;
using Azure.Core;
using Azure.Identity;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace Microsoft.DotNet.Docker.UpdateDependencies;

/// <summary>
/// Provides Azure credentials for Azure DevOps service connections, so that each Azure resource
/// is accessed with its own identity.
/// </summary>
public interface IAzureCredentialProvider
{
/// <summary>
/// Gets a credential for the named service connection.
/// </summary>
TokenCredential GetCredential(string serviceConnectionName);
}

/// <summary>
/// The information needed to authenticate as an Azure DevOps service connection.
/// </summary>
/// <remarks>
/// Pipelines pass these to the tool as a JSON array in the AZURE_SERVICE_CONNECTIONS environment variable.
/// Keep the pipeline templates in sync with this model.
/// </remarks>
internal sealed record AzureServiceConnection(string Name, string TenantId, string ClientId, string Id);

/// <summary>
/// Names that pipelines use for service connections in AZURE_SERVICE_CONNECTIONS.
/// </summary>
internal static class ServiceConnectionNames
{
/// <summary>
/// The staging service connection, which can read internal .NET and PowerShell builds from Azure Storage.
/// </summary>
public const string Staging = "staging";
}

/// <summary>
/// Uses <see cref="AzurePipelinesCredential"/> for the service connections configured by the pipeline.
/// Outside of Azure Pipelines, falls back to the developer's Azure CLI login.
/// </summary>
/// <remarks>
/// A pipeline job must reference each service connection in one of its tasks (for example, a no-op
/// AzureCLI@2 task) for the service connection's OIDC token exchange to be allowed.
/// </remarks>
internal sealed class AzureCredentialProvider(IConfiguration configuration, IEnvironmentService environmentService)
: IAzureCredentialProvider
{
private const string ServiceConnectionsVariable = "AZURE_SERVICE_CONNECTIONS";

private readonly Dictionary<string, AzureServiceConnection> _serviceConnections =
ParseServiceConnections(configuration[ServiceConnectionsVariable]);

public TokenCredential GetCredential(string serviceConnectionName)
{
if (!environmentService.IsRunningInAzurePipelines())
{
return new AzureCliCredential();
}

if (!_serviceConnections.TryGetValue(serviceConnectionName, out AzureServiceConnection? serviceConnection))
{
string configuredNames = _serviceConnections.Count == 0
? "<none>"
: string.Join(", ", _serviceConnections.Keys);

throw new KeyNotFoundException(
$"Service connection '{serviceConnectionName}' was not found in {ServiceConnectionsVariable}."
+ $" Configured service connections: {configuredNames}.");
}

string systemAccessToken = environmentService.GetSystemAccessToken()
?? throw new InvalidOperationException(
$"SYSTEM_ACCESSTOKEN is required to use service connection '{serviceConnectionName}'.");

return new AzurePipelinesCredential(
tenantId: serviceConnection.TenantId,
clientId: serviceConnection.ClientId,
serviceConnectionId: serviceConnection.Id,
systemAccessToken: systemAccessToken);
}

private static Dictionary<string, AzureServiceConnection> ParseServiceConnections(string? json)
{
AzureServiceConnection[] serviceConnections = string.IsNullOrWhiteSpace(json)
? []
: JsonSerializer.Deserialize<AzureServiceConnection[]>(json, JsonSerializerOptions.Web) ?? [];

return serviceConnections.ToDictionary(sc => sc.Name, StringComparer.OrdinalIgnoreCase);
}
}

internal static class AzureCredentialProviderExtensions
{
public static IServiceCollection AddAzureCredentialProvider(this IServiceCollection services) =>
services.AddSingleton<IAzureCredentialProvider, AzureCredentialProvider>();
}
6 changes: 0 additions & 6 deletions eng/update-dependencies/Commands/DependencyCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,15 +18,9 @@ public static Command CreateCliCommand<TUpdater>(IServiceProvider services) wher
if (typeof(IBarChannelUpdater).IsAssignableFrom(typeof(TUpdater)))
command.Subcommands.Add(FromChannelCommand.CreateCliCommand<TUpdater>(services));

if (typeof(IPipelineBuildUpdater).IsAssignableFrom(typeof(TUpdater)))
command.Subcommands.Add(FromPipelineBuildCommand.CreateCliCommand<TUpdater>(services));

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

if (typeof(IVersionUpdater).IsAssignableFrom(typeof(TUpdater)))
command.Subcommands.Add(FromVersionCommand.CreateCliCommand<TUpdater>(services));

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

Expand Down
32 changes: 0 additions & 32 deletions eng/update-dependencies/Commands/FromPipelineBuildCommand.cs

This file was deleted.

22 changes: 0 additions & 22 deletions eng/update-dependencies/Commands/FromPipelineBuildOptions.cs

This file was deleted.

29 changes: 0 additions & 29 deletions eng/update-dependencies/Commands/FromVersionCommand.cs

This file was deleted.

Loading
Loading