1# --------------------------------------------------------------------------------------------
2# Copyright (c) Microsoft Corporation. All rights reserved.
3# Licensed under the MIT License. See License.txt in the project root for license information.
4# --------------------------------------------------------------------------------------------
5
6# TODO Move this to a package shared by CLI and SDK
7from enum import Enum
8from functools import total_ordering
9from importlib import import_module
10
11from knack.log import get_logger
12
13
14logger = get_logger(__name__)
15
16
17class APIVersionException(Exception):
18    def __init__(self, type_name, api_profile):
19        super(APIVersionException, self).__init__(type_name, api_profile)
20        self.type_name = type_name
21        self.api_profile = api_profile
22
23    def __str__(self):
24        return "Unable to get API version for type '{}' in profile '{}'".format(
25            self.type_name, self.api_profile)
26
27
28# Sentinel value for profile
29PROFILE_TYPE = object()
30
31
32class CustomResourceType:  # pylint: disable=too-few-public-methods
33    def __init__(self, import_prefix, client_name):
34        self.import_prefix = import_prefix
35        self.client_name = client_name
36
37
38class ResourceType(Enum):  # pylint: disable=too-few-public-methods
39
40    MGMT_APIMANAGEMENT = ('azure.mgmt.apimanagement', 'ApiManagementClient')
41    MGMT_KUSTO = ('azure.mgmt.kusto', 'KustoManagementClient')
42    MGMT_KEYVAULT = ('azure.mgmt.keyvault', 'KeyVaultManagementClient')
43    MGMT_STORAGE = ('azure.mgmt.storage', 'StorageManagementClient')
44    MGMT_COMPUTE = ('azure.mgmt.compute', 'ComputeManagementClient')
45    MGMT_NETWORK = ('azure.mgmt.network', 'NetworkManagementClient')
46    MGMT_NETWORK_DNS = ('azure.mgmt.dns', 'DnsManagementClient')
47    MGMT_AUTHORIZATION = ('azure.mgmt.authorization', 'AuthorizationManagementClient')
48    MGMT_CONTAINERREGISTRY = ('azure.mgmt.containerregistry', 'ContainerRegistryManagementClient')
49    MGMT_RESOURCE_FEATURES = ('azure.mgmt.resource.features', 'FeatureClient')
50    MGMT_RESOURCE_LINKS = ('azure.mgmt.resource.links', 'ManagementLinkClient')
51    MGMT_RESOURCE_LOCKS = ('azure.mgmt.resource.locks', 'ManagementLockClient')
52    MGMT_RESOURCE_POLICY = ('azure.mgmt.resource.policy', 'PolicyClient')
53    MGMT_RESOURCE_RESOURCES = ('azure.mgmt.resource.resources', 'ResourceManagementClient')
54    MGMT_RESOURCE_SUBSCRIPTIONS = ('azure.mgmt.resource.subscriptions', 'SubscriptionClient')
55    MGMT_RESOURCE_DEPLOYMENTSCRIPTS = ('azure.mgmt.resource.deploymentscripts', 'DeploymentScriptsClient')
56    MGMT_RESOURCE_TEMPLATESPECS = ('azure.mgmt.resource.templatespecs', 'TemplateSpecsClient')
57    MGMT_MONITOR = ('azure.mgmt.monitor', 'MonitorManagementClient')
58    DATA_KEYVAULT = ('azure.keyvault', 'KeyVaultClient')
59    DATA_KEYVAULT_KEYS = ('azure.keyvault.keys', 'KeyClient')
60    DATA_PRIVATE_KEYVAULT = ('azure.cli.command_modules.keyvault.vendored_sdks.azure_keyvault_t1', 'KeyVaultClient')
61    DATA_KEYVAULT_ADMINISTRATION_BACKUP = ('azure.keyvault.administration', 'KeyVaultBackupClient')
62    DATA_KEYVAULT_ADMINISTRATION_ACCESS_CONTROL = ('azure.keyvault.administration', 'KeyVaultAccessControlClient')
63    MGMT_EVENTHUB = ('azure.mgmt.eventhub', 'EventHubManagementClient')
64    MGMT_APPSERVICE = ('azure.mgmt.web', 'WebSiteManagementClient')
65    MGMT_IOTCENTRAL = ('azure.mgmt.iotcentral', 'IotCentralClient')
66    MGMT_IOTHUB = ('azure.mgmt.iothub', 'IotHubClient')
67    MGMT_ARO = ('azure.mgmt.redhatopenshift', 'AzureRedHatOpenShift4Client')
68    MGMT_DATABOXEDGE = ('azure.mgmt.databoxedge', 'DataBoxEdgeManagementClient')
69    MGMT_CUSTOMLOCATION = ('azure.mgmt.extendedlocation', 'CustomLocations')
70    MGMT_CONTAINERSERVICE = ('azure.mgmt.containerservice', 'ContainerServiceClient')
71    # the "None" below will stay till a command module fills in the type so "get_mgmt_service_client"
72    # can be provided with "ResourceType.XXX" to initialize the client object. This usually happens
73    # when related commands start to support Multi-API
74
75    DATA_COSMOS_TABLE = ('azure.multiapi.cosmosdb', None)
76    MGMT_ADVISOR = ('azure.mgmt.advisor', None)
77    MGMT_MEDIA = ('azure.mgmt.media', None)
78    MGMT_BACKUP = ('azure.mgmt.recoveryservicesbackup', None)
79    MGMT_BATCH = ('azure.mgmt.batch', None)
80    MGMT_BATCHAI = ('azure.mgmt.batchai', None)
81    MGMT_BILLING = ('azure.mgmt.billing', None)
82    MGMT_BOTSERVICE = ('azure.mgmt.botservice', None)
83    MGMT_CDN = ('azure.mgmt.cdn', None)
84    MGMT_COGNITIVESERVICES = ('azure.mgmt.cognitiveservices', None)
85    MGMT_CONSUMPTION = ('azure.mgmt.consumption', None)
86    MGMT_CONTAINERINSTANCE = ('azure.mgmt.containerinstance', None)
87    MGMT_COSMOSDB = ('azure.mgmt.cosmosdb', None)
88    MGMT_DEPLOYMENTMANAGER = ('azure.mgmt.deploymentmanager', None)
89    MGMT_DATALAKE_ANALYTICS = ('azure.mgmt.datalake.analytics', None)
90    MGMT_DATALAKE_STORE = ('azure.mgmt.datalake.store', None)
91    MGMT_DATAMIGRATION = ('azure.mgmt.datamigration', None)
92    MGMT_EVENTGRID = ('azure.mgmt.eventgrid', None)
93    MGMT_DEVTESTLABS = ('azure.mgmt.devtestlabs', None)
94    MGMT_MAPS = ('azure.mgmt.maps', None)
95    MGMT_POLICYINSIGHTS = ('azure.mgmt.policyinsights', None)
96    MGMT_RDBMS = ('azure.mgmt.rdbms', None)
97    MGMT_REDIS = ('azure.mgmt.redis', None)
98    MGMT_RELAY = ('azure.mgmt.relay', None)
99    MGMT_RESERVATIONS = ('azure.mgmt.reservations', None)
100    MGMT_SEARCH = ('azure.mgmt.search', None)
101    MGMT_SERVICEBUS = ('azure.mgmt.servicebus', None)
102    MGMT_SERVICEFABRIC = ('azure.mgmt.servicefabric', None)
103    MGMT_SIGNALR = ('azure.mgmt.signalr', None)
104    MGMT_SQL = ('azure.mgmt.sql', None)
105    MGMT_SQLVM = ('azure.mgmt.sqlvirtualmachine', None)
106    MGMT_MANAGEDSERVICES = ('azure.mgmt.managedservices', None)
107    MGMT_NETAPPFILES = ('azure.mgmt.netappfiles', None)
108    DATA_STORAGE = ('azure.multiapi.storage', None)
109    DATA_STORAGE_BLOB = ('azure.multiapi.storagev2.blob', None)
110    DATA_STORAGE_FILEDATALAKE = ('azure.multiapi.storagev2.filedatalake', None)
111    DATA_STORAGE_FILESHARE = ('azure.multiapi.storagev2.fileshare', None)
112    DATA_STORAGE_QUEUE = ('azure.multiapi.storagev2.queue', None)
113
114    def __init__(self, import_prefix, client_name):
115        """Constructor.
116
117        :param import_prefix: Path to the (unversioned) module.
118        :type import_prefix: str.
119        :param client_name: Name the client for this resource type.
120        :type client_name: str.
121        """
122        self.import_prefix = import_prefix
123        self.client_name = client_name
124
125
126class SDKProfile:  # pylint: disable=too-few-public-methods
127
128    def __init__(self, default_api_version, profile=None):
129        """Constructor.
130
131        :param str default_api_version: Default API version if not overridden by a profile. Nullable.
132        :param profile: A dict operation group name to API version.
133        :type profile: dict[str, str]
134        """
135        self.profile = profile if profile is not None else {}
136        self.profile[None] = default_api_version
137
138    @property
139    def default_api_version(self):
140        return self.profile[None]
141
142
143AZURE_API_PROFILES = {
144    'latest': {
145        ResourceType.MGMT_STORAGE: '2021-06-01',
146        ResourceType.MGMT_NETWORK: '2021-02-01',
147        ResourceType.MGMT_COMPUTE: SDKProfile('2021-07-01', {
148            'resource_skus': '2019-04-01',
149            'disks': '2020-12-01',
150            'disk_encryption_sets': '2020-12-01',
151            'disk_accesses': '2020-05-01',
152            'snapshots': '2020-12-01',
153            'galleries': '2021-07-01',
154            'gallery_images': '2020-09-30',
155            'gallery_image_versions': '2021-07-01',
156            'shared_galleries': '2020-09-30',
157            'virtual_machine_scale_sets': '2021-04-01',
158        }),
159        ResourceType.MGMT_RESOURCE_FEATURES: '2021-07-01',
160        ResourceType.MGMT_RESOURCE_LINKS: '2016-09-01',
161        ResourceType.MGMT_RESOURCE_LOCKS: '2016-09-01',
162        ResourceType.MGMT_RESOURCE_POLICY: '2020-09-01',
163        ResourceType.MGMT_RESOURCE_RESOURCES: '2021-04-01',
164        ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS: '2019-11-01',
165        ResourceType.MGMT_RESOURCE_DEPLOYMENTSCRIPTS: '2020-10-01',
166        ResourceType.MGMT_RESOURCE_TEMPLATESPECS: '2021-05-01',
167        ResourceType.MGMT_NETWORK_DNS: '2018-05-01',
168        ResourceType.MGMT_KEYVAULT: '2021-04-01-preview',
169        ResourceType.MGMT_AUTHORIZATION: SDKProfile('2020-04-01-preview', {
170            'classic_administrators': '2015-06-01',
171            'role_definitions': '2018-01-01-preview',
172            'provider_operations_metadata': '2018-01-01-preview'
173        }),
174        ResourceType.MGMT_CONTAINERREGISTRY: SDKProfile('2021-06-01-preview', {
175            'agent_pools': '2019-06-01-preview',
176            'tasks': '2019-06-01-preview',
177            'task_runs': '2019-06-01-preview',
178            'runs': '2019-06-01-preview',
179        }),
180        # The order does make things different.
181        # Please keep ResourceType.DATA_KEYVAULT_KEYS before ResourceType.DATA_KEYVAULT
182        ResourceType.DATA_KEYVAULT_KEYS: None,
183        ResourceType.DATA_KEYVAULT: '7.0',
184        ResourceType.DATA_PRIVATE_KEYVAULT: '7.2',
185        ResourceType.DATA_KEYVAULT_ADMINISTRATION_BACKUP: '7.2-preview',
186        ResourceType.DATA_KEYVAULT_ADMINISTRATION_ACCESS_CONTROL: '7.2-preview',
187        ResourceType.DATA_STORAGE: '2018-11-09',
188        ResourceType.DATA_STORAGE_BLOB: '2020-04-08',
189        ResourceType.DATA_STORAGE_FILEDATALAKE: '2020-02-10',
190        ResourceType.DATA_STORAGE_FILESHARE: '2019-07-07',
191        ResourceType.DATA_STORAGE_QUEUE: '2018-03-28',
192        ResourceType.DATA_COSMOS_TABLE: '2017-04-17',
193        ResourceType.MGMT_EVENTHUB: '2021-06-01-preview',
194        ResourceType.MGMT_MONITOR: SDKProfile('2019-06-01', {
195            'activity_log_alerts': '2017-04-01',
196            'activity_logs': '2015-04-01',
197            'alert_rule_incidents': '2016-03-01',
198            'alert_rules': '2016-03-01',
199            'autoscale_settings': '2015-04-01',
200            'baseline': '2018-09-01',
201            'baselines': '2019-03-01',
202            'diagnostic_settings': '2017-05-01-preview',
203            'diagnostic_settings_category': '2017-05-01-preview',
204            'event_categories': '2015-04-01',
205            'guest_diagnostics_settings': '2018-06-01-preview',
206            'guest_diagnostics_settings_association': '2018-06-01-preview',
207            'log_profiles': '2016-03-01',
208            'metric_alerts': '2018-03-01',
209            'metric_alerts_status': '2018-03-01',
210            'metric_baseline': '2018-09-01',
211            'metric_definitions': '2018-01-01',
212            'metric_namespaces': '2017-12-01-preview',
213            'metrics': '2018-01-01',
214            'operations': '2015-04-01',
215            'scheduled_query_rules': '2018-04-16',
216            'service_diagnostic_settings': '2016-09-01',
217            'tenant_activity_logs': '2015-04-01',
218            'vm_insights': '2018-11-27-preview',
219            'private_link_resources': '2019-10-17-preview',
220            'private_link_scoped_resources': '2019-10-17-preview',
221            'private_link_scope_operation_status': '2019-10-17-preview',
222            'private_link_scopes': '2019-10-17-preview',
223            'private_endpoint_connections': '2019-10-17-preview',
224            'subscription_diagnostic_settings': '2017-05-01-preview'
225        }),
226        ResourceType.MGMT_APPSERVICE: '2020-09-01',
227        ResourceType.MGMT_IOTHUB: '2021-07-01',
228        ResourceType.MGMT_IOTCENTRAL: '2018-09-01',
229        ResourceType.MGMT_ARO: '2020-04-30',
230        ResourceType.MGMT_DATABOXEDGE: '2021-02-01-preview',
231        ResourceType.MGMT_CUSTOMLOCATION: '2021-03-15-preview',
232        ResourceType.MGMT_CONTAINERSERVICE: SDKProfile('2021-07-01', {
233            'container_services': '2017-07-01',
234            'open_shift_managed_clusters': '2019-09-30-preview'
235        })
236    },
237    '2020-09-01-hybrid': {
238        ResourceType.MGMT_STORAGE: '2019-06-01',
239        ResourceType.MGMT_NETWORK: '2018-11-01',
240        ResourceType.MGMT_COMPUTE: SDKProfile('2020-06-01', {
241            'resource_skus': '2019-04-01',
242            'disks': '2019-07-01',
243            'disk_encryption_sets': '2019-07-01',
244            'disk_accesses': '2020-05-01',
245            'snapshots': '2019-07-01',
246            'galleries': '2019-12-01',
247            'gallery_images': '2019-12-01',
248            'gallery_image_versions': '2019-12-01',
249            'virtual_machine_scale_sets': '2020-06-01'
250        }),
251        ResourceType.MGMT_KEYVAULT: '2016-10-01',
252        ResourceType.MGMT_RESOURCE_FEATURES: '2021-07-01',
253        ResourceType.MGMT_RESOURCE_LINKS: '2016-09-01',
254        ResourceType.MGMT_RESOURCE_LOCKS: '2016-09-01',
255        ResourceType.MGMT_RESOURCE_POLICY: '2016-12-01',
256        ResourceType.MGMT_RESOURCE_RESOURCES: '2019-10-01',
257        ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS: '2016-06-01',
258        ResourceType.MGMT_RESOURCE_TEMPLATESPECS: '2015-01-01',
259        ResourceType.MGMT_NETWORK_DNS: '2016-04-01',
260        ResourceType.MGMT_AUTHORIZATION: SDKProfile('2015-07-01', {
261            'classic_administrators': '2015-06-01',
262            'policy_assignments': '2016-12-01',
263            'policy_definitions': '2016-12-01'
264        }),
265        # The order does make things different.
266        # Please keep ResourceType.DATA_KEYVAULT_KEYS before ResourceType.DATA_KEYVAULT
267        ResourceType.DATA_KEYVAULT_KEYS: None,
268        ResourceType.DATA_KEYVAULT: '2016-10-01',
269        ResourceType.DATA_STORAGE: '2018-11-09',
270        ResourceType.DATA_STORAGE_BLOB: '2019-07-07',
271        ResourceType.DATA_STORAGE_FILEDATALAKE: '2019-07-07',
272        ResourceType.DATA_STORAGE_FILESHARE: '2019-07-07',
273        ResourceType.DATA_STORAGE_QUEUE: '2019-07-07',
274        ResourceType.DATA_COSMOS_TABLE: '2017-04-17',
275        ResourceType.MGMT_APPSERVICE: '2018-02-01',
276        ResourceType.MGMT_EVENTHUB: '2021-06-01-preview',
277        ResourceType.MGMT_IOTHUB: '2019-07-01-preview',
278        ResourceType.MGMT_DATABOXEDGE: '2019-08-01',
279        ResourceType.MGMT_CONTAINERREGISTRY: '2019-05-01',
280        ResourceType.MGMT_CONTAINERSERVICE: SDKProfile('2020-11-01', {
281            'container_services': '2017-07-01',
282            'open_shift_managed_clusters': '2019-09-30-preview'
283        })
284    },
285    '2019-03-01-hybrid': {
286        ResourceType.MGMT_STORAGE: '2017-10-01',
287        ResourceType.MGMT_NETWORK: '2017-10-01',
288        ResourceType.MGMT_COMPUTE: SDKProfile('2017-12-01', {
289            'resource_skus': '2017-09-01',
290            'disks': '2017-03-30',
291            'snapshots': '2017-03-30'
292        }),
293        ResourceType.MGMT_RESOURCE_LINKS: '2016-09-01',
294        ResourceType.MGMT_RESOURCE_LOCKS: '2016-09-01',
295        ResourceType.MGMT_RESOURCE_POLICY: '2016-12-01',
296        ResourceType.MGMT_RESOURCE_RESOURCES: '2018-05-01',
297        ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS: '2016-06-01',
298        ResourceType.MGMT_RESOURCE_TEMPLATESPECS: '2015-01-01',
299        ResourceType.MGMT_NETWORK_DNS: '2016-04-01',
300        ResourceType.MGMT_KEYVAULT: '2016-10-01',
301        ResourceType.MGMT_AUTHORIZATION: SDKProfile('2015-07-01', {
302            'classic_administrators': '2015-06-01',
303            'policy_assignments': '2016-12-01',
304            'policy_definitions': '2016-12-01'
305        }),
306        # The order does make things different.
307        # Please keep ResourceType.DATA_KEYVAULT_KEYS before ResourceType.DATA_KEYVAULT
308        ResourceType.DATA_KEYVAULT_KEYS: None,
309        ResourceType.DATA_KEYVAULT: '2016-10-01',
310        ResourceType.DATA_STORAGE: '2017-11-09',
311        ResourceType.DATA_STORAGE_BLOB: '2017-11-09',
312        ResourceType.DATA_STORAGE_FILEDATALAKE: '2017-11-09',
313        ResourceType.DATA_STORAGE_FILESHARE: '2017-11-09',
314        ResourceType.DATA_STORAGE_QUEUE: '2017-11-09',
315        ResourceType.DATA_COSMOS_TABLE: '2017-04-17',
316        # Full MultiAPI support is not done in AppService, the line below is merely
317        # to have commands show up in the hybrid profile which happens to have the latest
318        # API versions
319        ResourceType.MGMT_APPSERVICE: '2018-02-01',
320        ResourceType.MGMT_EVENTHUB: '2021-06-01-preview',
321        ResourceType.MGMT_IOTHUB: '2019-03-22',
322        ResourceType.MGMT_DATABOXEDGE: '2019-08-01'
323    },
324    '2018-03-01-hybrid': {
325        ResourceType.MGMT_STORAGE: '2016-01-01',
326        ResourceType.MGMT_NETWORK: '2017-10-01',
327        ResourceType.MGMT_COMPUTE: SDKProfile('2017-03-30'),
328        ResourceType.MGMT_RESOURCE_LINKS: '2016-09-01',
329        ResourceType.MGMT_RESOURCE_LOCKS: '2016-09-01',
330        ResourceType.MGMT_RESOURCE_POLICY: '2016-12-01',
331        ResourceType.MGMT_RESOURCE_RESOURCES: '2018-02-01',
332        ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS: '2016-06-01',
333        ResourceType.MGMT_RESOURCE_TEMPLATESPECS: '2015-01-01',
334        ResourceType.MGMT_NETWORK_DNS: '2016-04-01',
335        ResourceType.MGMT_KEYVAULT: '2016-10-01',
336        ResourceType.MGMT_AUTHORIZATION: SDKProfile('2015-07-01', {
337            'classic_administrators': '2015-06-01'
338        }),
339        # The order does make things different.
340        # Please keep ResourceType.DATA_KEYVAULT_KEYS before ResourceType.DATA_KEYVAULT
341        ResourceType.DATA_KEYVAULT_KEYS: None,
342        ResourceType.DATA_KEYVAULT: '2016-10-01',
343        ResourceType.DATA_STORAGE: '2017-04-17',
344        ResourceType.DATA_STORAGE_BLOB: '2017-04-17',
345        ResourceType.DATA_STORAGE_FILEDATALAKE: '2017-04-17',
346        ResourceType.DATA_STORAGE_FILESHARE: '2017-04-17',
347        ResourceType.DATA_STORAGE_QUEUE: '2017-04-17',
348        ResourceType.DATA_COSMOS_TABLE: '2017-04-17'
349    },
350    '2017-03-09-profile': {
351        ResourceType.MGMT_STORAGE: '2016-01-01',
352        ResourceType.MGMT_NETWORK: '2015-06-15',
353        ResourceType.MGMT_COMPUTE: SDKProfile('2016-03-30'),
354        ResourceType.MGMT_RESOURCE_LINKS: '2016-09-01',
355        ResourceType.MGMT_RESOURCE_LOCKS: '2015-01-01',
356        ResourceType.MGMT_RESOURCE_POLICY: '2015-10-01-preview',
357        ResourceType.MGMT_RESOURCE_RESOURCES: '2016-02-01',
358        ResourceType.MGMT_RESOURCE_SUBSCRIPTIONS: '2016-06-01',
359        ResourceType.MGMT_RESOURCE_TEMPLATESPECS: '2015-01-01',
360        ResourceType.MGMT_NETWORK_DNS: '2016-04-01',
361        ResourceType.MGMT_KEYVAULT: '2016-10-01',
362        ResourceType.MGMT_AUTHORIZATION: SDKProfile('2015-07-01', {
363            'classic_administrators': '2015-06-01'
364        }),
365        # The order does make things different.
366        # Please keep ResourceType.DATA_KEYVAULT_KEYS before ResourceType.DATA_KEYVAULT
367        ResourceType.DATA_KEYVAULT_KEYS: None,
368        ResourceType.DATA_KEYVAULT: '2016-10-01',
369        ResourceType.DATA_STORAGE: '2015-04-05',
370        ResourceType.DATA_STORAGE_BLOB: '2015-04-05',
371        ResourceType.DATA_STORAGE_FILEDATALAKE: '2015-04-05',
372        ResourceType.DATA_STORAGE_FILESHARE: '2015-04-05',
373        ResourceType.DATA_STORAGE_QUEUE: '2015-04-05'
374    }
375}
376
377
378# We should avoid using ad hoc API versions,
379# use the version in a profile as much as possible.
380AD_HOC_API_VERSIONS = {
381    ResourceType.MGMT_NETWORK: {
382        'vm_default_target_network': '2018-01-01',
383        'nw_connection_monitor': '2019-06-01',
384        'container_network': '2018-08-01',
385        'appservice_network': '2020-04-01',
386        'appservice_ensure_subnet': '2019-02-01'
387    }
388}
389
390
391class _ApiVersions:  # pylint: disable=too-few-public-methods
392    def __init__(self, client_type, sdk_profile, post_process):
393        self._client_type = client_type
394        self._sdk_profile = sdk_profile
395        self._post_process = post_process
396        self._operations_groups_value = None
397        self._resolved = False
398
399    def _resolve(self):
400        if self._resolved:
401            return
402
403        self._operations_groups_value = {}
404        for operation_group_name, operation_type in self._client_type.__dict__.items():
405            if not isinstance(operation_type, property):
406                continue
407
408            value_to_save = self._sdk_profile.profile.get(
409                operation_group_name,
410                self._sdk_profile.default_api_version
411            )
412            self._operations_groups_value[operation_group_name] = self._post_process(value_to_save)
413        self._resolved = True
414
415    def __getattr__(self, item):
416        try:
417            self._resolve()
418            return self._operations_groups_value[item]
419        except KeyError:
420            raise AttributeError('Attribute {} does not exist.'.format(item))
421
422
423def _get_api_version_tuple(resource_type, sdk_profile, post_process=lambda x: x):
424    """Return a _ApiVersion instance where key are operation group and value are api version."""
425    return _ApiVersions(client_type=get_client_class(resource_type),
426                        sdk_profile=sdk_profile,
427                        post_process=post_process)
428
429
430def get_api_version(api_profile, resource_type, as_sdk_profile=False):
431    """Get the API version of a resource type given an API profile.
432
433    :param api_profile: The name of the API profile.
434    :type api_profile: str.
435    :param resource_type: The resource type.
436    :type resource_type: ResourceType.
437    :returns:  str -- the API version.
438    :raises: APIVersionException
439    """
440    try:
441        api_version = AZURE_API_PROFILES[api_profile][resource_type]
442        if as_sdk_profile:
443            return api_version  # Could be SDKProfile or string
444        if isinstance(api_version, SDKProfile):
445            api_version = _get_api_version_tuple(resource_type, api_version)
446        return api_version
447    except KeyError:
448        raise APIVersionException(resource_type, api_profile)
449
450
451@total_ordering
452class _SemVerAPIFormat:
453    """Basic semver x.y.z API format.
454    Supports x, or x.y, or x.y.z
455    """
456
457    def __init__(self, api_version_str):
458        try:
459            parts = api_version_str.split('.')
460            parts += [0, 0]  # At worst never read, at best minor/patch
461            self.major = int(parts[0])
462            self.minor = int(parts[1])
463            self.patch = int(parts[2])
464        except (ValueError, TypeError):
465            raise ValueError('The API version {} is not in a '
466                             'semver format'.format(api_version_str))
467
468    def __eq__(self, other):
469        return (self.major, self.minor, self.patch) == (other.major, other.minor, other.patch)
470
471    def __lt__(self, other):
472        return (self.major, self.minor, self.patch) < (other.major, other.minor, other.patch)
473
474
475@total_ordering  # pylint: disable=too-few-public-methods
476class _DateAPIFormat:
477    """ Class to support comparisons for API versions in
478        YYYY-MM-DD, YYYY-MM-DD-preview, YYYY-MM-DD-profile, YYYY-MM-DD-profile-preview
479        or any string that starts with YYYY-MM-DD format. A special case is made for 'latest'.
480    """
481
482    def __init__(self, api_version_str):
483        try:
484            self.latest = self.preview = False
485            self.yyyy = self.mm = self.dd = None
486            if api_version_str == 'latest':
487                self.latest = True
488            else:
489                if 'preview' in api_version_str:
490                    self.preview = True
491                parts = api_version_str.split('-')
492                self.yyyy = int(parts[0])
493                self.mm = int(parts[1])
494                self.dd = int(parts[2])
495        except (ValueError, TypeError):
496            raise ValueError('The API version {} is not in a '
497                             'supported format'.format(api_version_str))
498
499    def __eq__(self, other):
500        return self.latest == other.latest and self.yyyy == other.yyyy and self.mm == other.mm and \
501            self.dd == other.dd and self.preview == other.preview
502
503    def __lt__(self, other):  # pylint: disable=too-many-return-statements
504        if self.latest or other.latest:
505            if not self.latest and other.latest:
506                return True
507            if self.latest and not other.latest:
508                return False
509            return False
510        if self.yyyy < other.yyyy:
511            return True
512        if self.yyyy == other.yyyy:
513            if self.mm < other.mm:
514                return True
515            if self.mm == other.mm:
516                if self.dd < other.dd:
517                    return True
518                if self.dd == other.dd:
519                    if self.preview and not other.preview:
520                        return True
521        return False
522
523
524def _parse_api_version(api_version):
525    """Will try to parse it as a date, and if not working
526    as semver, and if still not working raise.
527    """
528    try:
529        return _DateAPIFormat(api_version)
530    except ValueError:
531        return _SemVerAPIFormat(api_version)
532
533
534def _cross_api_format_less_than(api_version, other):
535    """LT strategy that supports if types are different.
536
537    For now, let's assume that any Semver is higher than any DateAPI
538    This fits KeyVault, if later we have a counter-example we'll update
539    """
540    api_version = _parse_api_version(api_version)
541    other = _parse_api_version(other)
542
543    if type(api_version) is type(other):
544        return api_version < other
545    return isinstance(api_version, _DateAPIFormat) and isinstance(other, _SemVerAPIFormat)
546
547
548def _validate_api_version(api_version_str, min_api=None, max_api=None):
549    """Validate if api_version is inside the interval min_api/max_api.
550    """
551    if min_api and _cross_api_format_less_than(api_version_str, min_api):
552        return False
553    if max_api and _cross_api_format_less_than(max_api, api_version_str):
554        return False
555    return True
556
557
558def supported_api_version(api_profile, resource_type, min_api=None, max_api=None, operation_group=None):
559    """
560    Returns True if current API version for the resource type satisfies min/max range.
561    To compare profile versions, set resource type to None.
562    Can return a tuple<operation_group, bool> if the resource_type supports SDKProfile.
563    note: Currently supports YYYY-MM-DD, YYYY-MM-DD-preview, YYYY-MM-DD-profile
564    or YYYY-MM-DD-profile-preview  formatted strings.
565    """
566    if not isinstance(resource_type, (ResourceType, CustomResourceType)) and resource_type != PROFILE_TYPE:
567        raise ValueError("'resource_type' is required.")
568    if min_api is None and max_api is None:
569        raise ValueError('At least a min or max version must be specified')
570    api_version_obj = get_api_version(api_profile, resource_type, as_sdk_profile=True) \
571        if isinstance(resource_type, (ResourceType, CustomResourceType)) else api_profile
572    if isinstance(api_version_obj, SDKProfile):
573        api_version_obj = api_version_obj.profile.get(operation_group or '', api_version_obj.default_api_version)
574    return _validate_api_version(api_version_obj, min_api, max_api)
575
576
577def supported_resource_type(api_profile, resource_type):
578    if api_profile == 'latest' or resource_type is None:
579        return True
580    try:
581        return bool(AZURE_API_PROFILES[api_profile][resource_type])
582    except KeyError:
583        return False
584
585
586def _get_attr(sdk_path, mod_attr_path, checked=True):
587    try:
588        attr_mod, attr_path = mod_attr_path.split('#') \
589            if '#' in mod_attr_path else (mod_attr_path, '')
590        full_mod_path = '{}.{}'.format(sdk_path, attr_mod) if attr_mod else sdk_path
591        op = import_module(full_mod_path)
592        if attr_path:
593            # Only load attributes if needed
594            for part in attr_path.split('.'):
595                op = getattr(op, part)
596        return op
597    except (ImportError, AttributeError) as ex:
598        import traceback
599        logger.debug(traceback.format_exc())
600        if checked:
601            return None
602        raise ex
603
604
605def get_client_class(resource_type):
606    return _get_attr(resource_type.import_prefix, '#' + resource_type.client_name)
607
608
609def get_versioned_sdk_path(api_profile, resource_type, operation_group=None):
610    """ Patch the unversioned sdk path to include the appropriate API version for the
611        resource type in question.
612        e.g. Converts azure.mgmt.storage.operations.storage_accounts_operations to
613                      azure.mgmt.storage.v2016_12_01.operations.storage_accounts_operations
614                      azure.keyvault.v7_0.models.KeyVault
615    """
616    api_version = get_api_version(api_profile, resource_type)
617    if api_version is None:
618        return resource_type.import_prefix
619    if isinstance(api_version, _ApiVersions):
620        if operation_group is None:
621            raise ValueError("operation_group is required for resource type '{}'".format(resource_type))
622        api_version = getattr(api_version, operation_group)
623    return '{}.v{}'.format(resource_type.import_prefix, api_version.replace('-', '_').replace('.', '_'))
624
625
626def get_versioned_sdk(api_profile, resource_type, *attr_args, **kwargs):
627    checked = kwargs.get('checked', True)
628    sub_mod_prefix = kwargs.get('mod', None)
629    operation_group = kwargs.get('operation_group', None)
630    sdk_path = get_versioned_sdk_path(api_profile, resource_type, operation_group)
631    if not attr_args:
632        # No attributes to load. Return the versioned sdk
633        return import_module(sdk_path)
634    results = []
635    for mod_attr_path in attr_args:
636        if sub_mod_prefix and '#' not in mod_attr_path:
637            mod_attr_path = '{}#{}'.format(sub_mod_prefix, mod_attr_path)
638        loaded_obj = _get_attr(sdk_path, mod_attr_path, checked)
639        results.append(loaded_obj)
640    return results[0] if len(results) == 1 else results
641