diff --git a/build.ps1 b/build.ps1 index a7ae09dc9f..178c39ec83 100644 --- a/build.ps1 +++ b/build.ps1 @@ -65,14 +65,14 @@ else { # This means we would need to manually update our global.json file every time there is a new # .NET SDK available, and then all developers would need to immediately install this on their machines. # - # In our builds, we want the same "automatic roll-forward" behaviour that we get when we use the dotnet/sdk:8.0 docker + # In our builds, we want the same "automatic roll-forward" behaviour that we get when we use the dotnet/sdk:10.0 docker # images -- where we always get the latest patch version of the SDK without manual intervention. # # We achieve this with a small tweak to the Nuke bootstrapper to tell it to install the latest version from - # the 8.0 channel, regardless of what's in the global.json. + # the 10.0 channel, regardless of what's in the global.json. Remove-Variable DotNetVersion - $DotNetChannel = "8.0" + $DotNetChannel = "10.0" # ----- End Octopus Deploy Modification ----- # Install by channel or version diff --git a/build.sh b/build.sh index f10b818403..71b62c1189 100755 --- a/build.sh +++ b/build.sh @@ -91,14 +91,14 @@ else # This means we would need to manually update our global.json file every time there is a new # .NET SDK available, and then all developers would need to immediately install this on their machines. # - # In our builds, we want the same "automatic roll-forward" behaviour that we get when we use the dotnet/sdk:8.0 docker + # In our builds, we want the same "automatic roll-forward" behaviour that we get when we use the dotnet/sdk:10.0 docker # images -- where we always get the latest patch version of the SDK without manual intervention. # # We achieve this with a small tweak to the Nuke bootstrapper to tell it to install the latest version from - # the 8.0 channel, regardless of what's in the global.json. + # the 10.0 channel, regardless of what's in the global.json. unset DOTNET_VERSION - DOTNET_CHANNEL="8.0" + DOTNET_CHANNEL="10.0" # ----- End Octopus Deploy Modification ----- # Install by channel or version diff --git a/build/Build.PackageCalamariProjects.cs b/build/Build.PackageCalamariProjects.cs index 000ae8b3a9..a8c734802c 100644 --- a/build/Build.PackageCalamariProjects.cs +++ b/build/Build.PackageCalamariProjects.cs @@ -37,7 +37,7 @@ public partial class Build .Select(rid => { //we are making the bold assumption all projects only have a single target framework - var framework = project.GetTargetFrameworks()?.Single() ?? Frameworks.Net80; + var framework = project.GetTargetFrameworks()?.Single() ?? Frameworks.Net100; return new CalamariPackageMetadata(project, framework, rid); })) .ToList(); @@ -109,7 +109,7 @@ public partial class Build DotNetPublish(s => s .SetConfiguration(Configuration) .SetProject(helperProject) - .SetFramework(Frameworks.Net80) + .SetFramework(Frameworks.Net100) .SetRuntime(rid) .SetVersion(NugetVersion.Value) .SetInformationalVersion(OctoVersionInfo.Value?.InformationalVersion) diff --git a/build/Build.cs b/build/Build.cs index b8bc47e337..6f30215822 100644 --- a/build/Build.cs +++ b/build/Build.cs @@ -60,7 +60,7 @@ public Build() // Mimic the behaviour of this attribute, but lazily so we don't pay the OctoVersion cost when it isn't needed OctoVersionInfo = new Lazy(() => { - var attribute = new OctoVersionAttribute { BranchMember = nameof(BranchName), Framework = "net8.0"}; + var attribute = new OctoVersionAttribute { BranchMember = nameof(BranchName), Framework = Frameworks.Net100}; // the Attribute does all the work such as calling TeamCity.Instance?.SetBuildNumber for us var version = attribute.GetValue(null!, this); diff --git a/build/Frameworks.cs b/build/Frameworks.cs index f5d02a7a0b..427e4920ae 100644 --- a/build/Frameworks.cs +++ b/build/Frameworks.cs @@ -4,7 +4,7 @@ namespace Calamari.Build { public static class Frameworks { - public const string Net80 = "net8.0"; - public const string Net80Windows = "net8.0-windows"; + public const string Net100 = "net10.0"; + public const string Net100Windows = "net10.0-windows"; } } \ No newline at end of file diff --git a/build/Signing.cs b/build/Signing.cs index 88676b54e0..9f7d0fceff 100644 --- a/build/Signing.cs +++ b/build/Signing.cs @@ -90,7 +90,12 @@ static bool HasAuthenticodeSignature(string filePath) { try { + // SYSLIB0057 points at X509CertificateLoader, but that only loads certificate *files*. + // There is no replacement for reading the Authenticode signature embedded in a signed + // PE file, so this API is still the only way to do this check. +#pragma warning disable SYSLIB0057 X509Certificate.CreateFromSignedFile(filePath); +#pragma warning restore SYSLIB0057 return true; } catch diff --git a/build/_build.csproj b/build/_build.csproj index 1c2f2a7e5c..febd0a727b 100644 --- a/build/_build.csproj +++ b/build/_build.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 win-x64;linux-x64;linux-arm;linux-arm64 Calamari.Build CS0649;CS0169 @@ -25,6 +25,11 @@ + + + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -32,7 +37,7 @@ - + diff --git a/global.json b/global.json index 90faf1b627..d99e6aa850 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "8.0.419", + "version": "10.0.302", "rollForward": "latestFeature", "allowPrerelease": false } diff --git a/source/Calamari.Aws/Calamari.Aws.csproj b/source/Calamari.Aws/Calamari.Aws.csproj index bf30c58d0c..c2a19af229 100644 --- a/source/Calamari.Aws/Calamari.Aws.csproj +++ b/source/Calamari.Aws/Calamari.Aws.csproj @@ -18,7 +18,7 @@ Calamari.Aws.exe.manifest - net8.0 + net10.0 true diff --git a/source/Calamari.Azure/Calamari.Azure.csproj b/source/Calamari.Azure/Calamari.Azure.csproj index 35b6599c7f..627c21f2e8 100644 --- a/source/Calamari.Azure/Calamari.Azure.csproj +++ b/source/Calamari.Azure/Calamari.Azure.csproj @@ -5,7 +5,7 @@ Library Octopus Deploy Octopus Deploy Pty Ltd - net8.0 + net10.0 true diff --git a/source/Calamari.AzureAppService.Tests/Calamari.AzureAppService.Tests.csproj b/source/Calamari.AzureAppService.Tests/Calamari.AzureAppService.Tests.csproj index 12fcc3fea8..b52f72fccd 100644 --- a/source/Calamari.AzureAppService.Tests/Calamari.AzureAppService.Tests.csproj +++ b/source/Calamari.AzureAppService.Tests/Calamari.AzureAppService.Tests.csproj @@ -5,7 +5,7 @@ Calamari.AzureAppService.Tests false win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 - net8.0 + net10.0 true @@ -18,7 +18,6 @@ - diff --git a/source/Calamari.AzureAppService/Calamari.AzureAppService.csproj b/source/Calamari.AzureAppService/Calamari.AzureAppService.csproj index dde4314b95..37f7d96291 100644 --- a/source/Calamari.AzureAppService/Calamari.AzureAppService.csproj +++ b/source/Calamari.AzureAppService/Calamari.AzureAppService.csproj @@ -8,7 +8,7 @@ Exe win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 NU5104 - net8.0 + net10.0 true @@ -17,7 +17,7 @@ - + diff --git a/source/Calamari.AzureResourceGroup.Tests/Calamari.AzureResourceGroup.Tests.csproj b/source/Calamari.AzureResourceGroup.Tests/Calamari.AzureResourceGroup.Tests.csproj index c8fee844e7..95f4cbbe5a 100644 --- a/source/Calamari.AzureResourceGroup.Tests/Calamari.AzureResourceGroup.Tests.csproj +++ b/source/Calamari.AzureResourceGroup.Tests/Calamari.AzureResourceGroup.Tests.csproj @@ -4,7 +4,7 @@ Calamari.AzureResourceGroup.Tests false win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 - net8.0 + net10.0 true diff --git a/source/Calamari.AzureResourceGroup/Calamari.AzureResourceGroup.csproj b/source/Calamari.AzureResourceGroup/Calamari.AzureResourceGroup.csproj index 3864ecf36b..26b02d850f 100644 --- a/source/Calamari.AzureResourceGroup/Calamari.AzureResourceGroup.csproj +++ b/source/Calamari.AzureResourceGroup/Calamari.AzureResourceGroup.csproj @@ -7,7 +7,7 @@ false false win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 - net8.0 + net10.0 diff --git a/source/Calamari.AzureScripting.Tests/Calamari.AzureScripting.Tests.csproj b/source/Calamari.AzureScripting.Tests/Calamari.AzureScripting.Tests.csproj index eebed3764a..39a9321ca3 100644 --- a/source/Calamari.AzureScripting.Tests/Calamari.AzureScripting.Tests.csproj +++ b/source/Calamari.AzureScripting.Tests/Calamari.AzureScripting.Tests.csproj @@ -6,7 +6,7 @@ enable win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 false - net8.0 + net10.0 true diff --git a/source/Calamari.AzureScripting/Calamari.AzureScripting.csproj b/source/Calamari.AzureScripting/Calamari.AzureScripting.csproj index 2f6fb89ccd..de64d16e58 100644 --- a/source/Calamari.AzureScripting/Calamari.AzureScripting.csproj +++ b/source/Calamari.AzureScripting/Calamari.AzureScripting.csproj @@ -8,7 +8,7 @@ win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 true false - net8.0 + net10.0 true @@ -17,6 +17,7 @@ + diff --git a/source/Calamari.AzureScripting/CalamariCertificateStore.cs b/source/Calamari.AzureScripting/CalamariCertificateStore.cs index d7140ad9d8..c7d2df6be1 100644 --- a/source/Calamari.AzureScripting/CalamariCertificateStore.cs +++ b/source/Calamari.AzureScripting/CalamariCertificateStore.cs @@ -146,7 +146,7 @@ static bool HasPrivateKey(X509Certificate2 certificate2) { try { - var cert = new X509Certificate2(file, (string)null!, flags); + var cert = X509CertificateLoader.LoadPkcs12FromFile(file, null, flags); // ReSharper disable once InvertIf if (!HasPrivateKey(cert) && requirePrivateKey) diff --git a/source/Calamari.AzureServiceFabric.Tests/Calamari.AzureServiceFabric.Tests.csproj b/source/Calamari.AzureServiceFabric.Tests/Calamari.AzureServiceFabric.Tests.csproj index e0942a69eb..7185f57712 100644 --- a/source/Calamari.AzureServiceFabric.Tests/Calamari.AzureServiceFabric.Tests.csproj +++ b/source/Calamari.AzureServiceFabric.Tests/Calamari.AzureServiceFabric.Tests.csproj @@ -4,7 +4,7 @@ Calamari.AzureServiceFabric.Tests Calamari.AzureServiceFabric.Tests false - net8.0-windows + net10.0-windows win-x64 true diff --git a/source/Calamari.AzureServiceFabric/Calamari.AzureServiceFabric.csproj b/source/Calamari.AzureServiceFabric/Calamari.AzureServiceFabric.csproj index d6734fe7e2..8b433793c2 100644 --- a/source/Calamari.AzureServiceFabric/Calamari.AzureServiceFabric.csproj +++ b/source/Calamari.AzureServiceFabric/Calamari.AzureServiceFabric.csproj @@ -5,7 +5,7 @@ true false Exe - net8.0-windows + net10.0-windows win-x64 true diff --git a/source/Calamari.AzureServiceFabric/CalamariCertificateStore.cs b/source/Calamari.AzureServiceFabric/CalamariCertificateStore.cs index 623c4e4a0b..e617160899 100644 --- a/source/Calamari.AzureServiceFabric/CalamariCertificateStore.cs +++ b/source/Calamari.AzureServiceFabric/CalamariCertificateStore.cs @@ -160,7 +160,7 @@ static X509Certificate2 TryLoadCertificate(string file, X509KeyStorageFlags flag { try { - var cert = new X509Certificate2(file, password, flags); + var cert = X509CertificateLoader.LoadPkcs12FromFile(file, password, flags); // ReSharper disable once InvertIf if (!HasPrivateKey(cert) && requirePrivateKey) diff --git a/source/Calamari.AzureWebApp.Tests/Calamari.AzureWebApp.Tests.csproj b/source/Calamari.AzureWebApp.Tests/Calamari.AzureWebApp.Tests.csproj index a251ddc298..891b17b7b7 100644 --- a/source/Calamari.AzureWebApp.Tests/Calamari.AzureWebApp.Tests.csproj +++ b/source/Calamari.AzureWebApp.Tests/Calamari.AzureWebApp.Tests.csproj @@ -2,7 +2,7 @@ Calamari.AzureWebApp.Tests Calamari.AzureWebApp.Tests - net8.0 + net10.0 win-x64 false true diff --git a/source/Calamari.AzureWebApp/AzureWebAppBehaviour.cs b/source/Calamari.AzureWebApp/AzureWebAppBehaviour.cs index 5f18d72172..843db4cbe2 100644 --- a/source/Calamari.AzureWebApp/AzureWebAppBehaviour.cs +++ b/source/Calamari.AzureWebApp/AzureWebAppBehaviour.cs @@ -62,6 +62,14 @@ public async Task Execute(RunningDeployment deployment) RemoteCertificateValidationCallback originalServerCertificateValidationCallback = null; try { + // TODO: this callback appears to be dead code and should be removed or reinstated properly. + // NetCoreWebDeploymentExecutor runs Web Deploy in a separate net462 child process (the + // NetCoreShim), so a ServicePointManager callback registered here cannot affect the TLS + // validation the child performs. That has been true since Web Deploy moved into the shim, + // not something .NET 10 changed - but .NET 10's obsoletion message says these settings no + // longer affect SslStream or HttpClient, which makes it worth resolving deliberately rather + // than deleting as a drive-by in a framework migration. +#pragma warning disable SYSLIB0014 originalServerCertificateValidationCallback = ServicePointManager.ServerCertificateValidationCallback; ServicePointManager.ServerCertificateValidationCallback = WrapperForServerCertificateValidationCallback; @@ -70,6 +78,7 @@ public async Task Execute(RunningDeployment deployment) finally { ServicePointManager.ServerCertificateValidationCallback = originalServerCertificateValidationCallback; +#pragma warning restore SYSLIB0014 } } diff --git a/source/Calamari.AzureWebApp/Calamari.AzureWebApp.csproj b/source/Calamari.AzureWebApp/Calamari.AzureWebApp.csproj index 05304d5f16..6a2510d635 100644 --- a/source/Calamari.AzureWebApp/Calamari.AzureWebApp.csproj +++ b/source/Calamari.AzureWebApp/Calamari.AzureWebApp.csproj @@ -6,7 +6,7 @@ false Exe 8.0 - net8.0 + net10.0 win-x64 true diff --git a/source/Calamari.CloudAccounts/Calamari.CloudAccounts.csproj b/source/Calamari.CloudAccounts/Calamari.CloudAccounts.csproj index 77c55d6a89..7b9474bc6f 100644 --- a/source/Calamari.CloudAccounts/Calamari.CloudAccounts.csproj +++ b/source/Calamari.CloudAccounts/Calamari.CloudAccounts.csproj @@ -2,7 +2,7 @@ Calamari.CloudAccounts - net8.0 + net10.0 Octopus.Calamari.CloudAccounts Calamari.CloudAccounts Octopus Deploy diff --git a/source/Calamari.Common/Calamari.Common.csproj b/source/Calamari.Common/Calamari.Common.csproj index e6af9aaa82..cb9659fba1 100644 --- a/source/Calamari.Common/Calamari.Common.csproj +++ b/source/Calamari.Common/Calamari.Common.csproj @@ -1,7 +1,7 @@  - net8.0 + net10.0 enable anycpu false @@ -17,9 +17,16 @@ + + + + + diff --git a/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptBootstrapper.cs b/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptBootstrapper.cs index 0e92786e7b..a40cadc517 100644 --- a/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptBootstrapper.cs +++ b/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptBootstrapper.cs @@ -95,18 +95,63 @@ public static string FindBundledExecutable() throw new CommandException(string.Format("dotnet-script was not found at '{0}'", executable)); } - public static string FormatCommandArguments(string bootstrapFile, string? scriptParameters, string? nugetSource = null) + // dotnet-script 2.0 makes the isolated assembly load context the default and replaces the + // opt-in flag with an opt-out. Isolation is what makes native NuGet assets work (SQLite, + // SkiaSharp, Microsoft.Data.SqlClient - dotnet-script #763), and it also gives a script the + // package version it asked for rather than whichever version dotnet-script itself carries. + // The cost is that a type loaded via Assembly.LoadFrom is no longer reference-equal to the + // same type in the script's own closure. This flag restores the pre-2.0 behaviour. + // 1.6.0 does not recognise the flag, so it must not be passed to a customer's own + // locally-installed copy blindly - see RemoveLegacyIsolatedLoadContextFlag. + internal const string DisableIsolatedLoadContextArgument = "--disable-isolated-load-context"; + + // The 1.6.0 opt-in flag. 2.0 no longer recognises it, and dotnet-script forwards + // unrecognised options into the *script's* argument list rather than rejecting them + // (measured on both 1.6.0 and 2.0.1, silently and with exit 0). Left in place it would push + // every script argument along by one, so Env.ScriptArgs[0] becomes "--isolated-load-context". + internal const string LegacyIsolatedLoadContextArgument = "--isolated-load-context"; + + public static string FormatCommandArguments(string bootstrapFile, string? scriptParameters, string? nugetSource = null, bool disableIsolatedLoadContext = false) { var (scriptCommandArguments, scriptArguments) = RetrieveParameterValues(scriptParameters); + scriptCommandArguments = RemoveLegacyIsolatedLoadContextFlag(scriptCommandArguments); var encryptionKey = Convert.ToBase64String(VariableEncryptor.EncryptionKey); var source = string.IsNullOrWhiteSpace(nugetSource) ? "https://api.nuget.org/v3/index.json" : nugetSource; var commandArguments = new StringBuilder(); commandArguments.Append($"-s {source} "); + if (disableIsolatedLoadContext) commandArguments.Append($"{DisableIsolatedLoadContextArgument} "); if (!string.IsNullOrWhiteSpace(scriptCommandArguments)) commandArguments.Append($"{scriptCommandArguments} "); commandArguments.AppendFormat("\"{0}\" -- {1} \"{2}\"", bootstrapFile, scriptArguments, encryptionKey); return commandArguments.ToString(); } + /// + /// Drops the 1.6.0 --isolated-load-context flag from a step's script parameters. Isolation is + /// the default from 2.0 on, so removing the flag preserves exactly what the customer asked + /// for; leaving it in would instead inject the literal string as their script's first + /// argument. Compares whole tokens so --disable-isolated-load-context is left alone. + /// + internal static string? RemoveLegacyIsolatedLoadContextFlag(string? scriptCommandArguments) + { + if (string.IsNullOrWhiteSpace(scriptCommandArguments) + || scriptCommandArguments!.IndexOf(LegacyIsolatedLoadContextArgument, StringComparison.OrdinalIgnoreCase) < 0) + return scriptCommandArguments; + + var kept = scriptCommandArguments.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries) + .Where(token => !token.Equals(LegacyIsolatedLoadContextArgument, StringComparison.OrdinalIgnoreCase)); + + return string.Join(" ", kept); + } + + public static bool HasLegacyIsolatedLoadContextFlag(string? scriptParameters) + { + var (scriptCommandArguments, _) = RetrieveParameterValues(scriptParameters); + + return !string.IsNullOrWhiteSpace(scriptCommandArguments) + && scriptCommandArguments!.Split(new[] { ' ', '\t' }, StringSplitOptions.RemoveEmptyEntries) + .Any(token => token.Equals(LegacyIsolatedLoadContextArgument, StringComparison.OrdinalIgnoreCase)); + } + [return: NotNullIfNotNull("scriptParameters")] static (string? scriptCommandArguments, string? scriptArguments) RetrieveParameterValues(string? scriptParameters) { diff --git a/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptExecutor.cs b/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptExecutor.cs index 1df9532fc5..369dae3cee 100644 --- a/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptExecutor.cs +++ b/source/Calamari.Common/Features/Scripting/DotnetScript/DotnetScriptExecutor.cs @@ -11,6 +11,10 @@ namespace Calamari.Common.Features.Scripting.DotnetScript { public class DotnetScriptExecutor : ScriptExecutor { + const string DotnetRollForwardVariableName = "DOTNET_ROLL_FORWARD"; + const string RollForwardVariable = "Octopus.Action.Script.CSharp.RollForward"; + const string DisableIsolatedLoadContextVariable = "Octopus.Action.Script.CSharp.DisableIsolatedLoadContext"; + readonly ICommandLineRunner commandLineRunner; public DotnetScriptExecutor(ICommandLineRunner commandLineRunner, ILog log): base(log) @@ -33,17 +37,54 @@ protected override IEnumerable PrepareExecution(Script script, var configurationFile = DotnetScriptBootstrapper.PrepareConfigurationFile(workingDirectory, variables); var (bootstrapFile, otherTemporaryFiles) = DotnetScriptBootstrapper.PrepareBootstrapFile(script.File, configurationFile, workingDirectory, variables); var nugetSource = variables.Get("Octopus.Action.Script.CSharp.NuGetSource"); - var arguments = DotnetScriptBootstrapper.FormatCommandArguments(bootstrapFile, script.Parameters, nugetSource); + bool.TryParse(variables.Get(DisableIsolatedLoadContextVariable, "false"), out var disableIsolatedLoadContext); + + if (DotnetScriptBootstrapper.HasLegacyIsolatedLoadContextFlag(script.Parameters)) + Log.Verbose($"Ignoring '{DotnetScriptBootstrapper.LegacyIsolatedLoadContextArgument}' in the script parameters: " + + "the isolated assembly load context is the default from dotnet-script 2.0 on, so the flag is " + + $"redundant. Set {DisableIsolatedLoadContextVariable} to true to turn isolation off instead."); + + var arguments = DotnetScriptBootstrapper.FormatCommandArguments(bootstrapFile, script.Parameters, nugetSource, disableIsolatedLoadContext); bool.TryParse(variables.Get("Octopus.Action.Script.CSharp.BypassIsolation", "false"), out var bypassDotnetScriptIsolation); var cli = CreateCommandLineInvocation(executable, arguments, !string.IsNullOrWhiteSpace(localDotnetScriptPath)); - cli.EnvironmentVars = environmentVars; + cli.EnvironmentVars = WithRollForwardOverride(environmentVars, variables.Get(RollForwardVariable)); cli.WorkingDirectory = workingDirectory; cli.Isolate = !bypassDotnetScriptIsolation; yield return new ScriptExecution(cli, otherTemporaryFiles.Concat(new[] { bootstrapFile, configurationFile })); } - + + /// + /// The roll-forward default ships in the vendored dotnet-script.runtimeconfig.json + /// (see source/IncludeDotNetScript.targets), which is process-scoped and covers both the + /// Windows and Linux launch paths without Calamari having to do anything. + /// + /// This only handles the per-step override. DOTNET_ROLL_FORWARD sits above the + /// runtimeconfig in the host's precedence order - measured on a Windows worker: a + /// runtimeconfig asking for LatestMajor resolved to 8.0.27 rather than 10.0.8 purely + /// because an inherited DOTNET_ROLL_FORWARD=Major outranked it. Unlike the runtimeconfig it + /// also reaches a dotnet-script the customer installed themselves and put on the PATH, + /// which is preferred over our bundled copy. + /// + /// Setting an environment variable leaks it into every process the customer's script goes + /// on to start, so we only do it when a step has explicitly asked for a policy. + /// + static Dictionary? WithRollForwardOverride(Dictionary? environmentVars, string? rollForward) + { + if (string.IsNullOrWhiteSpace(rollForward)) + return environmentVars; + + var vars = environmentVars == null + ? new Dictionary() + : new Dictionary(environmentVars); + + vars[DotnetRollForwardVariableName] = rollForward; + + return vars; + } + + private string GetExecutable(string? localDotnetScriptPath, string bundledExecutable) { return string.IsNullOrWhiteSpace(localDotnetScriptPath) diff --git a/source/Calamari.ConsolidateCalamariPackages.Api/Calamari.ConsolidateCalamariPackages.Api.csproj b/source/Calamari.ConsolidateCalamariPackages.Api/Calamari.ConsolidateCalamariPackages.Api.csproj index 0270115b70..71a8e5c119 100644 --- a/source/Calamari.ConsolidateCalamariPackages.Api/Calamari.ConsolidateCalamariPackages.Api.csproj +++ b/source/Calamari.ConsolidateCalamariPackages.Api/Calamari.ConsolidateCalamariPackages.Api.csproj @@ -3,7 +3,7 @@ Octopus.Calamari.ConsolidatedPackage.Api Octopus.Calamari.ConsolidatedPackage.Api - net8.0 + net10.0 enable enable Octopus Deploy diff --git a/source/Calamari.ConsolidateCalamariPackages.Tests/Calamari.ConsolidateCalamariPackages.Tests.csproj b/source/Calamari.ConsolidateCalamariPackages.Tests/Calamari.ConsolidateCalamariPackages.Tests.csproj index 65dc78396a..3a4af6ae62 100644 --- a/source/Calamari.ConsolidateCalamariPackages.Tests/Calamari.ConsolidateCalamariPackages.Tests.csproj +++ b/source/Calamari.ConsolidateCalamariPackages.Tests/Calamari.ConsolidateCalamariPackages.Tests.csproj @@ -1,7 +1,7 @@ - net8.0 + net10.0 false true diff --git a/source/Calamari.ConsolidateCalamariPackages/Calamari.ConsolidateCalamariPackages.csproj b/source/Calamari.ConsolidateCalamariPackages/Calamari.ConsolidateCalamariPackages.csproj index 84c3745c6c..8a75547337 100644 --- a/source/Calamari.ConsolidateCalamariPackages/Calamari.ConsolidateCalamariPackages.csproj +++ b/source/Calamari.ConsolidateCalamariPackages/Calamari.ConsolidateCalamariPackages.csproj @@ -3,7 +3,7 @@ Octopus.Calamari.ConsolidatedPackage Octopus.Calamari.ConsolidatedPackage - net8.0 + net10.0 true true Octopus Deploy diff --git a/source/Calamari.Contracts/Calamari.Contracts.csproj b/source/Calamari.Contracts/Calamari.Contracts.csproj index 0600ca85cd..165c674019 100644 --- a/source/Calamari.Contracts/Calamari.Contracts.csproj +++ b/source/Calamari.Contracts/Calamari.Contracts.csproj @@ -3,7 +3,7 @@ Octopus.Calamari.Contracts Octopus.Calamari.Contracts - net8.0 + net10.0 enable enable Octopus Deploy diff --git a/source/Calamari.DockerCredentialHelper/Calamari.DockerCredentialHelper.csproj b/source/Calamari.DockerCredentialHelper/Calamari.DockerCredentialHelper.csproj index 347a26eec4..7245c8cf4f 100644 --- a/source/Calamari.DockerCredentialHelper/Calamari.DockerCredentialHelper.csproj +++ b/source/Calamari.DockerCredentialHelper/Calamari.DockerCredentialHelper.csproj @@ -4,7 +4,7 @@ Exe docker-credential-octopus Calamari.DockerCredentialHelper - net8.0 + net10.0 win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 enable true @@ -14,10 +14,4 @@ project reference and produces a non-runnable apphost in the test output. --> - - - - - diff --git a/source/Calamari.GoogleCloudAccounts/Calamari.GoogleCloudAccounts.csproj b/source/Calamari.GoogleCloudAccounts/Calamari.GoogleCloudAccounts.csproj index 012535291a..1250bf088d 100644 --- a/source/Calamari.GoogleCloudAccounts/Calamari.GoogleCloudAccounts.csproj +++ b/source/Calamari.GoogleCloudAccounts/Calamari.GoogleCloudAccounts.csproj @@ -2,7 +2,7 @@ Calamari.GoogleCloudAccounts - net8.0 + net10.0 CS8632 true diff --git a/source/Calamari.GoogleCloudScripting.Tests/Calamari.GoogleCloudScripting.Tests.csproj b/source/Calamari.GoogleCloudScripting.Tests/Calamari.GoogleCloudScripting.Tests.csproj index 7bf01fa582..ba7d2e9537 100644 --- a/source/Calamari.GoogleCloudScripting.Tests/Calamari.GoogleCloudScripting.Tests.csproj +++ b/source/Calamari.GoogleCloudScripting.Tests/Calamari.GoogleCloudScripting.Tests.csproj @@ -5,7 +5,7 @@ win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 false enable - net8.0 + net10.0 true diff --git a/source/Calamari.GoogleCloudScripting/Calamari.GoogleCloudScripting.csproj b/source/Calamari.GoogleCloudScripting/Calamari.GoogleCloudScripting.csproj index 2a2a3858b3..52006b187c 100644 --- a/source/Calamari.GoogleCloudScripting/Calamari.GoogleCloudScripting.csproj +++ b/source/Calamari.GoogleCloudScripting/Calamari.GoogleCloudScripting.csproj @@ -6,7 +6,7 @@ win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 false false - net8.0 + net10.0 CS8632 true diff --git a/source/Calamari.Scripting.Tests/Calamari.Scripting.Tests.csproj b/source/Calamari.Scripting.Tests/Calamari.Scripting.Tests.csproj index 61acd46f81..715f462dcd 100644 --- a/source/Calamari.Scripting.Tests/Calamari.Scripting.Tests.csproj +++ b/source/Calamari.Scripting.Tests/Calamari.Scripting.Tests.csproj @@ -2,7 +2,7 @@ Calamari.Scripting.Tests Calamari.Scripting.Tests - net8.0 + net10.0 win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 false true diff --git a/source/Calamari.Scripting/Calamari.Scripting.csproj b/source/Calamari.Scripting/Calamari.Scripting.csproj index 895a4d21d3..7c72026528 100644 --- a/source/Calamari.Scripting/Calamari.Scripting.csproj +++ b/source/Calamari.Scripting/Calamari.Scripting.csproj @@ -7,7 +7,7 @@ enable win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 true - net8.0 + net10.0 true diff --git a/source/Calamari.Scripting/DotnetScript/dotnet-script.1.6.0.zip b/source/Calamari.Scripting/DotnetScript/dotnet-script.1.6.0.zip deleted file mode 100644 index 88cda61773..0000000000 Binary files a/source/Calamari.Scripting/DotnetScript/dotnet-script.1.6.0.zip and /dev/null differ diff --git a/source/Calamari.Scripting/DotnetScript/dotnet-script.2.0.1.zip b/source/Calamari.Scripting/DotnetScript/dotnet-script.2.0.1.zip new file mode 100644 index 0000000000..65024309a4 Binary files /dev/null and b/source/Calamari.Scripting/DotnetScript/dotnet-script.2.0.1.zip differ diff --git a/source/Calamari.Scripting/DotnetScript/dotnet-script.runtimeconfig.json b/source/Calamari.Scripting/DotnetScript/dotnet-script.runtimeconfig.json new file mode 100644 index 0000000000..5d68f1c6ee --- /dev/null +++ b/source/Calamari.Scripting/DotnetScript/dotnet-script.runtimeconfig.json @@ -0,0 +1,14 @@ +{ + "runtimeOptions": { + "tfm": "net8.0", + "rollForward": "Major", + "framework": { + "name": "Microsoft.NETCore.App", + "version": "8.0.0" + }, + "configProperties": { + "System.Reflection.Metadata.MetadataUpdater.IsSupported": false, + "System.Runtime.Serialization.EnableUnsafeBinaryFormatterSerialization": false + } + } +} diff --git a/source/Calamari.Shared/Calamari.Shared.csproj b/source/Calamari.Shared/Calamari.Shared.csproj index d1a675e41f..64293a89d7 100644 --- a/source/Calamari.Shared/Calamari.Shared.csproj +++ b/source/Calamari.Shared/Calamari.Shared.csproj @@ -22,7 +22,7 @@ Calamari enable - net8.0 + net10.0 @@ -44,6 +44,10 @@ + + + + diff --git a/source/Calamari.Terraform.Tests/Calamari.Terraform.Tests.csproj b/source/Calamari.Terraform.Tests/Calamari.Terraform.Tests.csproj index 2fddd8279d..b560613e87 100644 --- a/source/Calamari.Terraform.Tests/Calamari.Terraform.Tests.csproj +++ b/source/Calamari.Terraform.Tests/Calamari.Terraform.Tests.csproj @@ -5,7 +5,7 @@ Calamari.Terraform.Tests win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 false - net8.0 + net10.0 CS8632 true diff --git a/source/Calamari.Terraform/Calamari.Terraform.csproj b/source/Calamari.Terraform/Calamari.Terraform.csproj index f06bb292ec..d348cb526e 100644 --- a/source/Calamari.Terraform/Calamari.Terraform.csproj +++ b/source/Calamari.Terraform/Calamari.Terraform.csproj @@ -6,7 +6,7 @@ false Exe win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 - net8.0 + net10.0 true diff --git a/source/Calamari.Testing/Calamari.Testing.csproj b/source/Calamari.Testing/Calamari.Testing.csproj index 1a774d5dbe..00b54946c4 100644 --- a/source/Calamari.Testing/Calamari.Testing.csproj +++ b/source/Calamari.Testing/Calamari.Testing.csproj @@ -4,7 +4,7 @@ enable https://github.com/OctopusDeploy/Calamari/ Apache-2.0 - net8.0 + net10.0 true Octopus.Calamari.Testing @@ -12,14 +12,18 @@ - + + + + + - diff --git a/source/Calamari.Tests/Calamari.Tests.csproj b/source/Calamari.Tests/Calamari.Tests.csproj index 908be1896f..bcd6344116 100644 --- a/source/Calamari.Tests/Calamari.Tests.csproj +++ b/source/Calamari.Tests/Calamari.Tests.csproj @@ -10,7 +10,7 @@ win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 - net8.0 + net10.0 true true diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.JsonFileOutput/Acme.JsonFileOutput.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.JsonFileOutput/Acme.JsonFileOutput.csproj index a672254824..c3387ff722 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.JsonFileOutput/Acme.JsonFileOutput.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.JsonFileOutput/Acme.JsonFileOutput.csproj @@ -5,7 +5,7 @@ en-US 1.0.0.0 ACME Corporation - net8.0 + net10.0 Acme.JsonFileOutput Acme.JsonFileOutput https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Package/Acme.Package.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Package/Acme.Package.csproj index aa15b8bac5..7f4e210107 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Package/Acme.Package.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Package/Acme.Package.csproj @@ -5,7 +5,7 @@ en-US 1.0.0.0 ACME Corporation - net8.0 + net10.0 Acme.Package Acme.Package https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.PackageBilingual/Acme.PackageBilingual.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.PackageBilingual/Acme.PackageBilingual.csproj index 6da45eedff..7aed0006ba 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.PackageBilingual/Acme.PackageBilingual.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.PackageBilingual/Acme.PackageBilingual.csproj @@ -5,7 +5,7 @@ en-US 1.0.0.0 ACME Corporation - net8.0 + net10.0 Acme.PackageBilingual Acme.PackageBilingual https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Service/Acme.Service.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Service/Acme.Service.csproj index dd5e74eadb..1800276d38 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Service/Acme.Service.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Service/Acme.Service.csproj @@ -4,7 +4,7 @@ A sample project containing a Windows service. en-US ACME Corporation - net8.0 + net10.0 Acme.Service Acme.Service https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.StructuredConfigFiles/Acme.StructuredConfigFiles.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.StructuredConfigFiles/Acme.StructuredConfigFiles.csproj index 782c942328..ead68801b7 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.StructuredConfigFiles/Acme.StructuredConfigFiles.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.StructuredConfigFiles/Acme.StructuredConfigFiles.csproj @@ -5,7 +5,7 @@ en-US 1.0.0.0 ACME Corporation - net8.0 + net10.0 Acme.StructuredConfigFiles Acme.StructuredConfigFiles https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web.Tests/Acme.Web.Tests.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web.Tests/Acme.Web.Tests.csproj index ab1d375751..259f71206e 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web.Tests/Acme.Web.Tests.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web.Tests/Acme.Web.Tests.csproj @@ -4,7 +4,7 @@ Test project for Calamari. en-US Octopus Deploy - net8.0 + net10.0 Acme.Web.Tests Acme.Web.Tests https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.Nix.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.Nix.csproj index ac21a9a30a..1128cb6a0a 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.Nix.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.Nix.csproj @@ -4,7 +4,7 @@ Test project for Calamari. en-US Octopus Deploy - net8.0 + net10.0 Acme.Web Acme.Web https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.csproj index 7d4370ee8b..df4b60c5d2 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Acme.Web/Acme.Web.csproj @@ -4,7 +4,7 @@ Test project for Calamari. en-US Octopus Deploy - net8.0 + net10.0 Acme.Web Acme.Web https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/Deployment/Packages/Octopus.Sample.AzureCloudService/Octopus.Sample.AzureCloudService.csproj b/source/Calamari.Tests/Fixtures/Deployment/Packages/Octopus.Sample.AzureCloudService/Octopus.Sample.AzureCloudService.csproj index b8413caa65..9361d9450d 100644 --- a/source/Calamari.Tests/Fixtures/Deployment/Packages/Octopus.Sample.AzureCloudService/Octopus.Sample.AzureCloudService.csproj +++ b/source/Calamari.Tests/Fixtures/Deployment/Packages/Octopus.Sample.AzureCloudService/Octopus.Sample.AzureCloudService.csproj @@ -5,7 +5,7 @@ en-US 1.0.0.0 Octopus Deploy - net8.0 + net10.0 Octopus.Sample.AzureCloudService Octopus.Sample.AzureCloudService https://github.com/OctopusDeploy/Calamari/ diff --git a/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptBootstrapperFixture.cs b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptBootstrapperFixture.cs index ba81b3c9a9..8b4f90b82c 100644 --- a/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptBootstrapperFixture.cs +++ b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptBootstrapperFixture.cs @@ -9,12 +9,16 @@ namespace Calamari.Tests.Fixtures.DotnetScript [TestFixture] public class DotnetScriptBootstrapperFixture { + // The --isolated-load-context cases expect the flag to be gone: dotnet-script 2.0 no longer + // recognises it and would forward it into the script's own arguments. Every other option the + // caller passes is left exactly where it was. [TestCase(null, null, null)] [TestCase("-- \"Parameter 1\" \"Parameter 2\"", null, "\"Parameter 1\" \"Parameter 2\"")] [TestCase("\"Parameter 1\" \"Parameter 2\"", null, "\"Parameter 1\" \"Parameter 2\"")] - [TestCase("--isolated-load-context -- \"Parameter 1\" \"Parameter 2\"", "--isolated-load-context ", "\"Parameter 1\" \"Parameter 2\"")] - [TestCase("--isolated-load-context -d -- \"Parameter 1\" \"Parameter 2\"", "--isolated-load-context -d ", "\"Parameter 1\" \"Parameter 2\"")] - [TestCase("--isolated-load-context --verbosity debug -- \"Parameter 1\" \"Parameter 2\"", "--isolated-load-context --verbosity debug ", "\"Parameter 1\" \"Parameter 2\"")] + [TestCase("--isolated-load-context -- \"Parameter 1\" \"Parameter 2\"", null, "\"Parameter 1\" \"Parameter 2\"")] + [TestCase("--isolated-load-context -d -- \"Parameter 1\" \"Parameter 2\"", "-d ", "\"Parameter 1\" \"Parameter 2\"")] + [TestCase("--isolated-load-context --verbosity debug -- \"Parameter 1\" \"Parameter 2\"", "--verbosity debug ", "\"Parameter 1\" \"Parameter 2\"")] + [TestCase("--verbosity debug -- \"Parameter 1\" \"Parameter 2\"", "--verbosity debug ", "\"Parameter 1\" \"Parameter 2\"")] public void FormatCommandArgumentsTest([CanBeNull] string scriptParameters, [CanBeNull] string commandArguments, [CanBeNull] string scriptArguments) { var bootstrapFile = "Bootstrap." + Guid.NewGuid().ToString().Substring(10) + "." + "Script.csx"; @@ -31,5 +35,62 @@ public void FormatCommandArguments_UsesCustomNuGetSource_WhenProvided() result.Should().Contain($"-s {customSource} "); result.Should().NotContain("api.nuget.org"); } + + [Test] + public void FormatCommandArguments_LeavesIsolationOn_ByDefault() + { + var result = DotnetScriptBootstrapper.FormatCommandArguments("Bootstrap.csx", null); + result.Should().NotContain("--disable-isolated-load-context"); + } + + [Test] + public void FormatCommandArguments_DisablesIsolatedLoadContext_WhenRequested() + { + var result = DotnetScriptBootstrapper.FormatCommandArguments("Bootstrap.csx", null, null, true); + result.Should().Contain("--disable-isolated-load-context "); + } + + [Test] + public void FormatCommandArguments_PlacesDisableIsolatedLoadContextBeforeTheScriptFile() + { + // Anything after the bootstrap file is passed to the script, not to dotnet-script. + const string bootstrapFile = "Bootstrap.csx"; + var result = DotnetScriptBootstrapper.FormatCommandArguments(bootstrapFile, "--verbosity debug -- \"Parameter 1\"", null, true); + result.IndexOf("--disable-isolated-load-context", StringComparison.Ordinal) + .Should() + .BeLessThan(result.IndexOf($"\"{bootstrapFile}\"", StringComparison.Ordinal)); + } + + [Test] + public void FormatCommandArguments_KeepsTheOptOutFlag_WhenTheCallerAlsoPassedTheLegacyOptIn() + { + // The legacy opt-in is dropped rather than treated as a conflicting instruction, so the + // step variable stays authoritative and the caller's flag cannot corrupt their arguments. + var result = DotnetScriptBootstrapper.FormatCommandArguments("Bootstrap.csx", "--isolated-load-context -- P0", null, true); + result.Should().Contain("--disable-isolated-load-context "); + result.Should().NotContain(" --isolated-load-context"); + } + + [TestCase("--isolated-load-context", "")] + [TestCase("--ISOLATED-LOAD-CONTEXT", "")] + [TestCase("--isolated-load-context -d", "-d")] + [TestCase("-d --isolated-load-context", "-d")] + [TestCase("--disable-isolated-load-context", "--disable-isolated-load-context")] + [TestCase("--verbosity debug", "--verbosity debug")] + [TestCase("", "")] + [TestCase(null, null)] + public void RemoveLegacyIsolatedLoadContextFlag_StripsWholeTokensOnly([CanBeNull] string input, [CanBeNull] string expected) + { + DotnetScriptBootstrapper.RemoveLegacyIsolatedLoadContextFlag(input).Should().Be(expected); + } + + [TestCase("--isolated-load-context -- P0", true)] + [TestCase("--disable-isolated-load-context -- P0", false)] + [TestCase("-- P0", false)] + [TestCase(null, false)] + public void HasLegacyIsolatedLoadContextFlag_DetectsOnlyTheLegacyOptIn([CanBeNull] string scriptParameters, bool expected) + { + DotnetScriptBootstrapper.HasLegacyIsolatedLoadContextFlag(scriptParameters).Should().Be(expected); + } } } \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptFixture.cs b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptFixture.cs index 15a485e5de..59ff18eb59 100644 --- a/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptFixture.cs +++ b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptFixture.cs @@ -110,26 +110,70 @@ public void ShouldConsumeParametersWithoutParametersPrefix() output.AssertOutput("Parameters Parameter0Parameter1"); } - [TestCase(true)] - [TestCase(false)] - public void UsingIsolatedAssemblyLoadContext(bool enableIsolatedLoadContext) + /// + /// IsolatedLoadContext.csx asks for NuGet.Commands 6.10.0.107, whose assemblies carry + /// assembly version 6.10.1.5. dotnet-script loads NuGet itself to service #r "nuget:", so + /// the version the script observes tells you which assembly load context won. + /// + /// Isolation on (the default from 2.0): the script's own closure loads in its own context and + /// it sees the version it asked for. Isolation off: the script binds to whatever + /// dotnet-script already has loaded in the default context - 6.14.3.1 in the 2.0.1 bundle. + /// + /// This test used to assert that isolation *off* fails outright, which held only because + /// 1.6.0 happened to bundle NuGet 6.10.0.107 - a lower version than the script's 6.10.1.5, + /// and the default context refuses a downgrade while accepting an upgrade. 2.0.1 bundles a + /// higher version, so the same collision now resolves silently to the wrong assembly. The + /// assertion is on the version binding rather than on a crash so that it keeps testing the + /// load context rather than an accident of which version happens to be vendored. + /// + [Test] + public void IsolatedAssemblyLoadContext_IsOnByDefault_SoAScriptGetsTheVersionItAskedFor() + { + var (output, _) = RunScript("IsolatedLoadContext.csx", + new Dictionary() + { + [SpecialVariables.Action.Script.ScriptParameters] = "-- Parameter0 Parameter1", + }); + + output.AssertSuccess(); + output.AssertOutput("NuGet.Commands version: 6.10.1.5"); + output.AssertOutput("Parameters Parameter0Parameter1"); + } + + [Test] + public void DisableIsolatedLoadContext_BindsTheScriptToDotnetScriptsOwnCopy() + { + var (output, _) = RunScript("IsolatedLoadContext.csx", + new Dictionary() + { + [SpecialVariables.Action.Script.ScriptParameters] = "-- Parameter0 Parameter1", + ["Octopus.Action.Script.CSharp.DisableIsolatedLoadContext"] = "true", + }); + + output.AssertSuccess(); + output.AssertOutput("NuGet.Commands version: 6.14.3.1"); + output.AssertOutput("Parameters Parameter0Parameter1"); + } + + /// + /// 2.0 dropped --isolated-load-context, and dotnet-script forwards options it does not + /// recognise into the script's own argument list instead of rejecting them. Left in place the + /// flag would become Env.ScriptArgs[0] and shift every real argument along by one, so + /// Calamari strips it. Isolation is the default now, so the customer still gets what they + /// asked for. + /// + [Test] + public void LegacyIsolatedLoadContextFlag_IsStrippedAndDoesNotReachTheScriptsArguments() { var (output, _) = RunScript("IsolatedLoadContext.csx", new Dictionary() { - [SpecialVariables.Action.Script.ScriptParameters] = $"{(enableIsolatedLoadContext ? "--isolated-load-context " : "")}-- Parameter0 Parameter1", + [SpecialVariables.Action.Script.ScriptParameters] = "--isolated-load-context -- Parameter0 Parameter1", }); - if (enableIsolatedLoadContext) - { - output.AssertSuccess(); - output.AssertOutput("NuGet.Commands version: 6.10.1.5"); - output.AssertOutput("Parameters Parameter0Parameter1"); - } - else - { - output.AssertFailure(); - output.AssertErrorOutput("Could not load file or assembly 'NuGet.Protocol, Version=6.10.1.5"); - } + + output.AssertSuccess(); + output.AssertOutput("NuGet.Commands version: 6.10.1.5"); + output.AssertOutput("Parameters Parameter0Parameter1"); } } } \ No newline at end of file diff --git a/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptRuntimeConfigFixture.cs b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptRuntimeConfigFixture.cs new file mode 100644 index 0000000000..26f1d6a33c --- /dev/null +++ b/source/Calamari.Tests/Fixtures/DotnetScript/DotnetScriptRuntimeConfigFixture.cs @@ -0,0 +1,115 @@ +#nullable enable +using System; +using System.IO; +using System.IO.Compression; +using System.Linq; +using Calamari.Testing.Helpers; +using FluentAssertions; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using NUnit.Framework; + +namespace Calamari.Tests.Fixtures.DotnetScript +{ + /// + /// dotnet-script is framework-dependent and the upstream build requests + /// Microsoft.NETCore.App 8.0.0 with no rollForward, so by default it will not start on a target + /// that has no 8.x runtime installed. Calamari ships a roll-forward policy over the top. + /// + /// The policy lives in source control as dotnet-script.runtimeconfig.json and is copied over the + /// extracted file by IncludeDotNetScript.targets. These tests guard the two ways that can break: + /// the copy silently not happening, and a future re-vendor bringing new upstream settings that + /// the override then clobbers. + /// + [TestFixture] + [Category(TestCategory.PlatformAgnostic)] + public class DotnetScriptRuntimeConfigFixture + { + /// + /// Major, not LatestMajor. Major only engages when the requested major is absent, so it fixes + /// the launch failure on a target with no 8.x runtime and is a no-op everywhere else. + /// LatestMajor would additionally move targets that work today onto the newest runtime, and + /// that regresses a real shape: dotnet-script derives its generated csproj TFM from the + /// loaded corelib, so on a target with the .NET 10 runtime but only the .NET 8 SDK it emits + /// net10.0 and the restore then fails with NETSDK1045. Measured, both directions - see the + /// PR that introduced this file. + /// + const string ExpectedRollForward = "Major"; + + [Test] + public void BundledDotnetScript_HasTheRollForwardPolicyApplied() + { + var runtimeConfig = JObject.Parse(File.ReadAllText(BundledRuntimeConfigPath())); + + runtimeConfig["runtimeOptions"]?["rollForward"]?.Value() + .Should() + .Be(ExpectedRollForward, + "without it, C# script steps fail to launch on a target that has no 8.x runtime"); + } + + /// + /// The override replaces the whole file, so anything upstream adds or changes would be + /// silently dropped. If this fails after re-vendoring the zip, reconcile the override with + /// the new upstream file rather than just updating the expectation. + /// + [Test] + public void BundledRuntimeConfig_DiffersFromUpstreamOnlyByRollForward() + { + var vendoredZip = FindVendoredZip(); + if (vendoredZip == null) + Assert.Inconclusive("Vendored dotnet-script zip not found - this test needs a source checkout. " + + "It cannot verify the override against upstream from a packaged test run."); + + using var archive = ZipFile.OpenRead(vendoredZip!); + var upstreamEntry = archive.Entries + .Single(e => e.FullName.EndsWith("dotnet-script.runtimeconfig.json")); + + using var reader = new StreamReader(upstreamEntry.Open()); + var upstream = JObject.Parse(reader.ReadToEnd()); + var shipped = JObject.Parse(File.ReadAllText(BundledRuntimeConfigPath())); + + upstream["runtimeOptions"]?["rollForward"] + .Should() + .BeNull("upstream is expected to set no policy - if it now does, the override may be redundant"); + + // Normalise away the one intended difference, then the two files must agree. + ((JObject)shipped["runtimeOptions"]!).Remove("rollForward"); + + JToken.DeepEquals(shipped, upstream) + .Should() + .BeTrue($"the shipped runtimeconfig should match upstream apart from rollForward.{Environment.NewLine}" + + $"upstream: {upstream.ToString(Formatting.None)}{Environment.NewLine}" + + $"shipped: {shipped.ToString(Formatting.None)}"); + } + + static string BundledRuntimeConfigPath() + { + var path = TestEnvironment.GetTestPath("dotnet-script", "dotnet-script.runtimeconfig.json"); + File.Exists(path) + .Should() + .BeTrue($"IncludeDotNetScript.targets should have extracted dotnet-script to {path}"); + return path; + } + + /// + /// Walks up from the test output to the checkout, since the zip is a source artefact and is + /// not copied to the build output. Deliberately uses Single: the targets file globs + /// dotnet-script.*.zip and would extract every match, so more than one zip is a build bug. + /// + static string? FindVendoredZip() + { + var directory = new DirectoryInfo(TestEnvironment.CurrentWorkingDirectory); + + while (directory != null) + { + var candidate = Path.Combine(directory.FullName, "source", "Calamari.Scripting", "DotnetScript"); + if (Directory.Exists(candidate)) + return Directory.GetFiles(candidate, "dotnet-script.*.zip").SingleOrDefault(); + + directory = directory.Parent; + } + + return null; + } + } +} diff --git a/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxyEnvironmentVariablesGeneratorFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxyEnvironmentVariablesGeneratorFixture.cs index 4609800cd5..46f287aee5 100644 --- a/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxyEnvironmentVariablesGeneratorFixture.cs +++ b/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxyEnvironmentVariablesGeneratorFixture.cs @@ -179,17 +179,17 @@ IEnumerable RunWith( void ResetProxyEnvironmentVariables() { - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, string.Empty); - Environment.SetEnvironmentVariable("HTTP_PROXY", string.Empty); - Environment.SetEnvironmentVariable("http_proxy", string.Empty); - Environment.SetEnvironmentVariable("HTTPS_PROXY", string.Empty); - Environment.SetEnvironmentVariable("https_proxy", string.Empty); - Environment.SetEnvironmentVariable("NO_PROXY", string.Empty); - Environment.SetEnvironmentVariable("no_proxy", string.Empty); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, null); + Environment.SetEnvironmentVariable("HTTP_PROXY", null); + Environment.SetEnvironmentVariable("http_proxy", null); + Environment.SetEnvironmentVariable("HTTPS_PROXY", null); + Environment.SetEnvironmentVariable("https_proxy", null); + Environment.SetEnvironmentVariable("NO_PROXY", null); + Environment.SetEnvironmentVariable("no_proxy", null); } void AssertAuthenticatedProxyUsed(IEnumerable result) diff --git a/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxySettingsInitializerFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxySettingsInitializerFixture.cs index 1f82d5eaf5..f234bf916b 100644 --- a/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxySettingsInitializerFixture.cs +++ b/source/Calamari.Tests/Fixtures/Integration/Proxies/ProxySettingsInitializerFixture.cs @@ -79,11 +79,11 @@ void SetEnvironmentVariables( void ResetProxyEnvironmentVariables() { - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, string.Empty); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, null); } void AssertCustomProxy(IProxySettings proxySettings, bool hasCredentials) @@ -101,8 +101,8 @@ void AssertCustomProxy(IProxySettings proxySettings, bool hasCredentials) } else { - proxy.Username.Should().BeNull(); - proxy.Password.Should().BeNull(); + proxy.Username.Should().BeNullOrEmpty(); + proxy.Password.Should().BeNullOrEmpty(); } } @@ -118,8 +118,8 @@ static void AssertSystemProxySettings(IProxySettings proxySettings, bool hasCred } else { - proxy.Username.Should().BeNull(); - proxy.Password.Should().BeNull(); + proxy.Username.Should().BeNullOrEmpty(); + proxy.Password.Should().BeNullOrEmpty(); } } diff --git a/source/Calamari.Tests/Fixtures/Integration/Proxies/ScriptProxyFixtureBase.cs b/source/Calamari.Tests/Fixtures/Integration/Proxies/ScriptProxyFixtureBase.cs index 705f82dc27..2af25d89ed 100644 --- a/source/Calamari.Tests/Fixtures/Integration/Proxies/ScriptProxyFixtureBase.cs +++ b/source/Calamari.Tests/Fixtures/Integration/Proxies/ScriptProxyFixtureBase.cs @@ -93,14 +93,14 @@ protected CalamariResult RunWith( void ResetProxyEnvironmentVariables() { - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, string.Empty); - EnvironmentHelper.SetEnvironmentVariable("HTTP_PROXY", string.Empty); - EnvironmentHelper.SetEnvironmentVariable("HTTPS_PROXY", string.Empty); - EnvironmentHelper.SetEnvironmentVariable("NO_PROXY", string.Empty); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, null); + EnvironmentHelper.SetEnvironmentVariable("HTTP_PROXY", null); + EnvironmentHelper.SetEnvironmentVariable("HTTPS_PROXY", null); + EnvironmentHelper.SetEnvironmentVariable("NO_PROXY", null); } protected virtual void AssertAuthenticatedProxyUsed(CalamariResult output) diff --git a/source/Calamari.Tests/Fixtures/Integration/Proxies/WebProxyInitializerFixture.cs b/source/Calamari.Tests/Fixtures/Integration/Proxies/WebProxyInitializerFixture.cs index ce31e3e832..0a2370f233 100644 --- a/source/Calamari.Tests/Fixtures/Integration/Proxies/WebProxyInitializerFixture.cs +++ b/source/Calamari.Tests/Fixtures/Integration/Proxies/WebProxyInitializerFixture.cs @@ -164,11 +164,11 @@ void RunWith( void ResetProxyEnvironmentVariables() { - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, string.Empty); - Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, string.Empty); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleUseDefaultProxy, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyHost, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPort, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyUsername, null); + Environment.SetEnvironmentVariable(EnvironmentVariables.TentacleProxyPassword, null); } void AssertAuthenticatedProxyUsed() diff --git a/source/Calamari.Tests/Fixtures/Nginx/NginxFixture.cs b/source/Calamari.Tests/Fixtures/Nginx/NginxFixture.cs index e5a4d507d4..08d0cfdb00 100644 --- a/source/Calamari.Tests/Fixtures/Nginx/NginxFixture.cs +++ b/source/Calamari.Tests/Fixtures/Nginx/NginxFixture.cs @@ -306,8 +306,7 @@ public void TestLocationsUnsuitableForFilenames() { var chainCertFilePath = TestEnvironment.GetTestPath("Helpers", "Certificates", "SampleCertificateFiles", "3-cert-chain.pfx"); - var certificateCollection = new X509Certificate2Collection(); - certificateCollection.Import(chainCertFilePath, "hello world", X509KeyStorageFlags.PersistKeySet); + var certificateCollection = X509CertificateLoader.LoadPkcs12CollectionFromFile(chainCertFilePath, "hello world", X509KeyStorageFlags.PersistKeySet); var certificate = certificateCollection.First(); var certificatePem = new string(PemEncoding.Write("CERTIFICATE", certificate.RawData)); diff --git a/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs b/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs index ccf927406e..8942cb427a 100644 --- a/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs +++ b/source/Calamari.Tests/Helpers/Certificates/SampleCertificate.cs @@ -139,7 +139,7 @@ public static void AssertHasPrivateKeyRights(CryptoKeySecurity privateKeySecurit X509Certificate2 LoadAsX509Certificate2() { - return new X509Certificate2(FilePath, Password, + return X509CertificateLoader.LoadPkcs12FromFile(FilePath, Password, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.Exportable | X509KeyStorageFlags.PersistKeySet); } diff --git a/source/Calamari.Tests/Helpers/CodeGenerator.cs b/source/Calamari.Tests/Helpers/CodeGenerator.cs index ad275b31e5..1cf63f10a1 100644 --- a/source/Calamari.Tests/Helpers/CodeGenerator.cs +++ b/source/Calamari.Tests/Helpers/CodeGenerator.cs @@ -26,11 +26,11 @@ CommandLineInvocation CreateCommandLineInvocation(string executable, string argu File.WriteAllText(Path.Combine(projectPath.FullName, "global.json"), @"{ ""sdk"": { - ""version"": ""8.0.10"", + ""version"": ""10.0.302"", ""rollForward"": ""latestFeature"" } }"); - var result = clr.Execute(CreateCommandLineInvocation("dotnet", "new console -f net8.0")); + var result = clr.Execute(CreateCommandLineInvocation("dotnet", "new console -f net10.0")); result.VerifySuccess(); var programCS = Path.Combine(projectPath.FullName, "Program.cs"); var newProgram = $@"using System; diff --git a/source/Calamari/Calamari.csproj b/source/Calamari/Calamari.csproj index 3fa2a5a8db..50cd121eaf 100644 --- a/source/Calamari/Calamari.csproj +++ b/source/Calamari/Calamari.csproj @@ -18,7 +18,7 @@ Calamari win-x64;linux-x64;osx-x64;linux-arm;linux-arm64 Calamari.exe.manifest - net8.0 + net10.0 CS8632 true @@ -39,7 +39,6 @@ - diff --git a/source/Directory.Build.props b/source/Directory.Build.props index c078493167..c68904c1f7 100644 --- a/source/Directory.Build.props +++ b/source/Directory.Build.props @@ -1,6 +1,6 @@ - true + true false diff --git a/source/IncludeDotNetScript.targets b/source/IncludeDotNetScript.targets index 1f6ed73182..0fbc140e22 100644 --- a/source/IncludeDotNetScript.targets +++ b/source/IncludeDotNetScript.targets @@ -3,6 +3,30 @@ + + + @@ -10,6 +34,7 @@ + @@ -18,6 +43,7 @@ + \ No newline at end of file