Back to Azure Sdk For

Provisioning Library Specification

eng/packages/http-client-csharp-provisioning/docs/provisioning-library-spec.md

2019-05-16T16-5224.1 KB
Original Source

Provisioning Library Specification

This document describes the structure and shape of provisioning libraries that the provisioning generator must produce. All generated types follow the patterns established by existing Azure.Provisioning.* libraries and map directly to Bicep resource definitions.


1. Overview

Provisioning libraries produce three categories of types:

CategoryBase ClassPurposeExample
ResourceProvisionableResourceTop-level ARM resourceAppConfigurationStore
ModelProvisionableConstructNested property objectAppConfigurationKeyVaultProperties
EnumenumConstrained string/int valueAppConfigurationCreateMode

Supporting types are also generated:

TypePurposeExample
BuiltInRoleRBAC role definitionsAppConfigurationBuiltInRole
ResourceVersionsSupported API versionsNested class inside resource

Key Principle

Provisioning types are Bicep-centric, not REST-centric. They mirror the Bicep resource schema — each property has a bicepPath (e.g., ["properties", "createMode"]) that maps directly to the Bicep template output. Properties use BicepValue<T> wrappers instead of raw .NET types.


2. Library Folder Structure

Each provisioning library lives alongside its corresponding management library under the same service directory:

sdk/keyvault/
├── Azure.ResourceManager.KeyVault/        ← Management library
│   ├── src/
│   ├── tests/
│   └── tsp-location.yaml
├── Azure.Provisioning.KeyVault/           ← Provisioning library (parallel)
│   ├── src/
│   │   ├── Generated/                     ← Generator output
│   │   └── ...                            ← Hand-written customizations (partial classes)
│   ├── tests/
│   ├── tsp-location.yaml                  ← Points to same TypeSpec spec, different emitter
│   └── Azure.Provisioning.KeyVault.csproj
└── ci.mgmt.yml

This co-location ensures the provisioning library is discovered and built alongside the management library for the same service. Both libraries point to the same TypeSpec spec in azure-rest-api-specs, but use different emitters (@azure-typespec/http-client-csharp-mgmt vs @azure-typespec/http-client-csharp-provisioning).

Note: Existing provisioning libraries currently live under sdk/provisioning/Azure.Provisioning.{ServiceName}. New TypeSpec-based provisioning libraries should be placed under the service directory instead.


3. Resource Types

Structure

A resource class:

  • Extends ProvisionableResource
  • Is public partial class
  • Has a constructor taking bicepIdentifier and optional resourceVersion
  • Calls the base constructor with the ARM resource type string and default API version
  • Overrides DefineProvisionableProperties() to declare all properties
  • Contains a nested ResourceVersions class listing supported GA API versions
  • Provides a static FromExisting() factory method
  • Optionally overrides GetResourceNameRequirements() for naming validation
  • Optionally includes CreateRoleAssignment() methods and GetKeys() / similar helpers

Example: Resource Class

csharp
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// <auto-generated/>

#nullable enable

using Azure.Core;
using Azure.Provisioning;
using Azure.Provisioning.Expressions;
using Azure.Provisioning.Primitives;
using Azure.Provisioning.Resources;
using System;
using System.ComponentModel;

namespace Azure.Provisioning.AppConfiguration;

/// <summary>
/// AppConfigurationStore.
/// </summary>
public partial class AppConfigurationStore : ProvisionableResource
{
    // ── Input properties (have setter) ──────────────────────────────

    /// <summary>
    /// The name of the configuration store.
    /// </summary>
    public BicepValue<string> Name
    {
        get { Initialize(); return _name!; }
        set { Initialize(); _name!.Assign(value); }
    }
    private BicepValue<string>? _name;

    /// <summary>
    /// Gets or sets the Location.
    /// </summary>
    public BicepValue<AzureLocation> Location
    {
        get { Initialize(); return _location!; }
        set { Initialize(); _location!.Assign(value); }
    }
    private BicepValue<AzureLocation>? _location;

    /// <summary>
    /// The SKU name of the configuration store.
    /// </summary>
    public BicepValue<string> SkuName
    {
        get { Initialize(); return _skuName!; }
        set { Initialize(); _skuName!.Assign(value); }
    }
    private BicepValue<string>? _skuName;

