Skip to content

Commit 700695b

Browse files
check bash exec & associative array commands are available and use printf instead of xxd
1 parent af5c0d3 commit 700695b

File tree

2 files changed

+128
-31
lines changed

2 files changed

+128
-31
lines changed

source/Calamari.Common/Features/Scripting/Bash/Bootstrap.sh

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -265,6 +265,24 @@ function log_environment_information
265265

266266
log_environment_information
267267

268+
function hex_to_bin_fd() {
269+
local hex="$1"
270+
local fd="$2"
271+
272+
# Process hex in reasonable chunks
273+
for ((i=0; i<${#hex}; i+=100)); do
274+
local chunk="${hex:$i:100}"
275+
local cmd="printf '"
276+
277+
for ((j=0; j<${#chunk}; j+=2)); do
278+
cmd+="\\x${chunk:$j:2}"
279+
done
280+
281+
cmd+="'"
282+
eval "$cmd" >&$fd
283+
done
284+
}
285+
268286
function decrypt_and_parse_variables {
269287
local encrypted="$1"
270288
local iv="$2"
@@ -287,7 +305,9 @@ function decrypt_and_parse_variables {
287305
local concatenated_hex
288306
concatenated_hex=$(printf "%s" "${hex_parts[@]}")
289307

290-
exec 3< <(echo -n "$concatenated_hex" | xxd -r -p)
308+
exec 3<> <(:)
309+
hex_to_bin_fd "$concatenated_hex" 3
310+
exec 3<&3
291311

292312
local idx
293313
for idx in "${!key_byte_lengths[@]}"; do
@@ -374,14 +394,15 @@ function report_kubernetes_manifest_file
374394
bashParametersArrayFeatureToggle=#### BashParametersArrayFeatureToggle ####
375395

376396
if [ "$bashParametersArrayFeatureToggle" = true ]; then
377-
if (( ${BASH_VERSINFO[0]:-0} > 4 || (${BASH_VERSINFO[0]:-0} == 4 && ${BASH_VERSINFO[1]:-0} > 2) )); then
378-
if command -v xxd > /dev/null; then
397+
if declare -gA test_array 2>/dev/null; then
398+
if exec 3<> <(:); then
399+
exec 3<&-
379400
decrypt_and_parse_variables "#### VARIABLESTRING.ENCRYPTED ####" "#### VARIABLESTRING.IV ####"
380401
else
381-
echo "xxd is not installed, this is required to use octopus_parameters"
402+
echo "Process substitution with exec is not supported. This is required to use octopus_parameters"
382403
fi
383404
else
384-
echo "Bash version 4.2 or later is required to use octopus_parameters"
405+
echo "Associative arrays are not supported in this Bash version. This is required to use octopus_parameters"
385406
fi
386407
fi
387408

source/Calamari.Tests/Fixtures/Bash/BashFixture.cs

Lines changed: 102 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,15 @@
22
using System.Collections.Generic;
33
using System.IO;
44
using Calamari.Common.Features.Processes;
5+
using System.Diagnostics;
6+
using System.Linq;
57
using Calamari.Common.FeatureToggles;
68
using Calamari.Common.Plumbing;
79
using Calamari.Common.Plumbing.Variables;
810
using Calamari.Deployment;
911
using Calamari.Testing.Requirements;
1012
using Calamari.Tests.Helpers;
13+
using FluentAssertions;
1114
using NUnit.Framework;
1215

1316
namespace Calamari.Tests.Fixtures.Bash
@@ -34,7 +37,7 @@ public void ShouldPrintEncodedVariable(FeatureToggle? featureToggle)
3437
[RequiresBashDotExeIfOnWindows]
3538
public void ShouldPrintSensitiveVariable(FeatureToggle? featureToggle)
3639
{
37-
var (output, _) = RunScript("print-sensitive-variable.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
40+
var (output, _) = RunScript("print-sensitive-variable.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
3841

3942
Assert.Multiple(() =>
4043
{
@@ -48,7 +51,7 @@ public void ShouldPrintSensitiveVariable(FeatureToggle? featureToggle)
4851
[RequiresBashDotExeIfOnWindows]
4952
public void ShouldCreateArtifact(FeatureToggle? featureToggle)
5053
{
51-
var (output, _) = RunScript("create-artifact.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
54+
var (output, _) = RunScript("create-artifact.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
5255

5356
Assert.Multiple(() =>
5457
{
@@ -62,21 +65,21 @@ public void ShouldCreateArtifact(FeatureToggle? featureToggle)
6265
[RequiresBashDotExeIfOnWindows]
6366
public void ShouldUpdateProgress(FeatureToggle? featureToggle)
6467
{
65-
var (output, _) = RunScript("update-progress.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
68+
var (output, _) = RunScript("update-progress.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
6669

6770
Assert.Multiple(() =>
6871
{
6972
output.AssertSuccess();
7073
output.AssertOutput("##octopus[progress percentage='NTA=' message='SGFsZiBXYXk=']");
7174
});
7275
}
73-
76+
7477
[TestCase(FeatureToggle.BashParametersArrayFeatureToggle)]
7578
[TestCase(null)]
7679
[RequiresBashDotExeIfOnWindows]
7780
public void ShouldReportKubernetesManifest(FeatureToggle? featureToggle)
7881
{
79-
var (output, _) = RunScript("report-kubernetes-manifest.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
82+
var (output, _) = RunScript("report-kubernetes-manifest.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
8083

8184
Assert.Multiple(() =>
8285
{
@@ -87,7 +90,7 @@ public void ShouldReportKubernetesManifest(FeatureToggle? featureToggle)
8790
output.AssertOutput("##octopus[k8s-manifest-applied manifest='ImFwaVZlcnNpb24iOiAidjEiXG4ia2luZCI6ICJOYW1lc3BhY2UiXG4ibWV0YWRhdGEiOlxuICAibmFtZSI6ICJkaWZmcyJcbiJsYWJlbHMiOlxuICAgICJuYW1lIjogImRpZmZzIlxu' ns='bXk=']");
8891
});
8992
}
90-
93+
9194
[TestCase(FeatureToggle.BashParametersArrayFeatureToggle)]
9295
[TestCase(null)]
9396
[RequiresBashDotExeIfOnWindows]
@@ -107,20 +110,23 @@ public void ShouldReportKubernetesManifestFile(FeatureToggle? featureToggle)
107110
""name"": ""diffs""
108111
""labels"":
109112
""name"": ""diffs""".ReplaceLineEndings("\n");
110-
113+
111114
var filePath = Path.Combine(tempPath, "ShouldWriteServiceMessageForKubernetesManifestFile.manifest.yaml");
112115
File.WriteAllText(filePath, manifest);
113116

114117
//if we are running on windows, we must be running via bash.exe, so we need to translate this to a wsl path
115118
var updatedFilePath = filePath;
116119
if (CalamariEnvironment.IsRunningOnWindows)
117120
{
118-
var qualifiedPath = filePath.Replace(@"\",@"\\");
121+
var qualifiedPath = filePath.Replace(@"\", @"\\");
119122

120123
var path = string.Empty;
121-
var result = SilentProcessRunner.ExecuteCommand("wsl", $"wslpath -a -u {qualifiedPath}", tempPath, output => path = output,
124+
var result = SilentProcessRunner.ExecuteCommand("wsl",
125+
$"wslpath -a -u {qualifiedPath}",
126+
tempPath,
127+
output => path = output,
122128
_ => { });
123-
129+
124130
if (result.ExitCode != 0)
125131
{
126132
Assert.Fail("Failed to convert windows path to WSL path");
@@ -135,7 +141,6 @@ public void ShouldReportKubernetesManifestFile(FeatureToggle? featureToggle)
135141

136142
try
137143
{
138-
139144
var (output, _) = RunScript("report-kubernetes-manifest-file.sh", additionalVariables);
140145

141146
Assert.Multiple(() =>
@@ -160,7 +165,7 @@ public void ShouldConsumeParametersWithQuotes(FeatureToggle? featureToggle)
160165
{
161166
var (output, _) = RunScript("parameters.sh",
162167
new Dictionary<string, string>()
163-
{ [SpecialVariables.Action.Script.ScriptParameters] = "\"Para meter0\" 'Para meter1'" }.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
168+
{ [SpecialVariables.Action.Script.ScriptParameters] = "\"Para meter0\" 'Para meter1'" }.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
164169

165170
Assert.Multiple(() =>
166171
{
@@ -174,8 +179,10 @@ public void ShouldConsumeParametersWithQuotes(FeatureToggle? featureToggle)
174179
[RequiresBashDotExeIfOnWindows]
175180
public void ShouldNotReceiveParametersIfNoneProvided(FeatureToggle? featureToggle)
176181
{
177-
var (output, _) = RunScript("parameters.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }), sensitiveVariablesPassword:
178-
"5XETGOgqYR2bRhlfhDruEg==");
182+
var (output, _) = RunScript("parameters.sh",
183+
new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }),
184+
sensitiveVariablesPassword:
185+
"5XETGOgqYR2bRhlfhDruEg==");
179186

180187
Assert.Multiple(() =>
181188
{
@@ -197,7 +204,7 @@ public void ShouldCallHello(FeatureToggle? featureToggle)
197204
["Variable3"] = "GHI",
198205
["Foo_bar"] = "Hello",
199206
["Host"] = "Never",
200-
}.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
207+
}.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
201208

202209
Assert.Multiple(() =>
203210
{
@@ -213,8 +220,9 @@ public void ShouldCallHelloWithSensitiveVariable(FeatureToggle? featureToggle)
213220
{
214221
var (output, _) = RunScript("hello.sh",
215222
new Dictionary<string, string>()
216-
{ ["Name"] = "NameToEncrypt" }.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }), sensitiveVariablesPassword:
217-
"5XETGOgqYR2bRhlfhDruEg==");
223+
{ ["Name"] = "NameToEncrypt" }.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }),
224+
sensitiveVariablesPassword:
225+
"5XETGOgqYR2bRhlfhDruEg==");
218226

219227
Assert.Multiple(() =>
220228
{
@@ -230,7 +238,7 @@ public void ShouldCallHelloWithNullVariable(FeatureToggle? featureToggle)
230238
{
231239
var (output, _) = RunScript("hello.sh",
232240
new Dictionary<string, string>()
233-
{ ["Name"] = null }.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
241+
{ ["Name"] = null }.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
234242

235243
Assert.Multiple(() =>
236244
{
@@ -246,8 +254,9 @@ public void ShouldCallHelloWithNullSensitiveVariable(FeatureToggle? featureToggl
246254
{
247255
var (output, _) = RunScript("hello.sh",
248256
new Dictionary<string, string>()
249-
{ ["Name"] = null }.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }), sensitiveVariablesPassword:
250-
"5XETGOgqYR2bRhlfhDruEg==");
257+
{ ["Name"] = null }.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }),
258+
sensitiveVariablesPassword:
259+
"5XETGOgqYR2bRhlfhDruEg==");
251260

252261
Assert.Multiple(() =>
253262
{
@@ -262,7 +271,7 @@ public void ShouldCallHelloWithNullSensitiveVariable(FeatureToggle? featureToggl
262271
public void ShouldNotFailOnStdErr(FeatureToggle? featureToggle)
263272
{
264273
var (output, _) = RunScript("stderr.sh",
265-
new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
274+
new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
266275

267276
Assert.Multiple(() =>
268277
{
@@ -278,7 +287,7 @@ public void ShouldFailOnStdErrWithTreatScriptWarningsAsErrors(FeatureToggle? fea
278287
{
279288
var (output, _) = RunScript("stderr.sh",
280289
new Dictionary<string, string>()
281-
{ [SpecialVariables.Action.FailScriptOnErrorOutput] = "True" }.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
290+
{ [SpecialVariables.Action.FailScriptOnErrorOutput] = "True" }.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
282291

283292
Assert.Multiple(() =>
284293
{
@@ -294,7 +303,7 @@ public void ShouldNotFailOnStdErrFromServiceMessagesWithTreatScriptWarningsAsErr
294303
{
295304
var (output, _) = RunScript("hello.sh",
296305
new Dictionary<string, string>()
297-
{ [SpecialVariables.Action.FailScriptOnErrorOutput] = "True" }.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
306+
{ [SpecialVariables.Action.FailScriptOnErrorOutput] = "True" }.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
298307

299308
output.AssertSuccess();
300309
}
@@ -304,7 +313,7 @@ public void ShouldNotFailOnStdErrFromServiceMessagesWithTreatScriptWarningsAsErr
304313
[TestCase(null)]
305314
public void ShouldSupportStrictVariableUnset(FeatureToggle? featureToggle)
306315
{
307-
var (output, _) = RunScript("strict-mode.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
316+
var (output, _) = RunScript("strict-mode.sh", new Dictionary<string, string>().AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
308317

309318
Assert.Multiple(() =>
310319
{
@@ -336,8 +345,9 @@ public void ShouldBeAbleToEnumerateVariableValues(FeatureToggle? featureToggle)
336345
["VariableName \n 11"] = "Value \n 11",
337346
["VariableName.prop.anotherprop 12"] = "Value.prop.12",
338347
["VariableName`prop`anotherprop` 13"] = "Value`prop`13",
348+
["VariableName 14 😭🙈👀"] = "Value 14 😭🙈👀",
339349
[specialCharacters] = specialCharacters
340-
}.AddFeatureToggleToDictionary(new List<FeatureToggle?>{ featureToggle }));
350+
}.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
341351

342352
output.AssertSuccess();
343353
if (featureToggle == FeatureToggle.BashParametersArrayFeatureToggle)
@@ -366,9 +376,75 @@ public void ShouldBeAbleToEnumerateVariableValues(FeatureToggle? featureToggle)
366376

367377
output.AssertOutput("Key: VariableName.prop.anotherprop 12, Value: Value.prop.12");
368378
output.AssertOutput("Key: VariableName`prop`anotherprop` 13, Value: Value`prop`13");
379+
output.AssertOutput("Key: VariableName 14 😭🙈👀, Value: Value 14 😭🙈👀");
369380
output.AssertOutput($"Key: {specialCharacters}, Value: {specialCharacters}");
370381
}
371382
}
383+
384+
[TestCase(FeatureToggle.BashParametersArrayFeatureToggle)]
385+
[TestCase(null)]
386+
[RequiresBashDotExeIfOnWindows]
387+
public void ShouldBeAbleToEnumerateLargeVariableSetsEfficiently(FeatureToggle? featureToggle)
388+
{
389+
// Create a dictionary with 10,000 variables with diverse characters
390+
var variables = new Dictionary<string, string>();
391+
var random = new Random(42); // Seed for reproducibility
392+
393+
// Generate 10,000 unique variables with diverse content
394+
for (int i = 0; i < 10000; i++)
395+
{
396+
string key = $"Key{i}_{Guid.NewGuid().ToString("N")}";
397+
string value = $"Value{i}_{Convert.ToBase64String(Guid.NewGuid().ToByteArray())}";
398+
399+
// Mix in some random Unicode characters
400+
if (random.Next(5) == 0)
401+
{
402+
key += (char)random.Next(0x1F600, 0x1F64F); // Emoji range
403+
value += Environment.NewLine + (char)random.Next(0x2600, 0x26FF); // Unicode symbols
404+
}
405+
406+
variables[key] = value;
407+
}
408+
409+
var sw = Stopwatch.StartNew();
410+
// Run the script with all these variables
411+
var (output, _) = RunScript("enumerate-variables.sh",
412+
variables.AddFeatureToggleToDictionary(new List<FeatureToggle?> { featureToggle }));
413+
sw.Stop();
414+
// This depends on the running machine, locally ~1000ms, in CI sometimes this is ~2000ms. We're being very conservative
415+
// but if there's a scenario where this test fails this should be increased. This test exists because there are
416+
// potential performance problems in encoding/decoding of variables in bash, this is a sanity check.
417+
sw.Elapsed.TotalMilliseconds.Should().BeLessThan(4000);
418+
419+
output.AssertSuccess();
420+
if (featureToggle == FeatureToggle.BashParametersArrayFeatureToggle)
421+
{
422+
var fullOutput = string.Join(Environment.NewLine, output.CapturedOutput.Infos);
423+
if (fullOutput.Contains("Bash version 4.2 or later is required to use octopus_parameters"))
424+
{
425+
output.AssertOutput("Still ran this script");
426+
return;
427+
}
428+
429+
// Get all output lines that start with "Key: "
430+
var outputLines = output.CapturedOutput.Infos
431+
.Where(line => line.StartsWith("Key: "))
432+
.ToList();
433+
434+
// Verify count matches
435+
Assert.That(outputLines.Count,
436+
Is.EqualTo(variables.Count),
437+
"Not all variables were processed");
438+
439+
// For each variable, construct the expected output format and verify it exists
440+
foreach (var kvp in variables)
441+
{
442+
string expectedOutput = $"Key: {kvp.Key}, Value: {kvp.Value}";
443+
Assert.That(outputLines.Contains(expectedOutput),
444+
$"Expected output line not found: '{expectedOutput}'");
445+
}
446+
}
447+
}
372448
}
373449

374450
public static class AdditionalVariablesExtensions
@@ -379,4 +455,4 @@ public static Dictionary<string, string> AddFeatureToggleToDictionary(this Dicti
379455
return variables;
380456
}
381457
}
382-
}
458+
}

0 commit comments

Comments
 (0)