Change repositories list in config

Allow use to add arbitrary repository to the list.

Change-Id: I7895a9a66d4499aabcd5eb9bd037e5b2bc1fa83c
This commit is contained in:
Yuriy Taraday 2016-10-11 16:28:51 +03:00
parent cb6bada442
commit 855fda0ef3
11 changed files with 147 additions and 128 deletions

View File

@ -72,3 +72,12 @@ override values from include in following documents::
- override_basic_value - override_basic_value
--- ---
override_value: from_include override_value: from_include
Configuration file sections
===========================
Here you can find description of configuration parameters in these sections:
.. toctree::
repositories

View File

@ -0,0 +1,60 @@
.. _config_repositories:
========================
``repositories`` section
========================
This section contains information about repositories with component definitions
that should be cloned by :command:`ccp fetch` command or used by other
:command:`ccp` commands.
Section-level parameters
========================
.. describe:: clone
Run :command:`ccp fetch` analogue before running other commands. Default:
``true``
.. describe:: clone_concurrency
Number of threads to use while cloning repos. Defaults to number of CPU cores
available.
.. describe:: repos
List of repository definitions (see :ref:`below <config_repo_def>`) that
should be used by CCP tool. Defaults to a list of repos provided by CCP
upstream.
.. _config_repo_path:
.. describe:: path
Path to a dir where all repos are to be cloned or should be expected to be
present.
.. describe:: skip_empty
Ignore empty repositories. Default: ``true``
.. _config_repo_def:
Repository definitions
======================
Every item from this list describes one component repository that should be
downloaded or used by CCP tool.
.. describe:: name
The name of the component, this is used as a name of directory in
:ref:`path <config_repo_path>` to clone or find component repo.
.. describe:: git_url
The URL where repo should be cloned from
.. describe:: git_ref
Git ref that should be checked out

View File

@ -14,7 +14,7 @@ User docs
quickstart quickstart
monitoring_and_logging monitoring_and_logging
config config/index
Advanced topics Advanced topics
--------------- ---------------

View File

@ -365,9 +365,9 @@ def build_components(components=None):
dockerfiles = {} dockerfiles = {}
try: try:
match = not bool(components) match = not bool(components)
for repository_name in CONF.repositories.names: for repository_def in CONF.repositories.repos:
dockerfiles.update( dockerfiles.update(
find_dockerfiles(repository_name, match=match)) find_dockerfiles(repository_def['name'], match=match))
find_dependencies(dockerfiles) find_dependencies(dockerfiles)

View File

@ -86,7 +86,7 @@ class Deploy(BaseCommand):
def do_fetch(): def do_fetch():
fetch.fetch_repositories(CONF.repositories.names) fetch.fetch_repositories()
class Fetch(BaseCommand): class Fetch(BaseCommand):

View File

@ -25,7 +25,7 @@ def get_resource_path(path):
def get_config_paths(): def get_config_paths():
components = list(CONF.repositories.names) components = [d['name'] for d in CONF.repositories.repos]
paths = [] paths = []
# Order does matter. At first we add global defaults. # Order does matter. At first we add global defaults.
for conf_path in ("resources/defaults.yaml", "resources/globals.yaml"): for conf_path in ("resources/defaults.yaml", "resources/globals.yaml"):
@ -48,13 +48,13 @@ def get_deploy_components_info(rendering_context=None):
rendering_context = CONF.configs._dict rendering_context = CONF.configs._dict
components_map = {} components_map = {}
for component in CONF.repositories.names: for component_ref in CONF.repositories.repos:
component_name = component_ref['name']
service_dir = os.path.join(CONF.repositories.path, service_dir = os.path.join(CONF.repositories.path,
component, component_name,
'service') 'service')
if not os.path.isdir(service_dir): if not os.path.isdir(service_dir):
continue continue
component_name = component
REPO_NAME_PREFIX = "fuel-ccp-" REPO_NAME_PREFIX = "fuel-ccp-"
if component_name.startswith(REPO_NAME_PREFIX): if component_name.startswith(REPO_NAME_PREFIX):
component_name = component_name[len(REPO_NAME_PREFIX):] component_name = component_name[len(REPO_NAME_PREFIX):]

View File