    /// <summary>
    /// Disables all authentication methods other than AAD authentication.
    /// </summary>
    public BicepValue<bool> DisableLocalAuth
    {
        get { Initialize(); return _disableLocalAuth!; }
        set { Initialize(); _disableLocalAuth!.Assign(value); }
    }
    private BicepValue<bool>? _disableLocalAuth;

    /// <summary>
    /// Gets or sets the Tags.
    /// </summary>
    public BicepDictionary<string> Tags
    {
        get { Initialize(); return _tags!; }
        set { Initialize(); _tags!.Assign(value); }
    }
    private BicepDictionary<string>? _tags;

    /// <summary>
    /// Key vault properties.
    /// </summary>
    public AppConfigurationKeyVaultProperties EncryptionKeyVaultProperties
    {
        get { Initialize(); return _encryptionKeyVaultProperties!; }
        set { Initialize(); AssignOrReplace(ref _encryptionKeyVaultProperties, value); }
    }
    private AppConfigurationKeyVaultProperties? _encryptionKeyVaultProperties;

    // ── Output properties (read-only, no setter) ────────────────────

    /// <summary>
    /// The creation date of configuration store.
    /// </summary>
    public BicepValue<DateTimeOffset> CreatedOn
    {
        get { Initialize(); return _createdOn!; }
    }
    private BicepValue<DateTimeOffset>? _createdOn;

    /// <summary>
    /// Gets the Id.
    /// </summary>
    public BicepValue<ResourceIdentifier> Id
    {
        get { Initialize(); return _id!; }
    }
    private BicepValue<ResourceIdentifier>? _id;

    /// <summary>
    /// The provisioning state of the configuration store.
    /// </summary>
    public BicepValue<AppConfigurationProvisioningState> ProvisioningState
    {
        get { Initialize(); return _provisioningState!; }
    }
    private BicepValue<AppConfigurationProvisioningState>? _provisioningState;

    // ── Constructor ─────────────────────────────────────────────────

    /// <summary>
    /// Creates a new AppConfigurationStore.
    /// </summary>
    /// <param name="bicepIdentifier">
    /// The the Bicep identifier name of the AppConfigurationStore 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.
    /// </param>
    /// <param name="resourceVersion">Version of the AppConfigurationStore.</param>
    public AppConfigurationStore(string bicepIdentifier, string? resourceVersion = default)
        : base(bicepIdentifier, "Microsoft.AppConfiguration/configurationStores", resourceVersion ?? "2024-06-01")
    {
    }

    // ── Property definitions (Bicep path mapping) ───────────────────

    /// <summary>
    /// Define all the provisionable properties of AppConfigurationStore.
    /// </summary>
    protected override void DefineProvisionableProperties()
    {
        base.DefineProvisionableProperties();

        // Input properties
        _name = DefineProperty<string>("Name", ["name"], isRequired: true);
        _location = DefineProperty<AzureLocation>("Location", ["location"], isRequired: true);
        _skuName = DefineProperty<string>("SkuName", ["sku", "name"]);
        _disableLocalAuth = DefineProperty<bool>("DisableLocalAuth", ["properties", "disableLocalAuth"]);
        _tags = DefineDictionaryProperty<string>("Tags", ["tags"]);
        _encryptionKeyVaultProperties = DefineModelProperty<AppConfigurationKeyVaultProperties>(
            "EncryptionKeyVaultProperties", ["properties", "encryption", "keyVaultProperties"]);

        // Output properties (server-provided, read-only)
        _createdOn = DefineProperty<DateTimeOffset>("CreatedOn", ["properties", "creationDate"], isOutput: true);
        _id = DefineProperty<ResourceIdentifier>("Id", ["id"], isOutput: true);
        _provisioningState = DefineProperty<AppConfigurationProvisioningState>(
            "ProvisioningState", ["properties", "provisioningState"], isOutput: true);
    }

    // ── ResourceVersions ────────────────────────────────────────────

    /// <summary>
    /// Supported AppConfigurationStore resource versions.
    /// </summary>
    public static class ResourceVersions
    {
        /// <summary>
        /// 2024-06-01.
        /// </summary>
        public static readonly string V2024_06_01 = "2024-06-01";

        /// <summary>
        /// 2024-05-01.
        /// </summary>
        public static readonly string V2024_05_01 = "2024-05-01";
    }

    // ── FromExisting ────────────────────────────────────────────────

