Skip to content

Commit 33caf11

Browse files
committed
detach azure stack from profiles
1 parent db505d1 commit 33caf11

38 files changed

+17801
-82
lines changed

src/azure-cli/azure/cli/command_modules/vm/__init__.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,64 @@ def load_arguments(self, command):
6565
pass
6666

6767

68-
COMMAND_LOADER_CLS = ComputeCommandsLoader
68+
class AzureStackComputeCommandsLoader(AzCommandsLoader):
69+
70+
def __init__(self, cli_ctx=None):
71+
from azure.cli.core.commands import CliCommandType
72+
compute_custom = CliCommandType(
73+
operations_tmpl='azure.cli.command_modules.vm.azure_stack.custom#{}',
74+
operation_group='virtual_machines'
75+
)
76+
super().__init__(cli_ctx=cli_ctx,
77+
resource_type=ResourceType.MGMT_COMPUTE,
78+
operation_group='virtual_machines',
79+
custom_command_type=compute_custom)
80+
81+
def load_command_table(self, args):
82+
from azure.cli.command_modules.vm.azure_stack.commands import load_command_table
83+
from azure.cli.core.aaz import load_aaz_command_table
84+
try:
85+
from . import aaz
86+
except ImportError:
87+
aaz = None
88+
89+
if aaz:
90+
load_aaz_command_table(
91+
loader=self,
92+
aaz_pkg_name=aaz.__name__,
93+
args=args
94+
)
95+
load_command_table(self, args)
96+
97+
profile = self.get_module_by_profile("commands")
98+
if profile and hasattr(profile, 'load_command_table'):
99+
profile.load_command_table(self, args)
100+
101+
return self.command_table
102+
103+
def load_arguments(self, command):
104+
from azure.cli.command_modules.vm.azure_stack._params import load_arguments
105+
load_arguments(self, command)
106+
107+
profile = self.get_module_by_profile("_params")
108+
if profile and hasattr(profile, 'load_arguments'):
109+
profile.load_arguments(self, command)
110+
111+
def get_module_name_by_profile(self, module_name):
112+
from azure.cli.core.aaz.utils import get_aaz_profile_module_name
113+
profile_module_name = get_aaz_profile_module_name(profile_name=self.cli_ctx.cloud.profile)
114+
if module_name:
115+
return f'azure.cli.command_modules.vm.azure_stack.{profile_module_name}.{module_name}'
116+
return f'azure.cli.command_modules.vm.azure_stack.{profile_module_name}'
117+
118+
def get_module_by_profile(self, name):
119+
import importlib
120+
module_name = self.get_module_name_by_profile(name)
121+
return importlib.import_module(module_name)
122+
123+
124+
def get_command_loader(cli_ctx):
125+
if cli_ctx.cloud.profile.lower() != "latest":
126+
return AzureStackComputeCommandsLoader
127+
128+
return ComputeCommandsLoader
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
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+
# --------------------------------------------------------------------------------------------
Lines changed: 282 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,282 @@
1+
# --------------------------------------------------------------------------
2+
# Copyright (c) Microsoft Corporation. All rights reserved.
3+
# Licensed under the MIT License. See License.txt in the project root for
4+
# license information.
5+
#
6+
# Code generated by Microsoft (R) AutoRest Code Generator.
7+
# Changes may cause incorrect behavior and will be lost if the code is
8+
# regenerated.
9+
#
10+
# Generation mode: Incremental
11+
# --------------------------------------------------------------------------
12+
13+
# pylint: disable=too-many-statements
14+
import json
15+
16+
from knack.log import get_logger
17+
from knack.util import CLIError
18+
19+
from azure.cli.core.commands.arm import resource_exists
20+
from azure.cli.core.commands.parameters import get_one_of_subscription_locations
21+
from ._client_factory import _compute_client_factory
22+
23+
try:
24+
from .manual.action import * # noqa: F403, pylint: disable=unused-wildcard-import,wildcard-import
25+
except ImportError:
26+
pass
27+
28+
logger = get_logger(__name__)
29+
30+
31+
def _resource_not_exists(cli_ctx, resource_type):
32+
def _handle_resource_not_exists(namespace):
33+
ns, t = resource_type.split('/')
34+
# pylint: disable=protected-access
35+
if resource_exists(cli_ctx, namespace._subscription_id, namespace.resource_group_name, namespace.name, ns, t):
36+
raise CLIError('Resource {} of type {} in group {} already exists.'.format(
37+
namespace.name,
38+
resource_type,
39+
namespace.resource_group_name))
40+
41+
return _handle_resource_not_exists
42+
43+
44+
def _get_thread_count():
45+
return 5 # don't increase too much till https://github.com/Azure/msrestazure-for-python/issues/6 is fixed
46+
47+
48+
def load_images_thru_services(cli_ctx, publisher, offer, sku, location, edge_zone, architecture):
49+
from concurrent.futures import ThreadPoolExecutor, as_completed
50+
51+
all_images = []
52+
client = _compute_client_factory(cli_ctx)
53+
if location is None:
54+
location = get_one_of_subscription_locations(cli_ctx)
55+
56+
def _load_images_from_publisher(publisher):
57+
from azure.core.exceptions import ResourceNotFoundError
58+
try:
59+
if edge_zone is not None:
60+
offers = edge_zone_client.list_offers(location=location, edge_zone=edge_zone, publisher_name=publisher)
61+
else:
62+
offers = client.virtual_machine_images.list_offers(location=location, publisher_name=publisher)
63+
except ResourceNotFoundError as e:
64+
logger.warning(str(e))
65+
return
66+
if offer:
67+
offers = [o for o in offers if _matched(offer, o.name)]
68+
for o in offers:
69+
try:
70+
if edge_zone is not None:
71+
skus = edge_zone_client.list_skus(location=location, edge_zone=edge_zone,
72+
publisher_name=publisher, offer=o.name)
73+
else:
74+
skus = client.virtual_machine_images.list_skus(location=location, publisher_name=publisher,
75+
offer=o.name)
76+
except ResourceNotFoundError as e:
77+
logger.warning(str(e))
78+
continue
79+
if sku:
80+
skus = [s for s in skus if _matched(sku, s.name)]
81+
for s in skus:
82+
try:
83+
expand = "properties/imageDeprecationStatus"
84+
if edge_zone is not None:
85+
images = edge_zone_client.list(location=location, edge_zone=edge_zone, publisher_name=publisher,
86+
offer=o.name, skus=s.name, expand=expand)
87+
else:
88+
images = client.virtual_machine_images.list(location=location, publisher_name=publisher,
89+
offer=o.name, skus=s.name, expand=expand)
90+
except ResourceNotFoundError as e:
91+
logger.warning(str(e))
92+
continue
93+
for i in images:
94+
image_info = {
95+
'publisher': publisher,
96+
'offer': o.name,
97+
'sku': s.name,
98+
'version': i.name,
99+
'architecture': i.additional_properties.get("properties", {}).get("architecture", None) or "",
100+
'imageDeprecationStatus': i.additional_properties.get(
101+
"properties", {}).get("imageDeprecationStatus", {}) or ""
102+
}
103+
if edge_zone is not None:
104+
image_info['edge_zone'] = edge_zone
105+
if architecture and architecture != image_info['architecture']:
106+
continue
107+
all_images.append(image_info)
108+
109+
if edge_zone is not None:
110+
from azure.cli.core.commands.client_factory import get_mgmt_service_client
111+
from azure.cli.core.profiles import ResourceType
112+
edge_zone_client = get_mgmt_service_client(cli_ctx,
113+
ResourceType.MGMT_COMPUTE).virtual_machine_images_edge_zone
114+
publishers = edge_zone_client.list_publishers(location=location, edge_zone=edge_zone)
115+
else:
116+
publishers = client.virtual_machine_images.list_publishers(location=location)
117+
if publisher:
118+
publishers = [p for p in publishers if _matched(publisher, p.name)]
119+
120+
publisher_num = len(publishers)
121+
if publisher_num > 1:
122+
with ThreadPoolExecutor(max_workers=_get_thread_count()) as executor:
123+
tasks = [executor.submit(_load_images_from_publisher, p.name) for p in publishers]
124+
for t in as_completed(tasks):
125+
t.result() # don't use the result but expose exceptions from the threads
126+
elif publisher_num == 1:
127+
_load_images_from_publisher(publishers[0].name)
128+
129+
return all_images
130+
131+
132+
def load_images_from_aliases_doc(cli_ctx, publisher=None, offer=None, sku=None, architecture=None):
133+
import requests
134+
from azure.cli.core.cloud import CloudEndpointNotSetException
135+
from azure.cli.core.util import should_disable_connection_verify
136+
from azure.cli.command_modules.vm._alias import alias_json
137+
try:
138+
target_url = cli_ctx.cloud.endpoints.vm_image_alias_doc
139+
except CloudEndpointNotSetException:
140+
logger.warning("'endpoint_vm_image_alias_doc' isn't configured. Please invoke 'az cloud update' to configure "
141+
"it or use '--all' to retrieve images from server. Use local copy instead.")
142+
dic = json.loads(alias_json)
143+
else:
144+
# under hack mode(say through proxies with unsigned cert), opt out the cert verification
145+
try:
146+
response = requests.get(target_url, verify=(not should_disable_connection_verify()))
147+
if response.status_code == 200:
148+
dic = json.loads(response.content.decode())
149+
else:
150+
logger.warning("Failed to retrieve image alias doc '%s'. Error: '%s'. Use local copy instead.",
151+
target_url, response)
152+
dic = json.loads(alias_json)
153+
except requests.exceptions.ConnectionError:
154+
logger.warning("Failed to retrieve image alias doc '%s'. Error: 'ConnectionError'. Use local copy instead.",
155+
target_url)
156+
dic = json.loads(alias_json)
157+
try:
158+
all_images = []
159+
result = (dic['outputs']['aliases']['value'])
160+
for v in result.values(): # loop around os
161+
for alias, vv in v.items(): # loop around distros
162+
all_images.append({
163+
'urnAlias': alias,
164+
'publisher': vv['publisher'],
165+
'offer': vv['offer'],
166+
'sku': vv['sku'],
167+
'version': vv['version'],
168+
'architecture': vv['architecture']
169+
})
170+
171+
all_images = [i for i in all_images if (_matched(publisher, i['publisher']) and
172+
_matched(offer, i['offer']) and
173+
_matched(sku, i['sku']) and
174+
_matched(architecture, i['architecture']))]
175+
return all_images
176+
except KeyError:
177+
raise CLIError('Could not retrieve image list from {} or local copy'.format(target_url))
178+
179+
180+
def load_extension_images_thru_services(cli_ctx, publisher, name, version, location,
181+
show_latest=False, partial_match=True):
182+
from concurrent.futures import ThreadPoolExecutor, as_completed
183+
from packaging.version import parse # pylint: disable=no-name-in-module,import-error
184+
all_images = []
185+
client = _compute_client_factory(cli_ctx)
186+
if location is None:
187+
location = get_one_of_subscription_locations(cli_ctx)
188+
189+
def _load_extension_images_from_publisher(publisher):
190+
from azure.core.exceptions import ResourceNotFoundError
191+
try:
192+
types = client.virtual_machine_extension_images.list_types(location, publisher)
193+
except ResourceNotFoundError as e:
194+
# PIR image publishers might not have any extension images, exception could raise
195+
logger.warning(str(e))
196+
types = []
197+
if name:
198+
types = [t for t in types if _matched(name, t.name, partial_match)]
199+
for t in types:
200+
try:
201+
versions = client.virtual_machine_extension_images.list_versions(
202+
location, publisher, t.name)
203+
except ResourceNotFoundError as e:
204+
logger.warning(str(e))
205+
continue
206+
if version:
207+
versions = [v for v in versions if _matched(version, v.name, partial_match)]
208+
209+
if show_latest:
210+
# pylint: disable=no-member
211+
versions.sort(key=lambda v: parse(v.name), reverse=True)
212+
try:
213+
all_images.append({
214+
'publisher': publisher,
215+
'name': t.name,
216+
'version': versions[0].name})
217+
except IndexError:
218+
pass # if no versions for this type continue to next type.
219+
else:
220+
for v in versions:
221+
all_images.append({
222+
'publisher': publisher,
223+
'name': t.name,
224+
'version': v.name})
225+
226+
publishers = client.virtual_machine_images.list_publishers(location=location)
227+
if publisher:
228+
publishers = [p for p in publishers if _matched(publisher, p.name, partial_match)]
229+
230+
publisher_num = len(publishers)
231+
if publisher_num > 1:
232+
with ThreadPoolExecutor(max_workers=_get_thread_count()) as executor:
233+
tasks = [executor.submit(_load_extension_images_from_publisher,
234+
p.name) for p in publishers]
235+
for t in as_completed(tasks):
236+
t.result() # don't use the result but expose exceptions from the threads
237+
elif publisher_num == 1:
238+
_load_extension_images_from_publisher(publishers[0].name)
239+
240+
return all_images
241+
242+
243+
def get_vm_sizes(cli_ctx, location):
244+
return list(_compute_client_factory(cli_ctx).virtual_machine_sizes.list(location))
245+
246+
247+
def _matched(pattern, string, partial_match=True):
248+
if not pattern:
249+
return True # empty pattern means wildcard-match
250+
pattern, string = pattern.lower(), string.lower()
251+
return pattern in string if partial_match else pattern == string
252+
253+
254+
def _create_image_instance(publisher, offer, sku, version):
255+
return {
256+
'publisher': publisher,
257+
'offer': offer,
258+
'sku': sku,
259+
'version': version
260+
}
261+
262+
263+
def _get_latest_image_version(cli_ctx, location, publisher, offer, sku, edge_zone=None):
264+
from azure.cli.core.azclierror import InvalidArgumentValueError
265+
if edge_zone is not None:
266+
from azure.cli.core.commands.client_factory import get_mgmt_service_client
267+
from azure.cli.core.profiles import ResourceType
268+
edge_zone_client = get_mgmt_service_client(cli_ctx, ResourceType.MGMT_COMPUTE).virtual_machine_images_edge_zone
269+
top_one = edge_zone_client.list(location, edge_zone, publisher, offer, sku, top=1, orderby='name desc')
270+
if not top_one:
271+
raise InvalidArgumentValueError("Can't resolve the version of '{}:{}:{}:{}'"
272+
.format(publisher, offer, sku, edge_zone))
273+
else:
274+
top_one = _compute_client_factory(cli_ctx).virtual_machine_images.list(location,
275+
publisher,
276+
offer,
277+
sku,
278+
top=1,
279+
orderby='name desc')
280+
if not top_one:
281+
raise InvalidArgumentValueError("Can't resolve the version of '{}:{}:{}'".format(publisher, offer, sku))
282+
return top_one[0].name

0 commit comments

Comments
 (0)