Experiment, Prototype, and Validate Azure Bicep with the Bicep Console

The Bicep console is a feature that helps with prototyping, experimenting, and validating Azure Bicep code snippets. Additionally, you can leverage the Bicep console to validate user-defined functions that are generated with GitHub Copilot to check if the output is as expected.

In this blog, you will learn what the Bicep console is, explore a few use cases, and learn how to use it with GitHub Copilot.

Note! This is currently an experimental feature. Expect limitations and breaking changes.

Bicep console on the terminal

The Bicep console is an experimental feature that allows you to run Bicep expressions and snippets directly in your terminal. The command to open the console is part of the bicep executable, and you can launch it by running bicep console on the terminal.

The Bicep console uses a REPL (Read–Eval–Print Loop), meaning you get an interactive shell that reads your input, parses it, and then prints the result. This is very handy, as it provides instant feedback within the terminal and does not require an Azure connection to produce output.

The following is supported:

  • Creating user-defined functions using the func keyword
  • Declaring variables using the var keyword
  • Creating user-defined types using the type keyword
  • Direct expressions (e.g. 1 + 1, contains(‘John Doe’, ‘John’), etc.)
  • Use of built-in functions

The Bicep console might look like a small feature, but it’s actually great for developer friendliness, as it removes the hassle of quickly experimenting with Bicep.

Before the console feature, if you wanted to experiment, you had to:

  1. Write a separate .bicep template with outputs and mock data just to test an expression.
  2. Have an Azure connection to start a deployment.
  3. Wait for the output to see whether the expression behaved as expected.

Now, with the Bicep console, you only need to start bicep console and run your expression.

There are a few scenarios where the bicep console can be particularly helpful:

  • Validate complex expressions
  • Learn about the examples from Microsoft docs
  • Validate user-defined functions

In this case, the goal is to validate a complex expression. In the example below, the expression uses multiple lambda functions for data manipulation and combines two arrays.

The purpose of the example below is to produce an output that fits the role assignment format used by the Azure Verified Module (sub-vending module). First, the array varMockedEntraGroupIds (containing mock data) is declared in the console and represents the data used in a parameter of a Bicep template. The variable outRoleAssignments is the output of the Bicep template. This is where the lambda expressions are applied to create the exact role assignment pattern the AVM module expects. Assigning outRoleAssignments to a variable is optional, you can also use the right-hand value directly.

var varMockedEntraGroupIds = [
{
uniqueName: 'Reader-Group'
roleToAssign: 'Reader'
groupId: '11111111-1111-1111-1111-111111111111'
}
{
uniqueName: 'Contributor-Group'
roleToAssign: 'Contributor'
groupId: '22222222-2222-2222-2222-222222222222'
}
{
uniqueName: 'DevOps-Group'
groupId: '33333333-3333-3333-3333-333333333333'
roleToAssign: null
}
]
var outRoleAssignments object[] = union(map(
filter(varMockedEntraGroupIds, item => !contains(item.uniqueName, 'DevOps')),
group => {
principalId: group.groupId
definition: group.roleToAssign
relativeScope: ''
principalType: 'Group'
}
),[
{
principalId: '44444444-4444-4444-4444-444444444444'
definition: 'Reader'
relativeScope: ''
principalType: 'ServicePrincipal'
}
])

This snippet can easily be validated in the bicep console, giving you quick output and instant feedback on what you may need to adjust. In this case, the output should be an array of 3 objects. Below you see the output:

Complex expression output on the bicep console

I like to learn by doing, and learning about the newest functions from Azure Bicep is easier than ever with bicep console. The Microsoft Azure Bicep documentation is comprehensive and includes plenty of Bicep code snippet examples. These examples can be run directly in the bicep console to understand how they work in a practical, interactive way.

Let’s take a look at the function shallowMerge. This function is fairly complex and one you might want to experiment with to understand it in practice. Below is the Bicep code snippet from the Microsoft Docs:

var firstArray = [{ one: 'a' }, { two: 'b' }, { two: 'c'}]
var secondArray = [{ one: 'a', nested: {a: 1, nested: {c: 3}} }, { two: 'b', nested: {b: 2}}]
output firstOutput object = shallowMerge(firstArray)
output secondOutput object = shallowMerge(secondArray)

The bicep console does not support the output keyword, so these should be changed to variables (var), or you can directly run shallowMerge(<input>). Below you see the output:

Microsoft learn example on the bicep console

The best scenario is to validate user-defined functions in the console. It’s a quick way to check whether the function does what you want, or to use it for developing a function.

In the example below, you can see a user-defined function that reverses a string:

func reverse(input string) string => join(map(range(0, length(input)), i => substring(input, length(input) - 1 - i, 1)), '')
view raw udf.bicep hosted with ❤ by GitHub

After you have loaded the user-defined function in the console, you can use the function directly. Below, you can see the output for the values ‘John’ and ‘racecar’:

Validating a custom user-defined function on the bicep console

Can you use the Bicep console to validate the generated code in the console? Yes, it’s possible but it requires a few extra steps.

I have tried to make it work with GitHub Copilot agent mode, but when the bicep console is started and the agent attempts to run a command in the interactive console using the #runInTerminal tool, it successfully executes the Bicep code snippet, however the agent does not receive any feedback, so it cannot confirm that the command was executed properly. This results in the agent being stuck and waiting for confirmation from the terminal that the command has successfully run.