@ -29,12 +29,10 @@ DEFAULTS = {
'clone_concurrency': multiprocessing.cpu_count(), 'clone_concurrency': multiprocessing.cpu_count(),
'skip_empty': True, 'skip_empty': True,
'path': os.path.expanduser('~/ccp-repos/'), 'path': os.path.expanduser('~/ccp-repos/'),
'hostname': 'git.openstack.org', 'repos': [{
'port': 443, 'name': name,
'protocol': 'https', 'git_url': 'https://git.openstack.org/openstack/{}'.format(name),
'project': 'openstack', } for name in DEFAULT_REPOS],
'username': None,
'names': DEFAULT_REPOS,
}, },
} }
@ -47,18 +45,19 @@ SCHEMA = {
'clone_concurrency': {'type': 'integer'}, 'clone_concurrency': {'type': 'integer'},
'skip_empty': {'type': 'boolean'}, 'skip_empty': {'type': 'boolean'},
'path': {'type': 'string'}, 'path': {'type': 'string'},
'hostname': {'type': 'string'}, 'repos': {
'port': {'type': 'integer'}, 'type': 'array',
'protocol': {'type': 'string'}, 'items': {
'project': {'type': 'string'}, 'type': 'object',
'username': {'anyOf': [{'type': 'string'}, {'type': 'null'}]}, 'additionalProperties': False,
'names': {'type': 'array', 'items': {'type': 'string'}}, 'required': ['name', 'git_url'],
'properties': {
'name': {'type': 'string'},
'git_url': {'type': 'string'},
'git_ref': {'type': 'string'},
},
},
},
}, },
}, },
} }
for repo in DEFAULT_REPOS:
conf_name = repo.replace('-', '_')
SCHEMA['repositories']['properties'][conf_name] = \
{'anyOf': [{'type': 'string'}, {'type': 'null'}]}
DEFAULTS['repositories'][conf_name] = None

View File

@ -14,28 +14,26 @@ LOG = logging.getLogger(__name__)
FETCH_TIMEOUT = 2 ** 16 # in seconds FETCH_TIMEOUT = 2 ** 16 # in seconds
def fetch_repository(repository_name): def fetch_repository(repository_def):
dest_dir = os.path.join(CONF.repositories.path, repository_name) name = repository_def['name']
dest_dir = os.path.join(CONF.repositories.path, name)
if os.path.isdir(dest_dir): if os.path.isdir(dest_dir):
LOG.debug('%s was already cloned, skipping', repository_name) LOG.debug('%s was already cloned, skipping', name)
return return
git_url = getattr(CONF.repositories, repository_name.replace('-', '_')) git_url = repository_def['git_url']
if git_url is None: git_ref = repository_def.get('git_ref')
username = CONF.repositories.username if git_ref:
if username is None: kwargs = {'branch': git_ref}
username = '' else:
else: kwargs = {}
username = username + '@' LOG.debug('Clonning %s from %s to %s', name, git_url, dest_dir)
fmt = '{0.protocol}://{1}{0.hostname}:{0.port}/{0.project}/{2}' git.Repo.clone_from(git_url, dest_dir, **kwargs)
git_url = fmt.format(CONF.repositories, username, repository_name) LOG.info('Cloned %s repo', name)
LOG.debug('Clonning %s from %s', repository_name, git_url)
git.Repo.clone_from(git_url, dest_dir)
LOG.info('Cloned %s repo', repository_name)
def fetch_repositories(repository_names=None): def fetch_repositories(repository_defs=None):
if repository_names is None: if repository_defs is None:
repository_names = CONF.repositories.names repository_defs = CONF.repositories.repos
LOG.info('Cloning repositories into %s', CONF.repositories.path) LOG.info('Cloning repositories into %s', CONF.repositories.path)
@ -44,9 +42,9 @@ def fetch_repositories(repository_names=None):
max_workers=CONF.repositories.clone_concurrency) as executor: max_workers=CONF.repositories.clone_concurrency) as executor:
future_list = [] future_list = []
try: try:
for repository_name in repository_names: for repository_def in repository_defs:
future_list.append(executor.submit( future_list.append(executor.submit(
fetch_repository, repository_name fetch_repository, repository_def
)) ))
for future in future_list: for future in future_list:

View File

