Skip to content

Commit a523406

Browse files
Add a new configuration to convert things to scope (#3037)
* add the configuration but it is untested yet * remove a temporary configuration since it is no longer needed or used in our SDKs * various changes and fixes for assigning a path as scope * fix some issues around usual scope * a format * introduce a patch especially for resourcemanager * generalized the previous fix * another fix for recent changes * fix test cases * add a new case in test project for the new scope * update documents * add test case to the testgen as well * fix the base test on scope resources * add some more clarification for generate arm resource extensions configuration * add some description * fix a using issue in generated samples
1 parent a161eb8 commit a523406

File tree

63 files changed

+15539
-7361
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+15539
-7361
lines changed

docs/mgmt/readme.md

Lines changed: 82 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -650,12 +650,34 @@ and since it is a scope resource without any configuration, its parent is anythi
650650
```csharp
651651
public static partial class ResourcesExtension
652652
{
653-
public static DeploymentCollection GetDeployments(this ArmResource armResource)
653+
public static DeploymentCollection GetDeployments(this ArmClient client, ResourceIdentifier scope)
654654
{
655655
/* ... */
656656
}
657657
}
658658
```
659+
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:
660+
```yaml
661+
generate-arm-resource-extensions:
662+
- /{scope}/providers/Microsoft.Resources/deployments/{deploymentName}
663+
```
664+
And you will see this change in the generated code:
665+
```diff
666+
public static partial class ResourcesExtension
667+
{
668+
public static DeploymentCollection GetDeployments(this ArmClient client, ResourceIdentifier scope)
669+
{
670+
/* ... */
671+
}
672+
+
673+
+ public static DeploymentCollection GetDeployments(this ArmResource armResource)
674+
+ {
675+
+ /* ... */
676+
+ }
677+
}
678+
```
679+
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.
680+
659681
To assign specific resource types to this scope, you can use the following configuration:
660682

661683
```yaml
@@ -689,6 +711,65 @@ public static partial class ResourcesExtension
689711
}
690712
```
691713

714+
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.
715+
716+
By default, because the generator will never find the `VirtualMachineResource` when generating this SDK, the parent resource of this `VmGuestConfigurationResource` will be `ResourceGroupResource`.
717+
```csharp
718+
public static partial class GuestConfigurationExtensions
719+
{
720+
public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ResourceGroupResource resourceGroup, string vmName)
721+
{
722+
/* ... */
723+
}
724+
}
725+
```
726+
727+
To show the relationship between resources across different RPs, we could convert it into a scope resource by using the following configuration:
728+
```yaml
729+
parameterized-scopes:
730+
- /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}
731+
```
732+
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:
733+
```diff
734+
public static partial class GuestConfigurationExtensions
735+
{
736+
- public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ResourceGroupResource resourceGroup, string vmName)
737+
- {
738+
- /* ... */
739+
- }
740+
+ public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ArmClient client, ResourceIdentifier scope)
741+
+ {
742+
+ if (!scope.ResourceType.Equals("Microsoft.Compute/virtualMachines"))
743+
+ throw new InvalidOperationException(string.Format("Invalid resource type {0} expected Microsoft.Compute/virtualMachines", scope.ResourceType));
744+
+ /* ... */
745+
+ }
746+
}
747+
```
748+
749+
This configuration works fine with the configuration introduced above:
750+
```yaml
751+
generate-arm-resource-extensions:
752+
- /subscriptions/{subscriptionId}/resourceGroups/{resourceGroupName}/providers/Microsoft.Compute/virtualMachines/{vmName}/providers/Microsoft.GuestConfiguration/guestConfigurationAssignments/{guestConfigurationAssignmentName}
753+
```
754+
and this is how the generated code would change:
755+
```diff
756+
public static partial class GuestConfigurationExtensions
757+
{
758+
public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ArmClient client, ResourceIdentifier scope)
759+
{
760+
if (!scope.ResourceType.Equals("Microsoft.Compute/virtualMachines"))
761+
throw new InvalidOperationException(string.Format("Invalid resource type {0} expected Microsoft.Compute/virtualMachines", scope.ResourceType));
762+
/* ... */
763+
}
764+
+
765+
+ public static VmGuestConfigurationCollection GetVmGuestConfigurations(this ArmResource armResource)
766+
+ {
767+
+ /* ... */
768+
+ }
769+
}
770+
```
771+
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`.
772+
692773
### SDK polishing configurations
693774

694775
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.

src/AutoRest.CSharp/Mgmt/AutoRest/MgmtConfiguration.cs

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,11 @@
33

44
using System;
55
using System.Collections.Generic;
6+
using System.Collections.Immutable;
67
using System.Linq;
78
using System.Text.Json;
89
using AutoRest.CSharp.AutoRest.Communication;
10+
using AutoRest.CSharp.Mgmt.Models;
911

1012
namespace AutoRest.CSharp.Input
1113
{
@@ -95,7 +97,7 @@ public MgmtConfiguration(
9597
IReadOnlyList<string> noResourceSuffix,
9698
IReadOnlyList<string> schemasToPrependRPPrefix,
9799
IReadOnlyList<string> generateArmResourceExtensions,
98-
IReadOnlyList<string> preventWrappingReturnType,
100+
IReadOnlyList<string> parameterizedScopes,
99101
MgmtDebugConfiguration mgmtDebug,
100102
JsonElement? requestPathToParent = default,
101103
JsonElement? requestPathToResourceName = default,
@@ -164,11 +166,7 @@ public MgmtConfiguration(
164166
NoResourceSuffix = noResourceSuffix;
165167
PrependRPPrefix = schemasToPrependRPPrefix;
166168
GenerateArmResourceExtensions = generateArmResourceExtensions;
167-
if (preventWrappingReturnType.Any())
168-
{
169-
Console.Error.WriteLine($"WARNING: The configuration 'prevent-wrapping-return-type' is a workaround and will be removed in the future.");
170-
}
171-
PreventWrappingReturnType = preventWrappingReturnType;
169+
RawParameterizedScopes = parameterizedScopes;
172170
IsArmCore = Configuration.DeserializeBoolean(armCore, false);
173171
DoesResourceModelRequireType = Configuration.DeserializeBoolean(resourceModelRequiresType, true);
174172
DoesResourceModelRequireName = Configuration.DeserializeBoolean(resourceModelRequiresName, true);
@@ -211,6 +209,10 @@ private static Dictionary<TKey, TValue> DeserializeDictionary<TKey, TValue>(Json
211209
public IReadOnlyDictionary<string, string[]> RequestPathToScopeResourceTypes { get; }
212210
public IReadOnlyDictionary<string, string[]> OperationPositions { get; }
213211
public IReadOnlyDictionary<string, string[]> MergeOperations { get; }
212+
public IReadOnlyList<string> RawParameterizedScopes { get; }
213+
private ImmutableHashSet<RequestPath>? _parameterizedScopes;
214+
internal ImmutableHashSet<RequestPath> ParameterizedScopes
215+
=> _parameterizedScopes ??= RawParameterizedScopes.Select(scope => RequestPath.FromString(scope)).ToImmutableHashSet();
214216
public IReadOnlyList<string> OperationGroupsToOmit { get; }
215217
public IReadOnlyList<string> RequestPathIsNonResource { get; }
216218
public IReadOnlyList<string> NoPropertyTypeReplacement { get; }
@@ -221,12 +223,11 @@ private static Dictionary<TKey, TValue> DeserializeDictionary<TKey, TValue>(Json
221223
public IReadOnlyList<string> KeepPluralResourceData { get; }
222224
public IReadOnlyList<string> PrependRPPrefix { get; }
223225
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> OperationIdMappings { get; }
224-
public IReadOnlyDictionary<string, string> UpdateRequiredCopy {get;}
226+
public IReadOnlyDictionary<string, string> UpdateRequiredCopy { get; }
225227
public IReadOnlyDictionary<string, IReadOnlyDictionary<string, string>> PatchInitializerCustomization { get; }
226228

227229
public IReadOnlyList<string> NoResourceSuffix { get; }
228230
public IReadOnlyList<string> GenerateArmResourceExtensions { get; }
229-
public IReadOnlyList<string> PreventWrappingReturnType { get; }
230231

231232
public bool IsArmCore { get; }
232233

@@ -244,7 +245,7 @@ internal static MgmtConfiguration GetConfiguration(IPluginCommunication autoRest
244245
noResourceSuffix: autoRest.GetValue<string[]?>("no-resource-suffix").GetAwaiter().GetResult() ?? Array.Empty<string>(),
245246
schemasToPrependRPPrefix: autoRest.GetValue<string[]?>("prepend-rp-prefix").GetAwaiter().GetResult() ?? Array.Empty<string>(),
246247
generateArmResourceExtensions: autoRest.GetValue<string[]?>("generate-arm-resource-extensions").GetAwaiter().GetResult() ?? Array.Empty<string>(),
247-
preventWrappingReturnType: autoRest.GetValue<string[]?>("prevent-wrapping-return-type").GetAwaiter().GetResult() ?? Array.Empty<string>(),
248+
parameterizedScopes: autoRest.GetValue<string[]?>("parameterized-scopes").GetAwaiter().GetResult() ?? Array.Empty<string>(),
248249
mgmtDebug: MgmtDebugConfiguration.GetConfiguration(autoRest),
249250
requestPathToParent: autoRest.GetValue<JsonElement?>("request-path-to-parent").GetAwaiter().GetResult(),
250251
requestPathToResourceName: autoRest.GetValue<JsonElement?>("request-path-to-resource-name").GetAwaiter().GetResult(),
@@ -281,7 +282,6 @@ internal void SaveConfiguration(Utf8JsonWriter writer)
281282
WriteNonEmptySettings(writer, nameof(NoResourceSuffix), NoResourceSuffix);
282283
WriteNonEmptySettings(writer, nameof(PrependRPPrefix), PrependRPPrefix);
283284
WriteNonEmptySettings(writer, nameof(GenerateArmResourceExtensions), GenerateArmResourceExtensions);
284-
WriteNonEmptySettings(writer, nameof(PreventWrappingReturnType), PreventWrappingReturnType);
285285
WriteNonEmptySettings(writer, nameof(OperationGroupsToOmit), OperationGroupsToOmit);
286286
WriteNonEmptySettings(writer, nameof(RequestPathToParent), RequestPathToParent);
287287
WriteNonEmptySettings(writer, nameof(OperationPositions), OperationPositions);
@@ -324,7 +324,6 @@ internal static MgmtConfiguration LoadConfiguration(JsonElement root)
324324
root.TryGetProperty(nameof(NoResourceSuffix), out var noResourceSuffixElement);
325325
root.TryGetProperty(nameof(PrependRPPrefix), out var prependRPPrefixElement);
326326
root.TryGetProperty(nameof(GenerateArmResourceExtensions), out var generateArmResourceExtensionsElement);
327-
root.TryGetProperty(nameof(PreventWrappingReturnType), out var preventWrappingReturnTypeElement);
328327
root.TryGetProperty(nameof(RequestPathToParent), out var requestPathToParent);
329328
root.TryGetProperty(nameof(RequestPathToResourceName), out var requestPathToResourceName);
330329
root.TryGetProperty(nameof(RequestPathToResourceData), out var requestPathToResourceData);
@@ -341,6 +340,7 @@ internal static MgmtConfiguration LoadConfiguration(JsonElement root)
341340
root.TryGetProperty(nameof(OverrideOperationName), out var operationIdToName);
342341
root.TryGetProperty(nameof(MergeOperations), out var mergeOperations);
343342
root.TryGetProperty(nameof(PromptedEnumValues), out var promptedEnumValuesElement);
343+
root.TryGetProperty(nameof(RawParameterizedScopes), out var parameterizedScopesElement);
344344

345345
var operationGroupToOmit = Configuration.DeserializeArray(operationGroupsToOmitElement);
346346
var requestPathIsNonResource = Configuration.DeserializeArray(requestPathIsNonResourceElement);
@@ -353,7 +353,7 @@ internal static MgmtConfiguration LoadConfiguration(JsonElement root)
353353
var noResourceSuffix = Configuration.DeserializeArray(noResourceSuffixElement);
354354
var prependRPPrefix = Configuration.DeserializeArray(prependRPPrefixElement);
355355
var generateArmResourceExtensions = Configuration.DeserializeArray(generateArmResourceExtensionsElement);
356-
var preventWrappingReturnType = Configuration.DeserializeArray(preventWrappingReturnTypeElement);
356+
var parameterizedScopes = Configuration.DeserializeArray(parameterizedScopesElement);
357357

358358
root.TryGetProperty("ArmCore", out var isArmCore);
359359
root.TryGetProperty(nameof(MgmtDebug), out var mgmtDebugRoot);
@@ -376,7 +376,7 @@ internal static MgmtConfiguration LoadConfiguration(JsonElement root)
376376
noResourceSuffix: noResourceSuffix,
377377
schemasToPrependRPPrefix: prependRPPrefix,
378378
generateArmResourceExtensions: generateArmResourceExtensions,
379-
preventWrappingReturnType: preventWrappingReturnType,
379+
parameterizedScopes: parameterizedScopes,
380380
mgmtDebug: MgmtDebugConfiguration.LoadConfiguration(mgmtDebugRoot),
381381
requestPathToParent: requestPathToParent,
382382
requestPathToResourceName: requestPathToResourceName,

src/AutoRest.CSharp/Mgmt/Decorator/ParameterMappingBuilder.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public static IEnumerable<ContextualParameterMapping> BuildContextualParameters(
4242
private static void BuildContextualParameterMappingHierarchy(RequestPath current, Stack<ContextualParameterMapping> parameterMappingStack, string idVariableName = "Id", string invocationSuffix = "")
4343
{
4444
// Check if the current path is a scope parameter
45-
if (current.IsParameterizedScope())
45+
if (current.IsRawParameterizedScope())
4646
{
4747
// in this case, we should only have one segment in this current path
4848
parameterMappingStack.Push(new ContextualParameterMapping(string.Empty, current.Last(), $"{idVariableName}{invocationSuffix}"));

src/AutoRest.CSharp/Mgmt/Decorator/ParentDetection.cs

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ public static IEnumerable<MgmtTypeProvider> Parent(this Resource resource)
3838

3939
private static IEnumerable<MgmtTypeProvider> GetParent(this Resource resource)
4040
{
41+
var scope = resource.RequestPath.GetScopePath();
4142
var resourceOperationSet = resource.OperationSet;
4243
var parentRequestPath = resourceOperationSet.ParentRequestPath(resource.ResourceType);
4344

@@ -46,6 +47,15 @@ private static IEnumerable<MgmtTypeProvider> GetParent(this Resource resource)
4647
// my parent is myself? Only tenant has this attribute, return empty
4748
return Enumerable.Empty<MgmtTypeProvider>();
4849
}
50+
// if the scope of this request path is parameterized, and the direct parent path we get from the resource list is parent of the scope, we return the scope as its parent since the scope here is a child
51+
// if the request path is a "by id" path, its scope is the same as itself, therefore this condition here is nullified and should be skipped
52+
if (!resource.RequestPath.IsById && scope.IsParameterizedScope() && (parentRequestPath.IsAncestorOf(scope) || parentRequestPath == scope))
53+
{
54+
// we already verified that the scope is parameterized, therefore we assert the type can never be null
55+
var types = resource.RequestPath.GetParameterizedScopeResourceTypes()!;
56+
return FindScopeParents(types).Distinct();
57+
}
58+
4959
if (MgmtContext.Library.TryGetArmResource(parentRequestPath, out var parent))
5060
{
5161
return parent.AsIEnumerable();
@@ -58,8 +68,7 @@ private static IEnumerable<MgmtTypeProvider> GetParent(this Resource resource)
5868
if (parentRequestPath.Equals(RequestPath.Subscription))
5969
return MgmtContext.Library.SubscriptionExtensions.AsIEnumerable();
6070
// the only option left is the tenant. But we have our last chance that its parent could be the scope of this
61-
var scope = parentRequestPath.GetScopePath();
62-
// if the scope of this request path is parameterized, we return the scope as its parent
71+
scope = parentRequestPath.GetScopePath(); // we do this because some request path its scope is the same as itself
6372
if (scope.IsParameterizedScope())
6473
{
6574
// we already verified that the scope is parameterized, therefore we assert the type can never be null
@@ -171,6 +180,7 @@ private static RequestPath GetParent(this RequestPath requestPath)
171180
var scope = requestPath.GetScopePath();
172181
var candidates = MgmtContext.Library.ResourceOperationSets.Select(operationSet => operationSet.GetRequestPath())
173182
.Concat(new List<RequestPath> { RequestPath.ResourceGroup, RequestPath.Subscription, RequestPath.ManagementGroup }) // When generating management group in management.json, the path is /providers/Microsoft.Management/managementGroups/{groupId} while RequestPath.ManagementGroup is /providers/Microsoft.Management/managementGroups/{managementGroupId}. We pick the first one.
183+
.Concat(Configuration.MgmtConfiguration.ParameterizedScopes)
174184
.Where(r => r.IsAncestorOf(requestPath)).OrderByDescending(r => r.Count);
175185
if (candidates.Any())
176186
{

0 commit comments

Comments
 (0)