Another approach I tried was using piped inputs, but this is not supported by the bicep console (yet). I have submitted a feature request to add support for piped inputs: https://github.com/Azure/bicep/issues/18410

In the meantime, while non-interactive console instructions are not supported, I worked around the limitation by using the command expect. This command can interact with applications such as the interactive bicep console, as it can control the console as if a human were typing commands. It’s a useful command for automating interactive applications like the bicep console.

The expect command offers a few commands/functions that GitHub Copilot agent can use:

  • Spawn – starts the Bicep console
  • Send – sends the Bicep expression to the console
  • Expect – checks the output. In this case, it’s only used to check for the end-of-file, because GitHub Copilot checks the terminal output instead.

The requirement is that you have Azure Bicep v0.39 (or higher) installed. In this version, you can run user-defined functions on the console. Additionally, if you are on macOS or Linux, the command expect is installed by default. If you are on Windows, I would recommend you to run WSL with Ubuntu (recommended and easiest approach) or instruct GitHub Copilot to write a small Python script to use the commands above (this requires the Python package wexpect).

---
description: "This chat mode helps you write User-Defined Functions for Azure Bicep"
tools: [
    "runCommands/runInTerminal",
    "runTasks",
    "edit",
    "search",
    "think",
    "fetch",
    "todos",
    "runCommands/getTerminalOutput",
  ]
---

# Azure Bicep User-Defined Function (UDF) Helper

Act as an expert on Azure Bicep User-Defined Functions (UDFs). Your role involves creating and testing these functions to ensure they meet specific requirements and enhance the functionality of Bicep deployments.

## UDF context

Before creating UDFs, use #fetch to review the official documentation:

- https://learn.microsoft.com/en-us/azure/azure-resource-manager/bicep/user-defined-functions

## Workflow for creating UDFs

1. **Understand Requirements**

   - Parse user's request for function purpose
   - Identify input parameters and expected output
   - Breakdown the implementation steps. Use #todos to manage tasks.

2. **Write the UDF**

   - Define function signature with parameters and output
   - Implement logic using Bicep syntax

3. **Validate Using Bicep Console**

   - Use `expect` command to automate the interactive console
   - Verify actual output matches expected output

4. **Iterate as Needed**
   - Refine function based on test results

## Testing and validation

To test the functions you use the `bicep console` command which opens a REPL (Read-Eval-Print Loop) environment for Bicep. You can interactively run Bicep expressions and see their results. Do not use `printf`, `az bicep`, or pipe input since `bicep console` requires interactive mode.

To test the generated functions, use the command `expect` to automate REPL consoles. Use #runCommands/runInTerminal tool to execute commands in the terminal and run the User-Defined Functions on the `bicep console`:

```bash
expect -c 'spawn bicep console; send "<ADD THE USER-DEFINED FUNCTION HERE>\r"; send "exit\r"; expect eof'
```

Escape single quotes in strings by using `'\''` within the expect command. When the output equals the expected output, the function is validated and you are done. Always use tool #runCommands/getTerminalOutput to fetch the output of the terminal command.

# Bicep console limitations

- Limited to expression evaluation and variable declarations
- No support for for-loop expressions, e.g. `[for i in range(0, x): i]` use `map()` instead

# Do the following

- Your only objective is to create and test User-Defined Functions (UDFs) for Azure Bicep.

## Do not do the following

- Do not use `bicep build` to compile Bicep files.
- Do not create or update any other files than `.bicep` files.
- Do not create temporary expect script files.
# Bicep spec - User Defined Functions reverse()

## Function Purpose

This function should reverse a string.

## Function Name

`reverse()`

## Function Input

* Parameter name: input
* Type: string

## Function Output

* Type: string

## Example Function Calling and Output

```bicep
output outReversedName string = reverse('John')
output outReversedRacecar string = reverse('racecar')
```

## Test cases

// Test Case 1
Input: reverse('John')
Expected: 'nhoJ'
Actual: 'nhoJ'

// Test Case 2
Input: reverse('racecar')
Expected: 'racecar'
Actual: 'racecar'
---
description: "Create and test an Azure Bicep User-Defined Function"
mode: Bicep User Defined Function Helper mode
tools: ["runInTerminal", "edit", "fetch", "think", "todos"]
---

# Create Azure Bicep User-Defined Function

Create a Bicep User-Defined Function (UDF) that meets the following requirements and validate it works correctly using the `bicep console`.

## Requirements

- **Purpose**: ${input:purpose:What should this function do?}
- **Input Parameters**: ${input:params:What parameters does it need? (e.g., string value, int length)}
- **Expected Output**: ${input:output:What should it return?}

Test the function thoroughly and confirm it produces the expected results.

In the screenshots below you see:

  1. A prompt is given using /create-bicep-udf and the specification is given to GitHub Copilot.
  2. GitHub Copilot has generated the function based on the specification file.
  3. GitHub Copilot spawns the bicep console, pastes the function and follows the test case as defined in the specification file.
A prompt (1), output from the prompt (2) and validation on the bicep console (3)

And another test is done for reverse(‘racecar’):

The second test scenario using value ‘racecar’ on the bicep console

The Bicep console may still be experimental, but it already deliver value when working with Azure Bicep. Whether you are validating complex expressions, experimenting with new features, or iterating on user-defined functions, the console gives instant feedback without spinning up a full deployment. It becomes even more powerful with combining it using GitHub Copilot, where you let an agent generate user-defined functions and validate it directly on the bicep console.

Leave a comment