diff --git a/src/azure-cli-core/azure/cli/core/extension/__init__.py b/src/azure-cli-core/azure/cli/core/extension/__init__.py index 83d478e858e..a30c47c9be7 100644 --- a/src/azure-cli-core/azure/cli/core/extension/__init__.py +++ b/src/azure-cli-core/azure/cli/core/extension/__init__.py @@ -26,8 +26,6 @@ EXTENSIONS_MOD_PREFIX = 'azext_' -WHL_METADATA_FILENAME = 'metadata.json' -EGG_INFO_METADATA_FILE_NAME = 'PKG-INFO' # used for dev packages AZEXT_METADATA_FILENAME = 'azext_metadata.json' EXT_METADATA_MINCLICOREVERSION = 'azext.minCliCoreVersion' @@ -135,12 +133,16 @@ def get_version(self): def get_metadata(self): from glob import glob + metadata = {} ext_dir = self.path or get_extension_path(self.name) - if not ext_dir or not os.path.isdir(ext_dir): return None - info_dirs = glob(os.path.join(ext_dir, self.name.replace('-', '_') + '-' + '*.dist-info')) + + # include *.egg-info and *.dist-info + info_dirs = glob(os.path.join(ext_dir, self.name.replace('-', '_') + '*.*-info')) + if not info_dirs: + return None azext_metadata = WheelExtension.get_azext_metadata(ext_dir) if azext_metadata: @@ -148,11 +150,17 @@ def get_metadata(self): for dist_info_dirname in info_dirs: try: - ext_whl_metadata = pkginfo.Wheel(dist_info_dirname) + if dist_info_dirname.endswith('.egg-info'): + ext_whl_metadata = pkginfo.Develop(dist_info_dirname) + elif dist_info_dirname.endswith('.dist-info'): + ext_whl_metadata = pkginfo.Wheel(dist_info_dirname) + else: + raise ValueError() + if self.name == ext_whl_metadata.name: metadata.update(vars(ext_whl_metadata)) except ValueError: - logger.warning('extension % contains invalid metadata for Python Package', self.name) + logger.warning('extension %s contains invalid metadata for Python Package', self.name) return metadata @@ -179,13 +187,13 @@ def get_all(): if os.path.isdir(EXTENSIONS_DIR): for ext_name in os.listdir(EXTENSIONS_DIR): ext_path = os.path.join(EXTENSIONS_DIR, ext_name) - pattern = os.path.join(ext_path, '*.dist-info') + pattern = os.path.join(ext_path, '*.*-info') # include *.egg-info and *.dist-info if os.path.isdir(ext_path) and glob(pattern): exts.append(WheelExtension(ext_name, ext_path)) if os.path.isdir(EXTENSIONS_SYS_DIR): for ext_name in os.listdir(EXTENSIONS_SYS_DIR): ext_path = os.path.join(EXTENSIONS_SYS_DIR, ext_name) - pattern = os.path.join(ext_path, '*.dist-info') + pattern = os.path.join(ext_path, '*.*-info') # include *.egg-info and *.dist-info if os.path.isdir(ext_path) and glob(pattern): ext = WheelExtension(ext_name, ext_path) if ext not in exts: @@ -205,7 +213,11 @@ def get_metadata(self): ext_dir = self.path if not ext_dir or not os.path.isdir(ext_dir): return None + egg_info_dirs = [f for f in os.listdir(ext_dir) if f.endswith('.egg-info')] + if not egg_info_dirs: + return None + azext_metadata = DevExtension.get_azext_metadata(ext_dir) if azext_metadata: metadata.update(azext_metadata) diff --git a/src/azure-cli-core/azure/cli/core/extension/tests/latest/__init__.py b/src/azure-cli-core/azure/cli/core/extension/tests/latest/__init__.py index 34913fb394d..4ee64e56f3f 100644 --- a/src/azure-cli-core/azure/cli/core/extension/tests/latest/__init__.py +++ b/src/azure-cli-core/azure/cli/core/extension/tests/latest/__init__.py @@ -2,3 +2,21 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. See License.txt in the project root for license information. # -------------------------------------------------------------------------------------------- + +import os +import unittest +import tempfile +import shutil + + +def get_test_data_file(filename): + return os.path.join(os.path.dirname(os.path.realpath(__file__)), 'data', filename) + + +class ExtensionTypeTestMixin(unittest.TestCase): + + def setUp(self): + self.ext_dir = tempfile.mkdtemp() + + def tearDown(self): + shutil.rmtree(self.ext_dir, ignore_errors=True) diff --git a/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/hello-0.1.0.tar.gz b/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/hello-0.1.0.tar.gz new file mode 100644 index 00000000000..40e60960b70 Binary files /dev/null and b/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/hello-0.1.0.tar.gz differ diff --git a/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/wheel_0_30_0_packed_extension-0.1.0-py3-none-any.whl b/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/wheel_0_30_0_packed_extension-0.1.0-py3-none-any.whl new file mode 100644 index 00000000000..452219407b9 Binary files /dev/null and b/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/wheel_0_30_0_packed_extension-0.1.0-py3-none-any.whl differ diff --git a/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/wheel_0_31_0_packed_extension-0.1.0-py3-none-any.whl b/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/wheel_0_31_0_packed_extension-0.1.0-py3-none-any.whl new file mode 100644 index 00000000000..802ed17e47c Binary files /dev/null and b/src/azure-cli-core/azure/cli/core/extension/tests/latest/data/wheel_0_31_0_packed_extension-0.1.0-py3-none-any.whl differ diff --git a/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_dev_type_extension.py b/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_dev_type_extension.py new file mode 100644 index 00000000000..5cc5d67f986 --- /dev/null +++ b/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_dev_type_extension.py @@ -0,0 +1,44 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import tempfile +import zipfile +import tarfile +import shutil +import unittest + +from azure.cli.core.extension import DevExtension +from azure.cli.core.extension.tests.latest import ExtensionTypeTestMixin, get_test_data_file + + +class TestWheelTypeExtensionMetadata(ExtensionTypeTestMixin): + + def test_reading_wheel_type_extension_from_develop_mode(self): + """ + Test develop type extension. + For scenario that user are developing extension via azdev + """ + + source_code_packaged = get_test_data_file('hello-0.1.0.tar.gz') + + with tarfile.open(source_code_packaged, 'r:gz') as tar: + tar.extractall(self.ext_dir) + + ext_name, ext_version = 'hello', '0.1.0' + + ext_extension = DevExtension(ext_name, os.path.join(self.ext_dir, ext_name + '-' + ext_version)) + metadata = ext_extension.get_metadata() # able to read metadata from source code + + # assert Python metadata + self.assertEqual(metadata['name'], ext_name) + self.assertEqual(metadata['version'], ext_version) + self.assertEqual(metadata['author'], 'Microsoft Corporation') + self.assertNotIn('metadata.json', os.listdir(os.path.join(self.ext_dir, ext_name + '-' + ext_version))) + + # assert Azure CLI extended metadata + self.assertTrue(metadata['azext.isPreview']) + self.assertTrue(metadata['azext.isExperimental']) + self.assertEqual(metadata['azext.minCliCoreVersion'], '2.0.67') diff --git a/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_wheel_type_extension.py b/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_wheel_type_extension.py new file mode 100644 index 00000000000..f740a5c65f1 --- /dev/null +++ b/src/azure-cli-core/azure/cli/core/extension/tests/latest/test_wheel_type_extension.py @@ -0,0 +1,107 @@ +# -------------------------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for license information. +# -------------------------------------------------------------------------------------------- + +import os +import tempfile +import zipfile +import tarfile +import shutil +import unittest + +from azure.cli.core.extension import WheelExtension +from azure.cli.core.extension.tests.latest import ExtensionTypeTestMixin, get_test_data_file + + +class TestWheelTypeExtensionMetadata(ExtensionTypeTestMixin): + + def test_reading_wheel_type_0_30_0_extension_metadata(self): + """ + Test wheel==0.30.0 containing metadata.json and we can handle it properly. + For scenario like 'az extenion add'. + """ + + # this wheel contains metadata.json and METADATA + wheel_0_30_0_packed = get_test_data_file('wheel_0_30_0_packed_extension-0.1.0-py3-none-any.whl') + + zf = zipfile.ZipFile(wheel_0_30_0_packed) + zf.extractall(self.ext_dir) + + ext_name, ext_version = 'hello', '0.1.0' + + whl_extension = WheelExtension(ext_name, self.ext_dir) + metadata = whl_extension.get_metadata() # able to read metadata from wheel==0.30.0 built extension + + # wheel type extension generates .dist-info + dist_info = ext_name + '-' + ext_version + '.dist-info' + + # assert Python metadata + self.assertEqual(metadata['name'], ext_name) + self.assertEqual(metadata['version'], ext_version) + self.assertEqual(metadata['author'], 'Microsoft Corporation') + self.assertIn('metadata.json', os.listdir(os.path.join(self.ext_dir, dist_info))) + + # assert Azure CLI extended metadata + self.assertTrue(metadata['azext.isPreview']) + self.assertTrue(metadata['azext.isExperimental']) + self.assertEqual(metadata['azext.minCliCoreVersion'], '2.0.67') + + def test_reading_wheel_type_0_31_0_extension_metadata(self): + """ + Test wheel>=0.31.0 not containing metadata.json but we can still handle it properly. + For scenario like 'az extenion add'. + """ + + # this wheel contains METADATA only + wheel_0_31_0_packed = get_test_data_file('wheel_0_31_0_packed_extension-0.1.0-py3-none-any.whl') + + zf = zipfile.ZipFile(wheel_0_31_0_packed) + zf.extractall(self.ext_dir) + + ext_name, ext_version = 'hello', '0.1.0' + + whl_extension = WheelExtension(ext_name, self.ext_dir) + metadata = whl_extension.get_metadata() # able to read metadata from wheel==0.30.0 built extension + + # wheel type extension generates .dist-info + dist_info = ext_name + '-' + ext_version + '.dist-info' + + # assert Python metadata + self.assertEqual(metadata['name'], ext_name) + self.assertEqual(metadata['version'], ext_version) + self.assertEqual(metadata['author'], 'Microsoft Corporation') + self.assertNotIn('metadata.json', os.listdir(os.path.join(self.ext_dir, dist_info))) + + # assert Azure CLI extended metadata + self.assertTrue(metadata['azext.isPreview']) + self.assertTrue(metadata['azext.isExperimental']) + self.assertEqual(metadata['azext.minCliCoreVersion'], '2.0.67') + + def test_reading_wheel_type_extension_from_develop_mode(self): + """ + Test wheel type extension but installing from source code. + For scenario that user are developing extension via 'pip install -e' directlly + and load it from _CUSTOM_EXT_DIR or GLOBAL_CONFIG_DIR + """ + + source_code_packaged = get_test_data_file('hello-0.1.0.tar.gz') + + with tarfile.open(source_code_packaged, 'r:gz') as tar: + tar.extractall(self.ext_dir) + + ext_name, ext_version = 'hello', '0.1.0' + + ext_extension = WheelExtension(ext_name, os.path.join(self.ext_dir, ext_name + '-' + ext_version)) + metadata = ext_extension.get_metadata() # able to read metadata from source code even in wheel type extension + + # assert Python metadata + self.assertEqual(metadata['name'], ext_name) + self.assertEqual(metadata['version'], ext_version) + self.assertEqual(metadata['author'], 'Microsoft Corporation') + self.assertNotIn('metadata.json', os.listdir(os.path.join(self.ext_dir, ext_name + '-' + ext_version))) + + # assert Azure CLI extended metadata + self.assertTrue(metadata['azext.isPreview']) + self.assertTrue(metadata['azext.isExperimental']) + self.assertEqual(metadata['azext.minCliCoreVersion'], '2.0.67')