diff --git a/pulp_ansible/app/urls.py b/pulp_ansible/app/urls.py index c4692b0d6..6f3df5a1e 100644 --- a/pulp_ansible/app/urls.py +++ b/pulp_ansible/app/urls.py @@ -174,37 +174,40 @@ name="collection-artifact-download", ), path( - "index///", + "all-collections/", + views_v3.UnpaginatedCollectionViewSet.as_view({"get": "list"}), + name="metadata-collection-list", + ), + path( + "all-versions/", + views_v3.UnpaginatedCollectionVersionViewSet.as_view({"get": "list"}), + name="metadata-collection-versions-list", + ), +] + +v3_collection_detail_urls = [ + path( + "", views_v3.CollectionViewSet.as_view( {"get": "retrieve", "patch": "update", "delete": "destroy"} ), name="collections-detail", ), path( - "index///versions/", + "versions/", views_v3.CollectionVersionViewSet.as_view({"get": "list"}), name="collection-versions-list", ), path( - "index///versions//", + "versions//", views_v3.CollectionVersionViewSet.as_view({"get": "retrieve", "delete": "destroy"}), name="collection-versions-detail", ), path( - "index///versions//docs-blob/", + "versions//docs-blob/", views_v3.CollectionVersionDocsViewSet.as_view({"get": "retrieve"}), name="collection-versions-detail-docs", ), - path( - "all-collections/", - views_v3.UnpaginatedCollectionViewSet.as_view({"get": "list"}), - name="metadata-collection-list", - ), - path( - "all-versions/", - views_v3.UnpaginatedCollectionVersionViewSet.as_view({"get": "list"}), - name="metadata-collection-versions-list", - ), ] namespace_urls = [ @@ -225,6 +228,10 @@ v3_plugin_urls = [ # path:var captures /, so it has to have something at the end to make it work # correctly. + path( + "content//collections/index///", + include(v3_collection_detail_urls), + ), path("content//collections/", include(v3_collection_urls)), path("content//namespaces/", include(namespace_urls)), path( diff --git a/pulp_ansible/tests/functional/api/collection/v3/test_collection_naming_edgecases.py b/pulp_ansible/tests/functional/api/collection/v3/test_collection_naming_edgecases.py new file mode 100644 index 000000000..557fcad7d --- /dev/null +++ b/pulp_ansible/tests/functional/api/collection/v3/test_collection_naming_edgecases.py @@ -0,0 +1,168 @@ +import json +import shutil +import subprocess +import os + +from orionutils.generator import build_collection +from orionutils.generator import randstr + +from pulp_smash.pulp3.bindings import monitor_task + + +def busted_collection_build(basedir=None, namespace=None, name=None, version=None): + """Make artifacts with namespaces and names that wouldn't normally be allowed.""" + + def file_checksum(fp): + """Quick sha256 maker.""" + pid = subprocess.run(f"sha256sum {fp}", shell=True, stdout=subprocess.PIPE) + checksum = pid.stdout.decode("utf-8").split()[0] + return checksum + + # make a content dir + content_dir = os.path.join(basedir, "content") + os.makedirs(content_dir) + + # make various files + for dname in ["docs", "plugins", "roles"]: + os.makedirs(os.path.join(content_dir, dname)) + with open(os.path.join(content_dir, "README.md"), "w") as f: + f.write("") + with open(os.path.join(content_dir, "plugins", "README.md"), "w") as f: + f.write("") + + # make files.json + fdata = {"files": [], "format": 1} + for root, dirs, files in os.walk(content_dir): + for dirname in dirs: + dirpath = os.path.join(root, dirname) + relative_dir = dirpath.replace(content_dir + "/", "") + fdata["files"].append( + { + "name": relative_dir, + "ftype": "dir", + "chksum_type": None, + "chksum_sha256": None, + "format": 1, + } + ) + for filen in files: + filepath = os.path.join(root, filen) + relative_file = filepath.replace(content_dir + "/", "") + fdata["files"].append( + { + "name": relative_file, + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": file_checksum(filepath), + "format": 1, + } + ) + with open(os.path.join(content_dir, "FILES.json"), "w") as f: + f.write(json.dumps(fdata, indent=2)) + + # make manifest.json + mdata = { + "format": 1, + "collection_info": { + "namespace": namespace, + "name": name, + "version": version, + "authors": ["your name "], + "readme": "README.md", + "tags": [], + "description": "your collection description", + "license": ["GPL-2.0-or-later"], + "license_file": None, + "dependencies": {}, + "repository": "http://example.com/repository", + "homepage": "http://example.com", + "issues": "http://example.com/issue/tracker", + }, + "file_manifest_file": { + "name": "FILES.json", + "ftype": "file", + "chksum_type": "sha256", + "chksum_sha256": file_checksum(os.path.join(content_dir, "FILES.json")), + "format": 1, + }, + } + with open(os.path.join(content_dir, "MANIFEST.json"), "w") as f: + f.write(json.dumps(mdata, indent=2)) + + # make the tarball + cmd = "tar czvf artifact.tar.gz *" + subprocess.run(cmd, shell=True, cwd=content_dir) + return os.path.join(content_dir, "artifact.tar.gz") + + +def test_collection_named_collections( + tmp_path, + ansible_repo, + ansible_distribution_factory, + ansible_collection_version_api_client, + galaxy_v3_content_collection_index_api, +): + # https://issues.redhat.com/browse/AAH-2158 + pulp_dist = ansible_distribution_factory(ansible_repo) + + spec = { + "repository": "https://github.com/foo/bar", + "namespace": randstr(), + "name": "collections", + "version": "1.0.0", + } + artifact = build_collection(base="skeleton", config=spec) + artifact_fn = os.path.join(tmp_path, os.path.basename(artifact.filename)) + shutil.copy(artifact.filename, artifact_fn) + + body = {"file": artifact_fn} + body["repository"] = ansible_repo.pulp_href + response = ansible_collection_version_api_client.create(**body) + monitor_task(response.task) + + # validate the collection shows up ... + resp = galaxy_v3_content_collection_index_api.list(pulp_dist.base_path) + assert resp.meta.count == 1 + assert resp.data[0].namespace == spec["namespace"] + assert resp.data[0].name == spec["name"] + + # validate we can get the direct path to the collection ... + resp = galaxy_v3_content_collection_index_api.read( + pulp_dist.base_path, namespace=spec["namespace"], name=spec["name"] + ) + assert resp.namespace == spec["namespace"] + assert resp.name == spec["name"] + + +def test_collection_named_with_slashes( + tmp_path, + ansible_repo, + ansible_distribution_factory, + ansible_collection_version_api_client, +): + # https://issues.redhat.com/browse/AAH-2158 + ansible_distribution_factory(ansible_repo) + + spec = { + "repository": "https://github.com/foo/bar", + "namespace": "collections/are/fun", + "name": "index/foo/myname", + "version": "1.0.0", + } + + tarball = busted_collection_build( + basedir=tmp_path, + namespace=spec["namespace"], + name=spec["name"], + version=spec["version"], + ) + + body = {"file": tarball} + body["repository"] = ansible_repo.pulp_href + + failed = False + try: + ansible_collection_version_api_client.create(**body) + except Exception: + failed = True + assert failed diff --git a/pulp_ansible/tests/functional/conftest.py b/pulp_ansible/tests/functional/conftest.py index f0e5e9e48..cb13e3d9d 100644 --- a/pulp_ansible/tests/functional/conftest.py +++ b/pulp_ansible/tests/functional/conftest.py @@ -24,6 +24,7 @@ PulpAnsibleApiV3PluginAnsibleClientConfigurationApi, PulpAnsibleDefaultApiV3PluginAnsibleClientConfigurationApi, PulpAnsibleApiV3PluginAnsibleContentNamespacesApi, + PulpAnsibleDefaultApiV3PluginAnsibleContentCollectionsIndexApi, ) @@ -116,6 +117,12 @@ def galaxy_v3_namespaces_api_client(ansible_bindings_client): return PulpAnsibleApiV3NamespacesApi(ansible_bindings_client) +@pytest.fixture +def galaxy_v3_content_collection_index_api(ansible_bindings_client): + """Provides the Galaxy V3 Collections Index API client object.""" + return PulpAnsibleDefaultApiV3PluginAnsibleContentCollectionsIndexApi(ansible_bindings_client) + + @pytest.fixture def galaxy_v3_plugin_namespaces_api_client(ansible_bindings_client): """Provides the Galaxy V3 Namespace API client object."""