@ -36,7 +36,7 @@ class TestUtils(base.TestCase):
base_dir = os.path.dirname(__file__) base_dir = os.path.dirname(__file__)
self.conf.repositories.path = os.path.join(base_dir, "test_repo_dir") self.conf.repositories.path = os.path.join(base_dir, "test_repo_dir")
self.conf.repositories.names = ["component"] self.conf.repositories.repos = [{"name": "component"}]
res = ( res = (
utils.get_deploy_components_info()["keystone"]["service_content"] utils.get_deploy_components_info()["keystone"]["service_content"]
@ -73,7 +73,7 @@ class TestUtils(base.TestCase):
base_dir = os.path.dirname(__file__) base_dir = os.path.dirname(__file__)
self.conf.repositories.path = os.path.join(base_dir, "test_repo_dir") self.conf.repositories.path = os.path.join(base_dir, "test_repo_dir")
self.conf.repositories.names = ["component"] self.conf.repositories.repos = [{"name": "component"}]
config.load_component_defaults() config.load_component_defaults()
@ -111,7 +111,7 @@ class TestUtils(base.TestCase):
base_dir = os.path.dirname(__file__) base_dir = os.path.dirname(__file__)
self.conf.repositories.path = os.path.join(base_dir, "test_repo_dir") self.conf.repositories.path = os.path.join(base_dir, "test_repo_dir")
self.conf.repositories.names = ["component"] self.conf.repositories.repos = [{"name": "component"}]
config.load_component_defaults() config.load_component_defaults()

View File

@ -27,7 +27,7 @@ class SafeCCPApp(cli.CCPApp):
# Debug does magic in cliff, we need it always on # Debug does magic in cliff, we need it always on
parser = super(SafeCCPApp, self).build_option_parser( parser = super(SafeCCPApp, self).build_option_parser(
description, version, argparse_kwargs) description, version, argparse_kwargs)
parser.set_defaults(debug=True) parser.set_defaults(debug=True, verbosity_level=2)
return parser return parser
def get_fuzzy_matches(self, cmd): def get_fuzzy_matches(self, cmd):
@ -143,7 +143,7 @@ class TestFetch(TestParser):
def test_parser(self): def test_parser(self):
self._run_app() self._run_app()
self.fetch_mock.assert_called_once_with(config.CONF.repositories.names) self.fetch_mock.assert_called_once_with()
class TestCleanup(TestParser): class TestCleanup(TestParser):

View File

@ -3,92 +3,45 @@ import os
import fixtures import fixtures
from fuel_ccp import fetch from fuel_ccp import fetch
from fuel_ccp.tests import base from fuel_ccp.tests import base
import mock
import testscenarios import testscenarios
@mock.patch('git.Repo.clone_from')
class TestFetch(testscenarios.WithScenarios, base.TestCase): class TestFetch(testscenarios.WithScenarios, base.TestCase):
component_def = {'name': 'compname', 'git_url': 'theurl'}
update_def = {}
expected_clone_call = None
dir_exists = False
scenarios = [ scenarios = [
("default", { ('exists', {'dir_exists': True}),
"option": None,
"value": None,
"url": "https://git.openstack.org:443/openstack/%s"}),
("hostname", {
"option": "hostname",
"value": "host.name",
"url": "https://host.name:443/openstack/%s"}),
('username', {
"option": "username",
"value": "someuser",
"url": "https://someuser@git.openstack.org:443/openstack/%s",
}),
('port', {
"option": "port",
"value": "9999",
'url': "https://git.openstack.org:9999/openstack/%s",
}),
('protocol', {
"option": "protocol",
"value": "ssh",
'url': "ssh://git.openstack.org:443/openstack/%s",
}),
('protocol', {
"option": "protocol",
"value": "http",
'url': "http://git.openstack.org:443/openstack/%s",
}),
('protocol', {
"option": "protocol",
"value": "git",
'url': "git://git.openstack.org:443/openstack/%s",
}),
('protocol', {
"option": "protocol",
"value": "https",
'url': "https://git.openstack.org:443/openstack/%s",
}),
('project', {
"option": "project",
"value": "someproject",
'url': "https://git.openstack.org:443/someproject/%s",
})
] ]
url = None
option = None
value = None
def setUp(self): def setUp(self):
super(TestFetch, self).setUp() super(TestFetch, self).setUp()
# Creating temporaty directory for repos # Creating temporaty directory for repos
tmp_dir = fixtures.TempDir() self.tmp_path = self.useFixture(fixtures.TempDir()).path
tmp_dir.setUp()
self.tmp_path = tmp_dir.path
self.conf['repositories']['path'] = self.tmp_path self.conf['repositories']['path'] = self.tmp_path
# Create temporary directory for openstack-base to not clone it fixture = fixtures.MockPatch('git.Repo.clone_from')
os.mkdir(os.path.join(self.tmp_path, 'ms-openstack-base')) self.mock_clone = self.useFixture(fixture).mock
def test_fetch_default_repositories(self, m_clone): def test_fetch_repository(self):
if self.option is not None: component_def = self.component_def.copy()
self.conf['repositories'][self.option] = self.value component_def.update(self.update_def)
self.conf['repositories']['path'] = self.tmp_path
components = ['fuel-ccp-debian-base', fixture = fixtures.MockPatch('os.path.isdir')
'fuel-ccp-entrypoint', isdir_mock = self.useFixture(fixture).mock
'fuel-ccp-etcd', isdir_mock.return_value = self.dir_exists
'fuel-ccp-glance',
'fuel-ccp-horizon', fetch.fetch_repository(component_def)
'fuel-ccp-keystone',
'fuel-ccp-mariadb', git_path = os.path.join(self.tmp_path, component_def['name'])
'fuel-ccp-memcached', isdir_mock.assert_called_once_with(git_path)
'fuel-ccp-neutron', if self.expected_clone_call:
'fuel-ccp-nova', git_ref = component_def.get('git_ref')
'fuel-ccp-rabbitmq', if git_ref:
'fuel-ccp-stacklight'] self.mock_clone.assert_called_once_with(
expected_calls = [ 'theurl', git_path, branch=git_ref)
mock.call( else:
self.url % (component), self.mock_clone.assert_called_once_with('theurl', git_path)
os.path.join(self.tmp_path, component)) else:
for component in components] self.mock_clone.assert_not_called()
for component, expected_call in zip(components, expected_calls):
fetch.fetch_repository(component)
self.assertIn(expected_call, m_clone.call_args_list)