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
397 stars 86 forks source link

[BUG] User Defined Function not found #3169

Closed modbase closed 2 weeks ago

modbase commented 2 weeks ago

Existing rule

No response

Description of the issue

I just started investigating PRRule for Azure, so hopefully the behaviour I get is simply a matter of incorrect use.

We have a large Bicep module library with custom made modules. Modules are separated in a folder per module. Besides the modules, we also have some shared Bicep code which contains User Defined Types and User Defined Functions used across the modules.

In summary, our folder structure is like this:

├───modules
│   ├───storage-account
│   │   └───.tests
│   │   |    └───main.tests.bicep
│   │   └───main.bicep
│   │   └───storage-account.bicep
...
└───shared
    └───functions.bicep
    └───types.bicep

I created a ps-rule.yml file with the following content:

include:
  module:
    # Import all the Azure Well-Architected Framework rules
    - PSRule.Rules.Azure

configuration:
  # Enable code analysis of bicep files
  AZURE_BICEP_FILE_EXPANSION: true
  # Validate that the bicep CLI is used
  AZURE_BICEP_CHECK_TOOL: true

input:
  pathIgnore:
  # Exclude module files
  - '**/*.bicep'
  # Include test files for modules
  - '!**/*.tests.bicep'

execution:
  # Disable warnings that files cannot be processed
  unprocessedObject: Ignore

output:
  # Show results for rules with the Fail, Pass, or Error outcome
  outcome: 'Processed'

I tried to run the built-in WAF rules on one of our modules. This module makes use of a User Defined Function within the /shared/functions.bicep file. So in the /modules/storage-account/main.bicep file we import this function, lets call it nameOfCustomFunction:

import { nameOfCustomFunction } from '../../shared/functions.bicep'

The main.tests.bicep file looks like this:

targetScope = 'subscription'

param location string = deployment().location

module rg_test '../../resource-group/main.bicep' = {
  name: 'RG_network_${uniqueString('20241107')}'
    params: {
      location: location
      name: 'somename'
    }
  }
}

module sa_test '../main.bicep' = {
  name: 'test_storage_account'
  params: {
    location: location
    name: 'test${uniqueString('20241107')}'
    sku: 'Standard_LRS'
    kind: 'StorageV2'
    resourceGroupName: rg_test.outputs.resourceGroupName
  }
}

Unfortunately, when running the tests using Assert-PSRule -InputPath .\modules\ we get the following error (redacted to remove some sensitive info):

Failed to expand bicep source '##redacted##\modules\storage-account\.tests\main.tests.bicep'. Exception calling "GetBicepResources" with "2" argument(s): "Unable to expand resources because the source file '##redacted##\modules\storage-account\.tests\main.tests.bicep' was not valid. An error occurred evaluating expression '[__bicep.nameOfCustomFunction(parameters('someParameter'), 6)]' line 1408. The function "__bicep.nameOfCustomFunction" was not found."

Apart from PSRule, the module builds and deploys perfectly, so we can be assured that there's in fact nothing wrong with the module itself. I tried using PRRule v2.9.0 and PSRule.Rule.Azure v1.39.3 and v1.40.0-B0063

Am I perhaps missing something?

Error messages

No response

Reproduction

Create a module which uses an exported User Defined Function defined within another file. Run the default rules.

Version of PSRule

2.9.0

Version of PSRule for Azure

v1.39.3 and v1.40.0-B0063

Additional context

No response

BernieWhite commented 2 weeks ago

Hi @modbase. Thanks for reporting the issue. There might be a little more information required, in the simple case with v1.39.3 I wasn't able to reproduce the issue.

I have constructed the sample below based on your information provided which does not generate the error you are experiencing. Are you able to update/ fill in the blanks so I can reproduce it locally.

Tests.Bicep.2.bicep

targetScope = 'resourceGroup'

module storage 'Tests.Bicep.2.child.bicep' = {
  name: 'storage'
  params: {
    prefix: 'sa'
  }
}

Tests.Bicep.2.child.bicep

targetScope = 'resourceGroup'

param prefix string

import { customNamingFunction } from './Tests.Bicep.2.fn.bicep'

resource storage 'Microsoft.Storage/storageAccounts@2023-05-01' = {
  name: customNamingFunction(prefix, 1)
  location: resourceGroup().location
  kind: 'StorageV2'
  sku: {
    name: 'Standard_LRS'
  }
}

Tests.Bicep.2.fn.bicep

targetScope = 'resourceGroup'

// A custom naming function.
@export()
func customNamingFunction(prefix string, instance int) string =>
  '${prefix}${uniqueString(resourceGroup().id)}${instance}'

Also if you have an Bicep experimental features enabled in bicepconfig.json please provide a copy of these options.

Thanks.

modbase commented 2 weeks ago

Hi @BernieWhite

Thanks for your fast response! I managed to reproduce the error with a simplified version. The issue seems to be when using a User Defined Function as a default value for a parameter. You can check the example code I've attached here.

psruletest.zip

BernieWhite commented 2 weeks ago

Thanks @modbase I was able to reproduce that locally.