- Understanding the Resource Hierarchy in Management Plane SDK
- How does the Generator Generate the Hierarchies
- Management Plane Configurations
- Change the Resource Data
- Change the Resource Name
- Change the Criteria of ARM Resources
- Change the Resource Type
- Mark a Request Path is a Non-Resource
- Change Parent of Request Paths
- Change Singleton Resources
- Change the Name of Operations
- Mark Privileged Operations
- List Exception
- Partial Resources
- Scope Resources
- SDK Polishing Configurations
- Management Debug Options
For the basic management plane SDK concepts, please refer the detailed document here first.
A resource is usually represented by three classes in the generated SDK: a [Resource]Data
class, a [Resource]Resource
class, and a [Resource]Collection
class.
This class represents the "model" of the resource, and it corresponds to a model definition in the swagger, usually with the same name by trimming the word Data
off.
This class represents the a full resource client object which contains a Data
property exposing the details as a [Resource]Data
type.
This class contains operations for a resource collection that belongs to a specific parent resource. It provides most of the logical collection operations.
Collection Behavior | Collection Method |
---|---|
Iterate/List | GetAll() |
Index | Get(string name) |
Add | CreateOrUpdate(string name, [Resource]Data data) |
Contains | Exists(string name) |
TryGet | GetIfExists(string name) |
The [Resource]Collection
class is designed to implement IEnumerable<[Resource]Resource>
and IAsyncEnumerable<[Resource]Resource>
interfaces.
The implementation method IEnumerable<[Resource]Resource>.GetEnumerator()
calls GetAll().GetEnumerator()
, therefore all the [Resource]Collection
class must have a GetAll()
method with no required parameters, otherwise the generator throws an exception complaining about this.
public partial class AvailabilitySetCollection : ArmCollection, IEnumerable<AvailabilitySetResource>, IAsyncEnumerable<AvailabilitySetResource>
{
IEnumerator<AvailabilitySetResource> IEnumerable<AvailabilitySetResource>.GetEnumerator()
{
return GetAll().GetEnumerator();
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetAll().GetEnumerator();
}
IAsyncEnumerator<AvailabilitySetResource> IAsyncEnumerable<AvailabilitySetResource>.GetAsyncEnumerator(CancellationToken cancellationToken)
{
return GetAllAsync(cancellationToken: cancellationToken).GetAsyncEnumerator(cancellationToken);
}
}
In addition, ARM has several rules about "List" operations of resources:
- R4014 NestedResourcesMustHaveListOperation
- R4016 TopLevelResourcesListByResourceGroup
- R4017 TopLevelResourcesListBySubscription
In rare cases, this rule is violated and a list-exception
configuration is required to temporarily solve this. Please see List exception section for details.
The parent resource of this resource carries all the methods to get its child resources by returning its corresponding collection. For instance, on the resource of VirtualNetworkResource
, you should find a method GetSubnets
which returns a SubnetCollection
to represent a collection of SubnetResource
:
public partial class VirtualNetworkResource : ArmResource
{
public virtual SubnetCollection GetSubnets()
{
/* ... */
}
}
For the cases that the parent resource is not in the same RP, the "get child resource method" is put in an extension class. For instance, the parent resource of VirtualNetworkResource
is the resource group, and you should find an extension method of ResourceGroupResource
in the Azure.ResourceManager.Network
package returning VirtualNetworkCollection
like this:
public static partial class NetworkExtensions
{
public static VirtualNetworkCollection GetVirtualNetworks(this ResourceGroupResource resourceGroup)
{
/* ... */
}
}
The management .Net SDK generates the resources with hierarchy, therefore the code generator makes quite a few modification on the code model input from the modelerfour
. To better recognize the hierarchical structure of resources, the code generator generates everything from the point of view of request paths, instead of operation groups. Therefore quite a few new configurations are introduced to tweak the behavior how the generator identifies the resource classes as well as the hierarchy.
The management generator first goes through all the operations and categorize them by resources, then calculate hierarchical structures on them.
The management generator does not categorize the operations by operation groups, instead, it categorizes the operations by "operation sets". An operation set is a set of operations with the same request path. For instance, all the operations under the request path /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{name}
assemble into an operation set.
An operation set is marked as a resource, only when the following conditions meet:
- The request path has even segments starting from the last
providers
segment. For instance,/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{name}
meets this requirement, but/{scope}/providers/Microsoft.Authorization/policyAssignments
does not. - The operation set must have a GET request.
- The schema of
200
response of the GET request is compatible to ARM's resource definition - it must haveid
,type
andname
(all of them are strings).
If all the above conditions are met, this operation set is marked as a "resource" by management generator, and the schema of the 200
response of the GET request is its corresponding ResourceData
.
There are multiple ways to tweak the criteria of identifying resources, please see change the criteria of ARM resources and change resource data section.
The name of a resource usually is derived from the name of the resource data, but with some exceptions. Please see change resource name for more details.
If a request path is incorrectly marked as a resource, you could use a configuration to make it non-resource again. Please see mark a request path is non-resource section.
After the management generator goes through all the resources, it is ready to build the hierarchy of resources.
Despite the package Azure.ResourceManager
is also mostly generated by the same generator, the generator has 5 built-in resources as parent fallbacks when the parent of a resource is not in this RP (The most common case of this is the ResourceGroup
which is a parent of a lot of resources):
Azure.ResourceManager.Tenant
Azure.ResourceManager.Subscription
Azure.ResourceManager.ResourceGroup
Azure.ResourceManager.ManagementGroup
Azure.ResourceManager.ArmResource
For each operation set, the management generator tries to find its parent, by going through all the existing resources to see if the request path of another resource is the prefix of this operation set. After going through all of the resources, we should get a list of candidates of resources that can be the parent or grand parent of this operation set, it takes the longest path as its direct parent. If none found, the generator tries the 5 built-in resources in this order:
ResourceGroup
Subscription
ManagementGroup
ArmResource
Tenant
Please note that the path ofTenant
is/
which makes it could be a parent of any resource.
Now every operation set (and it corresponds to a resource) has a parent and only has one parent, the resource hierarchy is built.
There is a way to change the hierarchical structure of resources by the configuration, please see change the parent of request paths section.
Every [Resource]Resource
class has a public static field for its resource type, like:
public partial class AvailabilitySetResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Compute/availabilitySets";
}
Once an operation set is marked as a resource, the generator is able to determine its resource type. In general, the resource type of a resource is calculated in this way:
- Take everything after the last
providers
segment. Anything before the lastproviders
segment is regarded as thescope
of this resource. For instance, for request path/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/hostGroups/{groupName}/hosts/{hostName}
, we getproviders/Microsoft.Compute/hostGroups/{groupName}/hosts/{hostName}
- Take the provider namespace as the namespace of the resource type, in this case, we get
Microsoft.Compute
, and we havehostGroups/{groupName}/hosts/{hostName}
left. - Take all the segments with even indices in whatever left in last step. In this case, we get
hostGroups
andhosts
. - Put them all together and we get the resource type of this resource,
Microsoft.Compute/hostGroups/hosts
.
In the design of .Net management SDK, one resource must only have one constant resource type, therefore the generator checks if the resource type which it calculates is a constant, if the resource type is not a constant, it means that the swagger might be using one request path to represent a set of similar resources with different resource types. If this happens, the generator tries to expand the resource types into multiple constant resource types.
For instance, if the above procedure is applied on this request path /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/dnsZones/{zoneName}/{recordType}/{relativeRecordSetName}
, it produces the resource type Microsoft.Network/dnsZones/{recordType}
which contains a variable in it and is not a constant. If this happens, the generator tries to expand it into multiple constant resource types. This requires the variables in the resulted resource type to be an enum type, if this condition is not met, the generator throws exceptions for you to resolve by adding configuration/directives.
We have some ways to change the resource type of a resource, please see change the resource type section.
Some resources are recognized as singleton resources automatically by the generator, for instance, a request path /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccount/{accountName}/blobServices/default
is a singleton resource with the name default
and its parent is StorageAccountResource
.
The generator marks a resource as a singleton, when its request path subtracted by the request path of its parent is a constant. For example, the resource with request path /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccount/{accountName}/blobServices/default
has a parent of /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccount/{accountName}
, if you subtract them, you should get blobServices/default
which is a constant, therefore the resource BlobServiceResource
is a singleton resource.
When a resource is marked as a singleton resource, it does not have its corresponding [Resource]Collection
class, only has the [Resource]Resource
class and the [Resource]Data
class. The method to get a singleton resource on its parent is called as Get[Resource]
in singular form, like
public partial class StorageAccountResource : ArmResource
{
public virtual BlobServiceResource GetBlobService()
{
/* ... */
}
}
If you want to change the behavior how the generator identifies singleton resources, please see change singleton resources section.
The configurations for management plane generator should be written in the readme.md
file or autorest.md
file that generates the SDK. You could take this file as an example.
The configuration request-path-to-resource-data
is a dictionary, its key is a request path, and the corresponding value is a schema name.
This configuration has two functionalities:
- If the request path is not a resource, the generator marks it as a resource.
- The corresponding resource data is set to be the schema specified by the value.
For instance, if we have this swagger:
"/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/{accountName}": {
"get": {
"operationId": "DatabaseAccounts_Get",
"parameters": [
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/SubscriptionIdParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/ResourceGroupNameParameter"
},
{
"$ref": "#/parameters/accountNameParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/ApiVersionParameter"
}
],
"responses": {
"200": {
"description": "The database account properties were retrieved successfully.",
"schema": {
"$ref": "#/definitions/DatabaseAccount"
}
}
}
},
"put": {
"operationId": "DatabaseAccounts_CreateOrUpdate",
"x-ms-long-running-operation": true,
"parameters": [
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/SubscriptionIdParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/ResourceGroupNameParameter"
},
{
"$ref": "#/parameters/accountNameParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/ApiVersionParameter"
},
{
"name": "createUpdateParameters",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/DatabaseAccountCreateUpdateParameters"
},
"description": "The parameters to provide for the current database account."
}
],
"responses": {
"200": {
"description": "The database account create or update operation will complete asynchronously.",
"schema": {
"$ref": "#/definitions/DatabaseAccount"
}
}
}
},
"delete": {
"operationId": "DatabaseAccounts_Delete",
"x-ms-long-running-operation": true,
"parameters": [
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/SubscriptionIdParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/ResourceGroupNameParameter"
},
{
"$ref": "#/parameters/accountNameParameter"
},
{
"$ref": "../../../../../common-types/resource-management/v1/types.json#/parameters/ApiVersionParameter"
}
],
"responses": {
"202": {
"description": "The database account delete operation will complete asynchronously."
},
"204": {
"description": "The specified account does not exist in the subscription."
}
}
}
}
The generated code has a resource DatabaseAccountResource
, DatabaseAccountCollection
, and DatabaseAccountData
. And if you want to assign another schema in these request as a resource data, you could add this configuration:
request-path-to-resource-data:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.DocumentDB/databaseAccounts/{accountName}: DatabaseAccountCreateUpdateParameters
As a result, the resource now names after the new resource data, and now you should have DatabaseAccountCreateUpdateParametersResource
, DatabaseAccountCreateUpdateParametersCollection
, and DatabaseAccountCreateUpdateParametersData
.
You can mark a non-resource request path as a resource, for instance, if we have this swagger:
"/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/sharedGalleries/{galleryUniqueName}": {
"get": {
"operationId": "SharedGalleries_Get",
"description": "Get a shared gallery by subscription id or tenant id.",
"parameters": [
{
"$ref": "#/parameters/SubscriptionIdParameter"
},
{
"$ref": "#/parameters/LocationNameParameter"
},
{
"name": "galleryUniqueName",
"in": "path",
"required": true,
"type": "string",
"description": "The unique name of the Shared Gallery."
},
{
"$ref": "#/parameters/ApiVersionParameter"
}
],
"responses": {
"200": {
"description": "OK",
"schema": {
"$ref": "#/definitions/SharedGallery"
}
},
"default": {
"description": "Error response describing why the operation failed.",
"schema": {
"$ref": "#/definitions/CloudError"
}
}
}
}
This request path looks like a resource, but unfortunately its corresponding schema SharedGallery
does not have all the three properties required by a resource, id
, type
and name
. Since this is a non-resource, this operation is generated into the extension class, like this:
public static partial class ComputeExtensions
{
public static Response<SharedGallery> GetSharedGallery(this ResourceGroupResource resourceGroup, string location, string galleryUniqueName, CancellationToken cancellationToken = default)
{
/* ... */
}
}
But we still could add the request-path-to-resource-data
configuration:
request-path-to-resource-data:
/subscriptions/{subscriptionId}/providers/Microsoft.Compute/locations/{location}/sharedGalleries/{galleryUniqueName}: SharedGallery
to mark it as a resource, and assign the schema SharedGallery
as its resource data. In this way, you should get the SharedGalleryResource
, SharedGalleryCollection
and SharedGalleryData
(which is the model generated from schema SharedGallery
) like normal resources. In this way, the Id of this non-resource is automatically constructed by the SDK using its own request path.
In most cases, a resource class shares the same name as the resource data class, and you should have a triplet of [Resource]Resource
, [Resource]Collection
, and [Resource]Data
.
But if the RP is complicated, there might be multiple resources sharing the same resource data schema. If this happens, the generator has multiple strategies to generate unique names for all those resources in order to make sure the generated code could properly compile. These auto-generated names might not be ideal, and you might need the request-path-to-resource-name
configuration to customize the resource names.
For instance, we have a resource AvailabilitySetResource
, AvailabilitySetCollection
and AvailabilitySetData
. If we add this configuration:
request-path-to-resource-name:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{name}: Something
Now you should have SomethingResource
instead of AvailabilitySetResource
, SomethingCollection
instead of AvailabilitySetCollection
, but still have the original resource data AvailabilitySetData
because this configuration only change the corresponding resource name and the collection name (if it has a collection).
By default, a resource requires its schema to meet ARM's resource criteria that the model must have id
, type
and name
. You can use the resource-model-requires-type
and resource-model-requires-name
configuration to loose this criteria.
For instance
resource-model-requires-type: false
This configuration globally changes the criteria of a resource model to only require id
and name
.
The resource type is usually automatically calculated by its request path, but there are situations that the resource type cannot be calculated or not correctly calculated. In this case, you need to use the request-path-to-resource-type
configuration to assign a resource type to the resource.
For instance, if we have the AvailabilitySetResource
class
public partial class AvailabilitySetResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Compute/availabilitySets";
}
and if we have this configuration
request-path-to-resource-type:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{name}: Microsoft.Compute/availabilitysets
Then you should have the following changes in AvailabilitySetResource
class
public partial class AvailabilitySetResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
- public static readonly ResourceType ResourceType = "Microsoft.Compute/availabilitySets";
+ public static readonly ResourceType ResourceType = "Microsoft.Compute/availabilitysets";
}
There are also some special request paths which is not possible to derive resource type from, for instance, /{linkId}
. When this happens, the generator throws exception and require the user to provide a request-path-to-resource-type
for it, or mark it as a non-resource using request-path-is-non-resource
configuration. Please see mark a request path is non-resource section.
By default the generator marks all the request paths that meet the criteria as resources. If there is a path you suppose it should be pure data instead of an ARM resource, you can use the request-path-is-non-resource
configuration.
For instance,
request-path-is-non-resource:
- /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{name}
This configuration marks the availability set request paths as a non-resource, therefore the operations of non-resources append to their corresponding parent.
By default, the generator automatically finds the parent of a request path, and append the operations of a non-resource to their corresponding parents.
You can manually assign the parent of an operation using the configuration request-path-to-parent
which is a dictionary from the request path of you want to change, to the request path you want to be the new parent.
For instance, Subnet
is the child resource of VirtualNetwork
, and VirtualNetwork
is the child resource of ResourceGroupResource
, you can easily see this in their paths:
- Subnet:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}
- VirtualNetwork:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{vnetName}
And you should have these in the generated code:
public partial static class NetworkExtensions
{
public static VirtualNetworkCollection GetVirtualNetworks()
{
/* ... */
}
}
public partial class VirtualNetworkResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Network/virtualNetworks";
public virtual SubnetCollection GetSubnets()
{
/* ... */
}
}
public partial class SubnetResource: ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Network/virtualNetworks/subnets";
}
if you manually assign the parent of subnet to resource group, using the following configuration:
request-path-to-parent:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Network/virtualNetworks/{vnetName}/subnets/{subnetName}: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}
You should have the following changes which shows that now the parent of resource SubnetResource
is ResourceGroupResource
:
public partial static class NetworkExtensions
{
public static VirtualNetworkCollection GetVirtualNetworks()
{
/* ... */
}
+ public virtual SubnetCollection GetSubnets()
+ {
+ /* ... */
+ }
}
public partial class VirtualNetworkResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Network/virtualNetworks";
- public virtual SubnetCollection GetSubnets()
- {
- /* ... */
- }
}
public partial class SubnetResource: ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Network/virtualNetworks/subnets";
}
Some resources are automatically recognized as singleton resources. If a resource is generated as a singleton resource, it does not have a corresponding collection class.
By default the generator makes a resource as a singleton, if it finds the extra part of its request path comparing to its parent is a constant.
Sometimes, you might need to manually make a resource singleton due to some issues in the swagger, you need to add the corresponding entries into the request-path-to-singleton-resource
configuration. This configuration is a map from the request path to the extra segments of the resource identifier of the singleton resource.
For instance, for some reasons, the resource ManagementPolicy
has the following path
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{accountName}/managementPolicies/{managementPolicyName}
Its name here {managementPolicyName}
is an extensible enum type, despite it only has one value default
right now. This is probably a mistake because it is not really extensible. But the generator cannot identify this because the name of this resource is not a constant, it generates this like a normal resource with a collection, like this:
public partial class StorageAccountResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Storage/storageAccounts";
public virtual ManagementPolicyCollection GetManagementPolicies()
{
return new ManagementPolicyCollection(Client, Id);
}
}
public partial class ManagementPolicyResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Storage/storageAccounts/managementPolicies";
}
public partial class ManagementPolicyCollection : ArmCollection
{
/* ... */
}
if you want to correct this, you could try the following configuration:
request-path-to-singleton-resource:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{accountName}/managementPolicies/{managementPolicyName}: managementPolicies/default
This marks the resource with path of /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Storage/storageAccounts/{accountName}/managementPolicies/{managementPolicyName}
as singleton resource, and its Id
is its parent's Id appending managementPolicies/default
. This produces the following diff:
public partial class StorageAccountResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Storage/storageAccounts";
- public virtual ManagementPolicyCollection GetManagementPolicies()
+ public virtual ManagementPolicyResource GetManagementPolicy()
{
- return new ManagementPolicyCollection(Client, Id);
+ return new ManagementPolicyResource(Client, new ResourceIdentifier(Id.ToString() + "managementPolicies/default"));
}
}
public partial class ManagementPolicyResource : ArmResource
{
/// <summary> Gets the resource type for the operations. </summary>
public static readonly ResourceType ResourceType = "Microsoft.Storage/storageAccounts/managementPolicies";
}
-public partial class ManagementPolicyCollection : ArmCollection
-{
- /* ... */
-}
We provide a new configuration override-operation-name
to assign new names to operations, which is a dictionary from the operationId of the operation to its new name.
For instance, originally, a VirtualMachineResource
has a method Start
which means to power on the machine, to align with the method PowerOff
, we could use the following configuration:
override-operation-name:
VirtualMachines_Start: PowerOn
and get the following changes in the generated code:
public partial class VirtualMachineResource : ArmResource
{
- public virtual async Task<ArmOperation> StartAsync(WaitUntil waitUntil, CancellationToken cancellationToken = default)
+ public virtual async Task<ArmOperation> PowerOnAsync(WaitUntil waitUntil, CancellationToken cancellationToken = default)
}
We provide a new configuration privileged-operations
to mark operations as privileged, which is a dictionary from the operationId
of the operation to the reason why the operation should be marked as privileged
For instance, if the operation with id Vaults_CreateOrUpdate
should be treated as privileged operation, we could use following configuration:
privileged-operations:
Vaults_CreateOrUpdate: The reason or uri about why the operation is privileged
and get the following changes in the generated code:
+[CallerShouldAudit("The reason or uri about why the operation is privileged")]
public virtual async Task<ArmOperation<VaultResource>> CreateOrUpdateAsync(WaitUntil waitUntil, string vaultName, VaultCreateOrUpdateContent content, CancellationToken cancellationToken = default)
...
+[CallerShouldAudit("The reason or uri about why the operation is privileged")]
public virtual ArmOperation<VaultResource> CreateOrUpdate(WaitUntil waitUntil, string vaultName, VaultCreateOrUpdateContent content, CancellationToken cancellationToken = default)
If the generator finds a case that a collection class does not have a GetAll
method, the following error message is thrown:
fatal | The ResourceCollection AvailabilitySetCollection (RequestPath: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{availabilitySetName}) does not have a GetAll method
When this happens, first we need to make sure if the swagger has a corresponding List
operation for your resource. This should be a GET
operation returns a collection of the resource data of your resource.
If there is no such operation, we need to double think if this is really a resource or it should be a singleton resource. You can use the request-path-is-non-resource
configuration to mark it as a non-resource (see Mark a request path is a non-resource section for details), or use the request-path-to-singleton-resource
configuration to mark it as a singleton-resource (see Change singleton resources section for details).
If there is such an operation, but it is not automatically recognized by the generator, you can use the following combination of configurations to put it inside the collection class as a GetAll
method:
request-path-to-parent:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{availabilitySetName}
override-operation-name:
AvailabilitySets_List: GetAll
operation-positions:
AvailabilitySets_List: collection
If all the above efforts are not met, you need to use the list-exception
configuration to temporarily disable this check for this collection class:
list-exception:
- /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/availabilitySets/{availabilitySetName}
Some operations might be operations of a resource from another RP, or some resources might have a parent resource from another RP.
For instance, in the RP compute
, we have a resource VirtualMachine
with the path of /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}
, in the meantime, in the RP network
, which is completely a different SDK, we have an operation with the path of /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}/publicIps
which is clearly listing something on the virtual machine. During the generation of network RP, the generator has no knowledge of the resources in another RP, therefore by default this operation will be generated like the following code as an operation on the ResourceGroupResource
because this is the best parent the generator could find within this RP.
public static partial class NetworkExtensions
{
public static Pageable<PublicIPAddress> GetPublicIPs(this ResourceGroupResource resourceGroup, string vmName)
{
/* the implementation */
}
}
The following configuration
partial-resources:
/subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}: VirtualMachine
will tell the generator that this path /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}
is also a valid resource, and the generator will put it into the list of its known resources, and generates a "partial resource" from it and the operation in above example will show up here:
public partial class VirtualMachineNetworkResource
{
public virtual Pageable<PublicIPAddress> GetPublicIPs()
{
/* the implementation */
}
}
The naming pattern of a partial resource is like [ResourceName][RPName]Resource
, where ResourceName
is the value you assigned in the configuration, RPName
is the name of this RP (for instance, Network
). The name of a partial resource is also configurable using the request-path-to-resource-name
configuration, please see change resource name section for more details.
The difference between a normal resource and a partial resource is that the partial resource is not a concrete resource, therefore it does not have a corresponding collection, and you cannot get the list of it from its direct parent (ResourceGroupResource in the above example).
But like normal resources, partial resources will have an extension method of Get[PartialResourceName]
on the ArmClient
to let you get it from its full Id.
Some resources are scope resources, which means that these resources can be created under different scopes, like subscriptions, resource groups, management groups, etc. In the swagger, we currently do not have an extension which assigns which type of resources can be the scope of this resource, therefore we add a configuration in our generator request-path-to-scope-resource-types
for this new information.
By default, the generator recognizes the first parameter in request path as a scope parameter if the parameter has x-ms-skip-url-encoding: true
on it, and the generator generates the resource assuming the scope can be anything. For instance, if we have a request path like /{scope}/providers/Microsoft.Resources/deployments/{deploymentName}
and you should have a resource generated like this:
public partial class DeploymentResource : ArmResource
{
/* ... */
}
public partial class DeploymentCollection : ArmCollection, IEnumerable<DeploymentResource>, IAsyncEnumerable<DeploymentResource>
{
/* ... */
}
public partial class DeploymentData
{
/* ... */
}
and since it is a scope resource without any configuration, its parent is anything, and you should have the following extension method:
public static partial class ResourcesExtension
{
public static DeploymentCollection GetDeployments(this ArmClient client, ResourceIdentifier scope)
{
/* ... */
}
}
If you would like to generate an extension method of the ArmResource
class for this scope resource, you need to add this configuration generate-arm-resource-extensions
by adding the request path of this scope resource:
generate-arm-resource-extensions:
- /{scope}/providers/Microsoft.Resources/deployments/{deploymentName}
And you will see this change in the generated code:
public static partial class ResourcesExtension
{
public static DeploymentCollection GetDeployments(this ArmClient client, ResourceIdentifier scope)
{
/* ... */
}
+
+ public static DeploymentCollection GetDeployments(this ArmResource armResource)
+ {
+ /* ... */
+ }
}
Please note this extension methods have a huge side effect: because all Resource
class would inherit from ArmResource
, once the user import the namespace of this SDK, they would see this GetDeployments
method on any Resource
instance, while in the real life, this scope resource might not be that general to be applied onto any resource coming from any RP. Please only use this configuration when it could be confirmed to support plenty of resources and has the plan to support more.
To assign specific resource types to this scope, you can use the following configuration:
request-path-to-scope-resource-types:
/{scope}/providers/Microsoft.Resources/deployments/{deploymentName}:
- subscriptions
- resourceGroups
- managementGroups
After we applied this configuration, you should have the following changes:
public static partial class ResourcesExtension
{
- public static DeploymentCollection GetDeployments(this ArmResource armResource)
- {
- /* ... */
- }
+ public static DeploymentCollection GetDeployments(this SubscriptionResource subscription)
+ {
+ /* ... */
+ }
+ public static DeploymentCollection GetDeployments(this ResourceGroupResource resourceGroup)
+ {
+ /* ... */
+ }
+ public static DeploymentCollection GetDeployments(this ManagementGroupResource managementGroup)
+ {
+ /* ... */
+ }
}
In some cases, we might have a resource that extends another resource from another RP. For instance this resource in guestconfiguration
RP: /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}/providers/Microsoft.GuestConfiguration/guestConfigurationAssignments/{guestConfigurationAssignmentName}
extends another resource virtual machine in the compute
RP.
By default, because the generator will never find the VirtualMachineResource
when generating this SDK, the parent resource of this VmGuestConfigurationResource
will be ResourceGroupResource
.
public static partial class GuestConfigurationExtensions
{
public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ResourceGroupResource resourceGroup, string vmName)
{
/* ... */
}
}
To show the relationship between resources across different RPs, we could convert it into a scope resource by using the following configuration:
parameterized-scopes:
- /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}
This configuration registers the listed request paths as resources the generator could recognize even if they might not exist in the current context. After applying this configuration, the generated code will have the following changes:
public static partial class GuestConfigurationExtensions
{
- public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ResourceGroupResource resourceGroup, string vmName)
- {
- /* ... */
- }
+ public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ArmClient client, ResourceIdentifier scope)
+ {
+ if (!scope.ResourceType.Equals("Microsoft.Compute/virtualMachines"))
+ throw new InvalidOperationException(string.Format("Invalid resource type {0} expected Microsoft.Compute/virtualMachines", scope.ResourceType));
+ /* ... */
+ }
}
This configuration works fine with the configuration introduced above:
generate-arm-resource-extensions:
- /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}/providers/Microsoft.GuestConfiguration/guestConfigurationAssignments/{guestConfigurationAssignmentName}
and this is how the generated code would change:
public static partial class GuestConfigurationExtensions
{
public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ArmClient client, ResourceIdentifier scope)
{
if (!scope.ResourceType.Equals("Microsoft.Compute/virtualMachines"))
throw new InvalidOperationException(string.Format("Invalid resource type {0} expected Microsoft.Compute/virtualMachines", scope.ResourceType));
/* ... */
}
+
+ public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ArmResource armResource)
+ {
+ /* ... */
+ }
}
Please note this ArmResource
extension method has the same side effect as explained above: this extension is only meant to be got or created under the virtual machine resource, but the extension method will let you see this method on any resource instance because they all inherit from ArmResource
.
During the SDK review, we would like to make some polish to our generated SDK according to the review comments, for instance, changing names of types, properties, making type of properties more specific, etc. To achieve these, you will need the SDK polishing configurations.
Please see this document for full details of the SDK polishing configurations.
A debug configuration is also introduced to show some extra information in the generated code. Usage:
mgmt-debug:
suppress-list-exception: true
The configuration suppress-list-exception
suppresses all the exceptions about the collection class without a GetAll
method.
The directives brought by AutoRest core module works fine with all the configuration above. Please refer this document for more details and usages.