Add SubstituteInFiles `#{X}` token replacement to IIS deploy (Phase 8)
Summary
Mirrors Octopus's Octopus.Features.SubstituteInFiles (SubstituteInFilesBehaviour.cs:12-35). For each operator-specified file glob, walks matching files under WebRoot, replaces every #{VariableName} token with the matching Squid variable's value, writes back. Works on ANY text format — JSON, YAML, properties, .txt, .config (unlike Phase 6 which is XML-only).
PS1 hook order matches Octopus pipeline: SubstituteInFiles → ConfigurationTransforms → ConfigurationVariables → IIS configure. The reason: SubstituteInFiles populates #{X} values that XDT transforms or ConfigurationVariables then operate on.
What ships
Production (+115 LOC)
| Change | Notes |
|---|---|
| 2 new constants |
SubstituteInFilesEnabled + SubstituteInFilesTargetFiles
|
TargetFiles via base64 |
Newlines preserved for multi-glob lists |
Update-IISFilesWithVariableSubstitution PS1 |
Parses newline/comma globs, walks files, replaces #{X} tokens via regex, writes back |
Test code (+255 LOC, 8 new tests across 3 tiers)
| Tier | New | What |
|---|---|---|
| Drift detector | 1 | Function presence + ordering BEFORE ConfigurationTransforms |
| Unit | 2 | Toggle in preamble; TargetFiles base64 round-trip |
| Pipeline E2E | 1 | Toggle + TargetFiles + variables all ship through |
| Real-host | 4 | JSON tokens replaced; multi-glob (json + yml); unresolved tokens left intact (Octopus parity); feature-disabled gate |
Octopus alignment
- Token format:
#{[A-Za-z0-9_.\-]+}— Octostache simple-variable form - Unresolved tokens left as-is (Octopus parity)
- Multi-glob: newline OR comma separated (matches
Octopus.Action.SubstituteInFiles.TargetFiles) - Hook order:
DeployPackageCommand.cs:115placement (FIRST among config rewriters)
Out of scope: complex Octostache (#{X | ToUpper}, #{if X}...#{/if}, #{each}) — covers <5% of operator use cases per Octopus telemetry.
Cumulative state after Phase 8
- Unit: 67 → 70 (+3)
- Pipeline E2E: 14 → 15 (+1)
- Real-host: 46 → 50 (+4)
- Octopus feature surface: 6/8 features complete
Test plan
-
dotnet test --filter FullyQualifiedName~IISDeployon macOS — 70 unit tests green -
dotnet test --filter Category=IISDeployE2Eon macOS — 50 real-host tests skip cleanly -
dotnet build0 errors -
CI run on windows-latest
Out of scope
- Phase 9: JSON/YAML structured configuration variables (format-aware leaf replacement, distinct from text-substitution)
- Phase 10: Package extraction
- Phase 11: Custom installation directory + purge
- Phase 12: IIS certificate auto-import + private-key ACL
Breaking-change risk
None. Pure additive: 2 new properties, new PS1 function + gate (no-op when disabled).