    /// <summary>
    /// Creates a reference to an existing AppConfigurationStore.
    /// </summary>
    public static AppConfigurationStore FromExisting(string bicepIdentifier, string? resourceVersion = default) =>
        new(bicepIdentifier, resourceVersion) { IsExistingResource = true };

    // ── Naming requirements ─────────────────────────────────────────

    /// <summary>
    /// Get the requirements for naming this AppConfigurationStore resource.
    /// </summary>
    [EditorBrowsable(EditorBrowsableState.Never)]
    public override ResourceNameRequirements GetResourceNameRequirements() =>
        new(minLength: 5, maxLength: 50, validCharacters:
            ResourceNameCharacters.LowercaseLetters |
            ResourceNameCharacters.UppercaseLetters |
            ResourceNameCharacters.Numbers |
            ResourceNameCharacters.Hyphen);
}

Property Categories

Properties fall into distinct categories based on their Bicep behavior:

CategoryWrapperHas Setter?DefineProperty flagsExample
Scalar inputBicepValue<T>Yes (.Assign())Name, Location, DisableLocalAuth
Required scalarBicepValue<T>Yes (.Assign())isRequired: trueName, Location
Enum inputBicepValue<TEnum>Yes (.Assign())PublicNetworkAccess, CreateMode
Model inputTModel (construct)Yes (AssignOrReplace)via DefineModelPropertyEncryptionKeyVaultProperties
Dictionary inputBicepDictionary<T>Yes (.Assign())via DefineDictionaryPropertyTags
List inputBicepList<T>Yes (.Assign())via DefineListProperty
Scalar outputBicepValue<T>No (read-only)isOutput: trueId, Endpoint, ProvisioningState
Model outputTModel (construct)No (read-only)isOutput: true via DefineModelPropertySystemData
List outputBicepList<T>No (read-only)isOutput: true via DefineListPropertyPrivateEndpointConnections
SecureBicepValue<T>VariesisSecure: truepassword / key properties

Bicep Path Mapping

The bicepPath parameter in DefineProperty maps the C# property to the Bicep resource schema hierarchy:

Bicep schema                           C# DefineProperty bicepPath
─────────────                          ──────────────────────────
name                                   ["name"]
location                               ["location"]
tags                                   ["tags"]
sku.name                               ["sku", "name"]
identity                               ["identity"]
properties.createMode                  ["properties", "createMode"]
properties.disableLocalAuth            ["properties", "disableLocalAuth"]
properties.encryption.keyVaultProperties  ["properties", "encryption", "keyVaultProperties"]
properties.provisioningState           ["properties", "provisioningState"]

The path directly corresponds to the nesting in the Bicep resource definition. See the Bicep resource template reference for the canonical schema of each resource type.

Property Getter/Setter Pattern

Input property (writable):

csharp
public BicepValue<T> PropertyName
{
    get { Initialize(); return _propertyName!; }
    set { Initialize(); _propertyName!.Assign(value); }
}
private BicepValue<T>? _propertyName;

Output property (read-only):

csharp
public BicepValue<T> PropertyName
{
    get { Initialize(); return _propertyName!; }
}
private BicepValue<T>? _propertyName;

Model property (nested construct):

csharp
// Input model
public ModelType PropertyName
{
    get { Initialize(); return _propertyName!; }
    set { Initialize(); AssignOrReplace(ref _propertyName, value); }
}
private ModelType? _propertyName;

// Output model
public ModelType PropertyName
{
    get { Initialize(); return _propertyName!; }
}
private ModelType? _propertyName;

Constructor Pattern

csharp
public ResourceTypeName(string bicepIdentifier, string? resourceVersion = default)
    : base(bicepIdentifier, "Microsoft.Provider/resourceTypes", resourceVersion ?? "YYYY-MM-DD")
{
}
  • bicepIdentifier: The Bicep symbolic name (used in resource <name> 'type@version')
  • Second parameter: The ARM resource type string (e.g., "Microsoft.AppConfiguration/configurationStores")
  • Third parameter: Default to the latest GA API version

ResourceVersions Nested Class

Lists all supported GA-only API versions (no previews) in reverse chronological order:

csharp
public static class ResourceVersions
{
    public static readonly string V2024_06_01 = "2024-06-01";
    public static readonly string V2024_05_01 = "2024-05-01";
    public static readonly string V2023_03_01 = "2023-03-01";
}

4. Model Types (ProvisionableConstruct)

Structure

