Search code examples
azureazure-resource-managerazure-bicep

azure bicep modules - condition on existing resource - how to?


I am trying to deploy different types of secrets using modules. The idea is to have "general" secrets, and the "storage related" secrets where the secret value is not passed directly but retrieved from properties of the storage. I designed the following Secret.bicep:

param keyVaultName string
param secretName string

@secure()
param secretValue string

@allowed([
  'general'
  'storage_conn_string'
])
param secretType string = 'general'

@description('For secretType = storage_*, this is how we will be passing storage name')
param storageAccountName string = 'dummyvalue123'


resource kv 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
  name: keyVaultName
}

resource secret'Microsoft.KeyVault/vaults/secrets@2023-02-01' = if (secretType == 'general') {
  parent: kv
  name: secretName
  properties: {
    value: secretValue
  }
}

// for secrets related to storage we will need to have storage resource:
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (startsWith(secretType, 'storage_')) {
  name: storageAccountName
}

// storage connection string
resource secret_conn_string 'Microsoft.KeyVault/vaults/secrets@2023-02-01' = if (secretType == 'storage_conn_string') {
  parent: kv
  name: secretName
  properties: {
    value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
  }
}

Now in main.bicep I have the following:


@secure()
param secretValueAPIkey string

...

module secretStorageDataConnectionString 'modules/KeyVault/Secret.bicep' = {
  // this needs to be a module and not a resource because we need 
  // to pass a connection string to storage, and storage is declared here as a module
  // so we cannot use the listKeys() method
  name: 'secretStorageDataConnectionString'
  params: {
    keyVaultName: kv.outputs.keyVaultName
    secretType : 'storage_conn_string'
    storageAccountName: storageAccount4data.outputs.dtls.name
    secretName: 'secretStorageDataConnectionString'
    secretValue: 'some-dummy-secret-value' //passing dummy value, we will retrieve it in via the module
  }
}

module secretAPIkey 'modules/KeyVault/Secret.bicep' = {
  name: 'secretAPIkey'
  params: {
    keyVaultName: kv.outputs.keyVaultName
    secretType: 'general'
    secretName: 'secretAPIkey'
    secretValue: secretValueAPIkey
  }
}

(Please note I skipped some bits e.g. the keyvault module, or storage module. They get deployed with no issue.)

EDIT:

storage account is created as a module:

module storageAccount4data 'modules/Storage/StorageAccount.bicep' = {
  name: storageAccount4dataName
  params: {
    location: location
    storageAccountName: storageAccount4dataName
  }
}

StorageAccount.bicep:

param storageAccountName string

...

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
  name: storageAccountName
  location: location
  sku: {
    name: storageAccountSkuName
  }
  kind: 'StorageV2'
}

output dtls object = {
  id: storageAccount.id
  name: storageAccount.name
  apiVersion: storageAccount.apiVersion
}

END OF EDIT

Now when deploying, the secretStorageDataConnectionString module is created without any issues, but the secreAPIkey module returns an error:

\"target\": \"/subscriptions/<...>/providers/Microsoft.Resources/deployments/secretAPIkey\"
\"message\": \"The Resource 'Microsoft.Storage/storageAccounts/dummyvalue123' under resource group <resource-group> was not found. ..."

For some reason the storage resource is still attempted to be created despite the if condition...?

I also tested using:

//secret.bicep
param storageAccountName string = ''

...

resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (!empty(storageAccountName)) {
  name: storageAccountName
}

but this one also does not seem to evaluate correctly...


Solution

  • The issue is with the if condition as it is not correctly evaluated in the bicep code.

    Usually, if the storageAccountName argument is left blank during deployment, the storage resource will not be created. If the storageAccountName argument is not empty at deployment time, the storage resource is created even if it isn't specified in the secretAPIkey module.

    When you deploy the bicep code, make sure the storage account name is not empty even though it is provided to the module.

    Everything worked as expected after giving the storage account name with the same code you used.

    param storageAccountName string

    secret.bicep:

    param keyVaultName string = 'newvaultj'
    param secretName string
    @secure()
    param secretValue string 
    @allowed([
      'general'
      'storage_conn_string'
    ])
    param secretType string = 'general'
    
    @description('For secretType = storage_*, this is how we will be passing storage name')
    param storageAccountName string = 'xxxxx'
    
    
    resource kv 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
      name: keyVaultName
    }
    
    resource secret'Microsoft.KeyVault/vaults/secrets@2023-02-01' = if (secretType == 'general') {
      parent: kv
      name: secretName
      properties: {
        value: secretValue
      }
    }
    
    resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' existing = if (startsWith(secretType, 'storage_')) {
      name: storageAccountName
    }
    
    resource secret_conn_string 'Microsoft.KeyVault/vaults/secrets@2023-02-01' = if (secretType == 'storage_conn_string') {
      parent: kv
      name: secretName
      properties: {
        value: 'DefaultEndpointsProtocol=https;AccountName=${storageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
      }
    }
    

    main.bicep:

    @secure()
    param secretValueAPIkey string = 'newapikey'
    param keyVaultName string = 'newvaultj'
    param storageAccountName string = 'xxxxx'
    
    module secretStorageDataConnectionString 'secret.bicep' = {
      name: 'secretStorageDataConnectionString'
      params: {
        keyVaultName: kv.name
        secretType : 'storage_conn_string'
        storageAccountName: storageAccount.name
        secretName: 'secretStorageDataConnectionString'
        secretValue: 'some-dummy-secret-value'
      }
    }
    
    module secretAPIkey 'secret.bicep' = {
      name: 'secretAPIkey'
      params: {
        keyVaultName: kv.name
        secretType: 'general'
        secretName: 'secretAPIkey'
        secretValue: secretValueAPIkey
      }
    }
    

    Updated code:

    param secretValueAPIkey string = 'newapikey'
    param keyVaultName string = 'newvaultj'
    param storageAccountName string = 'jaxxxx9920'
    param location string = resourceGroup().location
    resource kv 'Microsoft.KeyVault/vaults@2023-02-01' existing = {
      name: keyVaultName
    }
    module storageAccount4data 'StorageAccount.bicep' = {
      name: storageAccountName
      params: {
        location: location
        storageAccountName: storageAccountName
      }
    }
    
    module secretStorageDataConnectionString 'secret.bicep' = {
      name: 'secretStorageDataConnectionString'
      params: {
        keyVaultName: kv.name
        secretType : 'storage_conn_string'
        storageAccountName: storageAccount4data.name
        secretName: 'secretStorageDataConnectionString'
        secretValue: 'some-dummy-secret-value' //passing dummy value, we will retrieve it in via the module
      }
    }
    
    module secretAPIkey 'secret.bicep' = {
      name: 'secretAPIkey'
      params: {
        keyVaultName: kv.name
        secretType: 'general'
        secretName: 'secretAPIkey'
        secretValue: secretValueAPIkey
      }
    }
    

    Deployment succeeded:

    enter image description here

    enter image description here