Skip to content
Open
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
13 changes: 13 additions & 0 deletions src/BuildScriptGenerator.Common/SdkStorageConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,5 +21,18 @@ public static class SdkStorageConstants
public const string DotnetRuntimeVersionMetadataName = "Dotnet_runtime_version";
public const string LegacyDotnetRuntimeVersionMetadataName = "Runtime_version";
public const string OsTypeMetadataName = "Os_type";

// ACR-based SDK distribution constants
public const string EnableAcrSdkProviderKey = "ORYX_ENABLE_ACR_SDK_PROVIDER";
public const string AcrSdkRegistryUrlKeyName = "ORYX_ACR_SDK_REGISTRY_URL";
public const string DefaultAcrSdkRegistryUrl = "https://oryxacr.azurecr.io";
public const string AcrSdkRepositoryPrefix = "sdks";
public const string AcrDefaultVersionTag = "default";
public const string AcrCatalogTag = "catalog";
public const string AcrVersionLabelName = "org.oryx.version";
public const string AcrPlatformLabelName = "org.oryx.platform";
public const string AcrOsFlavorLabelName = "org.oryx.os-flavor";
public const string AcrDotnetRuntimeVersionLabelName = "org.oryx.dotnet-runtime-version";
public const string AcrDotnetSdkVersionLabelName = "org.oryx.dotnet-sdk-version";
}
}
105 changes: 105 additions & 0 deletions src/BuildScriptGenerator/AcrVersionProviderBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.Oryx.BuildScriptGenerator.Common;

