Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,7 +148,12 @@ public static (string bootstrapFile, string[] temporaryFiles) PrepareBootstrapFi
writer.NewLine = LinuxNewLine;
writer.WriteLine("#!/bin/bash");
writer.WriteLine("source \"$(pwd)/" + Path.GetFileName(configurationFile) + "\"");
writer.WriteLine("shift"); // Shift the variable decryption key out of scope of the user script (see: https://github.com/OctopusDeploy/Calamari/pull/773)
writer.WriteLine("shift"); // Shift the variable decryption key out of scope of the user script and script modules (see: https://github.com/OctopusDeploy/Calamari/pull/773)

var preloadModules = variables.Get(BashScriptVariables.PreloadScriptModules);
if (!string.IsNullOrWhiteSpace(preloadModules))
PreloadScriptModules(writer, preloadModules, scriptModulePaths);

writer.WriteLine("source \"$(pwd)/" + Path.GetFileName(script.File) + "\" " + script.Parameters);
writer.Flush();
}
Expand All @@ -158,6 +163,23 @@ public static (string bootstrapFile, string[] temporaryFiles) PrepareBootstrapFi
return (bootstrapFile, scriptModulePaths);
}


static void PreloadScriptModules(StreamWriter writer, string preloadModules, string[] scriptModulePaths)
{
var modules = preloadModules.Split(new[] { ',', ';' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var module in modules)
{
var sanitizedName = ScriptVariables.FormatScriptName(module.Trim());
var fileName = $"{sanitizedName}.sh";
var scriptModule = scriptModulePaths.FirstOrDefault(p => string.Equals(Path.GetFileName(p), fileName, StringComparison.OrdinalIgnoreCase));
if (scriptModule != null)
{
Log.VerboseFormat("Preloading script module '{0}'.", module.Trim());
writer.WriteLine("source \"$(pwd)/" + fileName + "\"");
}
}
}

static IEnumerable<string> PrepareScriptModules(IVariables variables, string workingDirectory)
{
foreach (var variableName in variables.GetNames().Where(ScriptVariables.IsLibraryScriptModule))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
namespace Calamari.Common.Features.Scripting.Bash
{
public static class BashScriptVariables
{
public static readonly string PreloadScriptModules = "Octopus.Action.Script.PreloadScriptModules";
}
}
120 changes: 120 additions & 0 deletions source/Calamari.Tests/Fixtures/Bash/BashFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -352,6 +352,126 @@ public void ShouldBeAbleToEnumerateVariableValues(FeatureToggle? featureToggle)
output.AssertOutput("Key: VariableName`prop`anotherprop` 13, Value: Value`prop`13");
output.AssertOutput($"Key: {specialCharacters}, Value: {specialCharacters}");
}

[Test]
[RequiresBashDotExeIfOnWindows]
public void ShouldPreloadScriptModules()
{
var (output, _) = RunScript("preload-modules.sh",
new Dictionary<string, string>()
{
["Octopus.Script.Module[test_module]"] = @"function test_function {
echo ""Function from preloaded module""
}

export PRELOADED_VAR=""module_value""",
["Octopus.Script.Module.Language[test_module]"] = "Bash",
["Octopus.Action.Script.PreloadScriptModules"] = "test_module"
});

Assert.Multiple(() =>
{
output.AssertSuccess();
output.AssertOutput("Calling test_function from preloaded module...");
output.AssertOutput("Function from preloaded module");
output.AssertOutput("Checking preloaded variable...");
output.AssertOutput("PRELOADED_VAR=module_value");
});
}

[Test]
[RequiresBashDotExeIfOnWindows]
public void ShouldPreloadMultipleScriptModulesInOrder()
{
var (output, _) = RunScript("preload-modules.sh",
new Dictionary<string, string>()
{
["Octopus.Script.Module[module1]"] = @"function test_function {
echo ""Module 1""
}

export PRELOADED_VAR=""value1""",
["Octopus.Script.Module.Language[module1]"] = "Bash",
["Octopus.Script.Module[module2]"] = @"function test_function {
echo ""Module 2""
}

export PRELOADED_VAR=""value2""",
["Octopus.Script.Module.Language[module2]"] = "Bash",
// module2 sourced last, so it wins
["Octopus.Action.Script.PreloadScriptModules"] = "module1,module2"
});

Assert.Multiple(() =>
{
output.AssertSuccess();
// Should use module2's function (last wins)
output.AssertOutput("Module 2");
// Should use module2's variable (last wins)
output.AssertOutput("PRELOADED_VAR=value2");
});
}

[Test]
[RequiresBashDotExeIfOnWindows]
[TestCase("module1,module2", "module1,module2")]
[TestCase("module1, module2", "module1,module2")]
[TestCase("module1 , module2", "module1,module2")]
[TestCase("module1;module2", "module1,module2")]
[TestCase("module1; module2", "module1,module2")]
[TestCase("module1 ; module2", "module1,module2")]
[TestCase("module1,module2;module3", "module1,module2,module3")]
[TestCase("module1, module2; module3", "module1,module2,module3")]
[TestCase("module1,,module2", "module1,module2")]
[TestCase(" module1 , module2 ", "module1,module2")]
[TestCase("module1, ,module2", "module1,module2")]
[TestCase("module1,missing_module", "module1")]
public void PreloadModulesShouldHandleVariousDelimiterCombinations(string moduleList, string expectedModules)
{
var (output, _) = RunScript("preload-modules.sh",
new Dictionary<string, string>()
{
["Octopus.Script.Module[module1]"] = @"function test_function {
echo ""Module 1""
}

if [ -z ""$PRELOADED_VAR"" ]; then
export PRELOADED_VAR=""module1""
else
export PRELOADED_VAR=""$PRELOADED_VAR,module1""
fi",
["Octopus.Script.Module.Language[module1]"] = "Bash",
["Octopus.Script.Module[module2]"] = @"function test_function {
echo ""Module 2""
}

if [ -z ""$PRELOADED_VAR"" ]; then
export PRELOADED_VAR=""module2""
else
export PRELOADED_VAR=""$PRELOADED_VAR,module2""
fi",
["Octopus.Script.Module.Language[module2]"] = "Bash",
["Octopus.Script.Module[module3]"] = @"function test_function {
echo ""Module 3""
}

if [ -z ""$PRELOADED_VAR"" ]; then
export PRELOADED_VAR=""module3""
else
export PRELOADED_VAR=""$PRELOADED_VAR,module3""
fi",
["Octopus.Script.Module.Language[module3]"] = "Bash",
["Octopus.Action.Script.PreloadScriptModules"] = moduleList
});

Assert.Multiple(() =>
{
output.AssertSuccess();

// Verify expected modules were loaded in the correct order
output.AssertOutput($"PRELOADED_VAR={expectedModules}");
});
}
}

public static class AdditionalVariablesExtensions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
#!/bin/bash

# Test that preloaded modules are available
echo "Calling test_function from preloaded module..."
test_function

echo "Checking preloaded variable..."
echo "PRELOADED_VAR=$PRELOADED_VAR"