in sdk/provisioning/Generator/src/Model/Resource.cs [10:352]
public class Resource(Specification spec, Type armType)
: TypeModel(
name: armType.Name.TrimSuffix("Resource")!,
ns: spec.Namespace,
// description: specification.DocComments.GetSummary(armType), // These aren't super meaningful doc comments so we'll avoid
armType: armType,
spec: spec)
{
public string? ResourceType { get; set; }
public string? ResourceNamespace { get; set; }
public string? DefaultResourceVersion { get; set; }
public IList<string>? ResourceVersions { get; set; }
public NameRequirements? NameRequirements { get; set; }
public bool GenerateRoleAssignment { get; set; } = false;
public Resource? ParentResource { get; set; }
public SimpleModel? GetKeysType { get; set; }
public bool GetKeysIsList { get; set; }
public override string ToString() => $"<Resource {Spec!.Name}::{Name}>";
public override void Lint()
{
base.Lint();
//if (NameRequirements is null) { Warn($"{GetTypeReference()} has no {nameof(NameRequirements)}."); }
if (DefaultResourceVersion is null) { Warn($"{ResourceType} has no {nameof(DefaultResourceVersion)}."); }
else if (ResourceVersions is null) { Warn($"{ResourceType} has no {nameof(ResourceVersions)}."); }
}
public override void Generate()
{
ContextualException.WithContext(
$"Generating resource {Namespace}.{Name}",
() =>
{
IndentWriter writer = new();
writer.WriteLine("// Copyright (c) Microsoft Corporation. All rights reserved.");
writer.WriteLine("// Licensed under the MIT License.");
writer.WriteLine();
writer.WriteLine("// <auto-generated/>");
writer.WriteLine();
writer.WriteLine("#nullable enable");
writer.WriteLine();
// Add the usings
HashSet<string> namespaces = CollectNamespaces();
if (FromExpression || GenerateRoleAssignment || GetKeysType is not null) { namespaces.Add("Azure.Provisioning.Expressions"); }
if (FromExpression || NameRequirements is not null || GetKeysType is not null) { namespaces.Add("System.ComponentModel"); }
if (GenerateRoleAssignment) { namespaces.Add("Azure.Provisioning.Authorization"); namespaces.Add("Azure.Provisioning.Roles"); }
namespaces.Remove(Namespace!);
foreach (string ns in namespaces.Order())
{
writer.WriteLine($"using {ns};");
}
writer.WriteLine();
writer.WriteLine($"namespace {Namespace};");
writer.WriteLine();
writer.WriteLine($"/// <summary>");
writer.WriteWrapped(Description ?? (Name + "."));
writer.WriteLine($"/// </summary>");
writer.WriteLine($"public partial class {Name} : ProvisionableResource");
using (writer.Scope("{", "}"))
{
var fence = new IndentWriter.Fenceposter();
// Write the properties
foreach (Property property in Properties)
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
if (!property.HideAccessors)
{
writer.WriteLine($"/// <summary>");
string orSets = property.IsReadOnly ? "" : " or sets";
writer.WriteWrapped(property.Description ?? $"Gets{orSets} the {property.Name}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"public {property.BicepTypeReference} {property.Name} ");
using (writer.Scope("{", "}"))
{
writer.WriteLine($"get {{ Initialize(); return {property.FieldName}!; }}");
if (!property.IsReadOnly)
{
writer.Write($"set {{ Initialize(); ");
if (property.PropertyType is SimpleModel || property.PropertyType is Resource)
{
writer.Write($"AssignOrReplace(ref {property.FieldName}, value);");
}
else
{
writer.Write($"{property.FieldName}!.Assign(value);");
}
writer.WriteLine($" }}");
}
}
}
writer.WriteLine($"private {property.BicepTypeReference}? {property.FieldName};");
}
if (ParentResource is not null)
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteLine($"/// Gets or sets a reference to the parent {ParentResource.Name}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"public {ParentResource.Name}? Parent");
using (writer.Scope("{", "}"))
{
writer.WriteLine($"get {{ Initialize(); return _parent!.Value; }}");
writer.WriteLine($"set {{ Initialize(); _parent!.Value = value; }}");
}
writer.WriteLine($"private ResourceReference<{ParentResource.Name}>? _parent;");
}
// Write the default value partial methods
foreach (Property property in Properties.Where(p => p.GenerateDefaultValue))
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Get the default value for the {property.Name} property.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"private partial {property.BicepTypeReference} Get{property.Name}DefaultValue();");
}
// Write the .ctor
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Creates a new {Name}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"/// <param name=\"bicepIdentifier\">");
writer.WriteWrapped($"The the Bicep identifier name of the {Name} resource. This can be used to refer to the resource in expressions, but is not the Azure name of the resource. This value can contain letters, numbers, and underscores.");
writer.WriteLine($"/// </param>");
writer.WriteLine($"/// <param name=\"resourceVersion\">Version of the {Name}.</param>");
writer.WriteLine($"public {Name}(string bicepIdentifier, string? resourceVersion = default)");
writer.Write($" : base(bicepIdentifier, \"{ResourceType}\", resourceVersion");
if (DefaultResourceVersion is not null)
{
writer.Write($" ?? \"{DefaultResourceVersion}\"");
}
writer.WriteLine(")");
using (writer.Scope("{", "}")) { }
// Write the properties
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Define all the provisionable properties of {Name}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"protected override void DefineProvisionableProperties()");
using (writer.Scope("{", "}"))
{
foreach (Property property in Properties)
{
writer.Write($"{property.FieldName} = ");
if (property.PropertyType is SimpleModel || property.PropertyType is Resource)
{
writer.Write($"DefineModelProperty");
}
else if (property.PropertyType is ListModel lst)
{
writer.Write($"DefineListProperty");
}
else if (property.PropertyType is DictionaryModel dict)
{
writer.Write($"DefineDictionaryProperty");
}
else
{
writer.Write($"DefineProperty");
}
writer.Write($"<{property.BicepPropertyTypeReference}>(\"{property.Name}\", ");
writer.Write($"[{string.Join(", ", (property.Path ?? [property.Name]).Select(s => $"\"{s}\""))}]");
if (property.IsRequired) { writer.Write($", isRequired: true"); }
if (property.IsReadOnly) { writer.Write($", isOutput: true"); }
if (property.IsSecure) { writer.Write($", isSecure: true"); }
if (property.GenerateDefaultValue) { writer.Write($", defaultValue: Get{property.Name}DefaultValue()"); }
if (property.Format is not null) { writer.Write($", format: \"{property.Format}\""); }
writer.WriteLine($");");
}
if (ParentResource is not null)
{
writer.WriteLine($"_parent = DefineResource<{ParentResource.Name}>(\"Parent\", [\"parent\"], isRequired: true);");
}
}
// Add the well known versions
if (ResourceVersions is not null)
{
fence = new IndentWriter.Fenceposter();
writer.WriteLine();
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Supported {Name} resource versions.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"public static class ResourceVersions");
using (writer.Scope("{", "}"))
{
foreach (string version in ResourceVersions)
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
string name = $"V{version.Replace("-", "_")}";
writer.WriteLine($"/// <summary>");
writer.WriteLine($"/// {version}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"public static readonly string {name} = \"{version}\";");
}
}
}
// Add the FromExisting method
if (Properties.Any(p => p.Name == "Name" && p.PropertyType?.Name == "String"))
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Creates a reference to an existing {Name}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"/// <param name=\"bicepIdentifier\">");
writer.WriteWrapped($"The the Bicep identifier name of the {Name} resource. This can be used to refer to the resource in expressions, but is not the Azure name of the resource. This value can contain letters, numbers, and underscores.");
writer.WriteLine($"/// </param>");
writer.WriteLine($"/// <param name=\"resourceVersion\">Version of the {Name}.</param>");
writer.WriteLine($"/// <returns>The existing {Name} resource.</returns>");
writer.WriteLine($"public static {Name} FromExisting(string bicepIdentifier, string? resourceVersion = default) =>");
using (writer.Scope())
{
writer.WriteLine($"new(bicepIdentifier, resourceVersion) {{ IsExistingResource = true }};");
}
}
// Add the name requirements
if (NameRequirements is not null)
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Get the requirements for naming this {Name} resource.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"/// <returns>Naming requirements.</returns>");
writer.WriteLine($"[EditorBrowsable(EditorBrowsableState.Never)]");
writer.WriteLine($"public override ResourceNameRequirements GetResourceNameRequirements() =>");
using (writer.Scope())
{
writer.Write($"new(minLength: {NameRequirements.Min}, maxLength: {NameRequirements.Max}, validCharacters: ");
List<string> cases = [];
if (NameRequirements.Lower) { cases.Add("LowercaseLetters"); }
if (NameRequirements.Upper) { cases.Add("UppercaseLetters"); }
if (NameRequirements.Numbers) { cases.Add("Numbers"); }
if (NameRequirements.Hyphen) { cases.Add("Hyphen"); }
if (NameRequirements.Underscore) { cases.Add("Underscore"); }
if (NameRequirements.Period) { cases.Add("Period"); }
if (NameRequirements.Parens) { cases.Add("Parentheses"); }
writer.Write(string.Join(" | ", cases.Select(c => $"ResourceNameCharacters.{c}")));
writer.WriteLine($");");
}
}
if (GetKeysType is not null)
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Get access keys for this {Name} resource.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"/// <returns>The keys for this {Name} resource.</returns>");
string keyType = GetKeysType.Name;
if (GetKeysIsList) { keyType = $"BicepList<{keyType}>"; }
string expr = $"new FunctionCallExpression(new MemberExpression(new IdentifierExpression(BicepIdentifier), \"listKeys\"))";
writer.WriteLine($"public {keyType} GetKeys()");
using (writer.Scope("{", "}"))
{
if (GetKeysIsList)
{
writer.WriteLine($"return {keyType}.FromExpression(");
using (writer.Scope())
{
writer.WriteLine($"e => {{ {GetKeysType.Name} key = new(); ((IBicepValue)key).Expression = e; return key; }},");
writer.WriteLine($"new MemberExpression({expr}, \"keys\"));");
}
}
else
{
writer.WriteLine($"{keyType} key = new();");
writer.WriteLine($"((IBicepValue)key).Expression = {expr};");
writer.WriteLine($"return key;");
}
}
}
// Add the role assignment
if (GenerateRoleAssignment)
{
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Creates a role assignment for a user-assigned identity that grants access to this {Name}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"/// <param name=\"role\">The role to grant.</param>");
writer.WriteLine($"/// <param name=\"identity\">The <see cref=\"UserAssignedIdentity\"/>.</param>");
writer.WriteLine($"/// <returns>The <see cref=\"RoleAssignment\"/>.</returns>");
writer.WriteLine($"public RoleAssignment CreateRoleAssignment({Spec!.Name}BuiltInRole role, UserAssignedIdentity identity) =>");
using (writer.Scope())
{
writer.WriteLine($"new($\"{{BicepIdentifier}}_{{identity.BicepIdentifier}}_{{{Spec!.Name}BuiltInRole.GetBuiltInRoleName(role)}}\")");
using (writer.Scope("{", "};"))
{
writer.Write($"Name = BicepFunction.CreateGuid(");
if (Properties.Any(p => p.Name == "Id")) { writer.Write("Id, "); }
writer.WriteLine($"identity.PrincipalId, BicepFunction.GetSubscriptionResourceId(\"Microsoft.Authorization/roleDefinitions\", role.ToString())),");
writer.WriteLine($"Scope = new IdentifierExpression(BicepIdentifier),");
writer.WriteLine($"PrincipalType = RoleManagementPrincipalType.ServicePrincipal,");
writer.WriteLine($"RoleDefinitionId = BicepFunction.GetSubscriptionResourceId(\"Microsoft.Authorization/roleDefinitions\", role.ToString()),");
writer.WriteLine($"PrincipalId = identity.PrincipalId");
}
}
if (fence.RequiresSeparator) { writer.WriteLine(); }
writer.WriteLine($"/// <summary>");
writer.WriteWrapped($"Creates a role assignment for a principal that grants access to this {Name}.");
writer.WriteLine($"/// </summary>");
writer.WriteLine($"/// <param name=\"role\">The role to grant.</param>");
writer.WriteLine($"/// <param name=\"principalType\">The type of the principal to assign to.</param>");
writer.WriteLine($"/// <param name=\"principalId\">The principal to assign to.</param>");
writer.WriteLine($"/// <param name=\"bicepIdentifierSuffix\">Optional role assignment identifier name suffix.</param>");
writer.WriteLine($"/// <returns>The <see cref=\"RoleAssignment\"/>.</returns>");
writer.WriteLine($"public RoleAssignment CreateRoleAssignment({Spec!.Name}BuiltInRole role, BicepValue<RoleManagementPrincipalType> principalType, BicepValue<Guid> principalId, string? bicepIdentifierSuffix = default) =>");
using (writer.Scope())
{
writer.WriteLine($"new($\"{{BicepIdentifier}}_{{{Spec!.Name}BuiltInRole.GetBuiltInRoleName(role)}}{{(bicepIdentifierSuffix is null ? \"\" : \"_\")}}{{bicepIdentifierSuffix}}\")");
using (writer.Scope("{", "};"))
{
writer.Write($"Name = BicepFunction.CreateGuid(");
if (Properties.Any(p => p.Name == "Id")) { writer.Write("Id, "); }
writer.WriteLine($"principalId, BicepFunction.GetSubscriptionResourceId(\"Microsoft.Authorization/roleDefinitions\", role.ToString())),");
writer.WriteLine($"Scope = new IdentifierExpression(BicepIdentifier),");
writer.WriteLine($"PrincipalType = principalType,");
writer.WriteLine($"RoleDefinitionId = BicepFunction.GetSubscriptionResourceId(\"Microsoft.Authorization/roleDefinitions\", role.ToString()),");
writer.WriteLine($"PrincipalId = principalId");
}
}
}
}
// Write out the model
Spec!.SaveFile($"{Name}.cs", writer.ToString());
});
}
}