Bicep shared moduly: Skip-if-exists pattern místo --force
Bicep shared moduly: Skip-if-exists pattern místo --force
V Christie's máme veřejně dobře zdokumentovaný setup – platform tým spravuje 76 sdílených Bicep modulů v Azure Container Registry a desítky workloadových repozitářů je konzumují přes br:registry/module:tag reference. Verze jsou pinované, semver disciplinovaný, schémata stabilní. Až do dne, kdy mi Russell, vedoucí platform týmu, ukázal nedávný incident.
Storage modul cmn-storage:1.4.2 se přepsal v rámci hotfixu. Konzument v retail subskripci na další az deployment viděl propertyName Foo of ResourceType Bar is not allowed. Pinned verze 1.4.2 se mu změnila pod rukama, protože publish pipeline používala --force.
Tahle událost rozjela ticket DO-429: nahradit --force skip-if-exists patternem a vynutit semver bump v PR validaci. Tady je playbook.
Proč --force vůbec existoval
Historický důvod byl pohodlí. CI pipeline publikovala při každém merge do main, a když někdo nezvedl verzi, build padl. Tým si zvykl psát --force jako rychlou opravu, místo aby řešil semver. Po roce a půl to bylo defaultní chování v hlavní pipeline.
# Před – publish-modules.yml (špatně)
- task: AzureCLI@2
inputs:
azureSubscription: $(azureServiceConnection)
scriptType: bash
inlineScript: |
for module in modules/*/; do
name=$(basename "$module")
az bicep publish \
--file "$module/main.bicep" \
--target "br:$(acrName).azurecr.io/bicep/$name:$(version)" \
--force # <-- problém
doneProblém shrnu třemi body: silently overwrites publikované verze, neexistuje audit, neexistuje žádný kontrolní bod, kdy se verze měla zvednout.
Skip-if-exists pattern
Princip: zkontroluj, jestli verze už existuje. Pokud ano, log a skip. Pokud ne, publish.
#!/usr/bin/env bash
set -euo pipefail
ACR_NAME="$1"
MODULE_NAME="$2"
VERSION="$3"
# Check, jestli tag existuje
if az acr repository show-tags \
--name "$ACR_NAME" \
--repository "bicep/$MODULE_NAME" \
--query "[?@=='$VERSION'] | [0]" -o tsv | grep -q "$VERSION"; then
echo "::notice::Module $MODULE_NAME:$VERSION already exists, skipping."
exit 0
fi
# Publish (bez --force)
az bicep publish \
--file "modules/$MODULE_NAME/main.bicep" \
--target "br:$ACR_NAME.azurecr.io/bicep/$MODULE_NAME:$VERSION"
echo "::notice::Published $MODULE_NAME:$VERSION"Tři vlastnosti, které tenhle skript dělají enterprise-ready:
set -euo pipefail– jakákoliv chyba zhasne pipeline, žádné silent failure- Skip log přes GitHub
::notice::– v job summary vidíte přesně, co se publikovalo a co bylo přeskočeno - Žádný
--force– pokud verze existuje, je to konec; ne se starší verze přepíše
PR validace: vynutit verze bump
Skip-if-exists řeší produkční riziko. Ale stále hrozí, že developer udělá změnu v modulu, nezvedne verzi, a merge projde – produkční konzumenti s pinned verzí novou změnu nikdy neuvidí.
Řešení: PR check, který sleduje diff a vyžaduje semver bump pro každý změněný modul.
# .github/workflows/pr-validate-bicep.yml
name: Validate Bicep module changes
on:
pull_request:
paths:
- 'modules/**'
jobs:
semver-check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # potřeba pro diff vůči main
- name: Check semver bump for changed modules
run: |
changed_modules=$(git diff --name-only origin/main...HEAD modules/ | awk -F'/' '{print $2}' | sort -u)
fail=0
for module in $changed_modules; do
metadata_file="modules/$module/metadata.json"
if ! git diff origin/main...HEAD -- "$metadata_file" | grep -q '"version"'; then
echo "::error::Module $module changed but version in $metadata_file not bumped."
fail=1
fi
done
exit $failKombinace skip-if-exists v publish + PR check na verze znamená, že nelze omylem nasadit změnu se starou verzí.
Konzument: jak verzi pinovat správně
Konzumentský repository pak pinuje konkrétní verzi:
module storage 'br:cmnregistry.azurecr.io/bicep/cmn-storage:1.4.3' = {
name: 'storageDeployment'
params: {
storageAccountName: name
location: location
sku: 'Standard_LRS'
}
}Co nedělat:
- Nepoužívat
latesttag – v ACR fyzicky existuje, ale popírá smysl semver - Nepoužívat rozsahy – Bicep registr range syntax (
^1.4.0) neumí; vždy konkrétní tag - Nepublikovat moduly z feature větve do produkčního registru – mějte oddělený
bicep-dev/repository v ACR
Renovate / Dependabot pro Bicep moduly
Pro udržování konzumentských repozitářů aktuálních doporučuji Renovate, který od verze 39 nativně rozumí Bicep registry referencím:
// renovate.json
{
"extends": ["config:recommended"],
"bicep": { "enabled": true },
"packageRules": [
{
"matchManagers": ["bicep"],
"matchUpdateTypes": ["minor", "patch"],
"automerge": true,
"automergeType": "pr"
},
{
"matchManagers": ["bicep"],
"matchUpdateTypes": ["major"],
"labels": ["bicep-major-bump", "needs-review"]
}
]
}Patch a minor verze se mergují automaticky (semver kontrakt říká, že jsou kompatibilní). Major bumpy padají na review – ten labeluje a notifikuje cloud platform team.
Audit: kdo a kdy přepsal modul
I s break-glass postupem chceme vědět, kdy se použil --force. V Christie's to řešíme přes ACR Activity Log s alertem:
resource forcePublishAlert 'Microsoft.Insights/scheduledQueryRules@2023-03-15-preview' = {
name: 'alert-bicep-module-overwrite'
location: location
properties: {
severity: 1
enabled: true
evaluationFrequency: 'PT15M'
windowSize: 'PT15M'
scopes: [logAnalyticsWorkspaceId]
criteria: {
allOf: [
{
query: '''
ContainerRegistryRepositoryEvents
| where Repository startswith "bicep/"
| where OperationName == "Push"
| summarize PushCount = count() by Repository, Tag = MediaType, Identity
| where PushCount > 1
'''
timeAggregation: 'Count'
operator: 'GreaterThan'
threshold: 0
}
]
}
actions: {
actionGroups: [securityActionGroupId]
}
}
}Pokud do stejného tagu přijde druhý push v 15-minutovém okně, jde alert na security team. Většinou je to legitimní break-glass, občas se tím chytí pipeline misconfiguration.
Rollout: jak nahradit --force v existujícím prostředí
V Christie's jsme to dělali ve čtyřech krocích:
| Krok | Týden | Riziko |
|---|---|---|
Implementovat skip-if-exists, ponechat --force jako fallback flag | 1 | Žádné – nic se nemění |
| Spustit oba módy paralelně, monitorovat skip-rate | 2–3 | Žádné – jen sběr dat |
| Komunikovat změnu napříč týmy, dát 1 týden notice | 4 | Žádné |
Odebrat --force flag, vynutit semver PR check | 5 | Nízké – občasný PR padne, dev opraví |
Po pěti týdnech jsme měli zero overwrite incidentů a žádný team se nestěžoval na pomalost publish procesu (skip je rychlejší než publish).
Závěr
--force v az bicep publish je v multi-team prostředí mina. Skip-if-exists pattern, semver PR validation a audit alert ji zneškodní za pět týdnů rolloutu. Cena je nula – jen disciplína a 50 řádků Bash skriptu.
Pokud řešíte podobnou situaci se sdílenými Bicep nebo Terraform moduly v enterprise měřítku, podívejte se na naše služby cloudové architektury nebo se ozvěte pro review vaší IaC supply chain.
O autorovi

Martin Rylko
Senior Cloud Architect & DevOps Engineer
Více než 14 let v IT – od on-premises datacenter a Hyper-V clusteringu po cloudovou infrastrukturu v Microsoft Azure. Specializuji se na Landing Zones, IaC automatizaci, Kubernetes a bezpečnostní compliance.
Nejcastejsi dotazy
Proč je --force při publikaci Bicep modulů problém?▾
Jak se semver vztahuje k publikaci Bicep modulů?▾
Co když potřebuji přepsat publikovanou verzi v krajní situaci?▾
Funguje skip-if-exists pattern i pro Azure DevOps i pro GitHub Actions?▾
Mohlo by vás zajímat
Bicep CI/CD: GitHub Actions pipeline pro Azure
Produkční deployment pipeline pro Bicep v GitHub Actions. What-if náhledy, schvalování prostředí, OIDC autentizace a rollback strategie.
ČístBicep Deployment Stacks: Lifecycle management bez ručního cleanupu
Deployment Stacks v Bicepu jsou způsob, jak Azure resources spravovat jako koherentní celek s denyAssignments, auto-cleanup a controlled deletion. Praktický průvodce s migration plánem z klasických deploymentů.
ČístTerraform Azure moduly: Privátní registr a testování
Znovupoužitelné Terraform moduly pro Azure s publikací do privátního registru, automatizované testování Terratest a verzovaná spotřeba modulů v produkci.
Číst