A model class:

  • Extends ProvisionableConstruct
  • Is public partial class
  • Has a parameterless public constructor
  • Overrides DefineProvisionableProperties() to declare properties
  • Uses the same property patterns as resource types (BicepValue<T>, Initialize(), Assign())

Example: Model Class

csharp
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// <auto-generated/>

#nullable enable

using Azure.Provisioning.Primitives;

namespace Azure.Provisioning.AppConfiguration;

/// <summary>
/// Settings concerning key vault encryption for a configuration store.
/// </summary>
public partial class AppConfigurationKeyVaultProperties : ProvisionableConstruct
{
    /// <summary>
    /// The URI of the key vault key used to encrypt data.
    /// </summary>
    public BicepValue<string> KeyIdentifier
    {
        get { Initialize(); return _keyIdentifier!; }
        set { Initialize(); _keyIdentifier!.Assign(value); }
    }
    private BicepValue<string>? _keyIdentifier;

    /// <summary>
    /// The client id of the identity which will be used to access key vault.
    /// </summary>
    public BicepValue<string> IdentityClientId
    {
        get { Initialize(); return _identityClientId!; }
        set { Initialize(); _identityClientId!.Assign(value); }
    }
    private BicepValue<string>? _identityClientId;

    /// <summary>
    /// Creates a new AppConfigurationKeyVaultProperties.
    /// </summary>
    public AppConfigurationKeyVaultProperties()
    {
    }

    /// <summary>
    /// Define all the provisionable properties of AppConfigurationKeyVaultProperties.
    /// </summary>
    protected override void DefineProvisionableProperties()
    {
        base.DefineProvisionableProperties();
        _keyIdentifier = DefineProperty<string>("KeyIdentifier", ["keyIdentifier"]);
        _identityClientId = DefineProperty<string>("IdentityClientId", ["identityClientId"]);
    }
}

Key Differences from Resource Types

AspectResource (ProvisionableResource)Model (ProvisionableConstruct)
Base classProvisionableResourceProvisionableConstruct
ConstructorTakes bicepIdentifier + resourceVersionParameterless
ARM type stringPassed to base ctorN/A
ResourceVersionsYes (nested class)No
FromExisting()YesNo
bicepPathFull path from root (e.g., ["properties", "encryption", "keyVaultProperties"])Relative path within parent (e.g., ["keyIdentifier"])

Bicep Path in Models

Model properties use relative paths — they represent the property path within the model object, not the full path from the resource root. The parent resource's DefineModelProperty establishes the prefix:

Resource:  _kvProps = DefineModelProperty<KVProperties>(..., ["properties", "encryption", "keyVaultProperties"]);
Model:     _keyIdentifier = DefineProperty<string>("KeyIdentifier", ["keyIdentifier"]);

Full Bicep path → properties.encryption.keyVaultProperties.keyIdentifier

5. Enum Types

Simple Enum

csharp
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.

// <auto-generated/>

namespace Azure.Provisioning.AppConfiguration;

/// <summary>
/// Indicates whether the configuration store need to be recovered.
/// </summary>
public enum AppConfigurationCreateMode
{
    /// <summary>
    /// Recover.
    /// </summary>
    Recover,

    /// <summary>
    /// Default.
    /// </summary>
    Default,
}

Enum with Serialization Mapping

When the Bicep/ARM value doesn't match a valid C# identifier, use [DataMember(Name = "...")]:

csharp
using System.Runtime.Serialization;

namespace Azure.Provisioning.AppConfiguration;

public enum DataPlaneProxyAuthenticationMode
{
    Local,

    [DataMember(Name = "Pass-through")]
    PassThrough,
}

Naming Convention

Enums are prefixed with the service name to avoid collisions across provisioning libraries:

  • AppConfigurationCreateMode (not CreateMode)
  • AppConfigurationProvisioningState (not ProvisioningState)
  • AppConfigurationPublicNetworkAccess (not PublicNetworkAccess)

6. Built-in Roles

