diff --git a/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs b/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs index 8047a069a8..39a6ad5d4a 100644 --- a/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs +++ b/source/Calamari.Common/FeatureToggles/OctopusFeatureToggle.cs @@ -10,12 +10,14 @@ public static class KnownSlugs public const string KustomizePatchImageUpdatesFeatureToggle = "kustomize-patch-image-updates"; public const string ArgoRolloutsSupportFeatureToggle = "argo-rollouts-support"; public const string UseDockerCredentialHelper = "calamari-use-docker-credential-helper"; + public const string GitDependenciesForScriptsFeatureToggle = "git-dependencies-for-scripts"; }; public static readonly OctopusFeatureToggle ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle = new(KnownSlugs.ArgoCDHelmReplacePathFromContainerReferenceFeatureToggle); public static readonly OctopusFeatureToggle KustomizePatchImageUpdatesFeatureToggle = new(KnownSlugs.KustomizePatchImageUpdatesFeatureToggle); public static readonly OctopusFeatureToggle ArgoRolloutsSupportFeatureToggle = new(KnownSlugs.ArgoRolloutsSupportFeatureToggle); public static readonly OctopusFeatureToggle UseDockerCredentialHelperFeatureToggle = new(KnownSlugs.UseDockerCredentialHelper); + public static readonly OctopusFeatureToggle GitDependenciesForScriptsFeatureToggle = new(KnownSlugs.GitDependenciesForScriptsFeatureToggle); public class OctopusFeatureToggle { diff --git a/source/Calamari.Tests/Fixtures/Commands/RunScriptGitDependenciesFixture.cs b/source/Calamari.Tests/Fixtures/Commands/RunScriptGitDependenciesFixture.cs new file mode 100644 index 0000000000..a14b3d809e --- /dev/null +++ b/source/Calamari.Tests/Fixtures/Commands/RunScriptGitDependenciesFixture.cs @@ -0,0 +1,140 @@ +using System; +using System.IO; +using System.IO.Compression; +using Calamari.Common.Features.Scripts; +using Calamari.Common.FeatureToggles; +using Calamari.Common.Plumbing.Extensions; +using Calamari.Common.Plumbing.FileSystem; +using Calamari.Common.Plumbing.Variables; +using Calamari.Testing.Helpers; +using Calamari.Tests.Fixtures.Integration.FileSystem; +using FluentAssertions; +using NUnit.Framework; +using SpecialVariables = Calamari.Deployment.SpecialVariables; + +namespace Calamari.Tests.Fixtures.Commands; + +[TestFixture] +[Category(TestCategory.CompatibleOS.OnlyNixOrMac)] +public class RunScriptGitDependenciesFixture +{ + const string GitDependencyName = "my-git-dep"; + const string VariablePassword = "password"; + + readonly ICalamariFileSystem fileSystem = TestCalamariPhysicalFileSystem.GetPhysicalFileSystem(); + + string executionDirectory; + string originalCwd; + CalamariExecutionVariableCollection variables; + + string ExtractedDependencyDirectory => Path.Combine(executionDirectory, GitDependencyName); + + [SetUp] + public void SetUp() + { + executionDirectory = fileSystem.CreateTemporaryDirectory(); + variables = new CalamariExecutionVariableCollection(); + + originalCwd = Directory.GetCurrentDirectory(); + Directory.SetCurrentDirectory(executionDirectory); + } + + [TearDown] + public void Cleanup() + { + Directory.SetCurrentDirectory(originalCwd); + fileSystem.DeleteDirectory(executionDirectory); + } + + [Test] + public void GitDependencyIsExtractedAndUsableByScriptWhenFeatureToggleIsEnabled() + { + var proofFile = Path.Combine(executionDirectory, "proof.txt"); + var zipPath = CreateZipWithEntry("scripts/helper.sh", $"touch \"{proofFile}\""); + + AddGitDependencyVariables(zipPath); + EnableGitDependenciesFeatureToggle(); + AddScript($". {GitDependencyName}/scripts/helper.sh"); + + RunScript().Should().Be(0); + + File.Exists(Path.Combine(ExtractedDependencyDirectory, "scripts", "helper.sh")) + .Should().BeTrue("the git dependency should be extracted into a folder named after the dependency in the working directory"); + File.Exists(proofFile).Should().BeTrue("the script should have been able to source the helper from the extracted git dependency"); + } + + [Test] + public void GitDependencyIsExtractedEvenWhenExtractFlagIsFalse() + { + var zipPath = CreateZipWithEntry("scripts/helper.sh", "echo hello"); + + AddGitDependencyVariables(zipPath); + variables.Add(new CalamariExecutionVariable(SpecialVariables.GitResources.Extract(GitDependencyName), "False", false)); + EnableGitDependenciesFeatureToggle(); + AddScript("echo running"); + + RunScript().Should().Be(0); + + File.Exists(Path.Combine(ExtractedDependencyDirectory, "scripts", "helper.sh")) + .Should().BeTrue("git dependencies should always be extracted, even when the Extract variable is false"); + } + + [Test] + public void GitDependencyIsNotStagedWhenFeatureToggleIsNotEnabled() + { + var zipPath = CreateZipWithEntry("scripts/helper.sh", "echo hello"); + + AddGitDependencyVariables(zipPath); + AddScript("echo running"); + + RunScript().Should().Be(0); + + Directory.Exists(ExtractedDependencyDirectory) + .Should().BeFalse("git dependencies should not be staged when the feature toggle is not enabled"); + } + + int RunScript() + { + var variablesPath = Path.Combine(executionDirectory, "variables.json"); + File.WriteAllBytes(variablesPath, AesEncryption.ForServerVariables(VariablePassword).Encrypt(variables.ToJsonString())); + + return Program.Main(new[] + { + "run-script", + "--variables", variablesPath, + "--variablesPassword", VariablePassword, + }); + } + + void AddScript(string scriptBody) + { + variables.AddRange(new[] + { + new CalamariExecutionVariable(ScriptVariables.ScriptBody, scriptBody, false), + new CalamariExecutionVariable(ScriptVariables.Syntax, ScriptSyntax.Bash.ToString(), false), + }); + } + + void AddGitDependencyVariables(string zipPath) + { + variables.Add(new CalamariExecutionVariable(SpecialVariables.GitResources.OriginalPath(GitDependencyName), zipPath, false)); + } + + void EnableGitDependenciesFeatureToggle() + { + variables.Add(new CalamariExecutionVariable(KnownVariables.EnabledFeatureToggles, OctopusFeatureToggles.KnownSlugs.GitDependenciesForScriptsFeatureToggle, false)); + } + + string CreateZipWithEntry(string entryPath, string content) + { + var zipPath = Path.Combine(executionDirectory, $"{GitDependencyName}.1.0.0.zip"); + using (var archive = ZipFile.Open(zipPath, ZipArchiveMode.Create)) + { + var entry = archive.CreateEntry(entryPath); + using var writer = new StreamWriter(entry.Open()); + writer.Write(content); + } + + return zipPath; + } +} diff --git a/source/Calamari/Commands/RunScriptCommand.cs b/source/Calamari/Commands/RunScriptCommand.cs index 3b310fc9a2..70af7594e4 100644 --- a/source/Calamari/Commands/RunScriptCommand.cs +++ b/source/Calamari/Commands/RunScriptCommand.cs @@ -82,6 +82,12 @@ public override int Execute(string[] commandLineArguments) new StageDependenciesConvention(packageFile, fileSystem, new CombinedPackageExtractor(log, fileSystem, variables, commandLineRunner), new PackageVariablesFactory()) }; + if (OctopusFeatureToggles.GitDependenciesForScriptsFeatureToggle.IsEnabled(deployment.Variables)) + { + //need to set "ForceExtract" or else the git repo cannot be extracted (no option to set this on the UI). + conventions.Add(new StageDependenciesConvention(null, fileSystem, new CombinedPackageExtractor(log, fileSystem, variables, commandLineRunner), new GitDependencyVariablesFactory(), true)); + } + conventions.AddRange(new IConvention[] { // Substitute the script source file new DelegateInstallConvention(d => substituteInFiles.Substitute(d.CurrentDirectory, ScriptFileTargetFactory(d).ToList())),