Skip to content

Skip Calamari for PowerShell scripts (was crashing with FileNotFoundException)

Placeholder ppxd requested to merge fix/tentacle-skip-calamari-for-powershell into main

Summary

  • Fix operator-visible failure on Windows IIS deploy (and any other Windows + PowerShell + variables.json path): the dispatch crashed with Unknown result (ticket or process not found) (exit code -1) because the agent invoked Calamari with a Bash-only contract on a PowerShell script.
  • Extract IsCalamariCompatible(ScriptType) returning true only for Bash. PowerShell scripts route through StartViaLauncher instead, which picks PwshCoreProcessLauncher or WindowsPowerShellProcessLauncher (auto-fallback per #352).

Root cause (from the operator's work dir dump)

C:\Windows\TEMP\squid-tentacle-{ticket}\output.log:

Unhandled exception. System.IO.FileNotFoundException:
  Could not find file 'C:\Windows\TEMP\squid-tentacle-{ticket}\script.sh'.
    at Squid.Calamari.Commands.WriteBootstrappedBashScriptStep.ExecuteAsync(...)
    at Squid.Calamari.Pipeline.ExecutionPipeline...

Same dir contains script.ps1 (96 KB — the rendered IIS deploy PowerShell with all $SquidParameters / $SquidVariables inlined). Tentacle wrote .ps1, Calamari read .sh. Two layered bugs:

File:Line Bug
LocalScriptService.cs:1275 psi.ArgumentList.Add("--script=script.sh") — hardcoded Bash filename
Squid.Calamari/Commands/RunScriptCommand.cs:26 Pipeline only contains WriteBootstrappedBashScriptStep — no WriteBootstrappedPowerShellScriptStep exists

The agent's routing decision File.Exists(variablesPath) && canUseCalamari (where canUseCalamari was Bash || PowerShell) sent PowerShell into a Bash-only child. Calamari crashed → state file stuck at Progress=Running, ProcessId=<dead> → server's GetStatus hit orphan detection at line 497 → returned Complete + UnknownResult (-1).

Why bypassing Calamari for PowerShell is safe

Every server-side PowerShell-emitting builder (IISDeployScriptBuilder, WindowsTentacleUpgradeStrategy, etc.) already inlines variables into the rendered script body via $SquidParameters / $SquidVariables hashtables. From the operator's failed dispatch:

$SquidParameters = @{}
$SquidParameters['Squid.Action.IISWebSite.WebSiteName'] = 'SquidWeb'
$SquidParameters['Squid.Action.IISWebSite.ApplicationPoolName'] = 'SquidPool'
# … 50+ more
$SquidVariables = @{}
$SquidVariables['Squid.Deployment.Id'] = 'Deployments-67'
$SquidVariables['Squid.Tentacle.OS'] = 'Microsoft Windows NT 10.0.19045.0'
# … all variables baked in

The script is self-contained from the moment the server emits it. Calamari's Bash-style export VAR=... preamble adds zero value on top of that.

Test plan

  • Truth-table pin: IsCalamariCompatible returns true only for Bash (5 syntaxes covered: Bash / PowerShell / Python / CSharp / FSharp)
  • Standalone regression Fact: IsCalamariCompatible(PowerShell) == false with operator-visible failure description in the assert message
  • Drift detector: BuildCalamariProcessStartInfo_HardcodesScriptSh_DocumentsBashOnlyContract pins the --script=script.sh literal so a future PR that introduces a per-syntax path is forced to update IsCalamariCompatible in the same change
  • 5517/5517 Squid.UnitTests green (no adjacent regressions)
  • Operator-side: re-trigger the IIS deploy on 1.7.9 — expect process spawn via WindowsPowerShellProcessLauncher (since pwsh not installed on host); expect IIS deploy to succeed

Operator's failure mode chain (now fixed)

1.7.0 — pwsh not installed              → Win32Exception (2) at process spawn                      [fixed by #352, shipped in 1.7.8]
1.7.8 — Windows IIS deploy              → FileNotFoundException 'script.sh' (Calamari bash-only)   [fixed by THIS PR, shipping in 1.7.9]
1.7.9+ — Windows IIS deploy             → succeeds via WindowsPowerShellProcessLauncher (or pwsh if installed)

Future work (not in this PR)

When Calamari grows a WriteBootstrappedPowerShellScriptStep AND switches its CLI invocation to a per-syntax script path (--script=script.ps1 for PowerShell), IsCalamariCompatible can be widened back. The co-dependency is documented in the helper's xmldoc + the drift-detector test.

Merge request reports

Loading