Bicep# (pronounced Bicep sharp) is a functional framework designed to streamline the use of Azure Bicep. By offering variables, types, and functions, Bicep# aims to simplify infrastructure as code and set the standards for what a good infrastructure as code framework looks like.
Bicep# provides the following advantages:
-
Customized environment variables: Bicep# automatically generates variables tailored to your policies, environment resources, and the latest Azure changes. This includes tenant-specific roles, enterprise apps, service tags, global settings and more, ensuring up-to-date and easy to use information.
-
Rethinks the module pattern: Module oriented libraries like Azure Verified Modules aim to modularize each resource. With Bicep#, the approach is to:
- Provide a way to mix and match better than modules: Bicep# provides centralized types, functions and variables you can use over and over again, without being isolated into a single module, where some types and patterns are actually not that reusable at all. For instance, in Bicep#, you make an NSG rule without having to look up what the service tags are, or what the destination service structure looks like, or without restricting in what module your NSG rule ends up in. You decide, and Bicep# gets you the additional information and structure you missed out on.
- Prevent overuse of modules: Many use cases for Bicep modules are obsolete with the introduction of user-defined concepts in Bicep. Technically, modules are just separate deployment templates (and server side sub-deployments in Azure). There used to be no other way to centralize logic than to use templates and reference them. With Bicep#, there shouldn't be much reason to keep modularizing everything with the concept of Bicep modules. That doesn't mean modules don't have a use case anymore, but it sure isn't as much of a big deal anymore.
- Respect the simplicity of Bicep, and only provide functionality that extends your workflow instead of overhauling it: Giving in on enterprise-scale libraries most likely means overhauling a large part of your infrastructure. When keeping things small and functional oriented, you can effectively refactor either big or small parts of your infrastructure, where it matters to you the most.
-
Compatible with Azure Container registry (ACR): All types, function and variables can seamlessly integrate with ACR.
-
Scalable framework design: Bicep# is designed with scalability in mind, providing a straightforward technical design to facilitate project development.
See Bicep# /lib/network in action!
The samples use local files and not from a registry, but give a good idea how to use Bicep#. Try out the samples by running the following commands:
az login
az deployment sub create --location westeurope --template-file samples/main.bicep --parameters samples/main.dev.bicepparam
Or just build the sample locally:
az bicep build --file samples/main.bicep
This project is currently in preview and only has the most basic functionality. You can help by expanding it.
Compiled files are outputted to /lib/private/variables/generated/.
To compile, make sure to install PowerShell Az (11.5.0 or higher) and Az.ResourceGraph (0.13.1 or higher), then run with:
- param
location
, a location (a valid Azure region), for instancewesteurope
; - param
allowPublicResources
, either0
or1
. Sets the public network access property on resources.
Connect-AzAccount
./scripts/Build-Lib.ps1 -location westeurope -allowPublicResources 0
Ideally, deploy the /lib/ files to an Azure Container Registry for improved reusability in your project or organization.
To deploy, make sure to:
- compile Bicep#;
- provision an Azure Container Registry into your tenant;
- add Bicep to your PATH by installing manually.
Then run with:
- param
acrLoginServer
, the login server of an Azure Container Registry, for instanceacrbicepsharp.azurecr.io
; - param
versionPostfix
, sets the version of the files to publish, if the version already exists, it will overwrite.
Connect-AzAccount
./scripts/Publish-Lib.ps1 -acrLoginServer "acrbicepsharp.azurecr.io" -versionPostfix 1
After deployment, you can use any of the public lib files in your own project by referring to the ACR in a Bicep file, for instance like this:
import * as sharpNetwork from 'br:acrbicepsharp.azurecr.io/bicepsharp/network:v1'
flowchart TB
ats[Azure tenant - source]
udv[User defined settings]
compiler[Bicep# compiler]
subgraph atd[Azure tenant - destination]
acr(Azure Container registry)
end
subgraph Bicep# public
publib(library files)
end
subgraph Bicep# private
pgen(json variables - generated) --> pconst
ptypes(bicep types) --> pfunc
pfunc(bicep functions)
pconst(bicep variables - static)
end
pfunc --> publib
pconst --> publib
ptypes --> publib
publib -- publish --> acr
ats --> compiler
udv --> compiler
compiler --> pgen
pgen --> pfunc
Bicep filenames are snake-case in order to support imports into Azure Container registry.
PowerShell filenames use Approved Verbs for PowerShell Commands and follow their default casing pattern.
Bicep variables, functions, types and all other filenames are lower camel case, as described in their Best practices.
The root of /lib/ contains public variables, types and functions to be used by the end-user, smartly grouped together, to;
- reduce the amount of imports for the end-user;
- reduce the total size of templates after import;
- keep the public facing files readable.
Lib files should only have a use case within two scopes at most, and ideally just one scope, except for the resource
and tag
lib.
Public lib | Groups together... | Makes sense to use in files with scope... |
---|---|---|
resource | Generic usages on resources, resource groups and modules | resourceGroup and subscription |
tag | Tagging | bicepparam and subscription |
authorization | Azure Role Based Access Control | resourceGroup |
network | Azure Networking | resourceGroup |
storage | Azure Storage | resourceGroup |
devops-services | DevOps Services | bicepparam |
entra-id | Entra ID | bicepparam |
Public functions are either;
- helper functions, prefixed by
get
,calculate
,create
etc..., or; - resource builder functions, prefixed by
build
and appended with the resource to build, for instancebuildVnet
. These functions will always return a complete resource with at leastname
andproperties
, as specified by the typesresourceFormat
andresourceFormatWithDefaultName
in /lib/private/types/resource.bicep.- Building the
properties
of a resource will always be done in a private function file at /lib/private/functions/. - Building the
name
of a resource will only be done in a private function file at /lib/private/functions/ if the name is being built in more than one public function or if it's more than a one-liner.
- Building the
Public variables are;
- never fully written down, but always specified and referenced from a private facing file at /lib/private/variables/.
Public types are;
- never fully written down, but always specified and referenced from a private facing file at /lib/private/types/.
/lib/private/ contains private variables, types and functions, grouped by resource type, to be used by public facing Bicep# files.
Preferably, introduce global values and use them directly inside functions instead of introducing a new parameter for a function. Global values make sense if they are common tenant-wide settings.
During compilation, /scripts/New-FrameworkGlobals.ps1 outputs separate text files in /lib/private/variables/generated/ for each global value, to preserve efficient loading with loadTextContent()
. Because we cannot use variables inside functions, these files can directly be used inside any private function.
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.