diff --git a/.github/workflows/mega-linter.yml b/.github/workflows/mega-linter.yml index c86a227..8a2198f 100644 --- a/.github/workflows/mega-linter.yml +++ b/.github/workflows/mega-linter.yml @@ -41,7 +41,7 @@ jobs: id: ml # You can override MegaLinter flavor used to have faster performances # More info at https://megalinter.io/flavors/ - uses: oxsecurity/megalinter@v8 + uses: oxsecurity/megalinter@v9 env: # All available variables are described in documentation # https://megalinter.io/configuration/ @@ -49,7 +49,7 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # ADD YOUR CUSTOM ENV VARIABLES HERE OR DEFINE THEM IN A FILE .mega-linter.yml AT THE ROOT OF YOUR REPOSITORY DISABLE: COPYPASTE,SPELL # Uncomment to disable copy-paste and spell checks - DISABLE_LINTERS: YAML_V8R,YAML_YAMLLINT,YAML_PRETTIER,JSON_PRETTIER,REPOSITORY_CHECKOV,POWERSHELL_POWERSHELL,ACTION_ACTIONLINT,REPOSITORY_GITLEAKS,REPOSITORY_GRYPE,REPOSITORY_KICS,REPOSITORY_TRIVY + DISABLE_LINTERS: YAML_V8R,YAML_YAMLLINT,YAML_PRETTIER,JSON_PRETTIER,REPOSITORY_CHECKOV,POWERSHELL_POWERSHELL,ACTION_ACTIONLINT,REPOSITORY_GITLEAKS,REPOSITORY_GRYPE,REPOSITORY_KICS,REPOSITORY_TRIVY,REPOSITORY_KINGFISHER # Upload MegaLinter artifacts - name: Archive production artifacts diff --git a/bicep-examples/onlyIfNotExists/README.md b/bicep-examples/onlyIfNotExists/README.md new file mode 100644 index 0000000..8b6bbeb --- /dev/null +++ b/bicep-examples/onlyIfNotExists/README.md @@ -0,0 +1,90 @@ +# Azure Bicep - @onlyIfNotExists Decorator + +## Introduction + +The `@onlyIfNotExists()` decorator in Azure Bicep enables **conditional resource deployment** based on whether a resource with the specified name already exists. When applied to a resource declaration, Azure Resource Manager will only create the resource if one with that name doesn't already exist in the target scope. + +It's particularly valuable for deploying resources that should only be created once, such as Key Vault secrets, where you want to avoid overwriting existing values or encountering "resource already exists" errors. + +> [!NOTE] +> The decorator checks only the resource **name** - it does not compare properties or configurations. If a resource with the same name exists, deployment is skipped entirely for that resource, regardless of whether its properties match the template definition. + +Learn more about the `@onlyIfNotExists()` decorator in the [official Microsoft Learn documentation](https://learn.microsoft.com/azure/azure-resource-manager/bicep/resource-declaration?WT.mc_id=MVP_319025#onlyifnotexists-decorator). + +## ✅ Benefits of Using @onlyIfNotExists + +✅ **Prevents Overwriting Critical Data**: Protects existing resources (like secrets with sensitive values) from being accidentally overwritten during redeployment. + +✅ **Idempotent Deployments**: Enables truly idempotent templates where multiple executions won't fail or cause unintended changes if resources already exist. + +✅ **Simplified Secret Management**: Perfect for one-time secret deployments where you want to set an initial value but never modify it through automation afterward. + +✅ **Reduces Deployment Errors**: Eliminates "resource already exists" conflicts in scenarios where you're uncertain whether a resource has been previously deployed. + +## ⚗️ Example: Key Vault Secret with @onlyIfNotExists + +This example demonstrates deploying an Azure Key Vault and a secret using the `@onlyIfNotExists()` decorator. The Key Vault will always be deployed (or updated), but the secret will **only be created if it doesn't already exist**. + +### Bicep Code Snippet + +```bicep +@secure() +param kvSecretValue string + +// Deploy secret to the Key Vault using @onlyIfNotExists decorator +@onlyIfNotExists() +resource kvSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { + name: '${keyVault.outputs.name}/mySecret' + properties: { + value: kvSecretValue + } +} +``` + +### How It Works + +1. **First Deployment**: The secret `mySecret` doesn't exist, so it's created with the value you pass at deployment time +2. **Subsequent Deployments**: The secret already exists with the same name, so deployment is skipped entirely - the existing value is preserved +3. **Name-Based Check**: Only the secret name is checked; if a secret named `mySecret` exists, it won't be recreated regardless of its current value (check the secret version after two deployment runs to verify functionality.) + +### Full Template + +The complete `main.bicep` file includes: + +- Azure Key Vault deployment using the Azure Verified Modules (AVM) pattern +- A secret resource with the `@onlyIfNotExists()` decorator +- Metadata for resource documentation +- Outputs for the Key Vault name, URI, and secret name + +### Scope Limitations + +The existence check is performed within the deployment scope (subscription, resource group, etc.). A resource in a different scope with the same name won't prevent creation. + +## 🚀 Deployment + +### Prerequisites + +- Azure subscription +- Bicep CLI 0.38.0 or later + +### Deploy with Azure CLI + +```bash +az group create --name rg-onlyIfNotExists-example --location uksouth + +az deployment group create \ + --resource-group rg-onlyIfNotExists-example \ + --template-file main.bicep \ + --parameters kvSecretValue="" +``` + +### Deploy with Azure PowerShell + +```powershell +New-AzResourceGroup -Name rg-onlyIfNotExists-example -Location uksouth + +New-AzResourceGroupDeployment ` + -ResourceGroupName rg-onlyIfNotExists-example ` + -TemplateFile main.bicep ` + -kvSecretValue '' +``` diff --git a/bicep-examples/onlyIfNotExists/main.bicep b/bicep-examples/onlyIfNotExists/main.bicep new file mode 100644 index 0000000..150b950 --- /dev/null +++ b/bicep-examples/onlyIfNotExists/main.bicep @@ -0,0 +1,49 @@ +targetScope = 'resourceGroup' + +metadata name = 'Key Vault with onlyIfNotExists Secret deployment' +metadata description = 'Showcasing Azure Bicep @onlyIfNotExists decorator for conditional secret deployment' + +@description('Azure region for deployments chosen from the resource group.') +param location string = resourceGroup().location + +@description('Key Vault name - globally unique.') +param kvName string = 'kv-${uniqueString(resourceGroup().id)}' + +@secure() +@description('Initial value for the Key Vault secret. Passed in at deploy time to avoid hardcoding secrets.') +param kvSecretValue string + +// Deploy Key Vault using AVM +module keyVault 'br/public:avm/res/key-vault/vault:0.13.3' = { + name: '${uniqueString(deployment().name, location)}-kv' + params: { + name: kvName + location: location + sku: 'standard' + enableRbacAuthorization: false + enableSoftDelete: true + softDeleteRetentionInDays: 7 + enablePurgeProtection: false + } +} + +// Deploy secret to the Key Vault using @onlyIfNotExists decorator +@onlyIfNotExists() +resource kvSecret 'Microsoft.KeyVault/vaults/secrets@2023-07-01' = { + name: '${kvName}/mySecret' + properties: { + value: kvSecretValue + } + dependsOn: [ + keyVault + ] +} + +@description('Key Vault name output.') +output keyVaultName string = keyVault.outputs.name + +@description('Key Vault URI output.') +output keyVaultUri string = keyVault.outputs.uri + +@description('Secret name output.') +output secretName string = last(split(kvSecret.name, '/'))