diff --git a/pegleg/cli.py b/pegleg/cli.py index 2f6b1d72..74f9a56c 100644 --- a/pegleg/cli.py +++ b/pegleg/cli.py @@ -29,7 +29,7 @@ CONTEXT_SETTINGS = { 'help_option_names': ['-h', '--help'], } -REPOSITORY_OPTION = click.option( +MAIN_REPOSITORY_OPTION = click.option( '-r', '--site-repository', 'site_repository', @@ -49,6 +49,22 @@ EXTRA_REPOSITORY_OPTION = click.option( 'site-definition for the site will be leveraged but can be overridden ' 'using -e global=/opt/global@revision.') +REPOSITORY_KEY_OPTION = click.option( + '-k', + '--repo-key', + 'repo_key', + help='The SSH public key to use when cloning remote authenticated ' + 'repositories.') + +REPOSITORY_USERNAME_OPTION = click.option( + '-u', + '--repo-username', + 'repo_username', + help= + 'The SSH username to use when cloning remote authenticated repositories ' + 'specified in the site-definition file. Any occurrences of REPO_USERNAME ' + 'will be replaced with this value.') + ALLOW_MISSING_SUBSTITUTIONS_OPTION = click.option( '-f', '--fail-on-missing-sub-src', @@ -99,8 +115,12 @@ def main(*, verbose): @main.group(help='Commands related to repositories') -@REPOSITORY_OPTION -def repo(*, site_repository): +@MAIN_REPOSITORY_OPTION +# TODO(felipemonteiro): Support EXTRA_REPOSITORY_OPTION as well to be +# able to lint multiple repos together. +@REPOSITORY_USERNAME_OPTION +@REPOSITORY_KEY_OPTION +def repo(*, site_repository, repo_key, repo_username): """Group for repo-level actions, which include: * lint: lint all sites across the repository @@ -108,6 +128,8 @@ def repo(*, site_repository): """ config.set_site_repo(site_repository) + config.set_repo_key(repo_key) + config.set_repo_username(repo_username) def _lint_helper(*, @@ -145,22 +167,10 @@ def lint_repo(*, fail_on_missing_sub_src, exclude_lint, warn_lint): @main.group(help='Commands related to sites') -@REPOSITORY_OPTION +@MAIN_REPOSITORY_OPTION @EXTRA_REPOSITORY_OPTION -@click.option( - '-k', - '--repo-key', - 'repo_key', - help='The SSH public key to use when cloning remote authenticated ' - 'repositories.') -@click.option( - '-u', - '--repo-username', - 'repo_username', - help= - 'The SSH username to use when cloning remote authenticated repositories ' - 'specified in the site-definition file. Any occurrences of REPO_USERNAME ' - 'will be replaced with this value.') +@REPOSITORY_USERNAME_OPTION +@REPOSITORY_KEY_OPTION def site(*, site_repository, extra_repositories, repo_key, repo_username): """Group for site-level actions, which include: @@ -238,7 +248,7 @@ def collect(*, save_location, validate, exclude_lint, warn_lint, site_name): 'output_stream', type=click.File(mode='w'), default=sys.stdout, - help='Where to output') + help='Where to output. Defaults to sys.stdout.') def list_(*, output_stream): engine.repository.process_site_repository(update_config=True) engine.site.list_(output_stream) @@ -251,7 +261,7 @@ def list_(*, output_stream): 'output_stream', type=click.File(mode='w'), default=sys.stdout, - help='Where to output') + help='Where to output. Defaults to sys.stdout.') @click.argument('site_name') def show(*, output_stream, site_name): engine.repository.process_repositories(site_name) @@ -265,7 +275,7 @@ def show(*, output_stream, site_name): 'output_stream', type=click.File(mode='w'), default=sys.stdout, - help='Where to output') + help='Where to output. Defaults to sys.stdout.') @click.argument('site_name') def render(*, output_stream, site_name): engine.repository.process_repositories(site_name) @@ -287,3 +297,34 @@ def lint_site(*, fail_on_missing_sub_src, exclude_lint, warn_lint, site_name): fail_on_missing_sub_src=fail_on_missing_sub_src, exclude_lint=exclude_lint, warn_lint=warn_lint) + + +@main.group(help='Commands related to types') +@MAIN_REPOSITORY_OPTION +@EXTRA_REPOSITORY_OPTION +@REPOSITORY_USERNAME_OPTION +@REPOSITORY_KEY_OPTION +def type(*, site_repository, extra_repositories, repo_key, repo_username): + """Group for repo-level actions, which include: + + * list: list all types across the repository + + """ + config.set_site_repo(site_repository) + config.set_extra_repo_store(extra_repositories or []) + config.set_repo_key(repo_key) + config.set_repo_username(repo_username) + + +@type.command('list', help='List known types') +@click.option( + '-o', + '--output', + 'output_stream', + type=click.File(mode='w'), + default=sys.stdout, + help='Where to output. Defaults to sys.stdout.') +def list_types(*, output_stream): + """List type names for a given repository.""" + engine.repository.process_site_repository(update_config=True) + engine.type.list_types(output_stream) diff --git a/pegleg/config.py b/pegleg/config.py index 28d6e1a1..4f099fde 100644 --- a/pegleg/config.py +++ b/pegleg/config.py @@ -19,7 +19,8 @@ except NameError: GLOBAL_CONTEXT = { 'site_repo': './', 'extra_repos': [], - 'site_path': 'site' + 'site_path': 'site', + 'type_path': 'type' } @@ -85,3 +86,12 @@ def get_rel_site_path(): def set_rel_site_path(p): p = p or 'site' GLOBAL_CONTEXT['site_path'] = p + + +def get_rel_type_path(): + return GLOBAL_CONTEXT.get('type_path', 'type') + + +def set_rel_type_path(p): + p = p or 'type' + GLOBAL_CONTEXT['type_path'] = p diff --git a/pegleg/engine/type.py b/pegleg/engine/type.py new file mode 100644 index 00000000..6661d2b2 --- /dev/null +++ b/pegleg/engine/type.py @@ -0,0 +1,34 @@ +# Copyright 2018 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import csv +import logging + +from pegleg.engine import util + +__all__ = ('list_types', ) + +LOG = logging.getLogger(__name__) + + +def list_types(output_stream): + """List type names for a given repository.""" + + # TODO(felipemonteiro): This should output a formatted table, not rows of + # data without delimited columns. + fieldnames = ['type_name'] + writer = csv.DictWriter( + output_stream, fieldnames=fieldnames, delimiter=' ') + for type_name in util.files.list_types(): + writer.writerow({'type_name': type_name}) diff --git a/pegleg/engine/util/files.py b/pegleg/engine/util/files.py index 2b421c64..8f578a3e 100644 --- a/pegleg/engine/util/files.py +++ b/pegleg/engine/util/files.py @@ -180,6 +180,18 @@ def list_sites(primary_repo_base=None): yield path +def list_types(primary_repo_base=None): + """Get a list of type directories in the primary repo.""" + if not primary_repo_base: + primary_repo_base = config.get_site_repo() + full_type_path = os.path.join(primary_repo_base, + config.get_rel_type_path()) + for path in os.listdir(full_type_path): + joined_path = os.path.join(full_type_path, path) + if os.path.isdir(joined_path): + yield path + + def directory_for(*, path): for r in config.all_repos(): if path.startswith(r): diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index c5a60476..77605d6d 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -244,3 +244,42 @@ class TestRepoCliActions(BaseCLIActionTest): # A successful result (while setting lint checks to exclude) should # output nothing. assert not result.output + + +class TestTypeCliActions(BaseCLIActionTest): + """Tests type-level CLI actions.""" + + def test_list_types_using_remote_repo_url(self): + """Validates list types action using remote repo URL.""" + # Scenario: + # + # 1) List types (should clone repo automatically) + + repo_url = 'https://github.com/openstack/%s@%s' % (self.repo_name, + self.repo_rev) + + # NOTE(felipemonteiro): Pegleg currently doesn't dump a table to stdout + # for this CLI call so mock out the csv DictWriter to determine output. + with mock.patch('pegleg.engine.type.csv.DictWriter') as mock_writer: + result = self.runner.invoke(cli.type, ['-r', repo_url, 'list']) + + assert result.exit_code == 0 + m_writer = mock_writer.return_value + m_writer.writerow.assert_any_call({'type_name': 'foundry'}) + + def test_list_types_using_local_repo_path(self): + """Validates list types action using local repo path.""" + # Scenario: + # + # 1) List types for local repo path + + repo_path = self.treasuremap_path + + # NOTE(felipemonteiro): Pegleg currently doesn't dump a table to stdout + # for this CLI call so mock out the csv DictWriter to determine output. + with mock.patch('pegleg.engine.type.csv.DictWriter') as mock_writer: + result = self.runner.invoke(cli.type, ['-r', repo_path, 'list']) + + assert result.exit_code == 0 + m_writer = mock_writer.return_value + m_writer.writerow.assert_any_call({'type_name': 'foundry'})