Cadl Azure Resource Manager Library
This is a library that provides model types and interfaces which can be used to define an Azure Resource Manager service API.
Table of Contents
Getting Started
To author an Azure Resource Manager service definition, you will first need to create a Cadl project for your service. The easiest way to do that is to follow the instructions on this page:
Concepts
Before using this library, you should familiarize yourself with the Cadl language and tools. You can do this by reading the Cadl tutorial.
Defining the Service
To define an Azure Resource Manager service, the first thing you will need to do is define the service namespace and decorate it with the serviceTitle
, serviceVersion
and armProviderNamespace
decorators:
@armProviderNamespace
@service({title: "<service name>", version: "<service version>"})
namespace <mynamespace>;
For example:
@armProviderNamespace
@service({
title: "Contoso User Service",
version: "2020-10-01-preview",
})
namespace Contoso.Users;
using
keyword
The Just after the namespace
declaration, you will also need to include a few using
statements to pull in symbols from the namespaces of libraries you will for your specification.
For example, these lines pull in symbols from the @cadl-lang/rest
and @azure-tools/cadl-azure-resource-manager
:
using Cadl.Http;
using Cadl.Rest;
using Azure.ResourceManager;
Defining Resource Types
A resource provider is composed of resources. The Cadl Azure Resource Manager library makes it much easier to define the structure and endpoints of such resources.
There are three essential components of a resource defined with Cadl:
- A model type representing the resource, derived from one of the base resource types
- A model type defining the properties of the resource type
- An interface that defines the operations that can be performed on the resource type, usually a combination of standard resource operations and custom actions
Read the Cadl tutorial to learn the basics about Cadl model types and interfaces.
properties
of the ARM resource
1. Define a model representing the Each resource type must have a properties type which defines its custom properties. This type will be exposed as the properties
property of the resource type.
@doc("The properties of UserResource")
model UserResourceProperties {
@doc("The user's full name")
fullName: string;
@doc("The user's email address.")
emailAddress: string;
}
2. Define a model representing the resource type
Resource types are defined as plain models which pull in a standard resource type using the is
keyword.
You define a resource type, you need the following:
- A
name
property which is marked with the following decorators-
@key
: Specifies the parameter name for this resource type in the service URI hierarchy -
@segment
: Specifies the name of the resource "collection", the URI segment that comes just before the parameter name which identifies the resource type
-
- A second model type which defines the resource type's custom properties as we described in step 1
Here we define a tracked resource called UserResource
:
@doc("A UserResource")
model UserResource is TrackedResource<UserResourceProperties> {
@key("userName")
@segment("users")
name: string;
}
3. Define an interface with operations for the resource type
@armResourceOperations
interface Users extends TrackedResourceOperations<UserResource> {}
This will now produce all the endpoints(get
, post
, put
, patch
and delete
, listByResourceGroup, listBySubscription) for a resource called UserResources
and the operations
endpoint for the service:
Method & Path | Description |
---|---|
GET /providers/Contoso.Users/operations |
List all operations for your service |
GET /subscriptions/{subscriptionId}/providers/Contoso.Users/users |
list all UserResource by subscription |
GET /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Contoso.Users/users |
list all UserResource by resource group |
GET /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Contoso.Users/users/{userName} |
get item |
PUT /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Contoso.Users/users/{userName} |
insert item |
PATCH /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Contoso.Users/users/{userName} |
patch item |
DELETE /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Contoso.Users/users/{userName} |
delete item |
Base Resource Types
Here are the base resource types you can use when defining your own ARM resources:
Name | Description |
---|---|
TrackedResource | Defines a normal ARM resource where TProperties is the model of the properties
|
ProxyResource | Defines a proxy ARM resource where TProperties is the model of the properties
|
ExtensionResource | Defines an extension ARM resource where TProperties is the model of the properties
|
Defining Child Resource Types
You can create parent/child relationships between resource types by using the @parentResource
decorator when defining a resource type.
For example, here's how you could create a new AddressResource
resource under the UserResource
defined above:
@doc("An address resource belonging to a user resource.")
@parentResource(UserResource)
model AddressResource is ProxyResource<AddressResourceProperties> {
@key("addressName")
@segment("addresses")
name: string;
}
@doc("The properties of AddressResource")
model AddressResourceProperties {
@doc("The street address.")
streetAddress: string;
@doc("The city of the address.")
city: string;
@doc("The state of the address.")
state: string;
@doc("The zip code of the address.")
zip: int32;
}
@armResourceOperations
interface Addresses extends ProxyResourceOperations<AddressResource> {}
Defining Custom Actions
Some resources will provide more than the standard CRUD operations and will need to define a custom action endpoint. Additional resource operations can be added to the interface
where you pulled in standard resource operations.
For example, to add an additional POST
action called /notify
to the standard operations of UserResource
:
@doc("The details of a user notification.")
model NotificationDetails {
@doc("The notification message.")
message: string;
@doc("If true, the notification is urgent.")
urgent: boolean;
}
@armResourceOperations
interface Users extends TrackedResourceOperations<UserResource, UserResourceProperties> {
@post
@doc("Send a notification to the user")
@segment("notify")
NotifyUser(
...ResourceInstanceParameters<UserResource>,
@body notification: NotificationDetails
): ArmResponse<string> | ErrorResponse;
}
ARM Response Types
Custom operations in ARM still need to respect the correct response schema. This library provides standard ARM response types to help with reusability and compliance.
Model | Code | Description |
---|---|---|
ArmResponse<T> |
200 | Base Arm 200 response. |
ArmCreatedResponse<T> |
201 | Resource created response |
ArmDeletedResponse |
200 | Resource deleted response |
ArmDeleteAcceptedResponse |
202 | Resource deletion in progress response |
ArmDeletedNoContentResponse |
204 | Resource deleted response |
Page<T> |
200 | Return a list of resource with ARM pagination |
ErrorResponse<T> |
x | Error response |
Common Operation Parameters
There are a number of model types which specify common parameters which are used in resource type operations:
Model | In | Description |
---|---|---|
ApiVersionParameter |
query |
api-version parameter |
SubscriptionIdParameter |
path | Subscription ID path parameter |
ResourceGroupNameParameter |
path | Resource Group Name path parameter |
CommonResourceParameters |
path & query | Group of Api version, Subscription ID and Resource group parameter |
ResourceUriParameter |
path | Resource uri path parameter |
OperationIdParameter |
path | Operation Id path parameter |
Generating an OpenAPI Specification
To generate an OpenAPI v2 (Swagger) specification from the service definition, run the following command inside of the project folder:
cadl compile . --emit @azure-tools/cadl-autorest
This will create a file in the cadl-output
subfolder called openapi.json
.
A Complete Example
Here's a complete example main.cadl
file based on all of the snippets in this README:
import "@cadl-lang/rest";
import "@cadl-lang/versioning";
import "@azure-tools/cadl-providerhub";
import "@azure-tools/cadl-azure-core";
import "@azure-tools/cadl-azure-resource-manager";
using Cadl.Http;
using Cadl.Rest;
using Cadl.Versioning;
using Azure.Core;
using Azure.ResourceManager;
@armProviderNamespace
@service({
title: "ContosoProviderHubClient",
version: "2021-01-01-preview",
})
@doc("Contoso Resource Provider management API.")
@versionedDependency(Azure.ResourceManager.Versions.v1_0_Preview_1)
namespace Microsoft.ContosoProviderHub;
interface Operations extends Azure.ResourceManager.Operations {}
@lroStatus
enum ProvisioningState {
...ResourceProvisioningState,
Provisioning,
Updating,
Deleting,
Accepted,
}
@doc("The properties of UserResource")
model UserResourceProperties {
@doc("The user's full name")
fullName: string;
@doc("The user's email address.")
emailAddress: string;
@doc("The status of the last operation.")
provisioningState?: ProvisioningState;
}
@doc("A UserResource")
model UserResource is TrackedResource<UserResourceProperties> {
@key("userName")
@segment("users")
@doc("Address name")
@path
name: string;
}
@doc("The details of a user notification.")
model NotificationDetails {
@doc("The notification message.")
message: string;
@doc("If true, the notification is urgent.")
urgent: boolean;
}
@armResourceOperations
interface Users extends TrackedResourceOperations<UserResource, UserResourceProperties> {
@post
@doc("Send a notification to the user")
@segment("notify")
notifyUser(
...ResourceInstanceParameters<UserResource>,
@body notification: NotificationDetails
): ArmResponse<string> | ErrorResponse;
}
@doc("An address resource belonging to a user resource.")
@parentResource(UserResource)
model AddressResource is ProxyResource<AddressResourceProperties> {
@doc("Address name")
@key("addressName")
@segment("addresses")
@path
name: string;
}
@doc("The properties of AddressResource")
model AddressResourceProperties {
@doc("The street address.")
streetAddress: string;
@doc("The city of the address.")
city: string;
@doc("The state of the address.")
state: string;
@doc("The zip code of the address.")
zip: int32;
@doc("The status of the last operation.")
provisioningState?: ProvisioningState;
}
@armResourceOperations
interface Addresses extends ProxyResourceOperations<AddressResource> {}