Skip to content

Enable multi-environment publish to shared APIM instance - #227

Open
Peter Hauge (petehauge) wants to merge 3 commits into
mainfrom
feat/multi-env-shared-apim
Open

Enable multi-environment publish to shared APIM instance#227
Peter Hauge (petehauge) wants to merge 3 commits into
mainfrom
feat/multi-env-shared-apim

Conversation

@petehauge

@petehaugePeter Hauge (petehauge) commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds multi-environment publish to a single shared Azure APIM instance via a new optional environment: block in configuration.<env>.yaml override files. Resource names are namespaced by namePrefix and/or nameSuffix, and API paths can be scoped by apiPathPrefix.

Related Issue(s)

Closes#132

What's included

New capability

  • environment: block in override files — configures per-environment name and path affixing so dev, qa, and prod can share one APIM
  • --delete-unmatched is now namespace-scoped when environment: is configured — a dev publish cannot delete prod resources on the shared instance
  • Automatic policy XML rewriting{{namedValue}} tokens, fragment-id, and backend-id attributes are rewritten to affixed names at publish time
  • Cross-resource reference rewriting — subscription scopes, apiRelease apiIds, MCP tool operation ids, and PolicyFragment references

Safety guardrails

  • Hard-block when environment: is present but both namePrefix and nameSuffix are missing/empty — path-only mode is intentionally unsupported because --delete-unmatched would silently destroy other environments' resources
  • Character validation on affixes ([A-Za-z0-9-] only)
  • Whitespace-only affixes rejected (trimmed to empty → error)
  • appliesTo validation catches unknown or non-affixable resource types

New files

  • src/services/env-mapper.ts — pure name/descriptor mapping module
  • src/services/env-mapping-validator.ts — pre-flight validator with hard-block for unsafe configurations
  • src/services/policy-ref-rewriter.ts — regex-based policy XML ref rewriter
  • docs/guides/multi-environment-shared-apim.md — new 440-line user guide with setup, safety notes, examples, and troubleshooting

Modified files

  • src/services/publish-service.ts — orchestration wiring; incremental-mode uses full listResources for validator and known-artifact set
  • src/services/resource-publisher.tsmapDescriptor applied at 5 PUT boundaries; 4 normalizers extended; NamedValue displayName / Api path prefix / PolicyFragment content / policy XML rewriting
  • src/services/delete-unmatched-service.ts — namespace scoping via toCanonicalDescriptor
  • src/models/config.tsEnvironmentOverride, KnownArtifactSets, extended PublishConfig
  • src/lib/config-loader.ts, src/services/override-merger.ts — schema-loader wiring
  • schemas/v1/override-config.schema.json — additive schema extension (v1 stays v1)
  • src/templates/configs/override-config.yaml, src/templates/copilot/configure-overrides-prompt.md — init scaffold
  • README.md, docs/guides/environment-overrides.md — cross-links to new guide

Tests

  • 6 new test files (env-mapper, policy-ref-rewriter, config.environment schema, delete-unmatched env-mapping, publish-service env-mapping, resource-publisher env-mapping)
  • 4 existing test files extended
  • 130+ new tests total; all 1234 tests pass

Design decisions (locked in with Peter Hauge (@petehauge))

  1. Both name and displayName are affixed for NamedValues so {{token}} references in policies work across environments
  2. Regex-based policy XML rewriting for {{token}} + targeted fragment-id/backend-id attributes (no full XML parse)
  3. Explicit per-API path override wins over apiPathPrefix
  4. Documentation, GlobalSchema, and PolicyRestriction are opt-in only (not in DEFAULT_APPLIES_TO)
  5. Schema stays v1 (additive change, no version bump)
  6. Path-only mode is blocked at validation time (see safety guardrails)

Reviews

Four internal squad reviews performed on this branch:

  • CodeReviewer: APPROVE (one non-blocking wording nit)
  • SecurityExpert: CLEAR (no findings)
  • ApiOpsLead: APPROVE-WITH-CONCERNS (required fix applied: import.meta.url in test)
  • ApimExpert: APPROVE-WITH-CONCERNS

Testing

npm test # 1234/1234 pass (excluding known pre-existing Windows CLI-help flake)
npm run lint # clean
npx tsc --noEmit # clean

