diff --git a/src/BuildScriptGenerator/BuildScriptGeneratorServiceCollectionExtensions.cs b/src/BuildScriptGenerator/BuildScriptGeneratorServiceCollectionExtensions.cs index dc63ad3c89..85ba400b02 100644 --- a/src/BuildScriptGenerator/BuildScriptGeneratorServiceCollectionExtensions.cs +++ b/src/BuildScriptGenerator/BuildScriptGeneratorServiceCollectionExtensions.cs @@ -41,6 +41,8 @@ public static IServiceCollection AddBuildScriptGeneratorServices(this IServiceCo services.AddSingleton(); services.AddSingleton(); services.AddSingleton(); + services.AddSingleton(); + services.AddSingleton(); services.AddHttpClient("general", httpClient => { // NOTE: Setting user agent is required to avoid receiving 403 Forbidden response. diff --git a/src/BuildScriptGenerator/Contracts/IMcrSdkProvider.cs b/src/BuildScriptGenerator/Contracts/IMcrSdkProvider.cs new file mode 100644 index 0000000000..a5ad272307 --- /dev/null +++ b/src/BuildScriptGenerator/Contracts/IMcrSdkProvider.cs @@ -0,0 +1,39 @@ +// -------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// -------------------------------------------------------------------------------------------- + +using System.Threading.Tasks; + +namespace Microsoft.Oryx.BuildScriptGenerator +{ + /// + /// Interface for MCR (Microsoft Container Registry) SDK provider that pulls + /// SDK tarballs from Docker images hosted in MCR. + /// + public interface IMcrSdkProvider + { + /// + /// The default base URL for MCR SDK images. + /// Images follow the convention: {BaseUrl}/{platformName}:{version}-{debianFlavor}. + /// + public const string DefaultMcrSdkImageBaseUrl = "mcr.microsoft.com/oryx/sdks"; + + /// + /// The directory inside the Docker image where the SDK tarball is stored. + /// + public const string SdkDirectoryInImage = "/sdks"; + + /// + /// Pulls an SDK tarball from a Docker image in MCR and stores it in the local cache. + /// The tarball is extracted from the image and placed at the same cache directory + /// used by so that existing installation scripts + /// can locate and extract it. + /// + /// The name of the platform (e.g., "nodejs", "python", "dotnet"). + /// The version of the SDK to pull. + /// The Debian flavor (e.g., "bookworm", "bullseye"). + /// True if the SDK was successfully pulled and cached; false otherwise. + Task PullSdkAsync(string platformName, string version, string debianFlavor); + } +} diff --git a/src/BuildScriptGenerator/Contracts/ISdkResolver.cs b/src/BuildScriptGenerator/Contracts/ISdkResolver.cs new file mode 100644 index 0000000000..0f5154c3b2 --- /dev/null +++ b/src/BuildScriptGenerator/Contracts/ISdkResolver.cs @@ -0,0 +1,29 @@ +// -------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// -------------------------------------------------------------------------------------------- + +namespace Microsoft.Oryx.BuildScriptGenerator +{ + /// + /// Resolves SDK binaries by trying multiple sources in priority order: + /// MCR Docker images → External SDK provider (blob storage) → CDN fallback. + /// + public interface ISdkResolver + { + /// + /// Attempts to fetch the SDK tarball for the given platform and version from available + /// SDK sources (MCR, External SDK provider). If a source succeeds, the tarball will be + /// available in the shared SDK cache directory and the caller can generate an installation + /// script that skips the binary download. + /// + /// The name of the platform (e.g., "nodejs", "python", "dotnet"). + /// The version of the SDK to fetch. + /// The Debian flavor (e.g., "bookworm", "bullseye"). + /// + /// True if the SDK was successfully fetched and cached from any source; + /// false if no source could provide the SDK (caller should fall back to CDN download). + /// + bool TryFetchSdk(string platformName, string version, string debianFlavor); + } +} diff --git a/src/BuildScriptGenerator/DefaultPlatformsInformationProvider.cs b/src/BuildScriptGenerator/DefaultPlatformsInformationProvider.cs index 8c11d5634e..533e227e41 100644 --- a/src/BuildScriptGenerator/DefaultPlatformsInformationProvider.cs +++ b/src/BuildScriptGenerator/DefaultPlatformsInformationProvider.cs @@ -52,6 +52,11 @@ public IEnumerable GetPlatformsInfo(RepositoryContext context) this.outputWriter.WriteLine("External SDK provider is enabled."); } + if (this.commonOptions.EnableMcrSdkProvider) + { + this.outputWriter.WriteLine("MCR SDK provider is enabled."); + } + foreach (var platform in this.platforms) { // Check if a platform is enabled or not diff --git a/src/BuildScriptGenerator/DotNetCore/DotnetCorePlatform.cs b/src/BuildScriptGenerator/DotNetCore/DotnetCorePlatform.cs index afeb21c9f3..cb48f484d6 100644 --- a/src/BuildScriptGenerator/DotNetCore/DotnetCorePlatform.cs +++ b/src/BuildScriptGenerator/DotNetCore/DotnetCorePlatform.cs @@ -34,7 +34,7 @@ internal class DotNetCorePlatform : IProgrammingPlatform private readonly BuildScriptGeneratorOptions commonOptions; private readonly DotNetCorePlatformInstaller platformInstaller; private readonly GlobalJsonSdkResolver globalJsonSdkResolver; - private readonly IExternalSdkProvider externalSdkProvider; + private readonly ISdkResolver sdkResolver; private readonly TelemetryClient telemetryClient; /// @@ -47,6 +47,7 @@ internal class DotNetCorePlatform : IProgrammingPlatform /// The options if .NET platform. /// The . /// The . + /// The . public DotNetCorePlatform( IDotNetCoreVersionProvider versionProvider, ILogger logger, @@ -55,7 +56,7 @@ public DotNetCorePlatform( IOptions dotNetCoreScriptGeneratorOptions, DotNetCorePlatformInstaller platformInstaller, GlobalJsonSdkResolver globalJsonSdkResolver, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) { this.versionProvider = versionProvider; @@ -65,7 +66,7 @@ public DotNetCorePlatform( this.commonOptions = commonOptions.Value; this.platformInstaller = platformInstaller; this.globalJsonSdkResolver = globalJsonSdkResolver; - this.externalSdkProvider = externalSdkProvider; + this.sdkResolver = sdkResolver; this.telemetryClient = telemetryClient; } @@ -244,36 +245,15 @@ public string GetInstallerScriptSnippet( } 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( + "DotNetCore SDK version {version} is not installed. Trying to fetch SDK.", + dotNetCorePlatformDetectorResult.SdkVersion); + + var sdkFetched = this.sdkResolver.TryFetchSdk( + this.Name, dotNetCorePlatformDetectorResult.SdkVersion, this.commonOptions.DebianFlavor); + + installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet( + dotNetCorePlatformDetectorResult.SdkVersion, skipSdkBinaryDownload: sdkFetched); } } else diff --git a/src/BuildScriptGenerator/McrSdkProvider.cs b/src/BuildScriptGenerator/McrSdkProvider.cs new file mode 100644 index 0000000000..c14c770f2f --- /dev/null +++ b/src/BuildScriptGenerator/McrSdkProvider.cs @@ -0,0 +1,243 @@ +// -------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// -------------------------------------------------------------------------------------------- + +using System; +using System.Diagnostics; +using System.IO; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Oryx.BuildScriptGenerator +{ + /// + /// Provides SDK download functionality by pulling Docker images from MCR + /// and extracting SDK tarballs from them. + /// + public class McrSdkProvider : IMcrSdkProvider + { + private const int DockerCommandTimeoutSeconds = 120; + private readonly ILogger logger; + private readonly IStandardOutputWriter outputWriter; + private readonly BuildScriptGeneratorOptions commonOptions; + + public McrSdkProvider( + IStandardOutputWriter outputWriter, + IOptions commonOptions, + ILogger logger) + { + this.logger = logger; + this.outputWriter = outputWriter; + this.commonOptions = commonOptions.Value; + } + + /// + public async Task PullSdkAsync(string platformName, string version, string debianFlavor) + { + var imageBaseUrl = this.commonOptions.McrSdkImageBaseUrl; + if (string.IsNullOrEmpty(imageBaseUrl)) + { + imageBaseUrl = IMcrSdkProvider.DefaultMcrSdkImageBaseUrl; + } + + var imageTag = $"{version}-{debianFlavor}"; + var imageReference = $"{imageBaseUrl}/{platformName}:{imageTag}"; + var blobName = BlobNameHelper.GetBlobNameForVersion(platformName, version, debianFlavor); + var sdkCacheDir = Path.Combine(ExternalSdkProvider.ExternalSdksStorageDir, platformName); + var targetFilePath = Path.Combine(sdkCacheDir, blobName); + + // Check if the SDK tarball already exists in the cache + if (File.Exists(targetFilePath)) + { + this.logger.LogInformation( + "SDK tarball for platform {platformName} version {version} already exists at {targetFilePath}. Skipping MCR pull.", + platformName, + version, + targetFilePath); + return true; + } + + this.logger.LogInformation( + "Pulling SDK for platform {platformName} version {version} from MCR image {imageReference}", + platformName, + version, + imageReference); + this.outputWriter.WriteLine( + $"Pulling SDK for platform {platformName} version {version} from MCR image {imageReference}..."); + + string containerId = null; + try + { + // Ensure the cache directory exists + Directory.CreateDirectory(sdkCacheDir); + + // Step 1: Pull the Docker image + var pullResult = await this.RunDockerCommandAsync($"pull {imageReference}"); + if (!pullResult.Success) + { + this.logger.LogError( + "Failed to pull MCR image {imageReference}. Exit code: {exitCode}, Error: {error}", + imageReference, + pullResult.ExitCode, + pullResult.StdErr); + return false; + } + + // Step 2: Create a container from the image (without starting it) + var createResult = await this.RunDockerCommandAsync($"create {imageReference}"); + if (!createResult.Success || string.IsNullOrWhiteSpace(createResult.StdOut)) + { + this.logger.LogError( + "Failed to create container from MCR image {imageReference}. Exit code: {exitCode}, Error: {error}", + imageReference, + createResult.ExitCode, + createResult.StdErr); + return false; + } + + containerId = createResult.StdOut.Trim(); + + // Step 3: Copy the SDK tarball from the container + var sourcePathInContainer = $"{IMcrSdkProvider.SdkDirectoryInImage}/{blobName}"; + var cpResult = await this.RunDockerCommandAsync($"cp {containerId}:{sourcePathInContainer} {targetFilePath}"); + if (!cpResult.Success) + { + this.logger.LogError( + "Failed to copy SDK tarball from container {containerId} path {sourcePathInContainer}. Exit code: {exitCode}, Error: {error}", + containerId, + sourcePathInContainer, + cpResult.ExitCode, + cpResult.StdErr); + return false; + } + + // Verify the file was actually copied + if (!File.Exists(targetFilePath)) + { + this.logger.LogError( + "SDK tarball was not found at {targetFilePath} after docker cp operation.", + targetFilePath); + return false; + } + + this.logger.LogInformation( + "Successfully pulled SDK for platform {platformName} version {version} from MCR to {targetFilePath}", + platformName, + version, + targetFilePath); + this.outputWriter.WriteLine( + $"Successfully pulled SDK for platform {platformName} version {version} from MCR."); + + return true; + } + catch (Exception ex) + { + this.logger.LogError( + ex, + "Error pulling SDK for platform {platformName} version {version} from MCR image {imageReference}", + platformName, + version, + imageReference); + this.outputWriter.WriteLine( + $"Error pulling SDK for platform {platformName} version {version} from MCR: {ex.Message}"); + + // Clean up any partially downloaded file + if (File.Exists(targetFilePath)) + { + try + { + File.Delete(targetFilePath); + } + catch (Exception cleanupEx) + { + this.logger.LogWarning(cleanupEx, "Failed to clean up partial SDK file at {targetFilePath}", targetFilePath); + } + } + + return false; + } + finally + { + // Step 4: Remove the container + if (!string.IsNullOrEmpty(containerId)) + { + try + { + await this.RunDockerCommandAsync($"rm {containerId}"); + } + catch (Exception ex) + { + this.logger.LogWarning(ex, "Failed to remove container {containerId}", containerId); + } + } + } + } + + private async Task RunDockerCommandAsync(string arguments) + { + var processStartInfo = new ProcessStartInfo + { + FileName = "docker", + Arguments = arguments, + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + }; + + this.logger.LogDebug("Running docker command: docker {arguments}", arguments); + + using var process = new Process { StartInfo = processStartInfo }; + process.Start(); + + var stdOutTask = process.StandardOutput.ReadToEndAsync(); + var stdErrTask = process.StandardError.ReadToEndAsync(); + + var completed = await Task.Run(() => process.WaitForExit(DockerCommandTimeoutSeconds * 1000)); + if (!completed) + { + this.logger.LogError("Docker command timed out after {timeout} seconds: docker {arguments}", DockerCommandTimeoutSeconds, arguments); + try + { + process.Kill(); + } + catch + { + // Best effort kill + } + + return new DockerCommandResult + { + Success = false, + ExitCode = -1, + StdOut = string.Empty, + StdErr = $"Command timed out after {DockerCommandTimeoutSeconds} seconds", + }; + } + + var stdOut = await stdOutTask; + var stdErr = await stdErrTask; + + return new DockerCommandResult + { + Success = process.ExitCode == 0, + ExitCode = process.ExitCode, + StdOut = stdOut, + StdErr = stdErr, + }; + } + + private class DockerCommandResult + { + public bool Success { get; set; } + + public int ExitCode { get; set; } + + public string StdOut { get; set; } + + public string StdErr { get; set; } + } + } +} diff --git a/src/BuildScriptGenerator/Node/NodePlatform.cs b/src/BuildScriptGenerator/Node/NodePlatform.cs index 87cbed0817..e8fa624322 100644 --- a/src/BuildScriptGenerator/Node/NodePlatform.cs +++ b/src/BuildScriptGenerator/Node/NodePlatform.cs @@ -85,7 +85,7 @@ internal class NodePlatform : IProgrammingPlatform private readonly INodePlatformDetector detector; private readonly IEnvironment environment; private readonly NodePlatformInstaller platformInstaller; - private readonly IExternalSdkProvider externalSdkProvider; + private readonly ISdkResolver sdkResolver; private readonly TelemetryClient telemetryClient; /// @@ -98,7 +98,7 @@ internal class NodePlatform : IProgrammingPlatform /// The detector of Node.js platform. /// The environment of Node.js platform. /// The . - /// The . + /// The . public NodePlatform( IOptions commonOptions, IOptions nodeScriptGeneratorOptions, @@ -107,7 +107,7 @@ public NodePlatform( INodePlatformDetector detector, IEnvironment environment, NodePlatformInstaller nodePlatformInstaller, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) { this.commonOptions = commonOptions.Value; @@ -117,7 +117,7 @@ public NodePlatform( this.detector = detector; this.environment = environment; this.platformInstaller = nodePlatformInstaller; - this.externalSdkProvider = externalSdkProvider; + this.sdkResolver = sdkResolver; this.telemetryClient = telemetryClient; } @@ -506,53 +506,15 @@ public string GetInstallerScriptSnippet( } else { - if (this.commonOptions.EnableExternalSdkProvider) - { - this.logger.LogDebug( - "Node version {version} is not installed. " + - "External SDK provider is enabled so trying to fetch SDK using it.", - detectorResult.PlatformVersion); - - try - { - var blobName = BlobNameHelper.GetBlobNameForVersion(this.Name, detectorResult.PlatformVersion, this.commonOptions.DebianFlavor); - var isExternalFetchSuccess = this.externalSdkProvider.RequestBlobAsync(this.Name, blobName).Result; - if (isExternalFetchSuccess) - { - this.logger.LogDebug( - "Node version {version} is fetched successfully using external SDK provider. " + - "So generating an installation script snippet which skips platform binary download.", - detectorResult.PlatformVersion); - - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(detectorResult.PlatformVersion, skipSdkBinaryDownload: true); - } - else - { - this.logger.LogDebug( - "Node version {version} is not fetched successfully using external SDK provider. " + - "So generating an installation script snippet for it.", - detectorResult.PlatformVersion); - - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet( - detectorResult.PlatformVersion); - } - } - catch (Exception ex) - { - this.logger.LogError(ex, "Error while fetching Node.js version {version} using external SDK provider.", detectorResult.PlatformVersion); - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(detectorResult.PlatformVersion); - } - } - else - { - this.logger.LogDebug( - "Node version {version} is not installed. " + - "So generating an installation script snippet for it.", - detectorResult.PlatformVersion); + this.logger.LogDebug( + "Node version {version} is not installed. Trying to fetch SDK.", + detectorResult.PlatformVersion); - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet( - detectorResult.PlatformVersion); - } + var sdkFetched = this.sdkResolver.TryFetchSdk( + this.Name, detectorResult.PlatformVersion, this.commonOptions.DebianFlavor); + + installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet( + detectorResult.PlatformVersion, skipSdkBinaryDownload: sdkFetched); } } else diff --git a/src/BuildScriptGenerator/Options/BuildScriptGeneratorOptions.cs b/src/BuildScriptGenerator/Options/BuildScriptGeneratorOptions.cs index e0c51dd5fd..b74fba039a 100644 --- a/src/BuildScriptGenerator/Options/BuildScriptGeneratorOptions.cs +++ b/src/BuildScriptGenerator/Options/BuildScriptGeneratorOptions.cs @@ -98,5 +98,9 @@ public class BuildScriptGeneratorOptions public string ImageType { get; set; } public bool OryxDisablePipUpgrade { get; set; } + + public bool EnableMcrSdkProvider { get; set; } + + public string McrSdkImageBaseUrl { get; set; } } } \ No newline at end of file diff --git a/src/BuildScriptGenerator/Php/PhpPlatform.cs b/src/BuildScriptGenerator/Php/PhpPlatform.cs index 2a189a077c..67dd5e17f0 100644 --- a/src/BuildScriptGenerator/Php/PhpPlatform.cs +++ b/src/BuildScriptGenerator/Php/PhpPlatform.cs @@ -33,7 +33,7 @@ internal class PhpPlatform : IProgrammingPlatform private readonly IPhpPlatformDetector detector; private readonly PhpPlatformInstaller phpInstaller; private readonly PhpComposerInstaller phpComposerInstaller; - private readonly IExternalSdkProvider externalSdkProvider; + private readonly ISdkResolver sdkResolver; private readonly TelemetryClient telemetryClient; /// @@ -47,6 +47,7 @@ internal class PhpPlatform : IProgrammingPlatform /// The . /// The . /// The . + /// The . public PhpPlatform( IOptions phpScriptGeneratorOptions, IOptions commonOptions, @@ -56,7 +57,7 @@ public PhpPlatform( IPhpPlatformDetector detector, PhpPlatformInstaller phpInstaller, PhpComposerInstaller phpComposerInstaller, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) { this.phpScriptGeneratorOptions = phpScriptGeneratorOptions.Value; @@ -67,7 +68,7 @@ public PhpPlatform( this.detector = detector; this.phpInstaller = phpInstaller; this.phpComposerInstaller = phpComposerInstaller; - this.externalSdkProvider = externalSdkProvider; + this.sdkResolver = sdkResolver; this.telemetryClient = telemetryClient; } @@ -293,54 +294,20 @@ public string GetMaxSatisfyingPhpComposerVersionAndVerify(string version) private void InstallPhp(string phpVersion, StringBuilder scriptBuilder) { - string script = null; if (this.phpInstaller.IsVersionAlreadyInstalled(phpVersion)) { this.logger.LogDebug("PHP version {version} is already installed. So skipping installing it again.", phpVersion); return; } - else - { - if (this.commonOptions.EnableExternalSdkProvider) - { - this.logger.LogDebug("Php version {version} is not installed. External SDK provider is enabled so trying to fetch SDK using it.", phpVersion); - try - { - var blobName = BlobNameHelper.GetBlobNameForVersion("php", phpVersion, this.commonOptions.DebianFlavor); - var isExternalFetchSuccess = this.externalSdkProvider.RequestBlobAsync(this.Name, blobName).Result; - if (isExternalFetchSuccess) - { - this.logger.LogDebug("Php version {version} is fetched successfully using external SDK provider. So generating an installation script snippet which skips platform binary download.", phpVersion); - - script = this.phpInstaller.GetInstallerScriptSnippet(phpVersion, skipSdkBinaryDownload: true); - } - else - { - this.logger.LogDebug("Php version {version} is not fetched successfully using external SDK provider. So generating an installation script snippet for it.", phpVersion); - script = this.phpInstaller.GetInstallerScriptSnippet(phpVersion); - } - } - catch (Exception ex) - { - this.logger.LogError(ex, "Error while fetching php version {version} using external SDK provider.", phpVersion); - script = this.phpInstaller.GetInstallerScriptSnippet(phpVersion); - } - } - else - { - this.logger.LogDebug("Php version {version} is not installed. So generating an installation script snippet for it.", phpVersion); - script = this.phpInstaller.GetInstallerScriptSnippet(phpVersion); - } - - scriptBuilder.AppendLine(script); - } + this.logger.LogDebug("PHP version {version} is not installed. Trying to fetch SDK.", phpVersion); + var sdkFetched = this.sdkResolver.TryFetchSdk("php", phpVersion, this.commonOptions.DebianFlavor); + var script = this.phpInstaller.GetInstallerScriptSnippet(phpVersion, skipSdkBinaryDownload: sdkFetched); + scriptBuilder.AppendLine(script); } private void InstallPhpComposer(string phpComposerVersion, StringBuilder scriptBuilder) { - // Install PHP Composer - string script = null; if (string.IsNullOrEmpty(phpComposerVersion)) { phpComposerVersion = PhpVersions.ComposerDefaultVersion; @@ -351,41 +318,10 @@ private void InstallPhpComposer(string phpComposerVersion, StringBuilder scriptB this.logger.LogDebug("PHP Composer version {version} is already installed. So skipping installing it again.", phpComposerVersion); return; } - else - { - if (this.commonOptions.EnableExternalSdkProvider) - { - this.logger.LogDebug("Php Composer version {version} is not installed. External SDK provider is enabled so trying to fetch SDK using it.", phpComposerVersion); - - try - { - var blobName = BlobNameHelper.GetBlobNameForVersion("php-composer", phpComposerVersion, this.commonOptions.DebianFlavor); - var isExternalFetchSuccess = this.externalSdkProvider.RequestBlobAsync("php-composer", blobName).Result; - if (isExternalFetchSuccess) - { - this.logger.LogDebug("Php composer version {version} is fetched successfully using external SDK provider. So generating an installation script snippet which skips platform binary download.", phpComposerVersion); - - script = this.phpComposerInstaller.GetInstallerScriptSnippet(phpComposerVersion, skipSdkBinaryDownload: true); - } - else - { - this.logger.LogDebug("Php comose version {version} is not fetched successfully using external SDK provider. So generating an installation script snippet for it.", phpComposerVersion); - script = this.phpComposerInstaller.GetInstallerScriptSnippet(phpComposerVersion); - } - } - catch (Exception ex) - { - this.logger.LogError(ex, "Error while fetching php composer version {version} using external SDK provider.", phpComposerVersion); - script = this.phpComposerInstaller.GetInstallerScriptSnippet(phpComposerVersion); - } - } - else - { - this.logger.LogDebug("Php composer version {version} is not installed. So generating an installation script snippet for it.", phpComposerVersion); - script = this.phpComposerInstaller.GetInstallerScriptSnippet(phpComposerVersion); - } - } + this.logger.LogDebug("PHP Composer version {version} is not installed. Trying to fetch SDK.", phpComposerVersion); + var sdkFetched = this.sdkResolver.TryFetchSdk("php-composer", phpComposerVersion, this.commonOptions.DebianFlavor); + var script = this.phpComposerInstaller.GetInstallerScriptSnippet(phpComposerVersion, skipSdkBinaryDownload: sdkFetched); scriptBuilder.AppendLine(script); } diff --git a/src/BuildScriptGenerator/Python/PythonPlatform.cs b/src/BuildScriptGenerator/Python/PythonPlatform.cs index 071b9fe8cd..864954b1e1 100644 --- a/src/BuildScriptGenerator/Python/PythonPlatform.cs +++ b/src/BuildScriptGenerator/Python/PythonPlatform.cs @@ -88,7 +88,7 @@ internal class PythonPlatform : IProgrammingPlatform private readonly ILogger logger; private readonly IPythonPlatformDetector detector; private readonly PythonPlatformInstaller platformInstaller; - private readonly IExternalSdkProvider externalSdkProvider; + private readonly ISdkResolver sdkResolver; private readonly TelemetryClient telemetryClient; /// @@ -100,6 +100,7 @@ internal class PythonPlatform : IProgrammingPlatform /// The logger of Python platform. /// The detector of Python platform. /// The . + /// The . public PythonPlatform( IOptions commonOptions, IOptions pythonScriptGeneratorOptions, @@ -107,7 +108,7 @@ public PythonPlatform( ILogger logger, IPythonPlatformDetector detector, PythonPlatformInstaller platformInstaller, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) { this.commonOptions = commonOptions.Value; @@ -116,7 +117,7 @@ public PythonPlatform( this.logger = logger; this.detector = detector; this.platformInstaller = platformInstaller; - this.externalSdkProvider = externalSdkProvider; + this.sdkResolver = sdkResolver; this.telemetryClient = telemetryClient; } @@ -395,36 +396,15 @@ public string GetInstallerScriptSnippet( } else { - if (this.commonOptions.EnableExternalSdkProvider) - { - this.logger.LogDebug("Python version {version} is not installed. External SDK provider is enabled so trying to fetch SDK using it.", detectorResult.PlatformVersion); - - try - { - var blobName = BlobNameHelper.GetBlobNameForVersion(this.Name, detectorResult.PlatformVersion, this.commonOptions.DebianFlavor); - var isExternalFetchSuccess = this.externalSdkProvider.RequestBlobAsync(this.Name, blobName).Result; - if (isExternalFetchSuccess) - { - this.logger.LogDebug("Python version {version} is fetched successfully using external SDK provider. So generating an installation script snippet which skips platform binary download.", detectorResult.PlatformVersion); - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(detectorResult.PlatformVersion, skipSdkBinaryDownload: true); - } - else - { - this.logger.LogDebug("Python version {version} is not fetched successfully using external SDK provider. So generating an installation script snippet for it.", detectorResult.PlatformVersion); - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(detectorResult.PlatformVersion); - } - } - catch (Exception ex) - { - this.logger.LogError(ex, "Error while fetching python version {version} using external SDK provider.", detectorResult.PlatformVersion); - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(detectorResult.PlatformVersion); - } - } - else - { - this.logger.LogDebug("Python version {version} is not installed. So generating an installation script snippet for it.", detectorResult.PlatformVersion); - installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet(detectorResult.PlatformVersion); - } + this.logger.LogDebug( + "Python version {version} is not installed. Trying to fetch SDK.", + detectorResult.PlatformVersion); + + var sdkFetched = this.sdkResolver.TryFetchSdk( + this.Name, detectorResult.PlatformVersion, this.commonOptions.DebianFlavor); + + installationScriptSnippet = this.platformInstaller.GetInstallerScriptSnippet( + detectorResult.PlatformVersion, skipSdkBinaryDownload: sdkFetched); } } else diff --git a/src/BuildScriptGenerator/SdkResolver.cs b/src/BuildScriptGenerator/SdkResolver.cs new file mode 100644 index 0000000000..cd39e69c45 --- /dev/null +++ b/src/BuildScriptGenerator/SdkResolver.cs @@ -0,0 +1,130 @@ +// -------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// -------------------------------------------------------------------------------------------- + +using System; +using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; + +namespace Microsoft.Oryx.BuildScriptGenerator +{ + /// + /// Default implementation of that resolves SDK binaries by + /// trying multiple sources in priority order: MCR → External SDK provider → CDN fallback. + /// + public class SdkResolver : ISdkResolver + { + private readonly BuildScriptGeneratorOptions commonOptions; + private readonly IMcrSdkProvider mcrSdkProvider; + private readonly IExternalSdkProvider externalSdkProvider; + private readonly ILogger logger; + + public SdkResolver( + IOptions commonOptions, + IMcrSdkProvider mcrSdkProvider, + IExternalSdkProvider externalSdkProvider, + ILogger logger) + { + this.commonOptions = commonOptions.Value; + this.mcrSdkProvider = mcrSdkProvider; + this.externalSdkProvider = externalSdkProvider; + this.logger = logger; + } + + /// + public bool TryFetchSdk(string platformName, string version, string debianFlavor) + { + // Try MCR SDK provider first (pulls SDK from a Docker image in MCR) + if (this.commonOptions.EnableMcrSdkProvider) + { + this.logger.LogDebug( + "{platformName} version {version} is not installed. " + + "MCR SDK provider is enabled, trying to pull SDK from MCR.", + platformName, + version); + + try + { + var isMcrFetchSuccess = this.mcrSdkProvider + .PullSdkAsync(platformName, version, debianFlavor).Result; + + if (isMcrFetchSuccess) + { + this.logger.LogDebug( + "{platformName} version {version} fetched successfully using MCR SDK provider.", + platformName, + version); + return true; + } + else + { + this.logger.LogDebug( + "{platformName} version {version} could not be fetched using MCR SDK provider. " + + "Falling back to other SDK sources.", + platformName, + version); + } + } + catch (Exception ex) + { + this.logger.LogError( + ex, + "Error fetching {platformName} version {version} using MCR SDK provider. " + + "Falling back to other SDK sources.", + platformName, + version); + } + } + + // Try external SDK provider (blob storage via Unix domain socket) + if (this.commonOptions.EnableExternalSdkProvider) + { + this.logger.LogDebug( + "{platformName} version {version} is not installed. " + + "External SDK provider is enabled, trying to fetch SDK using it.", + platformName, + version); + + try + { + var blobName = BlobNameHelper.GetBlobNameForVersion(platformName, version, debianFlavor); + var isExternalFetchSuccess = this.externalSdkProvider + .RequestBlobAsync(platformName, blobName).Result; + + if (isExternalFetchSuccess) + { + this.logger.LogDebug( + "{platformName} version {version} fetched successfully using external SDK provider.", + platformName, + version); + return true; + } + else + { + this.logger.LogDebug( + "{platformName} version {version} could not be fetched using external SDK provider.", + platformName, + version); + } + } + catch (Exception ex) + { + this.logger.LogError( + ex, + "Error fetching {platformName} version {version} using external SDK provider.", + platformName, + version); + } + } + + // No pre-fetch source succeeded; caller should fall back to CDN download + this.logger.LogDebug( + "{platformName} version {version} could not be fetched from any SDK source. " + + "Falling back to CDN-based installation.", + platformName, + version); + return false; + } + } +} diff --git a/src/BuildScriptGeneratorCli/Options/BuildScriptGeneratorOptionsSetup.cs b/src/BuildScriptGeneratorCli/Options/BuildScriptGeneratorOptionsSetup.cs index f3313c7c7e..47c15bf110 100644 --- a/src/BuildScriptGeneratorCli/Options/BuildScriptGeneratorOptionsSetup.cs +++ b/src/BuildScriptGeneratorCli/Options/BuildScriptGeneratorOptionsSetup.cs @@ -62,6 +62,8 @@ public void Configure(BuildScriptGeneratorLib.BuildScriptGeneratorOptions option // Dynamic install options.EnableDynamicInstall = this.GetBooleanValue(SettingsKeys.EnableDynamicInstall); options.EnableExternalSdkProvider = this.GetBooleanValue(SettingsKeys.EnableExternalSdkProvider); + options.EnableMcrSdkProvider = this.GetBooleanValue(SettingsKeys.EnableMcrSdkProvider); + options.McrSdkImageBaseUrl = this.GetStringValue(SettingsKeys.McrSdkImageBaseUrl); var dynamicInstallRootDir = this.GetStringValue(SettingsKeys.DynamicInstallRootDir); diff --git a/src/BuildScriptGeneratorCli/SettingsKeys.cs b/src/BuildScriptGeneratorCli/SettingsKeys.cs index b478745127..616922b886 100644 --- a/src/BuildScriptGeneratorCli/SettingsKeys.cs +++ b/src/BuildScriptGeneratorCli/SettingsKeys.cs @@ -75,5 +75,7 @@ public static class SettingsKeys public const string DebianFlavor = "DEBIAN_FLAVOR"; public const string CallerId = "CALLER_ID"; public const string OryxDisablePipUpgrade = "ORYX_DISABLE_PIP_UPGRADE"; + public const string EnableMcrSdkProvider = "ORYX_ENABLE_MCR_SDK_PROVIDER"; + public const string McrSdkImageBaseUrl = "ORYX_MCR_SDK_IMAGE_BASE_URL"; } } diff --git a/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCoreBuildScriptGenerationTest.cs b/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCoreBuildScriptGenerationTest.cs index 3621abb421..d34d3de84c 100644 --- a/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCoreBuildScriptGenerationTest.cs +++ b/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCoreBuildScriptGenerationTest.cs @@ -83,7 +83,7 @@ private DotNetCorePlatform CreateDotNetCorePlatform( isDotNetCoreVersionAlreadyInstalled = isDotNetCoreVersionAlreadyInstalled ?? true; DotNetCoreInstallationScript = DotNetCoreInstallationScript ?? "default-DotNetCore-installation-script"; var versionProvider = new TestDotNetCoreVersionProvider(supportedDotNetCoreVersions, defaultVersion); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); var detector = new TestDotNetCorePlatformDetector(detectedVersion: detectedVersion); var DotNetCoreInstaller = new TestDotNetCorePlatformInstaller( Options.Create(commonOptions), @@ -112,7 +112,7 @@ public TestDotNetCorePlatform( IDotNetCorePlatformDetector detector, DotNetCorePlatformInstaller DotNetCoreInstaller, GlobalJsonSdkResolver globalJsonSdkResolver, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) : base( DotNetCoreVersionProvider, @@ -122,7 +122,7 @@ public TestDotNetCorePlatform( DotNetCoreScriptGeneratorOptions, DotNetCoreInstaller, globalJsonSdkResolver, - externalSdkProvider, + sdkResolver, telemetryClient) { } diff --git a/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCorePlatformTest.cs b/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCorePlatformTest.cs index 328819275a..3900c63698 100644 --- a/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCorePlatformTest.cs +++ b/tests/BuildScriptGenerator.Tests/DotnetCore/DotNetCorePlatformTest.cs @@ -150,7 +150,7 @@ private DotNetCorePlatform CreatePlatform( var versionProvider = new TestDotNetCoreVersionProvider( supportedVersions, defaultVersion); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); var commonOptions = new BuildScriptGeneratorOptions(); var dotNetCoreScriptGeneratorOptions = new DotNetCoreScriptGeneratorOptions(); dotNetCoreScriptGeneratorOptions.DefaultRuntimeVersion = envVarDefaultVersion; @@ -178,7 +178,7 @@ public TestDotNetCorePlatform( IOptions dotNetCoreScriptGeneratorOptions, DotNetCorePlatformInstaller platformInstaller, GlobalJsonSdkResolver globalJsonSdkResolver, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) : base( versionProvider, @@ -188,7 +188,7 @@ public TestDotNetCorePlatform( dotNetCoreScriptGeneratorOptions, platformInstaller, globalJsonSdkResolver, - externalSdkProvider, + sdkResolver, telemetryClient) { } diff --git a/tests/BuildScriptGenerator.Tests/Node/NodeBuildScriptGenerationTest.cs b/tests/BuildScriptGenerator.Tests/Node/NodeBuildScriptGenerationTest.cs index 347d020f85..85392baa27 100644 --- a/tests/BuildScriptGenerator.Tests/Node/NodeBuildScriptGenerationTest.cs +++ b/tests/BuildScriptGenerator.Tests/Node/NodeBuildScriptGenerationTest.cs @@ -971,7 +971,7 @@ private static IProgrammingPlatform GetNodePlatform( new[] { "6.11.0", NodeVersions.Node8Version, NodeVersions.Node10Version, NodeVersions.Node12Version }, defaultVersion: defaultNodeVersion); - var externalSdkProvider = new TestExternalSdkProvider(); + var sdkResolver = new TestSdkResolver(); nodeScriptGeneratorOptions = nodeScriptGeneratorOptions ?? new NodeScriptGeneratorOptions(); commonOptions = commonOptions ?? new BuildScriptGeneratorOptions(); @@ -983,7 +983,7 @@ private static IProgrammingPlatform GetNodePlatform( detector: null, new TestEnvironment(), new NodePlatformInstaller(Options.Create(commonOptions), NullLoggerFactory.Instance), - externalSdkProvider, + sdkResolver, TelemetryClientHelper.GetTelemetryClient()); } diff --git a/tests/BuildScriptGenerator.Tests/Node/NodePlatformTest.cs b/tests/BuildScriptGenerator.Tests/Node/NodePlatformTest.cs index c28e3d0ce9..ff643a1d56 100644 --- a/tests/BuildScriptGenerator.Tests/Node/NodePlatformTest.cs +++ b/tests/BuildScriptGenerator.Tests/Node/NodePlatformTest.cs @@ -1010,7 +1010,7 @@ private TestNodePlatform CreateNodePlatform( nodeScriptGeneratorOptions = nodeScriptGeneratorOptions ?? new NodeScriptGeneratorOptions(); commonOptions = commonOptions ?? new BuildScriptGeneratorOptions(); var versionProvider = new TestNodeVersionProvider(supportedNodeVersions, defaultVersion); - var externalSdkProvider = new TestExternalSdkProvider(); + var sdkResolver = new TestSdkResolver(); var environment = new TestEnvironment(); var detector = new TestNodePlatformDetector(detectedVersion: detectedVersion); var platformInstaller = new NodePlatformInstaller( @@ -1024,7 +1024,7 @@ private TestNodePlatform CreateNodePlatform( detector, environment, platformInstaller, - externalSdkProvider, + sdkResolver, TelemetryClientHelper.GetTelemetryClient()); } @@ -1037,7 +1037,7 @@ private TestNodePlatform CreateNodePlatform( var environment = new TestEnvironment(); var versionProvider = new TestNodeVersionProvider(); - var externalSdkProvider = new TestExternalSdkProvider(); + var sdkResolver = new TestSdkResolver(); var detector = new TestNodePlatformDetector(detectedVersion: detectedVersion); return new TestNodePlatform( @@ -1048,7 +1048,7 @@ private TestNodePlatform CreateNodePlatform( detector, environment, platformInstaller, - externalSdkProvider, + sdkResolver, TelemetryClientHelper.GetTelemetryClient()); } @@ -1065,7 +1065,7 @@ private TestNodePlatform CreateNodePlatform( sdkAlreadyInstalled, NullLoggerFactory.Instance); var versionProvider = new TestNodeVersionProvider(); - var externalSdkProvider = new TestExternalSdkProvider(); + var sdkResolver = new TestSdkResolver(); var nodeScriptGeneratorOptions = new NodeScriptGeneratorOptions(); var detector = new TestNodePlatformDetector(); return new TestNodePlatform( @@ -1076,7 +1076,7 @@ private TestNodePlatform CreateNodePlatform( detector, environment, installer, - externalSdkProvider, + sdkResolver, TelemetryClientHelper.GetTelemetryClient()); } @@ -1100,7 +1100,7 @@ public TestNodePlatform( INodePlatformDetector detector, IEnvironment environment, NodePlatformInstaller nodePlatformInstaller, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) : base( cliOptions, @@ -1110,7 +1110,7 @@ public TestNodePlatform( detector, environment, nodePlatformInstaller, - externalSdkProvider, + sdkResolver, telemetryClient) { } diff --git a/tests/BuildScriptGenerator.Tests/Php/PhpPlatformTest.cs b/tests/BuildScriptGenerator.Tests/Php/PhpPlatformTest.cs index 57f06f0c92..05b3ae8323 100644 --- a/tests/BuildScriptGenerator.Tests/Php/PhpPlatformTest.cs +++ b/tests/BuildScriptGenerator.Tests/Php/PhpPlatformTest.cs @@ -568,7 +568,7 @@ private PhpPlatform CreatePhpPlatform( isPhpVersionAlreadyInstalled = isPhpVersionAlreadyInstalled ?? true; isPhpComposerAlreadyInstalled = isPhpComposerAlreadyInstalled ?? true; var versionProvider = new TestPhpVersionProvider(supportedPhpVersions, defaultVersion); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); supportedPhpComposerVersions = supportedPhpComposerVersions ?? new[] { PhpVersions.ComposerDefaultVersion }; defaultComposerVersion = defaultComposerVersion ?? PhpVersions.ComposerDefaultVersion; var composerVersionProvider = new TestPhpComposerVersionProvider( @@ -615,7 +615,7 @@ public TestPhpPlatform( IPhpPlatformDetector detector, PhpPlatformInstaller phpInstaller, PhpComposerInstaller phpComposerInstaller, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, TelemetryClient telemetryClient) : base( phpScriptGeneratorOptions, @@ -626,7 +626,7 @@ public TestPhpPlatform( detector, phpInstaller, phpComposerInstaller, - externalSdkProvider, + sdkResolver, telemetryClient) { } diff --git a/tests/BuildScriptGenerator.Tests/Php/PhpScriptGeneratorTest.cs b/tests/BuildScriptGenerator.Tests/Php/PhpScriptGeneratorTest.cs index 958a40ea0a..7575532c40 100644 --- a/tests/BuildScriptGenerator.Tests/Php/PhpScriptGeneratorTest.cs +++ b/tests/BuildScriptGenerator.Tests/Php/PhpScriptGeneratorTest.cs @@ -129,7 +129,7 @@ private IProgrammingPlatform GetScriptGenerator( { var phpVersionProvider = new TestPhpVersionProvider( supportedPhpVersions: new[] { "7.2.15", PhpVersions.Php73Version }); - var externalSdkProvider = new TestExternalSdkProvider(); + var sdkResolver = new TestSdkResolver(); var phpComposerVersionProvider = new TestPhpComposerVersionProvider( supportedPhpComposerVersions: new[] { "7.2.15", PhpVersions.ComposerDefaultVersion }); @@ -145,7 +145,7 @@ private IProgrammingPlatform GetScriptGenerator( detector: null, phpInstaller: null, phpComposerInstaller: null, - externalSdkProvider, + sdkResolver, TelemetryClientHelper.GetTelemetryClient()); } diff --git a/tests/BuildScriptGenerator.Tests/Python/PythonPlatformTests.cs b/tests/BuildScriptGenerator.Tests/Python/PythonPlatformTests.cs index f068c1a5ba..c4a90931a7 100644 --- a/tests/BuildScriptGenerator.Tests/Python/PythonPlatformTests.cs +++ b/tests/BuildScriptGenerator.Tests/Python/PythonPlatformTests.cs @@ -1,4 +1,4 @@ -// -------------------------------------------------------------------------------------------- +// -------------------------------------------------------------------------------------------- // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT license. // -------------------------------------------------------------------------------------------- @@ -37,7 +37,7 @@ public void GeneratedSnippet_DoesNotHaveInstallScript_IfDynamicInstallIsDisabled var pythonScriptGeneratorOptions = new PythonScriptGeneratorOptions(); var commonOptions = new BuildScriptGeneratorOptions() { EnableDynamicInstall = false }; var versionProvider = new TestPythonVersionProvider(new[] { "3.7.5", "3.8.0" }, defaultVersion: "3.7.5"); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); var platformInstaller = new TestPythonPlatformInstaller( isVersionAlreadyInstalled: false, Options.Create(commonOptions), @@ -72,7 +72,7 @@ public void GeneratedSnippet_HasInstallationScript_IfDynamicInstallIsEnabled() var pythonScriptGeneratorOptions = new PythonScriptGeneratorOptions(); var commonOptions = new BuildScriptGeneratorOptions() { EnableDynamicInstall = true }; var versionProvider = new TestPythonVersionProvider(new[] { "3.7.5", "3.8.0" }, defaultVersion: "3.7.5"); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); var platformInstaller = new TestPythonPlatformInstaller( isVersionAlreadyInstalled: false, Options.Create(commonOptions), @@ -112,7 +112,7 @@ public void UsesExternalProvider_IfDynamicInstallAndExternalProviderEnabled_AndS DebianFlavor = OsTypes.DebianBookworm, }; var versionProvider = new TestPythonVersionProvider(new[] { "3.7.5", "3.8.0" }, defaultVersion: "3.7.5"); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); var platformInstaller = new TestPythonPlatformInstaller( isVersionAlreadyInstalled: false, Options.Create(commonOptions), @@ -148,7 +148,7 @@ public void GeneratedSnippet_DoesNotHaveInstallScript_IfVersionIsAlreadyPresentO var pythonScriptGeneratorOptions = new PythonScriptGeneratorOptions(); var commonOptions = new BuildScriptGeneratorOptions() { EnableDynamicInstall = true }; var versionProvider = new TestPythonVersionProvider(new[] { "3.7.5", "3.8.0" }, defaultVersion: "3.7.5"); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); var platformInstaller = new TestPythonPlatformInstaller( isVersionAlreadyInstalled: true, Options.Create(commonOptions), @@ -187,7 +187,7 @@ public void GeneratedSnippet_HaveInstallScript_IfCustomRequirementsTxtPathSpecif CustomRequirementsTxtPath = "foo/requirements.txt" }; var versionProvider = new TestPythonVersionProvider(new[] { "3.7.5", "3.8.0" }, defaultVersion: "3.7.5"); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); var platformInstaller = new TestPythonPlatformInstaller( isVersionAlreadyInstalled: false, Options.Create(commonOptions), @@ -418,7 +418,7 @@ public void Detect_ReturnsExpectedVersion_BasedOnHierarchy( } private PythonPlatform CreatePlatform( IPythonVersionProvider pythonVersionProvider, - IExternalSdkProvider externalSdkProvider, + ISdkResolver sdkResolver, PythonPlatformInstaller platformInstaller, BuildScriptGeneratorOptions commonOptions = null, PythonScriptGeneratorOptions pythonScriptGeneratorOptions = null) @@ -432,7 +432,7 @@ private PythonPlatform CreatePlatform( NullLogger.Instance, detector: null, platformInstaller, - externalSdkProvider, + sdkResolver, TelemetryClientHelper.GetTelemetryClient()); } @@ -448,7 +448,7 @@ private PythonPlatform CreatePlatform( var versionProvider = new TestPythonVersionProvider( supportedPythonVersions: supportedVersions, defaultVersion: defaultVersion); - var externalSdkProvider = new TestExternalSdkProvider(); + var externalSdkProvider = new TestSdkResolver(); commonOptions = commonOptions ?? new BuildScriptGeneratorOptions(); pythonScriptGeneratorOptions = pythonScriptGeneratorOptions ?? new PythonScriptGeneratorOptions(); var detector = new TestPythonPlatformDetector(detectedVersion: detectedVersion); @@ -503,4 +503,4 @@ public override string GetInstallerScriptSnippet(string version, bool skipSdkBin } } } -} \ No newline at end of file +} diff --git a/tests/Oryx.Tests.Common/TestSdkResolver.cs b/tests/Oryx.Tests.Common/TestSdkResolver.cs new file mode 100644 index 0000000000..f5b154d2c8 --- /dev/null +++ b/tests/Oryx.Tests.Common/TestSdkResolver.cs @@ -0,0 +1,24 @@ +// -------------------------------------------------------------------------------------------- +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT license. +// -------------------------------------------------------------------------------------------- + +using Microsoft.Oryx.BuildScriptGenerator; + +namespace Microsoft.Oryx.Tests.Common +{ + public class TestSdkResolver : ISdkResolver + { + private readonly bool _sdkFetchResult; + + public TestSdkResolver(bool sdkFetchResult = false) + { + _sdkFetchResult = sdkFetchResult; + } + + public bool TryFetchSdk(string platformName, string version, string debianFlavor) + { + return _sdkFetchResult; + } + } +}