namespace Microsoft.Oryx.BuildScriptGenerator
{
/// <summary>
/// Base class for ACR-based SDK version providers. Parallel to <see cref="SdkStorageVersionProviderBase"/>
/// but discovers versions via OCI Distribution API (tag listing + image config labels) instead of
/// Azure Blob Storage listing with XML metadata.
/// </summary>
public class AcrVersionProviderBase
{
private readonly ILogger logger;
private readonly string debianFlavor;

public AcrVersionProviderBase(
IOptions<BuildScriptGeneratorOptions> commonOptions,
IHttpClientFactory httpClientFactory,
ILoggerFactory loggerFactory)
{
var options = commonOptions.Value;
this.logger = loggerFactory.CreateLogger(this.GetType());
this.debianFlavor = options.DebianFlavor;

var registryUrl = string.IsNullOrEmpty(options.OryxAcrSdkRegistryUrl)
? SdkStorageConstants.DefaultAcrSdkRegistryUrl
: options.OryxAcrSdkRegistryUrl;

this.OciClient = new OciRegistryClient(registryUrl, httpClientFactory, loggerFactory);
}

protected OciRegistryClient OciClient { get; }

/// <summary>
/// Lists available versions for a platform from ACR tags.
/// Tags are in the format "{osFlavor}-{version}" (e.g. "bookworm-20.19.3").
/// Tags ending with "-default" or "-catalog" are excluded.
/// </summary>
protected PlatformVersionInfo GetAvailableVersionsFromAcr(string platformName)
{
var repository = $"{SdkStorageConstants.AcrSdkRepositoryPrefix}/{platformName}";

this.logger.LogDebug("Getting available versions for {platformName} from ACR repository {repository}.", platformName, repository);

var allTags = this.GetTags(repository);
var supportedVersions = this.FilterVersionTags(allTags);
var defaultVersion = this.GetDefaultVersion(repository);

this.logger.LogDebug(
"Found {count} versions for {platformName} on ACR (default: {default}).",
supportedVersions.Count,
platformName,
defaultVersion ?? "none");

return PlatformVersionInfo.CreateAvailableOnAcr(supportedVersions, defaultVersion);
}

private List<string> GetTags(string repository)
{
try
{
return this.OciClient.GetAllTagsAsync(repository).GetAwaiter().GetResult();
}
catch (Exception ex)
{
this.logger.LogError(ex, "Failed to get tags from ACR for {repository}.", repository);
throw;
}
}

private List<string> FilterVersionTags(List<string> allTags)
{
var prefix = $"{this.debianFlavor}-";
return allTags
.Where(t => t.StartsWith(prefix, StringComparison.OrdinalIgnoreCase)
&& !t.EndsWith($"-{SdkStorageConstants.AcrDefaultVersionTag}", StringComparison.OrdinalIgnoreCase)
&& !t.EndsWith($"-{SdkStorageConstants.AcrCatalogTag}", StringComparison.OrdinalIgnoreCase))
.Select(t => t.Substring(prefix.Length))
.ToList();
}

private string GetDefaultVersion(string repository)
{
try
{
return this.OciClient.GetDefaultVersionAsync(repository, this.debianFlavor).GetAwaiter().GetResult();
}
catch (Exception ex)
{
this.logger.LogWarning(ex, "Failed to get default version from ACR for {repository}.", repository);
return null;
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ public static IServiceCollection AddBuildScriptGeneratorServices(this IServiceCo
services.AddSingleton<DefaultPlatformsInformationProvider>();
services.AddSingleton<PlatformsInstallationScriptProvider>();
services.AddSingleton<IExternalSdkProvider, ExternalSdkProvider>();
services.AddSingleton<IExternalAcrSdkProvider, ExternalAcrSdkProvider>();
services.AddHttpClient("general", httpClient =>
{
// NOTE: Setting user agent is required to avoid receiving 403 Forbidden response.
Expand Down
31 changes: 31 additions & 0 deletions src/BuildScriptGenerator/Contracts/IExternalAcrSdkProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// --------------------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license.
// --------------------------------------------------------------------------------------------

using System.Threading.Tasks;

namespace Microsoft.Oryx.BuildScriptGenerator
{
/// <summary>
/// Interface for external ACR-based SDK provider that communicates with LWASv2
/// to request SDK downloads from Azure Container Registry (WAWS Images ACR).
/// This is the ACR equivalent of <see cref="IExternalSdkProvider"/> which uses blob storage.
/// </summary>
/// <remarks>
/// Gated by the <c>ORYX_ENABLE_ACR_SDK_PROVIDER</c> feature flag.
/// When enabled and LWASv2 is available, this provider tells LWASv2 to pull
/// the SDK OCI image from the WAWS Images ACR and extract the SDK tarball to disk.
/// </remarks>
public interface IExternalAcrSdkProvider
{
/// <summary>
/// Requests LWASv2 to pull an SDK image from the WAWS Images ACR and extract it to the local cache.
/// </summary>
/// <param name="platformName">The platform name (e.g., "nodejs", "python", "dotnet", "php").</param>
/// <param name="version">The SDK version (e.g., "20.19.3").</param>
/// <param name="debianFlavor">The Debian flavor (e.g., "bookworm", "bullseye").</param>
/// <returns>True if the SDK was successfully pulled and extracted by LWASv2.</returns>
Task<bool> RequestSdkFromAcrAsync(string platformName, string version, string debianFlavor);
}
}
136 changes: 93 additions & 43 deletions src/BuildScriptGenerator/DotNetCore/DotnetCorePlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ internal class DotNetCorePlatform : IProgrammingPlatform
private readonly DotNetCorePlatformInstaller platformInstaller;
private readonly GlobalJsonSdkResolver globalJsonSdkResolver;
private readonly IExternalSdkProvider externalSdkProvider;
private readonly IExternalAcrSdkProvider externalAcrSdkProvider;
private readonly TelemetryClient telemetryClient;

/// <summary>
Expand All @@ -56,6 +57,7 @@ public DotNetCorePlatform(
DotNetCorePlatformInstaller platformInstaller,
GlobalJsonSdkResolver globalJsonSdkResolver,
IExternalSdkProvider externalSdkProvider,
IExternalAcrSdkProvider externalAcrSdkProvider,
TelemetryClient telemetryClient)
{
this.versionProvider = versionProvider;
Expand All @@ -66,6 +68,7 @@ public DotNetCorePlatform(
this.platformInstaller = platformInstaller;
this.globalJsonSdkResolver = globalJsonSdkResolver;
this.externalSdkProvider = externalSdkProvider;
this.externalAcrSdkProvider = externalAcrSdkProvider;
this.telemetryClient = telemetryClient;
}

Expand Down Expand Up @@ -233,55 +236,38 @@ public string GetInstallerScriptSnippet(
$"'{typeof(DotNetCorePlatformDetectorResult)}' but got '{detectorResult.GetType()}'.");
}

string installationScriptSnippet = null;
if (this.commonOptions.EnableDynamicInstall)
if (!this.commonOptions.EnableDynamicInstall)
{
this.logger.LogDebug("Dynamic install is enabled.");
this.logger.LogDebug("Dynamic install is not enabled.");
return null;
}

if (this.platformInstaller.IsVersionAlreadyInstalled(dotNetCorePlatformDetectorResult.SdkVersion))
{
this.logger.LogDebug("DotNetCore SDK version {globalJsonSdkVersion} is already installed. So skipping installing it again.", dotNetCorePlatformDetectorResult.SdkVersion);
}
else
{
if (this.commonOptions.EnableExternalSdkProvider)
{
this.logger.LogDebug("DotNetCore SDK version {version} is not installed. External SDK provider is enabled so trying to fetch SDK using it.", dotNetCorePlatformDetectorResult.SdkVersion);

try
{
var blobName = BlobNameHelper.GetBlobNameForVersion(this.Name, dotNetCorePlatformDetectorResult.SdkVersion, this.commonOptions.DebianFlavor);
var isExternalFetchSuccess = this.externalSdkProvider.RequestBlobAsync(this.Name, blobName).Result;
if (isExternalFetchSuccess)
{
this.logger.LogDebug("DotNetCore SDK version {version} is fetched successfully using external SDK provider. So generating an installation script snippet which skips platform binary download.", dotNetCorePlatformDetectorResult.SdkVersion);
installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(dotNetCorePlatformDetectorResult.SdkVersion, skipSdkBinaryDownload: true);
}
else
{
this.logger.LogDebug("DotNetCore SDK version {version} is not fetched successfully using external SDK provider. So generating an installation script snippet for it.", dotNetCorePlatformDetectorResult.SdkVersion);
installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(dotNetCorePlatformDetectorResult.SdkVersion);
}
}
catch (Exception ex)
{
this.logger.LogError(ex, "Error while fetching DotNetCore SDK version version {version} using external SDK provider.", dotNetCorePlatformDetectorResult.SdkVersion);
installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(dotNetCorePlatformDetectorResult.SdkVersion);
}
}
else
{
this.logger.LogDebug("DotNetCore SDK version {globalJsonSdkVersion} is not installed. So generating an installation script snippet for it.", dotNetCorePlatformDetectorResult.SdkVersion);
installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(dotNetCorePlatformDetectorResult.SdkVersion);
}
}
this.logger.LogDebug("Dynamic install is enabled.");

var sdkVersion = dotNetCorePlatformDetectorResult.SdkVersion;

if (this.platformInstaller.IsVersionAlreadyInstalled(sdkVersion))
{
this.logger.LogDebug(
"DotNetCore SDK version {globalJsonSdkVersion} is already installed. So skipping installing it again.",
sdkVersion);
return null;
}
else

if (this.commonOptions.EnableExternalSdkProvider)
{
this.logger.LogDebug("Dynamic install is not enabled.");
return this.TryInstallFromExternalSdkProvider(sdkVersion);
}

if (this.commonOptions.EnableAcrSdkProvider)
{
return this.TryInstallFromAcrSdkProvider(sdkVersion);
}

return installationScriptSnippet;
this.logger.LogDebug(
"DotNetCore SDK version {globalJsonSdkVersion} is not installed. So generating an installation script snippet for it.",
sdkVersion);
return this.platformInstaller.GetInstallerScriptSnippet(sdkVersion);
}

/// <inheritdoc/>
Expand Down Expand Up @@ -359,6 +345,70 @@ private static void SetStartupFileNameInfoInManifestFile(
buildProperties[DotNetCoreManifestFilePropertyKeys.StartupDllFileName] = startupDllFileName;
}

private string TryInstallFromAcrSdkProvider(string sdkVersion)
{
this.logger.LogDebug(
"DotNetCore SDK version {version} is not installed. ACR SDK provider is enabled, so trying to fetch SDK using it.",
sdkVersion);

try
{
if (this.externalAcrSdkProvider.RequestSdkFromAcrAsync(
this.Name, sdkVersion, this.commonOptions.DebianFlavor).Result)
{
this.logger.LogDebug(
"DotNetCore SDK version {version} is fetched successfully using ACR SDK provider. Skipping platform binary download.",
sdkVersion);
return this.platformInstaller.GetInstallerScriptSnippet(sdkVersion, skipSdkBinaryDownload: true);
}

this.logger.LogDebug(
"DotNetCore SDK version {version} is not fetched via ACR SDK provider. Falling back to CDN download.",
sdkVersion);
}
catch (Exception ex)
{
this.logger.LogError(
ex,
"Error while fetching DotNetCore SDK version {version} using ACR SDK provider. Falling back to CDN download.",
sdkVersion);
}

return this.platformInstaller.GetInstallerScriptSnippet(sdkVersion);
}

private string TryInstallFromExternalSdkProvider(string sdkVersion)
{
this.logger.LogDebug(
"DotNetCore SDK version {version} is not installed. External SDK provider is enabled so trying to fetch SDK using it.",
sdkVersion);

try
{
var blobName = BlobNameHelper.GetBlobNameForVersion(this.Name, sdkVersion, this.commonOptions.DebianFlavor);
if (this.externalSdkProvider.RequestBlobAsync(this.Name, blobName).Result)
{
this.logger.LogDebug(
"DotNetCore SDK version {version} is fetched successfully using external SDK provider. Skipping platform binary download.",
sdkVersion);
return this.platformInstaller.GetInstallerScriptSnippet(sdkVersion, skipSdkBinaryDownload: true);
}

this.logger.LogDebug(
"DotNetCore SDK version {version} is not fetched successfully using external SDK provider. Generating installation script snippet.",
sdkVersion);
}
catch (Exception ex)
{
this.logger.LogError(
ex,
"Error while fetching DotNetCore SDK version version {version} using external SDK provider.",
sdkVersion);
}

return this.platformInstaller.GetInstallerScriptSnippet(sdkVersion);
}

private string GetSdkVersion(
RepositoryContext context,
string runtimeVersion,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public static IServiceCollection AddDotNetCoreScriptGeneratorServices(this IServ
services.AddSingleton<DotNetCoreExternalVersionProvider>();
services.AddSingleton<DotNetCorePlatformInstaller>();
services.AddSingleton<GlobalJsonSdkResolver>();
services.AddSingleton<DotNetCoreAcrVersionProvider>();
return services;
}
}
Expand Down
Loading