-
Notifications
You must be signed in to change notification settings - Fork 0
Home
Charming lets you generate ARM templates using C# by creating simple POCO objects. It is simply a specialised serialiser (currently uses the awesome Json.NET library under the hood for serialising) that serialises objects representing the various elements of an ARM template into valid json that can be readily used for deployment.
The generated json is in an expanded form, intended to be treated as a transpiled artifact and thereby eliminating the need for some of the typically used constructs within an ARM template such as parameters, variables, conditions, copy loops and functions. You can and would still reference some of the available template functions like resourceGroup()
, resourceId()
, etc. to pass around resource information and the library provides some nice helper methods to assist you in forming the function calls.
Charming provides the following core abstractions and types:
The Resource<TProperties>
abstract class represents a resource where TProperties
is a generic parameter type for the properties of that resource. Its base class Resource
can also be used directly for resources that do not need a properties
section.
The following snippet defines a log analytics workspace:
public class Workspace : Resource<WorkspaceProperties>
{
public Workspace() =>
(Type, ApiVersion) = ("Microsoft.OperationalInsights/workspaces", "2020-03-01-preview");
}
public class WorkspaceProperties
{
public int? RetentionInDays { get; set; }
}
Note that the RetentionInDays
property is defined as a nullable int?
. This is necessary to avoid having retentionInDays
property always be set to zero in the generated json, when it is not explicitly set. As a general rule of thumb, always prefer nullable types for property data types, so that if they are not set, then they will not appear in the generated json.
Some of the most commonly used resource types have already been defined in the Charming.Types nuget package and you can reference them instead of creating custom ones. See here for more details.
To add child resources:
var containers = new[]
{
new StorageAccountBlobServiceContainer("container1"),
new StorageAccountBlobServiceContainer("container2"),
};
var blobService = new StorageAccountBlobService("default")
.WithResources(containers);
// or directly add to the list
blobService.Resources.Add(new StorageAccountBlobServiceContainer("container3"));
To add dependencies:
var vmScaleSet = new VirtualMachineScaleSet("sample-vm-scaleset")
.WithDependencies(new VirtualNetwork("sample-vnet"));
// or directly add to the list
vmScaleSet.DependsOn.Add(new LoadBalancer("sample-lb"));
To add tags:
var tags = new Dictionary<string, string>
{
["environment"] = "dev",
["department"] = "finance",
};
var resourceGroup = new ResourceGroup("sample-rg").WithTags(tags);
// or directly add to the dictionary
resourceGroup.Tags.Add("version", "1.0");
The Output
class represents an output item of a template. It has a Key
and a Value
property and some static helper methods to create an Output
object.
Note that the Value
property can be one of these three types:
-
ArrayOutputValue
: represents an array of either string or object output items -
ObjectOutputValue
: represents an object via a mapping ofstring
and otherOutputValue
s -
StringOutputValue
: represents a simplestring
value
To create an array output of string values:
var storageAccounts = new[]
{
new StorageAccount("samplestorage1"),
new StorageAccount("samplestorage2"),
};
var values = storageAccounts
.Select(x => $"{Functions.Reference(x)}.primaryEndpoints.blob".Box());
var output = Output.Array("storageEndpoints", values);
To create an array output of object values:
var storageAccounts = new[]
{
new StorageAccount("samplestorage1"),
new StorageAccount("samplestorage2"),
};
var values = storageAccounts
.Select(x => new Dictionary<string, string>
{
["BlobEndpoint"] = $"{Functions.Reference(x)}.primaryEndpoints.blob".Box(),
["Status"] = $"{Functions.Reference(x)}.statusOfPrimary".Box(),
});
var output = Output.Array("storageEndpoints", values);
To create an object output:
var storage = new StorageAccount("samplestorage");
var output = Output.Object("storage", Functions.ReferenceFull(storage).Box());
To create an object output with mutiple properties:
var storage = new StorageAccount("samplestorage");
var keyvault = new Vault("sample-kv");
var value = new Dictionary<string, string>
{
["storage"] = Functions.Reference(storage).Box(),
["keyvault"] = Functions.Reference(keyvault).Box(),
};
var output = Output.Object("deployment", value);
To create a string output:
var output = Output.String("resourceGroupName", Functions.ResourceGroup().Name.Box());
Note that both the Object
and String
helper methods have overloads that take a boolean isSecure
to let you specify if you want to output a secureobject
or securestring
respectively instead.
Charming provides a bunch of convenience static methods hanging off the Functions
class that help form a template function call string.
To construct the reference()
function call:
var storage = new StorageAccount("samplestorage");
var simpleOutput = Output.Object("storage", Functions.Reference(storage).Box());
var fullOutput = Output.Object("storage", Functions.ReferenceFull(storage).Box());
Note that the Box()
string extension method encloses the resulting string within square brackets []
.
To construct the resourceId()
function call:
var storage = new StorageAccount("samplestorage");
var output1 = Output.String("storageResourceId", Functions.ResourceId(storage).Box());
var otherResourceGroup = "other-rg";
var storageFromOtherResourceGroup = new StorageAccount("samplestorage");
var output2 = Output.String("storageResourceId", Functions.ResourceId(otherResourceGroup, storage).Box());
var otherSubscriptionId = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var storageFromOtherSubscription = new StorageAccount("samplestorage");
var output3 = Output.String("storageResourceId", Functions.ResourceId(otherSubscriptionId, otherResourceGroup, storage).Box());
To construct the resourceGroup()
function call:
var output1 = Output.Object("resourceGroup", Functions.ResourceGroup().Box());
var output2 = Output.String("resourceGroupId", Functions.ResourceGroup().Id.Box());
To construct the subscription()
function call:
var output1 = Output.Object("subscription", Functions.Subscription().Box());
var output2 = Output.String("subscriptionId", Functions.Subscription().Id.Box());
Both ResourceGroup()
and Subscription()
methods return an object containing the relevant properties that can be call off that method. The examples above demonstrate the Id
property being called.
If the function you need is not available, then you could simply create a string with the function call:
var deployment = new Deployment("sample-deploy");
deployment.Properties = new DeploymentProperties
{
Mode = DeploymentMode.Incremental,
TemplateLink = new TemplateLinkInfo
{
Uri = "uri(deployment().properties.templateLink.uri, 'azuredeploy.json')",
},
};
The ITemplate
interface represents a template. Charming comes with two implementations out of the box:
-
SubscriptionDeploymentTemplate
: use this type to represent a template that is to deployed at a subscription level. -
ResourceGroupDeploymentTemplate
: use this type to represent a template that is to deployed at a resource group level.
The Schema
property on the template is set by default to the location of the latest json schema version.
Once all resources and outputs have been wired up to the template object, then simply call ToJson()
to generate the output json. This method has an overload that takes a SerializerOptions
object that lets you configure the serialisation behaviour.
var template = new ResourceGroupDeploymentTemplate()
.WithResources(new StorageAccount("samplestorage"));
var json = template.ToJson(new SerializerOptions { Indent = false });
The accompanying Charming.Types nuget package provides a set of types representing commonly used Azure resources and the recommended approach is to add a reference to this package instead of the core Charming package.
All related resources are grouped under a single namespace, for instance adding using Charming.Types.KeyVault;
would bring into scope the Vault
resource type (representing a keyvault) as well as all its child resource types such as VaultSecret
, VaultAccessPolicy
, etc.
Note that the resource type names follow the naming from Azure resource provider namespaces.