Azure / PSRule.Rules.Azure

Rules to validate Azure resources and infrastructure as code (IaC) using PSRule.
https://azure.github.io/PSRule.Rules.Azure/
MIT License
394 stars 86 forks source link

Compiled Bicep output with API Connection might be missing dynamic properties #2424

Closed tstooke closed 1 year ago

tstooke commented 1 year ago

Description of the issue

Bicep expansion appears to be incomplete and throws errors when running PSRule tests against a module that exposes an object created by Azure as an output variable.

We have a Service Bus module that automatically creates the API Connection Resources for use by our Logic Apps. To avoid hard-coding names and references, we include some properties from the Connections' properites.api object as outputs for the module. PSRule seems to have trouble dealing with the multiple layers or it's not generating a placeholder for the Azure-based API properties or it's something completely different.

To Reproduce

Use this main.bicep file:

param Location string = resourceGroup().location
param ServiceBusSkuName string = 'Standard'

resource serviceBus 'Microsoft.ServiceBus/namespaces@2022-01-01-preview' = {
  name: 'myServiceBus'
  location: Location
  sku: {
    name: ServiceBusSkuName
    tier: ServiceBusSkuName
  }
  properties: {
    minimumTlsVersion: '1.2'
  }

  resource sendAuthRule 'AuthorizationRules' = {
    name: 'SendAccess'
    properties: {
      rights: [
        'Send'
      ]
    }
  }

  resource listenAuthRule 'AuthorizationRules' = {
    name: 'ListenAccess'
    properties: {
      rights: [
        'Listen'
      ]
    }
  }

  // Queues and Topics omitted

}

resource sendApiConnection 'Microsoft.Web/connections@2016-06-01' = {
  name: 'sender-connection'
  location: Location
  properties: {
    api: {
      id: subscriptionResourceId('Microsoft.Web/locations/managedApis', Location, 'servicebus')
    }
    displayName: 'sender-connection'
    parameterValues: {
      connectionString: listKeys(serviceBus::sendAuthRule.name, serviceBus.apiVersion).primaryConnectionString
    }
  }
}

resource listenApiConnection 'Microsoft.Web/connections@2016-06-01' = {
  name: 'listener-connection'
  location: Location
  properties: {
    api: {
      id: subscriptionResourceId('Microsoft.Web/locations/managedApis', Location, 'servicebus')
    }
    displayName: 'listener-connection'
    parameterValues: {
      connectionString: listKeys(serviceBus::listenAuthRule.name, serviceBus.apiVersion).primaryConnectionString
    }
  }
}

output SendConnection object = {
  Name: sendApiConnection.name
  Id: sendApiConnection.id
  Api: {
    Name: sendApiConnection.properties.api.name
    Type: sendApiConnection.properties.api.type
  }
}

output ListenConnection object = {
  Name: listenApiConnection.name
  Id: listenApiConnection.id
  Api: {
    Name: listenApiConnection.properties.api.name
    Type: listenApiConnection.properties.api.type
  }
}

And this test file:

#disable-next-line no-hardcoded-location
var location = 'eastus'

module serviceBus './main.bicep' = {
  name: 'my-test-service-bus'
  params: {
    Location: location
    ServiceBusSkuName: 'Standard'
  }
}

var logicAppParams = {
  '$connections': {
    value: {
      serviceBusSender: {
        connectionId: serviceBus.outputs.SendConnection.Id
        connectionName: serviceBus.outputs.SendConnection.Name
        id: subscriptionResourceId(serviceBus.outputs.SendConnection.Api.Type, location, serviceBus.outputs.SendConnection.Api.Name)
      }
      serviceBusListener: {
        connectionId: serviceBus.outputs.ListenConnection.Id
        connectionName: serviceBus.outputs.ListenConnection.Name
        id: subscriptionResourceId(serviceBus.outputs.ListenConnection.Api.Type, location, serviceBus.outputs.ListenConnection.Api.Name)
      }
    }
  }
}

resource workflow 'Microsoft.Logic/workflows@2019-05-01' = {
  name: 'a-test-logic-app'
  location: location
  properties: {
    state: 'Enabled'
    definition: {
        // Actual definition omitted
    }
    parameters: logicAppParams
  }
}

Expected behaviour

No compiler errors and all tests run and pass.

Error output

With the above sample files, we get this error:

[ERROR] Failed to expand bicep source 'main.tests.bicep'.
Exception calling "GetBicepResources" with "2" argument(s):
"Unable to expand resources because the source file 'main.tests.bicep' was not valid.
An error occurred evaluating expression 
'[subscriptionResourceId(reference(resourceId('Microsoft.Resources/deployments', 'my-test-service-bus'), '2022-09-01').outputs.SendConnection.value.Api.Type, variables('location'), reference(resourceId('Microsoft.Resources/deployments', 'my-test-service-bus'), '2022-09-01').outputs.SendConnection.value.Api.Name)]'.
The arguments for 'SubscriptionResourceId' are not in the expected format or type."

When we have a similar setup in our real files, we get a different error message, but it appears to be related to the same spot.

##[error]Failed to expand bicep source 'main.tests.bicep'.
Exception calling "GetBicepResources" with "2" argument(s): 
"Unable to expand resources because the source file 'main.tests.bicep' was not valid. 
An error occurred evaluating expression 
'[subscriptionResourceId(reference(resourceId('Microsoft.Resources/deployments', format('{0}connection1{1}', variables('deploymentNamePrefix'), variables('deploymentNameSuffix'))), '2022-09-01').outputs.ApiType.value, variables('globalSettings').AzureLocation, reference(resourceId('Microsoft.Resources/deployments', format('{0}connection1{1}', variables('deploymentNamePrefix'), variables('deploymentNameSuffix'))), '2022-09-01').outputs.ApiName.value)]'.
Arithmetic operation resulted in an overflow."

NOTE: These errors have slightly different dot-notation chains because they come from two slightly different ways that we've exposed the API Connection properties. The ultimate issue, though, is that those API Connection properties don't seem to be available in the output when the Bicep file is compiled by PSRule. Perhaps those API Connection properties are only available at runtime when the ARM Deployment handles things?

Module in use and version:

Captured output from $PSVersionTable:

Name                           Value
----                           -----
PSVersion                      7.3.6
PSEdition                      Core
GitCommitId                    7.3.6
OS                             Microsoft Windows 10.0.19045
Platform                       Win32NT
PSCompatibleVersions           {1.0, 2.0, 3.0, 4.0…}
PSRemotingProtocolVersion      2.3
SerializationVersion           1.1.0.1
WSManStackVersion              3.0

Also Note: When we turn off the PSRule step in our CI/CD pipeline, the actual Bicep deployment does work. It seems to be just an issue with how PSRule is handling things.

tstooke commented 1 year ago

@BernieWhite Just curious to know if there's any update on this one. Is it a bigger issue to fix than some of the others we've seen? This is preventing us from either using PSRule or from removing some hard-coded values from our Bicep files. Thanks!

BernieWhite commented 1 year ago

@tstooke Runtime properties can introduce several complications that can be hard to work past.

Runtime properties are any property that you don't define in code but expect to be there, because they are automatically set by ARM.

We'll try to get this in for v1.31.x, which should land by mid next month.

tstooke commented 1 year ago

I expected it would be a bit more complicated and possibly delicate, and we appreciate any extra effort needed to get it working. I'm curious to see how it gets implemented in the engine!