Skip to content

G1.2: ConfigurationTransformsStep — apply XDT to *.config files

Placeholder ppxd requested to merge feat/calamari-g1-2-xdt-transforms into main

Summary

Phase G1.2 of the Calamari Refactor Roadmap. Closes the IIS deploy handler's currently-degraded "Configuration transforms" toggle by giving Calamari a real XDT engine for the Bash deploy path.

What's new

Two components under Squid.Calamari.Commands.Configuration:

Component Lines Tests Purpose
XdtTransformer ~80 7 Pure-function wrapper over Microsoft.Web.XmlTransform with typed TransformResult, atomic write, XML-decl preservation
ConfigurationTransformsStep ~140 13 Pipeline step: auto-pair *.{Env}.config + *.Release.config, operator-supplied transform => base pairs, malformed-line tolerance

Plus ConfigurationTransformsVariableNames public constants for cross-project wire-contract pinning.

Dependency add

  • Microsoft.Web.Xdt 3.1.0 (~150 KB) — Microsoft's official XDT engine, used by .NET Framework web.config transform tooling. Per the Calamari Architecture Decision, heavy-lifting work like this belongs in Calamari (fork-isolated child process, has access to extracted files on the agent).

Pipeline order

ResolveWorkingDirectory
  → LoadVariablesFromFiles
  → SubstituteInFiles         (G1.1 — #{Token} replacement; runs first so transform files have concrete values)
  → ConfigurationTransforms   (G1.2 — XDT; runs on already-substituted files)
  → WriteBootstrappedBashScript
  → ExecuteScriptWithEngine
  → BuildRunScriptCommandResult
  → CleanupTemporaryFiles

Matches Octopus's Octopus.Features.ConfigurationTransforms ordering — SubstituteInFiles → ConfigurationTransforms is critical: if the order were reversed, transform files containing #{Token} references would be parsed by the XDT engine BEFORE the tokens were resolved, producing garbage transforms.

Auto-pairing semantic (Octopus parity)

For each *.config file in the working directory:

  1. Always applied (if it exists): *.Release.config — matches .NET FX build-time XDT default
  2. Conditionally applied: *.{EnvironmentName}.config — driven by Squid.Action.IISWebSite.ConfigurationTransforms.EnvironmentName variable

A "base config" is any *.config whose stem has no . (e.g. web.config ✓, web.Production.config ✗). Heuristic avoids treating transforms as their own targets.

Operator-supplied pairs

ConfigurationTransforms.AdditionalTransforms (newline-separated):

custom-overrides.config => app.config
overrides/prod.config => web.config

Malformed line (no =>) → skip with stderr warning, continue with the rest. Don't fail the whole deploy on a typo.

Wire-contract pinning

Cross-project drift detector in Squid.UnitTests.IISConfigurationTransformsWireContractTests asserts all three variable literals match between server (IISDeployProperties) and agent (ConfigurationTransformsVariableNames). Same pattern as G1.1.

Test plan

  • 7 unit tests on XdtTransformer — SetAttributes/Insert directives, missing files, malformed XML, identity transform, XML-declaration preservation
  • 13 unit tests on ConfigurationTransformsStep — enable-gate × 5, auto-pair × 3, operator-pair × 3, edge cases × 2
  • 4 cross-project drift tests in Squid.UnitTests pinning server ↔️ agent contract
  • 5611/5611 Squid.UnitTests green (+4)
  • 203/203 Squid.Calamari.Tests green (+24 net new)

What's NOT in this PR

  • G1.3 (StructuredConfigVariablesStep) — JSON path edits on appsettings.json; next PR
  • G1.4 (ExtractPackageStep) — package archive extraction; separate PR
  • G1.5 (Convention hooks) — PreDeploy/Deploy/PostDeploy scripts; separate PR

Operator-visible improvement

Per the failed-deploy log the operator shared earlier:

ConfigurationTransforms: processing *.config under '.'
警告: ConfigurationTransforms: Microsoft.Web.XmlTransform not available on this agent.

For Bash deploys, the Calamari child now has the assembly bundled — transforms apply cleanly. For Windows IIS deploys (which use the PowerShell path per PR #353), the PS script still has its own inline XDT check; making that path also benefit from this is a future PR.

Refs

  • Notion: 🦑 Squid — Calamari Refactor Roadmap (Post 1.8.1) — phase plan
  • PR #363 (G1.1) — the precedent for the wire-contract pinning + pipeline integration shape

Merge request reports

Loading