Add an optional `environment:` block to `configuration.<env>.yaml` override
files that enables publishing dev, qa, and prod to a single shared Azure API
Management instance. Resource names are namespaced via `namePrefix` and/or
`nameSuffix`, and API paths can be scoped via `apiPathPrefix`.
Key changes:
- New `env-mapper` module — pure name/descriptor mapping (canonical <-> deployed)
- New `env-mapping-validator` — pre-flight validation with hard-block when the
environment block is present but lacks any name affix (path-only mode is
intentionally unsupported because --delete-unmatched would destroy other
environments' resources on the shared instance)
- New `policy-ref-rewriter` — regex-based rewrite of {{namedValue}} tokens,
fragment-id, and backend-id attributes in policy XML
- Publisher integration at all 5 PUT boundaries with mapDescriptor
- Normalizers extended for subscription scopes, apiRelease apiIds, MCP tool
operation ids, and named-value references
- NamedValue displayName affixing, Api path prefixing, PolicyFragment content
rewriting, and policy XML rewriting
- delete-unmatched-service namespace-scoped via toCanonicalDescriptor: a dev
publish cannot delete prod resources sharing the same APIM
- Schema v1 extended additively (no version bump) with `environment` property
- init scaffold and Copilot prompt updated
- New user guide: docs/guides/multi-environment-shared-apim.md
Closes#132
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
@petehaugePeter Hauge (petehauge) changed the title feat: multi-environment publish to shared APIM instanceEnable multi-environment publish to shared APIM instanceJul 6, 2026

CopilotAI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds support for publishing multiple environments (dev/qa/prod) into a single shared Azure API Management instance by introducing an optional environment: block in override configs, applying name/path affixes at publish time, rewriting cross-resource references, and scoping --delete-unmatched to the active environment namespace.

Changes:

  • Introduce env mapping (prefix/suffix + appliesTo + apiPathPrefix) with runtime validation and wire it through publish, resource publishing, and delete-unmatched.
  • Add policy XML reference rewriting ({{namedValue}}, fragment-id, backend-id) and other cross-resource reference rewrites (subscription scope, apiRelease apiId, MCP tool operationIds).
  • Extend templates/schema/docs and add comprehensive unit test coverage for the new behavior.

Reviewed changes

Copilot reviewed 27 out of 28 changed files in this pull request and generated 3 comments.

Show a summary per file
FileDescription
tests/unit/templates/configs/config-templates.test.tsAsserts override template includes commented environment: block and substitutions.
tests/unit/services/resource-publisher.env-mapping.test.tsValidates env-mapping behavior at PUT boundaries and reference rewrites.
tests/unit/services/publish-service.test.tsAdds regression coverage for incremental mode using full artifact set for env mapping / known artifacts.
tests/unit/services/publish-service.env-mapping.test.tsUnit tests for env-mapping validation behavior and warnings/errors.
tests/unit/services/policy-ref-rewriter.test.tsUnit tests for policy XML token/attribute rewrites with known-sets gating.
tests/unit/services/override-merger.test.tsVerifies deep-merge behavior for logger credentials overrides.
tests/unit/services/env-mapper.test.tsUnit tests for mapping primitives (affixing, canonicalization, descriptor mapping).
tests/unit/services/delete-unmatched.env-mapping.test.tsEnsures delete-unmatched is namespace-scoped under env mapping.
tests/unit/models/config.environment.test.tsValidates schema acceptance/rejection for the new environment field.
tests/unit/lib/config-loader.test.tsEnsures YAML override loader parses/validates environment overrides at runtime.
src/templates/copilot/configure-overrides-prompt.mdUpdates Copilot prompt to guide users on shared-APIM vs dedicated setups.
src/templates/configs/override-config.yamlAdds commented environment: block scaffold and doc link.
src/services/resource-publisher.tsApplies env mapping at PUT boundaries; adds known-artifact sets; rewrites policy refs and other cross-resource references.
src/services/publish-service.tsBuilds env mapping + known artifact sets (uses full artifact list in incremental mode).
src/services/policy-ref-rewriter.tsNew policy XML reference rewriter (regex-based with known-sets gating).
src/services/override-merger.tsTightens typing for override section keys (excludes non-section fields like environment).
src/services/env-mapping-validator.tsNew validator enforcing safe shared-APIM configuration and logging warnings.
src/services/env-mapper.tsNew mapping utilities for deployed/canonical naming and descriptor segment mapping.
src/services/delete-unmatched-service.tsScopes deletions to current env namespace and compares canonical names against local artifacts.
src/models/config.tsAdds EnvironmentOverride, KnownArtifactSets, and publish-time envMapping fields.
src/lib/config-loader.tsParses environment: override block and warns on unknown keys.
scripts/generate-schemas.mjsExtends schema generator to include environmentOverride definition.
schemas/v1/override-config.schema.jsonAdds environment property + environmentOverride definition.
README.mdAdds guide links for environment overrides and shared-APIM publishing.
package.jsonAdds ajv dev dependency for schema validation tests.
package-lock.jsonUpdates lockfile for ajv dependency resolution.
docs/guides/multi-environment-shared-apim.mdNew guide documenting shared-APIM workflow, safety model, and limitations.
docs/guides/environment-overrides.mdAdds section linking to shared-APIM guidance and clarifying canonical naming.

Comment threadtests/unit/services/publish-service.test.ts Outdated
Comment threadsrc/services/env-mapper.ts Outdated
Comment threadsrc/services/delete-unmatched-service.ts Outdated
Peter Haugeand others added 2 commits July 5, 2026 23:16
…imit
Top-level APIM resource names (APIs, NamedValues, Products, Backends, etc.)
are capped at 80 characters. When the environment name affix pushes a
deployed name past that threshold, the ARM PUT fails mid-publish with a
cryptic 400 that does not identify the affix as the cause.
The validator now iterates over artifact descriptors whose type is in
`appliesTo` and emits a logger.warn (not an error) when
`namePrefix + canonicalName + nameSuffix` exceeds 80 characters. The warning
includes the offending deployed name, the length, the resource type, and
remediation guidance. It fires during `apiops publish --dry-run` too.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
- env-mapper: consolidate EnvironmentOverride to a single definition in
src/models/config.ts; drop the unsafe `as EnvironmentOverride | undefined`
cast; buildEnvMapping now safely converts string[] appliesTo values to
a ReadonlySet<ResourceType> via a validated filter. The prior duplicate
interface let string[] and ResourceType[] shapes drift silently.
- delete-unmatched-service: trim trailing whitespace on the "prefix dropped
mid-life" doc comment.
- publish-service test: split `async () => { const client = ...` onto two
lines to match surrounding style.
Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Support multi-environment deployment to a single APIM instance

3 participants

@petehauge@EMaher