Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Extensions] Enable CLI extensions to include packages in the 'azure' namespace #13163

Merged
merged 15 commits into from
Jun 18, 2020
Merged
9 changes: 1 addition & 8 deletions doc/extensions/authoring.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,7 @@ See [Extension Metadata](metadata.md) for more information.
### Limit dependencies in setup.py

- Before adding a dependency to your setup.py, check that it's not already available in [azure-cli-core setup.py](https://github.com/Azure/azure-cli/blob/master/src/azure-cli-core/setup.py).
- For Azure SDKs, use autorest to generate your SDK into a package that isn't under the `azure` directory.
- You can verify that your extension doesn't use the `azure` directory by opening your `.whl` and opening the `top_level.txt` file in the `*.dist-info` directory. It should not contain `azure`.
- Azure SDK or Azure Management SDK dependencies may be overridden by the versions installed as requirements of azure-cli-core. If you use any, test carefully, gracefully handle API changes, and be prepared to release updates. You might also consider rebasing the libraries under a different namespace (besides `azure`) to avoid conflicting with core CLI functionality.

### How do I know I'm using my dev extension(s)?

Expand All @@ -126,10 +125,4 @@ See [Extension Metadata](metadata.md) for more information.
- e.g. `python3.6 -m venv env36` and `python3.8 -m venv env38`.


:zap: IMPORTANT :zap:
- Since azure-cli uses the `azure` directory, no extension can use this.
- This applies to all other dependencies used by azure-cli-core.
- See [this Stack Overflow question](https://stackoverflow.com/questions/8936884/python-import-path-packages-with-the-same-name-in-different-folders).


Also, see the [FAQ](faq.md).
18 changes: 18 additions & 0 deletions src/azure-cli-core/azure/cli/core/extension/operations.py
Original file line number Diff line number Diff line change
Expand Up @@ -347,6 +347,24 @@ def reload_extension(extension_name, extension_module=None):
def add_extension_to_path(extension_name, ext_dir=None):
ext_dir = ext_dir or get_extension(extension_name).path
sys.path.append(ext_dir)
# If this path update should have made a new "azure" module available,
# extend the existing module with its path. This allows extensions to
# include (or depend on) Azure SDK modules that are not yet part of
# the CLI. This applies to both the "azure" and "azure.mgmt" namespaces,
# but ensures that modules installed by the CLI take priority.
azure_dir = os.path.join(ext_dir, "azure")
if os.path.isdir(azure_dir):
import azure
azure.__path__.append(azure_dir)
azure_mgmt_dir = os.path.join(azure_dir, "mgmt")
if os.path.isdir(azure_mgmt_dir):
try:
# Should have been imported already, so this will be quick
import azure.mgmt
except ImportError:
pass
else:
azure.mgmt.__path__.append(azure_mgmt_dir)


def get_lsb_release():
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,12 @@
import shutil
import hashlib
import mock
import sys

from azure.cli.core.util import CLIError
from azure.cli.core.extension import build_extension_path
from azure.cli.core.extension.operations import (list_extensions, add_extension, show_extension,
remove_extension, update_extension,
from azure.cli.core.extension import get_extension, build_extension_path
from azure.cli.core.extension.operations import (add_extension_to_path, list_extensions, add_extension,
show_extension, remove_extension, update_extension,
list_available_extensions, OUT_KEY_NAME, OUT_KEY_VERSION,
OUT_KEY_METADATA, OUT_KEY_PATH)
from azure.cli.core.extension._resolve import NoExtensionCandidatesError
Expand Down Expand Up @@ -387,6 +388,56 @@ def test_update_extension_extra_index_url(self):
ext = show_extension(MY_EXT_NAME)
self.assertEqual(ext[OUT_KEY_VERSION], '0.0.4+dev')

def test_add_extension_to_path(self):
add_extension(cmd=self.cmd, source=MY_EXT_SOURCE)
num_exts = len(list_extensions())
self.assertEqual(num_exts, 1)
ext = get_extension('myfirstcliextension')
old_path = sys.path[:]
try:
add_extension_to_path(ext.name)
self.assertSequenceEqual(old_path, sys.path[:-1])
self.assertEqual(ext.path, sys.path[-1])
finally:
sys.path[:] = old_path

def test_add_extension_azure_to_path(self):
import azure
import azure.mgmt
old_path_0 = list(sys.path)
old_path_1 = list(azure.__path__)
old_path_2 = list(azure.mgmt.__path__)

add_extension(cmd=self.cmd, source=MY_EXT_SOURCE)
ext = get_extension('myfirstcliextension')
azure_dir = os.path.join(ext.path, "azure")
azure_mgmt_dir = os.path.join(azure_dir, "mgmt")
os.mkdir(azure_dir)
os.mkdir(azure_mgmt_dir)

try:
add_extension_to_path(ext.name)
new_path_1 = list(azure.__path__)
new_path_2 = list(azure.mgmt.__path__)
finally:
sys.path.remove(ext.path)
remove_extension(ext.name)
if isinstance(azure.__path__, list):
azure.__path__[:] = old_path_1
else:
list(azure.__path__)
if isinstance(azure.mgmt.__path__, list):
azure.mgmt.__path__[:] = old_path_2
else:
list(azure.mgmt.__path__)
self.assertSequenceEqual(old_path_1, new_path_1[:-1])
self.assertSequenceEqual(old_path_2, new_path_2[:-1])
self.assertEqual(azure_dir, new_path_1[-1])
self.assertEqual(azure_mgmt_dir, new_path_2[-1])
self.assertSequenceEqual(old_path_0, list(sys.path))
self.assertSequenceEqual(old_path_1, list(azure.__path__))
self.assertSequenceEqual(old_path_2, list(azure.mgmt.__path__))

def _setup_cmd(self):
cmd = mock.MagicMock()
cmd.cli_ctx = DummyCli()
Expand Down