Featured

Behind the scenes of Bicep.LocalDeploy: A Developer's Guide

Learn how you can generate documentation from your model's source code

Behind the scenes of Bicep.LocalDeploy
Behind the scenes of Bicep.LocalDeploy

Bicep's local-deploy resources are rapidly growing, but the models developed often lag behind in documentation.

Keeping documentation in sync with code is always a struggle. As a developer, you typically start by writing the code. You create the baseHandler, define the models, and lastly, implement the handler for what you want to expose to your end-user. Only after things work, you still need to create a README.md file explaining how users can leverage the functionality.

What if your code could describe itself? And what if the documentation was generated directly from the annotations you already wrote while defining the models?

That's precisely why I wrote the Bicep.LocalDeploy library and bicep-local-docgen CLI utility. In this blog post, you will learn:

  • Why Bicep.LocalDeploy was developed.
  • How it works.
  • How to get started.

Why Bicep.LocalDeploy

Despite its name, Bicep.LocalDeploy doesn't come out of the kitchen from the Bicep team, but it's a library and a tool for producing better documentation for your developed resources.

See, typing out documentation for Infrastructure as Code (IaC) has always been a challenging task. In the context of local-deploy resources, developers wire up the models, handlers, and outputs, and then move on. The problem? Documentation is an afterthought, often writing manually README.md files.

This creates friction. Every code change you make has the risk of drifting away from what's written in the docs.

In the C# ecosystem, this is often tackled with annotation-driven approaches. For example, attributes or decorators allow developers to enrich code with metadata that can be consumed for auto-generated documentation.

If you've written a local-deploy model before, you probably recognized that you already have defined some of this metadata:

[ResourceType("Product")]
public class Product
{
    /// <summary>
    /// Gets or sets the name of the product.
    /// </summary>
    /// <value>The product name. Cannot be null or empty.</value>
    [TypeProperty("The name of the product.", ObjectTypePropertyFlags.Required)]
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the price of the product in decimal format.
    /// </summary>
    /// <value>The product price. Must be greater than or equal to zero.</value>
    [TypeProperty("The price of the product.", ObjectTypePropertyFlags.Required)]
    public decimal Price { get; set; }
}

Here's where you already have defined two key pieces that are useful for your end-users to read:

  • The properties have attributes defined with a description.
  • The properties ObjectTypePropertyFlag states whether properties are mandatory or not.

What if you could extend this while coding out your model?

That's the goal of the Bicep.LocalDeploy library: let developers annotate their Bicep properties directly in the code. From there on, the companion tool bicep-local-docgen can automatically generate consistent up-to-date documentation. The result is less time spent maintaining README.md (or any other name) files.

How it works

At its core, Bicep.LocalDeploy introduces individual annotations that can be attached directly to your model code. These annotations can describe:

  • Frontmatter data, such as the category to which the model belongs.
  • The first heading data.
  • Provide examples.
  • Custom sections.

Once those annotations are in place, the bicep-local-docgen takes over. This CLI utility scans the *.cs files, reads the annotations, and generates Markdown files. That means you don't have to write out any Markdown files yourselves. No more manual edits required.

Here's the revisited example from earlier with annotations from Bicep.LocalDeploy:

using Bicep.LocalDeploy;
using Bicep.Local.Extension.Types.Attributes;
using Azure.Bicep.Types.Concrete;
using System.Text.Json.Serialization;

namespace MyCommerce.Models.Product;

[BicepFrontMatter("category", "Commerce")]
[BicepDocHeading("Product", "Represents a product with name and pricing information.")]
[BicepDocExample(
    "Creating a basic product",
    "This example shows how to create a simple product with name and price.",
    @"
resource product 'Product' = {
  name: 'Premium Widget'
  price: 99.99
}
"
)]
[BicepDocCustom("Additional reference", @"For more information, see the following links:

- [Product Management Guide][00]

<!-- Link reference definitions -->
[00]: https://example.com/docs/products")]
[ResourceType("Product")]
public class Product
{
    /// <summary>
    /// Gets or sets the name of the product.
    /// </summary>
    /// <value>The product name. Cannot be null or empty.</value>
    [TypeProperty("The name of the product.", ObjectTypePropertyFlags.Required)]
    public string Name { get; set; } = string.Empty;

    /// <summary>
    /// Gets or sets the price of the product in decimal format.
    /// </summary>
    /// <value>The product price. Must be greater than or equal to zero.</value>
    [TypeProperty("The price of the product.", ObjectTypePropertyFlags.Required)]
    public decimal Price { get; set; }
}

Getting started

Now that you've seen the concept in action, let's walk through how to set it up yourself. Getting started is simple: add the Bicep.LocalDeploy package to your project, add annotations to your code, and run the bicep-local-docgen tool to generate the docs.

Let's do it step-by-step. I'm assuming you have the .NET SDK 9.0 or above installed:

  1. Create a new project file named MyCommerce.csproj with the following content:
<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <RootNamespace>MyCommerce</RootNamespace>
    <AssemblyName>my-commerce</AssemblyName>
    <IncludeNativeLibrariesForSelfExtract>true</IncludeNativeLibrariesForSelfExtract>
    <PublishSingleFile>true</PublishSingleFile>
    <SelfContained>true</SelfContained>
    <InvariantGlobalization>true</InvariantGlobalization>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
    <AppendTargetFrameworkToOutputPath>false</AppendTargetFrameworkToOutputPath>
    <AppendRuntimeIdentifierToOutputPath>false</AppendRuntimeIdentifierToOutputPath>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Azure.Bicep.Local.Extension" Version="0.37.4" />
    <PackageReference Include="Azure.Identity" Version="1.14.2" />
  </ItemGroup>
</Project>
  1. From the project directory run: dotnet add package Bicep.LocalDeploy.

This will add the library providing annotation attributes.

  1. Create a folder Models and add a new file named Product.cs.
  2. Copy the content from the earlier section into this file.
  3. Install the bicep-local-docgen by running: dotnet tool install --global bicep-local-docgen.
  4. Execute bicep-local-docgen generate --source Models -v in your terminal to produce documentation.
Running `bicep-local-docgen`

By default, the CLI utility produces files in the docs directory. You can always add the --output option to store the generated docs in a different directory. If you open the file in your favorite editor, you notice the generated docs.

Generated Markdown file

With the docs now generated, you're ready to publish them into your repository.

Key takeaways

Using the Bicep.LocalDeploy and bicep-local-docgen tool demonstrated how much easier it is to create documentation. By annotating your models and generating docs automatically, you eliminate the manual work. It also keeps your code aligned with your docs. At least, if you keep the annotations up to date, as well.

Here are the main benefits you'll notice when adopting this approach:

  • Annotations simplify documentation: Adding metadata directly to your models ensures docs are always up to date.
  • Automated generation: The bicep-local-docgen was designed on top of the library to produce Markdown documentation.
  • Easy setup: Simply add the NuGet package to your project and run the CLI tool.
  • Flexible output: Add the annotations you want, and the tool grabs it up.

Summary

The Bicep.LocalDeploy library provides simple annotations that you can add directly next to your source code. This way, the CLI utility can read the annotations and keep your documentation up to date without requiring manual updates.

In this way, documentation also becomes standardized if you follow the same style in each model. If you wanna learn more, check out the official GitHub repository.

There are already a couple of repositories making use of this library: