1
0
mirror of https://github.com/ansible/mazer.git synced 2026-02-05 12:45:17 +01:00

Add --editable install option (#141)

This commit is contained in:
jctanner
2018-09-13 09:08:19 -04:00
committed by Chris Houseknecht
parent ba4c673025
commit 5ce6b26907
8 changed files with 79 additions and 25 deletions

View File

@@ -9,5 +9,5 @@ install:
- pip install -r requirements_test.txt
script:
- flake8 ansible_galaxy_cli ansible_galaxy
- make lint
- python setup.py pytest

View File

@@ -27,7 +27,7 @@ clean-test: ## remove test and coverage artifacts
rm -fr .pytest_cache
lint: ## check style with flake8
flake8 ansible_galaxy_cli tests
flake8 ansible_galaxy_cli ansible_galaxy tests
test: ## run tests quickly with the default Python
py.test

View File

@@ -1,4 +1,5 @@
import logging
import os
from ansible_galaxy import display
from ansible_galaxy import exceptions
@@ -29,7 +30,7 @@ def raise_without_ignore(ignore_errors, msg=None, rc=1):
# TODO: this will eventually be replaced by a content_spec 'resolver' that may
# hit galaxy api
def _build_content_set(content_spec_strings, install_content_type, galaxy_context,
namespace_override=None):
namespace_override=None, editable=False):
# TODO: split this into methods that build GalaxyContent items from the content_specs
# and another that installs a set of GalaxyContents
# roles were specified directly, so we'll just go out grab them
@@ -40,7 +41,8 @@ def _build_content_set(content_spec_strings, install_content_type, galaxy_contex
for content_spec_string in content_spec_strings:
content_spec_ = content_spec.content_spec_from_string(content_spec_string.strip(),
namespace_override=namespace_override)
namespace_override=namespace_override,
editable=editable)
log.info('content install content_spec: %s', content_spec_)
@@ -65,19 +67,22 @@ def _build_content_set(content_spec_strings, install_content_type, galaxy_contex
# pass a list of content_spec objects
def install_content_specs(galaxy_context, content_spec_strings, install_content_type,
editable=False,
namespace_override=None,
display_callback=None,
# TODO: error handling callback ?
ignore_errors=False,
no_deps=False,
force_overwrite=False):
log.debug('editable: %s', editable)
log.debug('content_spec_strings: %s', content_spec_strings)
log.debug('install_content_type: %s', install_content_type)
requested_contents = _build_content_set(content_spec_strings=content_spec_strings,
install_content_type=install_content_type,
galaxy_context=galaxy_context,
namespace_override=namespace_override)
namespace_override=namespace_override,
editable=editable)
return install_contents(galaxy_context, requested_contents, install_content_type,
display_callback=display_callback,
@@ -124,6 +129,10 @@ def install_contents(galaxy_context, requested_contents, install_content_type,
# less error prone mid 'transaction'
log.debug('Processing %s as %s', content.name, content.content_type)
if content.content_spec.fetch_method == content_spec.FetchMethods.EDITABLE:
install_editable_content(content)
continue
log.debug('About to find() requested content: %s', content)
# See if we can find metadata and/or download the archive before we try to
@@ -228,3 +237,23 @@ def install_contents(galaxy_context, requested_contents, install_content_type,
display_callback('- dependency %s is already installed, skipping.' % dep_role.name)
return 0
def install_editable_content(content):
'''Link the content path to the local checkout, similar to pip install -e'''
# is it a directory or is it a tarball?
if not os.path.isdir(os.path.abspath(content.src)):
log.warning("%s needs to be a local directory for an editable install" % content.src)
raise_without_ignore(None, None)
namespace = content.content_spec.namespace
repository = content.content_spec.name
dst_ns_root = os.path.join(content.path, namespace)
dst_repo_root = os.path.join(content.path, namespace, repository)
if not os.path.exists(dst_ns_root):
os.makedirs(dst_ns_root)
if not os.path.exists(dst_repo_root):
os.symlink(os.path.abspath(content.src), dst_repo_root)

View File

@@ -3,6 +3,7 @@ import os
from ansible_galaxy import galaxy_content_spec
from ansible_galaxy import content_spec_parse
from ansible_galaxy import exceptions
from ansible_galaxy.models.content_spec import ContentSpec
log = logging.getLogger(__name__)
@@ -21,9 +22,10 @@ class FetchMethods(object):
LOCAL_FILE = 'LOCAL_FILE'
REMOTE_URL = 'REMOTE_URL'
GALAXY_URL = 'GALAXY_URL'
EDITABLE = 'EDITABLE'
def choose_content_fetch_method(content_spec_string):
def choose_content_fetch_method(content_spec_string, editable=False):
log.debug('content_spec_string: %s', content_spec_string)
if is_scm(content_spec_string):
@@ -32,15 +34,22 @@ def choose_content_fetch_method(content_spec_string):
comma_parts = content_spec_string.split(',', 1)
potential_filename = comma_parts[0]
if os.path.isfile(potential_filename):
fetch_method = None
if editable and os.path.isdir(potential_filename):
fetch_method = FetchMethods.EDITABLE
elif os.path.isfile(potential_filename):
# installing a local tar.gz
return FetchMethods.LOCAL_FILE
if '://' in content_spec_string:
return FetchMethods.REMOTE_URL
# if it doesnt look like anything else, assume it's galaxy
return FetchMethods.GALAXY_URL
fetch_method = FetchMethods.LOCAL_FILE
elif '://' in content_spec_string:
fetch_method = FetchMethods.REMOTE_URL
elif '.' in content_spec_string and len(content_spec_string.split('.', 1)) == 2:
fetch_method = FetchMethods.GALAXY_URL
else:
msg = ('Failed to determine fetch method for content spec %s. '
'Expecting a Galaxy name, SCM path, remote URL, path to a local '
'archive file, or -e option and a directory path' % content_spec_string)
raise exceptions.GalaxyError(msg)
return fetch_method
def resolve(data):
@@ -70,8 +79,8 @@ def resolve(data):
return data
def spec_data_from_string(content_spec_string):
fetch_method = choose_content_fetch_method(content_spec_string)
def spec_data_from_string(content_spec_string, editable=False):
fetch_method = choose_content_fetch_method(content_spec_string, editable=editable)
log.debug('fetch_method: %s', fetch_method)
@@ -91,8 +100,8 @@ def spec_data_from_string(content_spec_string):
return spec_data
def content_spec_from_string(content_spec_string, namespace_override=None):
spec_data = spec_data_from_string(content_spec_string)
def content_spec_from_string(content_spec_string, namespace_override=None, editable=False):
spec_data = spec_data_from_string(content_spec_string, editable=editable)
log.debug('spec_data: %s', spec_data)
@@ -115,7 +124,3 @@ def content_spec_from_string(content_spec_string, namespace_override=None):
spec_string=spec_data.get('spec_string'),
fetch_method=spec_data.get('fetch_method'),
src=spec_data.get('src'))
# {'src': 'testing.fake_role_name', 'name': 'fake_role_name', 'namespace': 'testing', 'version': None, 'scm': None,
# 'spec_string': 'testing.fake_role_name', 'fetch_method': 'GALAXY_URL'}
# return ContentSpec(**spec_data)

View File

@@ -101,6 +101,8 @@ class GalaxyCLI(cli.CLI):
self.parser.add_option('-g', '--global', dest='global_install', action='store_true',
help='Install content to the path containing your global or system-wide content. The default is the '
'global_content_path configured in your mazer.yml file (/usr/share/ansible/content, if not configured)')
self.parser.add_option('-e', '--editable', dest='editable_install', action='store_true',
help='Link a local directory into the content path for development and testing')
self.parser.add_option('-i', '--ignore-errors', dest='ignore_errors', action='store_true', default=False,
help='Ignore errors and continue with the next specified repo.')
self.parser.add_option('-n', '--no-deps', dest='no_deps', action='store_true', default=False, help='Don\'t download roles listed as dependencies')
@@ -303,6 +305,7 @@ class GalaxyCLI(cli.CLI):
try:
rc = install.install_content_specs(galaxy_context,
editable=self.options.editable_install,
content_spec_strings=requested_content_specs,
install_content_type=install_content_type,
namespace_override=self.options.namespace,

View File

@@ -1,9 +1,10 @@
import logging
import os
import tempfile
import pytest
from ansible_galaxy import content_spec
from ansible_galaxy import exceptions
from ansible_galaxy.models.content_spec import ContentSpec
log = logging.getLogger(__name__)
@@ -63,3 +64,21 @@ def test_content_spec_from_string(content_spec_case):
# assert attr.asdict(result) == attr.asdict(content_spec_case['expected'])
assert result == content_spec_case['expected']
def test_content_spec_editable():
tmpdir = tempfile.mkdtemp()
result = content_spec.content_spec_from_string(tmpdir, editable=True)
os.rmdir(tmpdir)
assert result.name == tmpdir
assert result.fetch_method == 'EDITABLE'
@pytest.mark.xfail(raises=exceptions.GalaxyError)
def test_content_spec_fail():
content_spec.content_spec_from_string('foo.')
@pytest.mark.xfail(raises=exceptions.GalaxyError)
def test_content_editable_fail():
content_spec.content_spec_from_string('foo', editable=True)

View File

@@ -4,7 +4,6 @@ import pytest
from ansible_galaxy import content_spec_parse
from ansible_galaxy import exceptions
from ansible_galaxy import galaxy_content_spec
log = logging.getLogger(__name__)

View File

@@ -6,7 +6,6 @@
import pytest
@pytest.fixture
def response():
"""Sample pytest fixture.