PageRenderTime 94ms CodeModel.GetById 21ms RepoModel.GetById 0ms app.codeStats 1ms

/src/ResourceManager/Resources/Commands.ResourceManager/Cmdlets/Components/ApiVersionHelper.cs

https://gitlab.com/jslee1/azure-powershell
C# | 217 lines | 118 code | 27 blank | 72 comment | 12 complexity | a64bbc5b5a5250183f898f1f333e6d89 MD5 | raw file
  1. // ----------------------------------------------------------------------------------
  2. //
  3. // Copyright Microsoft Corporation
  4. // Licensed under the Apache License, Version 2.0 (the "License");
  5. // you may not use this file except in compliance with the License.
  6. // You may obtain a copy of the License at
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. // Unless required by applicable law or agreed to in writing, software
  9. // distributed under the License is distributed on an "AS IS" BASIS,
  10. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  11. // See the License for the specific language governing permissions and
  12. // limitations under the License.
  13. // ----------------------------------------------------------------------------------
  14. using Microsoft.Azure.Commands.Common.Authentication.Models;
  15. namespace Microsoft.Azure.Commands.ResourceManager.Cmdlets.Components
  16. {
  17. using Entities.Providers;
  18. using Extensions;
  19. using System;
  20. using System.Collections.Generic;
  21. using System.Globalization;
  22. using System.Linq;
  23. using System.Runtime.Caching;
  24. using System.Threading;
  25. using System.Threading.Tasks;
  26. /// <summary>
  27. /// Helper class for determining the API version
  28. /// </summary>
  29. internal static class ApiVersionHelper
  30. {
  31. /// <summary>
  32. /// Determines the API version.
  33. /// </summary>
  34. /// <param name="context">The azure profile.</param>
  35. /// <param name="resourceId">The resource Id.</param>
  36. /// <param name="cancellationToken">The cancellation token</param>
  37. /// <param name="pre">When specified, indicates if pre-release API versions should be considered.</param>
  38. internal static Task<string> DetermineApiVersion(AzureContext context, string resourceId, CancellationToken cancellationToken, bool? pre = null, Dictionary<string, string> cmdletHeaderValues = null)
  39. {
  40. var providerNamespace = ResourceIdUtility.GetExtensionProviderNamespace(resourceId)
  41. ?? ResourceIdUtility.GetProviderNamespace(resourceId);
  42. var resourceType = ResourceIdUtility.GetExtensionResourceType(resourceId: resourceId, includeProviderNamespace: false)
  43. ?? ResourceIdUtility.GetResourceType(resourceId: resourceId, includeProviderNamespace: false);
  44. return ApiVersionHelper.DetermineApiVersion(context: context, providerNamespace: providerNamespace, resourceType: resourceType, cancellationToken: cancellationToken, pre: pre, cmdletHeaderValues: cmdletHeaderValues);
  45. }
  46. /// <summary>
  47. /// Determines the API version.
  48. /// </summary>
  49. /// <param name="context">The azure profile.</param>
  50. /// <param name="providerNamespace">The provider namespace.</param>
  51. /// <param name="resourceType">The resource type.</param>
  52. /// <param name="cancellationToken">The cancellation token</param>
  53. /// <param name="pre">When specified, indicates if pre-release API versions should be considered.</param>
  54. internal static Task<string> DetermineApiVersion(AzureContext context, string providerNamespace, string resourceType, CancellationToken cancellationToken, bool? pre = null, Dictionary<string, string> cmdletHeaderValues = null)
  55. {
  56. var cacheKey = ApiVersionCache.GetCacheKey(providerNamespace: providerNamespace, resourceType: resourceType);
  57. var apiVersions = ApiVersionCache.Instance
  58. .AddOrGetExisting(cacheKey: cacheKey, getFreshData: () => ApiVersionHelper.GetApiVersionsForResourceType(
  59. context,
  60. providerNamespace: providerNamespace,
  61. resourceType: resourceType,
  62. cancellationToken: cancellationToken,
  63. cmdletHeaderValues: cmdletHeaderValues));
  64. apiVersions = apiVersions.CoalesceEnumerable().ToArray();
  65. var apiVersionsToSelectFrom = apiVersions;
  66. if (pre == null || pre == false)
  67. {
  68. apiVersionsToSelectFrom = apiVersions
  69. .Where(apiVersion => apiVersion.IsDecimal(NumberStyles.AllowDecimalPoint) || apiVersion.IsDateTime("yyyy-mm-dd", DateTimeStyles.None))
  70. .ToArray();
  71. }
  72. var selectedApiVersion = apiVersionsToSelectFrom.OrderByDescending(apiVersion => apiVersion).FirstOrDefault();
  73. if (string.IsNullOrWhiteSpace(selectedApiVersion) && apiVersions.Any())
  74. {
  75. // fall back on pre-release APIs if they're the only ones available.
  76. selectedApiVersion = apiVersions.OrderByDescending(apiVersion => apiVersion).FirstOrDefault();
  77. }
  78. var result = string.IsNullOrWhiteSpace(selectedApiVersion)
  79. ? Constants.DefaultApiVersion
  80. : selectedApiVersion;
  81. return Task.FromResult(result);
  82. }
  83. /// <summary>
  84. /// Determines the list of api versions currently supported by the RP.
  85. /// </summary>
  86. /// <param name="context">The azure profile.</param>
  87. /// <param name="providerNamespace">The provider namespace.</param>
  88. /// <param name="resourceType">The resource type.</param>
  89. /// <param name="cancellationToken">The cancellation token.</param>
  90. private static string[] GetApiVersionsForResourceType(AzureContext context, string providerNamespace, string resourceType, CancellationToken cancellationToken, Dictionary<string, string> cmdletHeaderValues = null)
  91. {
  92. var resourceManagerClient = ResourceManagerClientHelper.GetResourceManagerClient(context, cmdletHeaderValues);
  93. var defaultSubscription = context.Subscription;
  94. var resourceCollectionId = defaultSubscription == null
  95. ? "/providers"
  96. : string.Format("/subscriptions/{0}/providers", defaultSubscription.Id);
  97. var providers = PaginatedResponseHelper.Enumerate(
  98. getFirstPage: () => resourceManagerClient
  99. .ListObjectColleciton<ResourceProviderDefinition>(
  100. resourceCollectionId: resourceCollectionId,
  101. apiVersion: Constants.DefaultApiVersion,
  102. cancellationToken: cancellationToken),
  103. getNextPage: nextLink => resourceManagerClient
  104. .ListNextBatch<ResourceProviderDefinition>(
  105. nextLink: nextLink,
  106. cancellationToken: cancellationToken),
  107. cancellationToken: cancellationToken);
  108. return providers
  109. .CoalesceEnumerable()
  110. .Where(provider => providerNamespace.EqualsInsensitively(provider.Namespace))
  111. .SelectMany(provider => provider.ResourceTypes)
  112. .Where(type => resourceType.EqualsInsensitively(type.ResourceType))
  113. .Select(type => type.ApiVersions)
  114. .FirstOrDefault();
  115. }
  116. /// <summary>
  117. /// A singleton instance of the <see cref="ApiVersionCache"/>
  118. /// </summary>
  119. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1001:TypesThatOwnDisposableFieldsShouldBeDisposable", Justification = "Instances of this type are meant to be singletons.")]
  120. private class ApiVersionCache
  121. {
  122. /// <summary>
  123. /// The API version cache
  124. /// </summary>
  125. internal static readonly ApiVersionCache Instance = new ApiVersionCache(TimeSpan.FromHours(1));
  126. /// <summary>
  127. /// The cache data expiration time
  128. /// </summary>
  129. internal TimeSpan CacheDataExpirationTime { get; private set; }
  130. /// <summary>
  131. /// Initializes a new instance of the <see cref="ApiVersionCache" /> class.
  132. /// </summary>
  133. /// <param name="cacheDataExpirationTime">The polling interval.</param>
  134. private ApiVersionCache(TimeSpan cacheDataExpirationTime)
  135. {
  136. this.CacheDataExpirationTime = cacheDataExpirationTime;
  137. }
  138. /// <summary>
  139. /// Adds or gets existing data from cache.
  140. /// </summary>
  141. /// <param name="cacheKey">The cache key.</param>
  142. /// <param name="getFreshData">The delegate used to get data if cache is empty or stale.</param>
  143. internal string[] AddOrGetExisting(string cacheKey, Func<string[]> getFreshData)
  144. {
  145. cacheKey = cacheKey.ToUpperInvariant();
  146. var cacheItem = this.GetCacheItem(cacheKey: cacheKey);
  147. if (cacheItem == null)
  148. {
  149. var expirationTime = DateTime.UtcNow.Add(this.CacheDataExpirationTime);
  150. cacheItem = getFreshData();
  151. if (cacheItem != null)
  152. {
  153. this.SetCacheItem(
  154. cacheKey: cacheKey,
  155. data: cacheItem,
  156. absoluteExpirationTime: expirationTime);
  157. }
  158. }
  159. return cacheItem;
  160. }
  161. /// <summary>
  162. /// Gets the cache entry.
  163. /// </summary>
  164. /// <param name="cacheKey">The cache key.</param>
  165. private string[] GetCacheItem(string cacheKey)
  166. {
  167. return MemoryCache.Default[cacheKey].Cast<string[]>();
  168. }
  169. /// <summary>
  170. /// Adds the cache entry.
  171. /// </summary>
  172. /// <param name="cacheKey">The cache key.</param>
  173. /// <param name="data">The data to add.</param>
  174. /// <param name="absoluteExpirationTime">The absolute expiration time.</param>
  175. private void SetCacheItem(string cacheKey, string[] data, DateTimeOffset absoluteExpirationTime)
  176. {
  177. MemoryCache.Default.Set(key: cacheKey, value: data, absoluteExpiration: absoluteExpirationTime);
  178. }
  179. /// <summary>
  180. /// Gets the cache key.
  181. /// </summary>
  182. /// <param name="providerNamespace">The provider namespace.</param>
  183. /// <param name="resourceType">The resource type.</param>
  184. internal static string GetCacheKey(string providerNamespace, string resourceType)
  185. {
  186. return string.Format("{0}/{1}", providerNamespace.CoalesceString(), resourceType.CoalesceString()).ToUpperInvariant();
  187. }
  188. }
  189. }
  190. }