csharp
public readonly partial struct AppConfigurationBuiltInRole : IEquatable<AppConfigurationBuiltInRole>
{
    private readonly string _value;

    private AppConfigurationBuiltInRole(string value) { _value = value; }

    /// <summary>
    /// Full access to App Configuration data.
    /// </summary>
    public static AppConfigurationBuiltInRole AppConfigurationDataOwner { get; } =
        new("5ae67dd6-50cb-40e7-96ff-dc2bfa4b606b");

    // ... more roles ...

    public static bool operator ==(AppConfigurationBuiltInRole left, AppConfigurationBuiltInRole right) =>
        left.Equals(right);
    public static bool operator !=(AppConfigurationBuiltInRole left, AppConfigurationBuiltInRole right) =>
        !left.Equals(right);
    public static implicit operator AppConfigurationBuiltInRole(string value) => new(value);

    public bool Equals(AppConfigurationBuiltInRole other) => string.Equals(_value, other._value, ...);
    public override bool Equals(object obj) => obj is AppConfigurationBuiltInRole other && Equals(other);
    public override int GetHashCode() => _value?.GetHashCode() ?? 0;
    public override string ToString() => _value;

    internal static string GetBuiltInRoleName(AppConfigurationBuiltInRole value) =>
        value._value switch { "5ae67dd6..." => "AppConfigurationDataOwner", ... };
}

7. Generator Capabilities

The generator produces provisioning types with the following features:

AspectDetails
Base classProvisionableResource for resources, ProvisionableConstruct for models
PropertiesBicepValue<T>, BicepList<T>, BicepDictionary<T> wrappers
Constructor(string bicepIdentifier, string? resourceVersion) with base ctor
DefineProvisionableProperties()Correct bicep path mapping
ResourceVersionsNested class with GA API version constants
FromExisting()Static factory method
No serialization filesSerialization providers suppressed
Post-processor pruningUnreferenced models/enums auto-removed
Property flatteningDecorator-driven via @flattenProperty
DiscriminatorPolymorphic type hierarchies for resources and models
Flat namespaceAll types in Azure.Provisioning.{ServiceName} without .Models
Parent propertyTyped Parent property for child resources
Property namingNames resolved through mgmt visitor pipeline (e.g., NameVisitor) via CreatePropertyCore

8. Property Flattening

ARM resources often have a properties bag that contains most user-settable fields. In earlier provisioning libraries, these were always flattened into the resource class directly. The new generator only flattens when the @flattenProperty decorator is present in the TypeSpec definition.

With @flattenProperty:

ARM JSON Schema:                     Provisioning Resource:
{                                    class AppConfigurationStore
  "name": "...",                         Name           → ["name"]
  "location": "...",                     Location       → ["location"]
  "properties": {                        DisableLocalAuth → ["properties", "disableLocalAuth"]
    "disableLocalAuth": true,            SoftDeleteRetentionInDays → ["properties", "softDeleteRetentionInDays"]
    "softDeleteRetentionInDays": 7,
  }
}

Without @flattenProperty (default):

ARM JSON Schema:                     Provisioning Resource:
{                                    class ConfigurationStoreData
  "name": "...",                         Name           → ["name"]
  "location": "...",                     Location       → ["location"]
  "properties": { ... }                  Properties     → ["properties"]  (model type)
}

Whether or not @flattenProperty is present depends on the TypeSpec definition. The generator does not hardcode any flattening — it is purely decorator-driven.


9. Generated File Layout

Azure.Provisioning.AppConfiguration/
└── src/
    └── Generated/
        ├── AppConfigurationStore.cs                    # Resource
        ├── AppConfigurationKeyValue.cs                 # Child resource
        ├── AppConfigurationReplica.cs                  # Child resource
        ├── AppConfigurationSnapshot.cs                 # Child resource
        ├── AppConfigurationPrivateEndpointConnection.cs # Child resource
        ├── AppConfigurationBuiltInRole.cs              # Role definitions
        ├── AppConfigurationCreateMode.cs               # Enum
        ├── AppConfigurationProvisioningState.cs         # Enum
        ├── AppConfigurationPublicNetworkAccess.cs       # Enum
        ├── DataPlaneProxyAuthenticationMode.cs          # Enum
        ├── AppConfigurationKeyVaultProperties.cs        # Model (construct)
        ├── AppConfigurationDataPlaneProxyProperties.cs  # Model (construct)
        ├── AppConfigurationStoreApiKey.cs               # Model (construct)
        └── SnapshotKeyValueFilter.cs                    # Model (construct)
  • All types go in the Generated/ root (flat namespace, no Models/ subfolder)
  • All types share the same namespace: Azure.Provisioning.{ServiceName}
  • No .Serialization.cs files — serialization is handled by DefineProvisionableProperties()
  • No Internal/ directory — no internal helper types needed