From f8f76628500052ad3521fbec16c073ae7f99d287 Mon Sep 17 00:00:00 2001 From: Jordan Borean Date: Thu, 14 Nov 2019 05:02:58 +1000 Subject: [PATCH] Add the ability to ignore files and collection build (#64688) --- .../fragments/ansible-galaxy-ignore.yaml | 3 + docs/docsite/_static/ansible.css | 5 +- .../rst/dev_guide/developing_collections.rst | 39 ++++++++- docs/templates/collections_galaxy_meta.rst.j2 | 9 ++ lib/ansible/cli/galaxy.py | 1 + lib/ansible/galaxy/collection.py | 35 ++++---- .../galaxy/data/collections_galaxy_meta.yml | 12 +++ test/units/galaxy/test_collection.py | 83 +++++++++++++++++-- 8 files changed, 159 insertions(+), 28 deletions(-) create mode 100644 changelogs/fragments/ansible-galaxy-ignore.yaml diff --git a/changelogs/fragments/ansible-galaxy-ignore.yaml b/changelogs/fragments/ansible-galaxy-ignore.yaml new file mode 100644 index 00000000000..5a9d1c2657d --- /dev/null +++ b/changelogs/fragments/ansible-galaxy-ignore.yaml @@ -0,0 +1,3 @@ +minor_changes: +- ansible-galaxy - Always ignore the ``tests/output`` directory when building a collection as it is used by ``ansible-test`` for test output (https://github.com/ansible/ansible/issues/59228). +- ansible-galaxy - Added the ability to ignore further files and folders using a pattern with the ``build_ignore`` key in a collection's ``galaxy.yml`` (https://github.com/ansible/ansible/issues/59228). diff --git a/docs/docsite/_static/ansible.css b/docs/docsite/_static/ansible.css index 6f604fd6cdd..4cfe5cc7d10 100644 --- a/docs/docsite/_static/ansible.css +++ b/docs/docsite/_static/ansible.css @@ -13,7 +13,7 @@ * -------------- ----------- * **NAME** This is a multi-line description * str/required that can span multiple lines - * + * added in x.y * With multiple paragraphs * -------------- ----------- * @@ -21,9 +21,10 @@ * str is given the class .value-type * / is given the class .value-separator * required is given the class .value-required + * added in x.y is given the class .value-added-in *//*! The extra .rst-content is so this will override rtd theme */.rst-content table.documentation-table td{vertical-align:top}table.documentation-table td:first-child{white-space:nowrap;vertical-align:top}table.documentation-table td:first-child p:first-child{font-weight:700;display:inline}/*! This is now redundant with above position-based styling *//*! table.documentation-table .value-name { font-weight: bold; display: inline; } -*/table.documentation-table .value-type{font-size:x-small;color:purple;display:inline}table.documentation-table .value-separator{font-size:x-small;display:inline}table.documentation-table .value-required{font-size:x-small;color:red;display:inline}/*! Ansible-specific CSS pulled out of rtd theme for 2.9 */.DocSiteProduct-header{flex:1;-webkit-flex:1;padding:10px 20px 20px;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;align-items:center;-webkit-align-items:center;justify-content:flex-start;-webkit-justify-content:flex-start;margin-left:20px;margin-right:20px;text-decoration:none;font-weight:400;font-family:'Open Sans',sans-serif}.DocSiteProduct-header:active,.DocSiteProduct-header:focus,.DocSiteProduct-header:visited{color:#fff}.DocSiteProduct-header--core{font-size:25px;background-color:#5bbdbf;border:2px solid #5bbdbf;border-top-left-radius:4px;border-top-right-radius:4px;color:#fff;padding-left:2px;margin-left:2px}.DocSiteProduct-headerAlign{width:100%}.DocSiteProduct-logo{width:60px;height:60px;margin-bottom:-9px}.DocSiteProduct-logoText{margin-top:6px;font-size:25px;text-align:left}.DocSiteProduct-CheckVersionPara{margin-left:2px;padding-bottom:4px;margin-right:2px;margin-bottom:10px}/*! Ansible color scheme */.wy-nav-top,.wy-side-nav-search{background-color:#5bbdbf}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#5bbdbf}.wy-menu-vertical a{padding:0}.wy-menu-vertical a.reference.internal{padding:.4045em 1.618em}/*! Override sphinx rtd theme max-with of 800px */.wy-nav-content{max-width:100%}/*! Override sphinx_rtd_theme - keeps left-nav from overwriting Documentation title */.wy-nav-side{top:45px}/*! Ansible - changed absolute to relative to remove extraneous side scroll bar */.wy-grid-for-nav{position:relative}/*! Ansible - remove so highlight indenting is correct */.rst-content .highlighted{padding:0}.DocSiteBanner{display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;flex-wrap:wrap;-webkit-flex-wrap:wrap;margin-bottom:25px}.DocSiteBanner-imgWrapper{max-width:100%}td,th{min-width:100px}table{overflow-x:auto;display:block;max-width:100%}.documentation-table td.elbow-placeholder{border-left:1px solid #000;border-top:0;width:30px;min-width:30px}.documentation-table td,.documentation-table th{padding:4px;border-left:1px solid #000;border-top:1px solid #000}.documentation-table{border-right:1px solid #000;border-bottom:1px solid #000}@media print{*{background:0 0!important;color:#000!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}#nav,a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}/*! Don't show links for images, or javascript/internal links */pre,blockquote{border:0 solid #999;page-break-inside:avoid}thead{display:table-header-group}/*! h5bp.com/t */tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}#google_image_div,.DocSiteBanner{display:none!important}}#sideBanner,.DocSite-globalNav{display:none}.DocSite-sideNav{display:block;margin-bottom:40px}.DocSite-nav{display:none}.ansibleNav{background:#000;padding:0 20px;width:auto;border-bottom:1px solid #444;font-size:14px;z-index:1}.ansibleNav ul{list-style:none;padding-left:0;margin-top:0}.ansibleNav ul li{padding:7px 0;border-bottom:1px solid #444}.ansibleNav ul li:last-child{border:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:6px 0}.ansibleNav ul li a:hover{color:#5bbdbf;background:0 0}@media screen and (min-width:768px){.DocSite-globalNav{display:block;position:fixed}#sideBanner{display:block}.DocSite-sideNav{display:none}.DocSite-nav{flex:initial;-webkit-flex:initial;display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;justify-content:flex-start;-webkit-justify-content:flex-start;padding:15px;background-color:#000;text-decoration:none;font-family:'Open Sans',sans-serif}.DocSiteNav-logo{width:28px;height:28px;margin-right:8px;margin-top:-6px;position:fixed;z-index:1}.DocSiteNav-title{color:#fff;font-size:20px;position:fixed;margin-left:40px;margin-top:-4px;z-index:1}.ansibleNav{height:45px;width:100%;font-size:13px;padding:0 60px 0 0}.ansibleNav ul{float:right;display:flex;flex-wrap:nowrap;margin-top:13px}.ansibleNav ul li{padding:0;border-bottom:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:8px 13px}}@media screen and (min-width:768px){#sideBanner,.DocSite-globalNav{display:block}.DocSite-sideNav{display:none}.DocSite-nav{flex:initial;-webkit-flex:initial;display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;justify-content:flex-start;-webkit-justify-content:flex-start;padding:15px;background-color:#000;text-decoration:none;font-family:'Open Sans',sans-serif}.DocSiteNav-logo{width:28px;height:28px;margin-right:8px;margin-top:-6px;position:fixed}.DocSiteNav-title{color:#fff;font-size:20px;position:fixed;margin-left:40px;margin-top:-4px}.ansibleNav{height:45px;font-size:13px;padding:0 60px 0 0}.ansibleNav ul{float:right;display:flex;flex-wrap:nowrap;margin-top:13px}.ansibleNav ul li{padding:0;border-bottom:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:8px 13px}} +*/table.documentation-table .value-type{font-size:x-small;color:purple;display:inline}table.documentation-table .value-separator{font-size:x-small;display:inline}table.documentation-table .value-required{font-size:x-small;color:red;display:inline} .value-added-in{font-size:x-small;font-style:italic;color:green;display:inline}/*! Ansible-specific CSS pulled out of rtd theme for 2.9 */.DocSiteProduct-header{flex:1;-webkit-flex:1;padding:10px 20px 20px;display:flex;display:-webkit-flex;flex-direction:column;-webkit-flex-direction:column;align-items:center;-webkit-align-items:center;justify-content:flex-start;-webkit-justify-content:flex-start;margin-left:20px;margin-right:20px;text-decoration:none;font-weight:400;font-family:'Open Sans',sans-serif}.DocSiteProduct-header:active,.DocSiteProduct-header:focus,.DocSiteProduct-header:visited{color:#fff}.DocSiteProduct-header--core{font-size:25px;background-color:#5bbdbf;border:2px solid #5bbdbf;border-top-left-radius:4px;border-top-right-radius:4px;color:#fff;padding-left:2px;margin-left:2px}.DocSiteProduct-headerAlign{width:100%}.DocSiteProduct-logo{width:60px;height:60px;margin-bottom:-9px}.DocSiteProduct-logoText{margin-top:6px;font-size:25px;text-align:left}.DocSiteProduct-CheckVersionPara{margin-left:2px;padding-bottom:4px;margin-right:2px;margin-bottom:10px}/*! Ansible color scheme */.wy-nav-top,.wy-side-nav-search{background-color:#5bbdbf}.wy-menu-vertical header,.wy-menu-vertical p.caption{color:#5bbdbf}.wy-menu-vertical a{padding:0}.wy-menu-vertical a.reference.internal{padding:.4045em 1.618em}/*! Override sphinx rtd theme max-with of 800px */.wy-nav-content{max-width:100%}/*! Override sphinx_rtd_theme - keeps left-nav from overwriting Documentation title */.wy-nav-side{top:45px}/*! Ansible - changed absolute to relative to remove extraneous side scroll bar */.wy-grid-for-nav{position:relative}/*! Ansible - remove so highlight indenting is correct */.rst-content .highlighted{padding:0}.DocSiteBanner{display:flex;display:-webkit-flex;justify-content:center;-webkit-justify-content:center;flex-wrap:wrap;-webkit-flex-wrap:wrap;margin-bottom:25px}.DocSiteBanner-imgWrapper{max-width:100%}td,th{min-width:100px}table{overflow-x:auto;display:block;max-width:100%}.documentation-table td.elbow-placeholder{border-left:1px solid #000;border-top:0;width:30px;min-width:30px}.documentation-table td,.documentation-table th{padding:4px;border-left:1px solid #000;border-top:1px solid #000}.documentation-table{border-right:1px solid #000;border-bottom:1px solid #000}@media print{*{background:0 0!important;color:#000!important;text-shadow:none!important;filter:none!important;-ms-filter:none!important}#nav,a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}.ir a:after,a[href^="javascript:"]:after,a[href^="#"]:after{content:""}/*! Don't show links for images, or javascript/internal links */pre,blockquote{border:0 solid #999;page-break-inside:avoid}thead{display:table-header-group}/*! h5bp.com/t */tr,img{page-break-inside:avoid}img{max-width:100%!important}@page{margin:.5cm}h2,h3,p{orphans:3;widows:3}h2,h3{page-break-after:avoid}#google_image_div,.DocSiteBanner{display:none!important}}#sideBanner,.DocSite-globalNav{display:none}.DocSite-sideNav{display:block;margin-bottom:40px}.DocSite-nav{display:none}.ansibleNav{background:#000;padding:0 20px;width:auto;border-bottom:1px solid #444;font-size:14px;z-index:1}.ansibleNav ul{list-style:none;padding-left:0;margin-top:0}.ansibleNav ul li{padding:7px 0;border-bottom:1px solid #444}.ansibleNav ul li:last-child{border:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:6px 0}.ansibleNav ul li a:hover{color:#5bbdbf;background:0 0}@media screen and (min-width:768px){.DocSite-globalNav{display:block;position:fixed}#sideBanner{display:block}.DocSite-sideNav{display:none}.DocSite-nav{flex:initial;-webkit-flex:initial;display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;justify-content:flex-start;-webkit-justify-content:flex-start;padding:15px;background-color:#000;text-decoration:none;font-family:'Open Sans',sans-serif}.DocSiteNav-logo{width:28px;height:28px;margin-right:8px;margin-top:-6px;position:fixed;z-index:1}.DocSiteNav-title{color:#fff;font-size:20px;position:fixed;margin-left:40px;margin-top:-4px;z-index:1}.ansibleNav{height:45px;width:100%;font-size:13px;padding:0 60px 0 0}.ansibleNav ul{float:right;display:flex;flex-wrap:nowrap;margin-top:13px}.ansibleNav ul li{padding:0;border-bottom:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:8px 13px}}@media screen and (min-width:768px){#sideBanner,.DocSite-globalNav{display:block}.DocSite-sideNav{display:none}.DocSite-nav{flex:initial;-webkit-flex:initial;display:flex;display:-webkit-flex;flex-direction:row;-webkit-flex-direction:row;justify-content:flex-start;-webkit-justify-content:flex-start;padding:15px;background-color:#000;text-decoration:none;font-family:'Open Sans',sans-serif}.DocSiteNav-logo{width:28px;height:28px;margin-right:8px;margin-top:-6px;position:fixed}.DocSiteNav-title{color:#fff;font-size:20px;position:fixed;margin-left:40px;margin-top:-4px}.ansibleNav{height:45px;font-size:13px;padding:0 60px 0 0}.ansibleNav ul{float:right;display:flex;flex-wrap:nowrap;margin-top:13px}.ansibleNav ul li{padding:0;border-bottom:none}.ansibleNav ul li a{color:#fff;text-decoration:none;text-transform:uppercase;padding:8px 13px}} diff --git a/docs/docsite/rst/dev_guide/developing_collections.rst b/docs/docsite/rst/dev_guide/developing_collections.rst index e0d14ae9650..1875cdf377a 100644 --- a/docs/docsite/rst/dev_guide/developing_collections.rst +++ b/docs/docsite/rst/dev_guide/developing_collections.rst @@ -234,15 +234,50 @@ a tarball of the built collection in the current directory which can be uploaded .. note:: - * Certain files and folders are excluded when building the collection artifact. This is not currently configurable and is a work in progress so the collection artifact may contain files you would not wish to distribute. + * Certain files and folders are excluded when building the collection artifact. See :ref:`ignoring_files_and_folders_collections` to exclude other files you would not wish to distribute. * If you used the now-deprecated ``Mazer`` tool for any of your collections, delete any and all files it added to your :file:`releases/` directory before you build your collection with ``ansible-galaxy``. - * You must also delete the :file:`tests/output` directory if you have been testing with ``ansible-test``. * The current Galaxy maximum tarball size is 2 MB. This tarball is mainly intended to upload to Galaxy as a distribution method, but you can use it directly to install the collection on target systems. +.. _ignoring_files_and_folders_collections: + +Ignoring files and folders +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +By default the build step will include all the files in the collection directory in the final build artifact except for the following: + +* ``galaxy.yml`` +* ``*.pyc`` +* ``*.retry`` +* ``tests/output`` +* previously built artifacts in the root directory +* Various version control directories like ``.git/`` + +To exclude other files and folders when building the collection, you can set a list of file glob-like patterns in the +``build_ignore`` key in the collection's ``galaxy.yml`` file. These patterns use the following special characters for +wildcard matching: + +* ``*``: Matches everything +* ``?``: Matches any single character +* ``[seq]``: Matches and character in seq +* ``[!seq]``:Matches any character not in seq + +For example, if you wanted to exclude the :file:`sensitive` folder within the ``playbooks`` folder as well any ``.tar.gz`` archives you +can set the following in your ``galaxy.yml`` file: + +.. code-block:: yaml + + build_ignore: + - playbooks/sensitive + - '*.tar.gz' + +.. note:: + This feature is only supported when running ``ansible-galaxy collection build`` with Ansible 2.10 or newer. + + .. _trying_collection_locally: Trying collection locally diff --git a/docs/templates/collections_galaxy_meta.rst.j2 b/docs/templates/collections_galaxy_meta.rst.j2 index b65faef1184..f7ca6670004 100644 --- a/docs/templates/collections_galaxy_meta.rst.j2 +++ b/docs/templates/collections_galaxy_meta.rst.j2 @@ -43,6 +43,15 @@ The ``galaxy.yml`` file must contain the following keys in valid YAML: required {%- endif %} + {% if 'version_added' in entry -%} + + .. rst-class:: value-added-in + + |br| version_added: @{ entry.version_added }@ + + |_| + + {%- endif %} - {% for desc in entry.description -%} @{ desc | trim | rst_ify }@ diff --git a/lib/ansible/cli/galaxy.py b/lib/ansible/cli/galaxy.py index ffe2074093f..487f21d0dbd 100644 --- a/lib/ansible/cli/galaxy.py +++ b/lib/ansible/cli/galaxy.py @@ -651,6 +651,7 @@ class GalaxyCLI(CLI): documentation='http://docs.example.com', homepage='http://example.com', issues='http://example.com/issue/tracker', + build_ignore=[], )) obj_path = os.path.join(init_path, namespace, collection_name) diff --git a/lib/ansible/galaxy/collection.py b/lib/ansible/galaxy/collection.py index 3eaca23fbbf..b3e150fb0af 100644 --- a/lib/ansible/galaxy/collection.py +++ b/lib/ansible/galaxy/collection.py @@ -353,7 +353,8 @@ def build_collection(collection_path, output_path, force): raise AnsibleError("The collection galaxy.yml path '%s' does not exist." % to_native(b_galaxy_path)) collection_meta = _get_galaxy_yml(b_galaxy_path) - file_manifest = _build_files_manifest(b_collection_path, collection_meta['namespace'], collection_meta['name']) + file_manifest = _build_files_manifest(b_collection_path, collection_meta['namespace'], collection_meta['name'], + collection_meta['build_ignore']) collection_manifest = _build_manifest(**collection_meta) collection_output = os.path.join(output_path, "%s-%s-%s.tar.gz" % (collection_meta['namespace'], @@ -598,12 +599,18 @@ def _get_galaxy_yml(b_galaxy_yml_path): return galaxy_yml -def _build_files_manifest(b_collection_path, namespace, name): - # Contains tuple of (b_filename, only root) where 'only root' means to only ignore the file in the root dir - b_ignore_files = frozenset([(b'*.pyc', False), (b'*.retry', False), - (to_bytes('{0}-{1}-*.tar.gz'.format(namespace, name)), True)]) - b_ignore_dirs = frozenset([(b'CVS', False), (b'.bzr', False), (b'.hg', False), (b'.git', False), (b'.svn', False), - (b'__pycache__', False), (b'.tox', False)]) +def _build_files_manifest(b_collection_path, namespace, name, ignore_patterns): + # We always ignore .pyc and .retry files as well as some well known version control directories. The ignore + # patterns can be extended by the build_ignore key in galaxy.yml + b_ignore_patterns = [ + b'galaxy.yml', + b'*.pyc', + b'*.retry', + b'tests/output', # Ignore ansible-test result output directory. + to_bytes('{0}-{1}-*.tar.gz'.format(namespace, name)), # Ignores previously built artifacts in the root dir. + ] + b_ignore_patterns += [to_bytes(p) for p in ignore_patterns] + b_ignore_dirs = frozenset([b'CVS', b'.bzr', b'.hg', b'.git', b'.svn', b'__pycache__', b'.tox']) entry_template = { 'name': None, @@ -626,16 +633,15 @@ def _build_files_manifest(b_collection_path, namespace, name): } def _walk(b_path, b_top_level_dir): - is_root = b_path == b_top_level_dir - for b_item in os.listdir(b_path): b_abs_path = os.path.join(b_path, b_item) b_rel_base_dir = b'' if b_path == b_top_level_dir else b_path[len(b_top_level_dir) + 1:] - rel_path = to_text(os.path.join(b_rel_base_dir, b_item), errors='surrogate_or_strict') + b_rel_path = os.path.join(b_rel_base_dir, b_item) + rel_path = to_text(b_rel_path, errors='surrogate_or_strict') if os.path.isdir(b_abs_path): - if any(b_item == b_path for b_path, root_only in b_ignore_dirs - if not root_only or root_only == is_root): + if any(b_item == b_path for b_path in b_ignore_dirs) or \ + any(fnmatch.fnmatch(b_rel_path, b_pattern) for b_pattern in b_ignore_patterns): display.vvv("Skipping '%s' for collection build" % to_text(b_abs_path)) continue @@ -655,10 +661,7 @@ def _build_files_manifest(b_collection_path, namespace, name): _walk(b_abs_path, b_top_level_dir) else: - if b_item == b'galaxy.yml': - continue - elif any(fnmatch.fnmatch(b_item, b_pattern) for b_pattern, root_only in b_ignore_files - if not root_only or root_only == is_root): + if any(fnmatch.fnmatch(b_rel_path, b_pattern) for b_pattern in b_ignore_patterns): display.vvv("Skipping '%s' for collection build" % to_text(b_abs_path)) continue diff --git a/lib/ansible/galaxy/data/collections_galaxy_meta.yml b/lib/ansible/galaxy/data/collections_galaxy_meta.yml index b7bd69942dc..c3c34372528 100644 --- a/lib/ansible/galaxy/data/collections_galaxy_meta.yml +++ b/lib/ansible/galaxy/data/collections_galaxy_meta.yml @@ -96,3 +96,15 @@ description: - The URL to the collection issue tracker. type: str + +- key: build_ignore + description: + - A list of file glob-like patterns used to filter any files or directories + that should not be included in the build artifact. + - A pattern is matched from the relative path of the file or directory of the + collection directory. + - This uses C(fnmatch) to match the files or directories. + - Some directories and files like C(galaxy.yml), C(*.pyc), C(*.retry), and + C(.git) are always filtered. + type: list + version_added: '2.10' diff --git a/test/units/galaxy/test_collection.py b/test/units/galaxy/test_collection.py index 18df8bbdcd6..3efd47d5d81 100644 --- a/test/units/galaxy/test_collection.py +++ b/test/units/galaxy/test_collection.py @@ -248,24 +248,37 @@ def test_build_ignore_files_and_folders(collection_input, monkeypatch): git_folder = os.path.join(input_dir, '.git') retry_file = os.path.join(input_dir, 'ansible.retry') + tests_folder = os.path.join(input_dir, 'tests', 'output') + tests_output_file = os.path.join(tests_folder, 'result.txt') + os.makedirs(git_folder) + os.makedirs(tests_folder) + with open(retry_file, 'w+') as ignore_file: ignore_file.write('random') ignore_file.flush() - actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection') + with open(tests_output_file, 'w+') as tests_file: + tests_file.write('random') + tests_file.flush() + + actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', []) assert actual['format'] == 1 for manifest_entry in actual['files']: - assert manifest_entry['name'] not in ['.git', 'ansible.retry', 'galaxy.yml'] + assert manifest_entry['name'] not in ['.git', 'ansible.retry', 'galaxy.yml', 'tests/output', 'tests/output/result.txt'] expected_msgs = [ + "Skipping '%s/galaxy.yml' for collection build" % to_text(input_dir), "Skipping '%s' for collection build" % to_text(retry_file), "Skipping '%s' for collection build" % to_text(git_folder), + "Skipping '%s' for collection build" % to_text(tests_folder), ] - assert mock_display.call_count == 2 + assert mock_display.call_count == 4 assert mock_display.mock_calls[0][1][0] in expected_msgs assert mock_display.mock_calls[1][1][0] in expected_msgs + assert mock_display.mock_calls[2][1][0] in expected_msgs + assert mock_display.mock_calls[3][1][0] in expected_msgs def test_build_ignore_older_release_in_root(collection_input, monkeypatch): @@ -285,7 +298,7 @@ def test_build_ignore_older_release_in_root(collection_input, monkeypatch): file_obj.write('random') file_obj.flush() - actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection') + actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', []) assert actual['format'] == 1 plugin_release_found = False @@ -296,8 +309,62 @@ def test_build_ignore_older_release_in_root(collection_input, monkeypatch): assert plugin_release_found - assert mock_display.call_count == 1 - assert mock_display.mock_calls[0][1][0] == "Skipping '%s' for collection build" % to_text(release_file) + expected_msgs = [ + "Skipping '%s/galaxy.yml' for collection build" % to_text(input_dir), + "Skipping '%s' for collection build" % to_text(release_file) + ] + assert mock_display.call_count == 2 + assert mock_display.mock_calls[0][1][0] in expected_msgs + assert mock_display.mock_calls[1][1][0] in expected_msgs + + +def test_build_ignore_patterns(collection_input, monkeypatch): + input_dir = collection_input[0] + + mock_display = MagicMock() + monkeypatch.setattr(Display, 'vvv', mock_display) + + actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', + ['*.md', 'plugins/action', 'playbooks/*.j2']) + assert actual['format'] == 1 + + expected_missing = [ + 'README.md', + 'docs/My Collection.md', + 'plugins/action', + 'playbooks/templates/test.conf.j2', + 'playbooks/templates/subfolder/test.conf.j2', + ] + + # Files or dirs that are close to a match but are not, make sure they are present + expected_present = [ + 'docs', + 'roles/common/templates/test.conf.j2', + 'roles/common/templates/subfolder/test.conf.j2', + ] + + actual_files = [e['name'] for e in actual['files']] + for m in expected_missing: + assert m not in actual_files + + for p in expected_present: + assert p in actual_files + + expected_msgs = [ + "Skipping '%s/galaxy.yml' for collection build" % to_text(input_dir), + "Skipping '%s/README.md' for collection build" % to_text(input_dir), + "Skipping '%s/docs/My Collection.md' for collection build" % to_text(input_dir), + "Skipping '%s/plugins/action' for collection build" % to_text(input_dir), + "Skipping '%s/playbooks/templates/test.conf.j2' for collection build" % to_text(input_dir), + "Skipping '%s/playbooks/templates/subfolder/test.conf.j2' for collection build" % to_text(input_dir), + ] + assert mock_display.call_count == len(expected_msgs) + assert mock_display.mock_calls[0][1][0] in expected_msgs + assert mock_display.mock_calls[1][1][0] in expected_msgs + assert mock_display.mock_calls[2][1][0] in expected_msgs + assert mock_display.mock_calls[3][1][0] in expected_msgs + assert mock_display.mock_calls[4][1][0] in expected_msgs + assert mock_display.mock_calls[5][1][0] in expected_msgs def test_build_ignore_symlink_target_outside_collection(collection_input, monkeypatch): @@ -309,7 +376,7 @@ def test_build_ignore_symlink_target_outside_collection(collection_input, monkey link_path = os.path.join(input_dir, 'plugins', 'connection') os.symlink(outside_dir, link_path) - actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection') + actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', []) for manifest_entry in actual['files']: assert manifest_entry['name'] != 'plugins/connection' @@ -333,7 +400,7 @@ def test_build_copy_symlink_target_inside_collection(collection_input): os.symlink(roles_target, roles_link) - actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection') + actual = collection._build_files_manifest(to_bytes(input_dir), 'namespace', 'collection', []) linked_entries = [e for e in actual['files'] if e['name'].startswith('playbooks/roles/linked')] assert len(linked_entries) == 3