pulp-ansible 0.25.1__py3-none-any.whl → 0.25.3__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pulp_ansible/app/__init__.py +1 -1
- pulp_ansible/app/galaxy/v3/views.py +18 -12
- pulp_ansible/app/migrations/0057_collectionversion_sha256_migrate.py +2 -2
- pulp_ansible/app/serializers.py +16 -0
- pulp_ansible/app/tasks/collections.py +79 -41
- pulp_ansible/tests/functional/api/collection/v3/test_collection.py +37 -12
- pulp_ansible/tests/functional/api/collection/v3/test_deprecation.py +29 -8
- {pulp_ansible-0.25.1.dist-info → pulp_ansible-0.25.3.dist-info}/METADATA +5 -6
- {pulp_ansible-0.25.1.dist-info → pulp_ansible-0.25.3.dist-info}/RECORD +13 -13
- {pulp_ansible-0.25.1.dist-info → pulp_ansible-0.25.3.dist-info}/WHEEL +1 -1
- {pulp_ansible-0.25.1.dist-info → pulp_ansible-0.25.3.dist-info}/entry_points.txt +0 -0
- {pulp_ansible-0.25.1.dist-info → pulp_ansible-0.25.3.dist-info}/licenses/LICENSE +0 -0
- {pulp_ansible-0.25.1.dist-info → pulp_ansible-0.25.3.dist-info}/top_level.txt +0 -0
pulp_ansible/app/__init__.py
CHANGED
|
@@ -2,6 +2,7 @@ from datetime import datetime
|
|
|
2
2
|
from gettext import gettext as _
|
|
3
3
|
import semantic_version
|
|
4
4
|
import base64
|
|
5
|
+
import re
|
|
5
6
|
|
|
6
7
|
from django.db import DatabaseError, IntegrityError
|
|
7
8
|
from django.db.models import F, OuterRef, Exists, Subquery, Prefetch
|
|
@@ -621,7 +622,7 @@ class CollectionArtifactDownloadView(GalaxyAuthMixin, views.APIView, AnsibleDist
|
|
|
621
622
|
DEFAULT_ACCESS_POLICY = _PERMISSIVE_ACCESS_POLICY
|
|
622
623
|
|
|
623
624
|
@staticmethod
|
|
624
|
-
def log_download(request,
|
|
625
|
+
def log_download(request, namespace, name, version, distro_base_path):
|
|
625
626
|
"""Log the download of the collection version."""
|
|
626
627
|
|
|
627
628
|
def _get_org_id(request):
|
|
@@ -639,7 +640,7 @@ class CollectionArtifactDownloadView(GalaxyAuthMixin, views.APIView, AnsibleDist
|
|
|
639
640
|
|
|
640
641
|
return identity["internal"]["org_id"]
|
|
641
642
|
|
|
642
|
-
#
|
|
643
|
+
# Get user IP
|
|
643
644
|
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
|
|
644
645
|
ip = x_forwarded_for.split(",")[0] if x_forwarded_for else request.META.get("REMOTE_ADDR")
|
|
645
646
|
|
|
@@ -650,13 +651,9 @@ class CollectionArtifactDownloadView(GalaxyAuthMixin, views.APIView, AnsibleDist
|
|
|
650
651
|
distribution.repository_version or distribution.repository.latest_version()
|
|
651
652
|
)
|
|
652
653
|
|
|
653
|
-
# Getting collection version
|
|
654
|
-
ns, name, version = filename.split("-", maxsplit=2)
|
|
655
|
-
# Get off the ending .tar.gz
|
|
656
|
-
version = ".".join(version.split(".")[:3])
|
|
657
654
|
collection_version = get_object_or_404(
|
|
658
655
|
CollectionVersion.objects.filter(pk__in=repository_version.content),
|
|
659
|
-
namespace=
|
|
656
|
+
namespace=namespace,
|
|
660
657
|
name=name,
|
|
661
658
|
version=version,
|
|
662
659
|
)
|
|
@@ -681,11 +678,10 @@ class CollectionArtifactDownloadView(GalaxyAuthMixin, views.APIView, AnsibleDist
|
|
|
681
678
|
return
|
|
682
679
|
raise
|
|
683
680
|
|
|
684
|
-
def count_download(
|
|
685
|
-
ns, name, _ = filename.split("-", maxsplit=2)
|
|
681
|
+
def count_download(namespace, name):
|
|
686
682
|
try:
|
|
687
683
|
collection, created = CollectionDownloadCount.objects.get_or_create(
|
|
688
|
-
namespace=
|
|
684
|
+
namespace=namespace, name=name, defaults={"download_count": 1}
|
|
689
685
|
)
|
|
690
686
|
if not created:
|
|
691
687
|
collection.download_count = F("download_count") + 1
|
|
@@ -713,13 +709,23 @@ class CollectionArtifactDownloadView(GalaxyAuthMixin, views.APIView, AnsibleDist
|
|
|
713
709
|
filename=self.kwargs["filename"],
|
|
714
710
|
)
|
|
715
711
|
|
|
712
|
+
match = re.fullmatch(
|
|
713
|
+
r"(?P<namespace>\w+)-(?P<name>\w+)-(?P<version>[\w.-]+)\.tar\.gz",
|
|
714
|
+
self.kwargs["filename"],
|
|
715
|
+
)
|
|
716
|
+
if not match:
|
|
717
|
+
raise NotFound()
|
|
718
|
+
namespace = match.group("namespace")
|
|
719
|
+
name = match.group("name")
|
|
720
|
+
version = match.group("version")
|
|
721
|
+
|
|
716
722
|
if settings.ANSIBLE_COLLECT_DOWNLOAD_LOG:
|
|
717
723
|
CollectionArtifactDownloadView.log_download(
|
|
718
|
-
request,
|
|
724
|
+
request, namespace, name, version, distro_base_path
|
|
719
725
|
)
|
|
720
726
|
|
|
721
727
|
if settings.ANSIBLE_COLLECT_DOWNLOAD_COUNT:
|
|
722
|
-
CollectionArtifactDownloadView.count_download(
|
|
728
|
+
CollectionArtifactDownloadView.count_download(namespace, name)
|
|
723
729
|
|
|
724
730
|
if (
|
|
725
731
|
distribution.content_guard
|
|
@@ -16,11 +16,11 @@ def add_sha256_to_current_models(apps, schema_editor):
|
|
|
16
16
|
|
|
17
17
|
for collection_version in (
|
|
18
18
|
CollectionVersion.objects.prefetch_related(
|
|
19
|
-
"
|
|
19
|
+
"contentartifact_set", "contentartifact_set__artifact"
|
|
20
20
|
)
|
|
21
21
|
.filter(sha256="")
|
|
22
22
|
.only("pk", "sha256")
|
|
23
|
-
.iterator()
|
|
23
|
+
.iterator(chunk_size=2000)
|
|
24
24
|
):
|
|
25
25
|
content_artifact = collection_version.contentartifact_set.get()
|
|
26
26
|
if content_artifact.artifact:
|
pulp_ansible/app/serializers.py
CHANGED
|
@@ -658,6 +658,22 @@ class CollectionVersionSerializer(ContentChecksumSerializer, CollectionVersionUp
|
|
|
658
658
|
|
|
659
659
|
creating = True
|
|
660
660
|
|
|
661
|
+
def __init__(self, *args, **kwargs):
|
|
662
|
+
super().__init__(*args, **kwargs)
|
|
663
|
+
try:
|
|
664
|
+
request = self.context["request"]
|
|
665
|
+
if request.method != "GET":
|
|
666
|
+
return
|
|
667
|
+
except (AttributeError, TypeError, KeyError):
|
|
668
|
+
# The serializer was not initialized with request context.
|
|
669
|
+
return
|
|
670
|
+
query_params = request.query_params
|
|
671
|
+
if self.include_arg_name in query_params or self.exclude_arg_name in query_params:
|
|
672
|
+
return
|
|
673
|
+
for fieldname in ["files", "manifest", "docs_blob", "contents"]:
|
|
674
|
+
# Redact these fields by default as they have no size limit.
|
|
675
|
+
self.fields.pop(fieldname)
|
|
676
|
+
|
|
661
677
|
def validate(self, data):
|
|
662
678
|
"""Run super() validate if creating, else return data."""
|
|
663
679
|
# This validation is for creating CollectionVersions
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import asyncio
|
|
2
|
+
import functools
|
|
2
3
|
import hashlib
|
|
3
4
|
import json
|
|
4
5
|
import logging
|
|
@@ -8,11 +9,11 @@ from gettext import gettext as _
|
|
|
8
9
|
from operator import attrgetter
|
|
9
10
|
from urllib.parse import urljoin
|
|
10
11
|
from uuid import uuid4
|
|
12
|
+
import tempfile
|
|
11
13
|
|
|
12
14
|
import yaml
|
|
13
15
|
from aiohttp.client_exceptions import ClientError, ClientResponseError
|
|
14
16
|
from asgiref.sync import sync_to_async
|
|
15
|
-
from async_lru import alru_cache
|
|
16
17
|
from django.conf import settings
|
|
17
18
|
from django.db import transaction
|
|
18
19
|
from django.db.models import Q
|
|
@@ -32,7 +33,6 @@ from pulpcore.plugin.models import (
|
|
|
32
33
|
ProgressReport,
|
|
33
34
|
PulpTemporaryFile,
|
|
34
35
|
Remote,
|
|
35
|
-
RepositoryContent,
|
|
36
36
|
RepositoryVersion,
|
|
37
37
|
Task,
|
|
38
38
|
)
|
|
@@ -40,6 +40,8 @@ from pulpcore.plugin.stages import (
|
|
|
40
40
|
ArtifactDownloader,
|
|
41
41
|
ArtifactSaver,
|
|
42
42
|
ContentSaver,
|
|
43
|
+
ContentAssociation,
|
|
44
|
+
EndStage,
|
|
43
45
|
DeclarativeArtifact,
|
|
44
46
|
DeclarativeContent,
|
|
45
47
|
DeclarativeVersion,
|
|
@@ -49,6 +51,7 @@ from pulpcore.plugin.stages import (
|
|
|
49
51
|
RemoteArtifactSaver,
|
|
50
52
|
ResolveContentFutures,
|
|
51
53
|
Stage,
|
|
54
|
+
create_pipeline,
|
|
52
55
|
)
|
|
53
56
|
from rest_framework.serializers import ValidationError
|
|
54
57
|
from semantic_version import SimpleSpec, Version
|
|
@@ -79,6 +82,26 @@ from pulp_ansible.app.tasks.utils import (
|
|
|
79
82
|
log = logging.getLogger(__name__)
|
|
80
83
|
|
|
81
84
|
|
|
85
|
+
def async_cache(f):
|
|
86
|
+
# A simple implementation for caching an async function with hashable arguments.
|
|
87
|
+
_cache = {}
|
|
88
|
+
|
|
89
|
+
@functools.wraps(f)
|
|
90
|
+
async def _wrapped_f(*args):
|
|
91
|
+
try:
|
|
92
|
+
return _cache[args]
|
|
93
|
+
except KeyError:
|
|
94
|
+
result = await f(*args)
|
|
95
|
+
_cache[args] = result
|
|
96
|
+
if len(_cache) > 128:
|
|
97
|
+
# This is a bit ad hoc and will give fifo instead of lru behaviour.
|
|
98
|
+
# I doubt it even makes a difference here.
|
|
99
|
+
_cache.pop(next(iter(_cache.keys())))
|
|
100
|
+
return result
|
|
101
|
+
|
|
102
|
+
return _wrapped_f
|
|
103
|
+
|
|
104
|
+
|
|
82
105
|
# semantic_version.SimpleSpec interpretes "*" as ">=0.0.0"
|
|
83
106
|
class AnsibleSpec(SimpleSpec):
|
|
84
107
|
def __init__(self, expression):
|
|
@@ -106,13 +129,15 @@ async def declarative_content_from_git_repo(remote, url, git_ref=None, metadata_
|
|
|
106
129
|
if git_ref:
|
|
107
130
|
try:
|
|
108
131
|
gitrepo = Repo.clone_from(
|
|
109
|
-
url, uuid4(), depth=1, branch=git_ref, multi_options=["--recurse-submodules"]
|
|
132
|
+
url, str(uuid4()), depth=1, branch=git_ref, multi_options=["--recurse-submodules"]
|
|
110
133
|
)
|
|
111
134
|
except GitCommandError:
|
|
112
|
-
gitrepo = Repo.clone_from(url, uuid4(), multi_options=["--recurse-submodules"])
|
|
135
|
+
gitrepo = Repo.clone_from(url, str(uuid4()), multi_options=["--recurse-submodules"])
|
|
113
136
|
gitrepo.git.checkout(git_ref)
|
|
114
137
|
else:
|
|
115
|
-
gitrepo = Repo.clone_from(
|
|
138
|
+
gitrepo = Repo.clone_from(
|
|
139
|
+
url, str(uuid4()), depth=1, multi_options=["--recurse-submodules"]
|
|
140
|
+
)
|
|
116
141
|
commit_sha = gitrepo.head.commit.hexsha
|
|
117
142
|
metadata, artifact_path = sync_collection(gitrepo.working_dir, ".")
|
|
118
143
|
artifact = Artifact.init_and_validate(artifact_path)
|
|
@@ -196,14 +221,7 @@ def sync(remote_pk, repository_pk, mirror, optimize):
|
|
|
196
221
|
if not remote.url:
|
|
197
222
|
raise ValueError(_("A CollectionRemote must have a 'url' specified to synchronize."))
|
|
198
223
|
|
|
199
|
-
|
|
200
|
-
for namespace, name in AnsibleCollectionDeprecated.objects.filter(
|
|
201
|
-
pk__in=repository.latest_version().content
|
|
202
|
-
).values_list("namespace", "name"):
|
|
203
|
-
deprecation_before_sync.add(f"{namespace}.{name}")
|
|
204
|
-
first_stage = CollectionSyncFirstStage(
|
|
205
|
-
remote, repository, is_repo_remote, deprecation_before_sync, optimize
|
|
206
|
-
)
|
|
224
|
+
first_stage = CollectionSyncFirstStage(remote, repository, is_repo_remote, optimize)
|
|
207
225
|
if first_stage.should_sync is False:
|
|
208
226
|
log.debug(_("no-op: remote wasn't updated since last sync."))
|
|
209
227
|
return
|
|
@@ -217,23 +235,6 @@ def sync(remote_pk, repository_pk, mirror, optimize):
|
|
|
217
235
|
repository.last_synced_metadata_time = first_stage.last_synced_metadata_time
|
|
218
236
|
repository.save(update_fields=["last_synced_metadata_time"])
|
|
219
237
|
|
|
220
|
-
# This goes against all rules!
|
|
221
|
-
# Content must be added and removed via the dedicated functions, which is impossible after the
|
|
222
|
-
# repository version was finalized.
|
|
223
|
-
# TODO Fix this!
|
|
224
|
-
to_undeprecate = Q()
|
|
225
|
-
undeprecated = deprecation_before_sync.difference(first_stage.deprecation_after_sync)
|
|
226
|
-
if undeprecated:
|
|
227
|
-
for collection in undeprecated:
|
|
228
|
-
namespace, name = collection.split(".")
|
|
229
|
-
to_undeprecate |= Q(namespace=namespace, name=name)
|
|
230
|
-
deprecated = AnsibleCollectionDeprecated.objects.filter(to_undeprecate)
|
|
231
|
-
RepositoryContent.objects.filter(
|
|
232
|
-
repository=repository,
|
|
233
|
-
content__in=deprecated,
|
|
234
|
-
version_removed__isnull=True,
|
|
235
|
-
).update(version_removed=repo_version)
|
|
236
|
-
|
|
237
238
|
|
|
238
239
|
def import_collection(
|
|
239
240
|
temp_file_pk,
|
|
@@ -478,13 +479,58 @@ class AnsibleDeclarativeVersion(DeclarativeVersion):
|
|
|
478
479
|
|
|
479
480
|
return pipeline
|
|
480
481
|
|
|
482
|
+
def create(self):
|
|
483
|
+
"""
|
|
484
|
+
Perform the work. This is the long-blocking call where all syncing occurs.
|
|
485
|
+
|
|
486
|
+
Returns: The created RepositoryVersion or None if it represents no change from the latest.
|
|
487
|
+
"""
|
|
488
|
+
with tempfile.TemporaryDirectory(dir="."):
|
|
489
|
+
with self.repository.new_version() as new_version:
|
|
490
|
+
deprecation_before_sync = {
|
|
491
|
+
(namespace, name)
|
|
492
|
+
for namespace, name in AnsibleCollectionDeprecated.objects.filter(
|
|
493
|
+
pk__in=self.repository.latest_version().content
|
|
494
|
+
).values_list("namespace", "name")
|
|
495
|
+
}
|
|
496
|
+
loop = asyncio.get_event_loop()
|
|
497
|
+
stages = self.pipeline_stages(new_version)
|
|
498
|
+
stages.append(UndeprecateStage(deprecation_before_sync))
|
|
499
|
+
stages.append(ContentAssociation(new_version, self.mirror))
|
|
500
|
+
stages.append(EndStage())
|
|
501
|
+
pipeline = create_pipeline(stages)
|
|
502
|
+
loop.run_until_complete(pipeline)
|
|
503
|
+
|
|
504
|
+
if deprecation_before_sync:
|
|
505
|
+
to_undeprecate = Q()
|
|
506
|
+
for namespace, name in deprecation_before_sync:
|
|
507
|
+
to_undeprecate |= Q(namespace=namespace, name=name)
|
|
508
|
+
new_version.remove_content(
|
|
509
|
+
AnsibleCollectionDeprecated.objects.filter(to_undeprecate)
|
|
510
|
+
)
|
|
511
|
+
|
|
512
|
+
return new_version if new_version.complete else None
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
class UndeprecateStage(Stage):
|
|
516
|
+
def __init__(self, deprecation_before_sync):
|
|
517
|
+
self.deprecation_before_sync = deprecation_before_sync
|
|
518
|
+
|
|
519
|
+
async def run(self):
|
|
520
|
+
async for batch in self.batches():
|
|
521
|
+
for d_content in batch:
|
|
522
|
+
if isinstance(d_content.content, AnsibleCollectionDeprecated):
|
|
523
|
+
key = (d_content.content.namespace, d_content.content.name)
|
|
524
|
+
self.deprecation_before_sync.discard(key)
|
|
525
|
+
await self.put(d_content)
|
|
526
|
+
|
|
481
527
|
|
|
482
528
|
class CollectionSyncFirstStage(Stage):
|
|
483
529
|
"""
|
|
484
530
|
The first stage of a pulp_ansible sync pipeline.
|
|
485
531
|
"""
|
|
486
532
|
|
|
487
|
-
def __init__(self, remote, repository, is_repo_remote,
|
|
533
|
+
def __init__(self, remote, repository, is_repo_remote, optimize):
|
|
488
534
|
"""
|
|
489
535
|
The first stage of a pulp_ansible sync pipeline.
|
|
490
536
|
|
|
@@ -492,15 +538,12 @@ class CollectionSyncFirstStage(Stage):
|
|
|
492
538
|
remote (CollectionRemote): The remote data to be used when syncing
|
|
493
539
|
repository (AnsibleRepository): The repository being syncedself.
|
|
494
540
|
is_repo_remote (bool): True if the remote is the repository's remote.
|
|
495
|
-
deprecation_before_sync (set): Set of deprecations before the sync.
|
|
496
541
|
optimize (boolean): Whether to optimize sync or not.
|
|
497
542
|
|
|
498
543
|
"""
|
|
499
544
|
super().__init__()
|
|
500
545
|
self.remote = remote
|
|
501
546
|
self.repository = repository
|
|
502
|
-
self.deprecation_before_sync = deprecation_before_sync
|
|
503
|
-
self.deprecation_after_sync = set()
|
|
504
547
|
self.collection_info = parse_collections_requirements_file(remote.requirements_file)
|
|
505
548
|
self.exclude_info = {}
|
|
506
549
|
self.add_dependents = self.collection_info and self.remote.sync_dependencies
|
|
@@ -521,7 +564,7 @@ class CollectionSyncFirstStage(Stage):
|
|
|
521
564
|
self._should_we_sync()
|
|
522
565
|
)
|
|
523
566
|
|
|
524
|
-
@
|
|
567
|
+
@async_cache
|
|
525
568
|
async def _get_root_api(self, root):
|
|
526
569
|
"""
|
|
527
570
|
Returns the root api path and api version.
|
|
@@ -557,7 +600,7 @@ class CollectionSyncFirstStage(Stage):
|
|
|
557
600
|
|
|
558
601
|
return endpoint, api_version
|
|
559
602
|
|
|
560
|
-
@
|
|
603
|
+
@async_cache
|
|
561
604
|
async def _get_paginated_collection_api(self, root):
|
|
562
605
|
"""
|
|
563
606
|
Returns the collection api path and api version.
|
|
@@ -767,7 +810,6 @@ class CollectionSyncFirstStage(Stage):
|
|
|
767
810
|
d_content = DeclarativeContent(
|
|
768
811
|
content=AnsibleCollectionDeprecated(namespace=namespace, name=name),
|
|
769
812
|
)
|
|
770
|
-
self.deprecation_after_sync.add(f"{namespace}.{name}")
|
|
771
813
|
await self.put(d_content)
|
|
772
814
|
tasks.append(
|
|
773
815
|
loop.create_task(
|
|
@@ -802,7 +844,6 @@ class CollectionSyncFirstStage(Stage):
|
|
|
802
844
|
d_content = DeclarativeContent(
|
|
803
845
|
content=AnsibleCollectionDeprecated(namespace=namespace, name=name),
|
|
804
846
|
)
|
|
805
|
-
self.deprecation_after_sync.add(f"{namespace}.{name}")
|
|
806
847
|
await self.put(d_content)
|
|
807
848
|
|
|
808
849
|
all_versions_of_collection = self._unpaginated_collection_version_metadata[namespace][name]
|
|
@@ -925,9 +966,6 @@ class CollectionSyncFirstStage(Stage):
|
|
|
925
966
|
namespace=collection["namespace"], name=collection["name"]
|
|
926
967
|
),
|
|
927
968
|
)
|
|
928
|
-
self.deprecation_after_sync.add(
|
|
929
|
-
f"{collection['namespace']}.{collection['name']}"
|
|
930
|
-
)
|
|
931
969
|
await self.put(d_content)
|
|
932
970
|
|
|
933
971
|
for collections_in_namespace in self._unpaginated_collection_version_metadata.values():
|
|
@@ -118,6 +118,13 @@ def collection_detail(http_session, collection_upload, pulp_dist, collection_art
|
|
|
118
118
|
return response.json()
|
|
119
119
|
|
|
120
120
|
|
|
121
|
+
@pytest.fixture(scope="class")
|
|
122
|
+
def collection_highest_version(http_session, collection_detail):
|
|
123
|
+
response = http_session.get(collection_detail["highest_version"]["href"])
|
|
124
|
+
response.raise_for_status()
|
|
125
|
+
return response.json()
|
|
126
|
+
|
|
127
|
+
|
|
121
128
|
@pytest.fixture(scope="class")
|
|
122
129
|
def pulp_dist(ansible_repository_factory, ansible_distribution_factory):
|
|
123
130
|
"""Create an Ansible Distribution to simulate the automation hub environment for testing."""
|
|
@@ -291,34 +298,52 @@ class TestCollection:
|
|
|
291
298
|
# # 'repository': 'http://github.example.com/orionuser1/skeleton',
|
|
292
299
|
# # 'tags': ['collectiontest']},
|
|
293
300
|
|
|
294
|
-
def
|
|
301
|
+
def test_collection_download_metadata_unauthorized_fails(
|
|
295
302
|
self,
|
|
296
303
|
http_session,
|
|
297
304
|
collection_detail,
|
|
298
|
-
collection_artifact,
|
|
299
305
|
):
|
|
300
|
-
"""Test collection download URL.
|
|
301
|
-
|
|
302
|
-
Should require authentication and redirect to a download location.
|
|
303
|
-
"""
|
|
304
306
|
response = http_session.get(collection_detail["highest_version"]["href"], auth=NullAuth())
|
|
305
307
|
assert response.status_code == 401
|
|
306
|
-
response = http_session.get(collection_detail["highest_version"]["href"])
|
|
307
|
-
response.raise_for_status()
|
|
308
|
-
version = response.json()
|
|
309
308
|
|
|
309
|
+
def test_collection_download_redirects(
|
|
310
|
+
self,
|
|
311
|
+
http_session,
|
|
312
|
+
collection_highest_version,
|
|
313
|
+
collection_artifact,
|
|
314
|
+
):
|
|
310
315
|
# Artifact Download Endoint
|
|
311
|
-
url =
|
|
316
|
+
url = collection_highest_version["download_url"]
|
|
312
317
|
|
|
313
318
|
with open(collection_artifact.filename, "rb") as fp:
|
|
314
319
|
tarball = fp.read()
|
|
315
320
|
|
|
316
|
-
response = http_session.get(url, auth=NullAuth())
|
|
317
|
-
assert response.status_code == 401
|
|
318
321
|
response = http_session.get(url)
|
|
319
322
|
assert response.status_code == 200, (url, response.request.headers)
|
|
320
323
|
assert response.content == tarball
|
|
321
324
|
|
|
325
|
+
def test_collection_download_redirect_fails_unauthorized(
|
|
326
|
+
self,
|
|
327
|
+
http_session,
|
|
328
|
+
collection_highest_version,
|
|
329
|
+
):
|
|
330
|
+
# Artifact Download Endoint
|
|
331
|
+
url = collection_highest_version["download_url"]
|
|
332
|
+
|
|
333
|
+
response = http_session.get(url, auth=NullAuth())
|
|
334
|
+
assert response.status_code == 401
|
|
335
|
+
|
|
336
|
+
def test_downloading_collection_with_bad_name_returns_not_found(
|
|
337
|
+
self,
|
|
338
|
+
http_session,
|
|
339
|
+
collection_highest_version,
|
|
340
|
+
):
|
|
341
|
+
# Artifact Download Endoint, but we mess up the address.
|
|
342
|
+
url = collection_highest_version["download_url"].replace("-", "_")
|
|
343
|
+
|
|
344
|
+
response = http_session.get(url)
|
|
345
|
+
assert response.status_code == 404
|
|
346
|
+
|
|
322
347
|
def test_collection_upload_repeat(
|
|
323
348
|
self, http_session, ansible_collection_factory, pulp_dist, collection_upload
|
|
324
349
|
):
|
|
@@ -35,7 +35,6 @@ def test_deprecation(
|
|
|
35
35
|
collections = ansible_bindings.PulpAnsibleApiV3CollectionsApi.list(
|
|
36
36
|
first_distribution.base_path, namespace="testing"
|
|
37
37
|
)
|
|
38
|
-
|
|
39
38
|
assert collections.data[0].deprecated
|
|
40
39
|
collections = ansible_bindings.PulpAnsibleApiV3CollectionsApi.list(
|
|
41
40
|
first_distribution.base_path, namespace="pulp"
|
|
@@ -62,15 +61,12 @@ def test_deprecation(
|
|
|
62
61
|
)
|
|
63
62
|
assert collections.data[0].deprecated
|
|
64
63
|
|
|
65
|
-
#
|
|
66
|
-
|
|
67
|
-
"k8s_demo_collection", "testing", first_distribution.base_path, {"deprecated": False}
|
|
68
|
-
)
|
|
69
|
-
monitor_task(result.task)
|
|
64
|
+
# Sync again to see if the deprecated state is kept.
|
|
65
|
+
ansible_sync_factory(second_repo, optimize=False)
|
|
70
66
|
collections = ansible_bindings.PulpAnsibleApiV3CollectionsApi.list(
|
|
71
|
-
|
|
67
|
+
second_distribution.base_path, namespace="testing"
|
|
72
68
|
)
|
|
73
|
-
assert
|
|
69
|
+
assert collections.data[0].deprecated
|
|
74
70
|
|
|
75
71
|
# Update the requirements to sync down both collections this time
|
|
76
72
|
requirements = (
|
|
@@ -83,9 +79,34 @@ def test_deprecation(
|
|
|
83
79
|
# Sync the second repo again
|
|
84
80
|
second_repo = ansible_sync_factory(second_repo)
|
|
85
81
|
|
|
82
|
+
# Assert the state of deprecated True for testing, False for pulp
|
|
83
|
+
collections = ansible_bindings.PulpAnsibleApiV3CollectionsApi.list(
|
|
84
|
+
second_distribution.base_path, namespace="testing"
|
|
85
|
+
)
|
|
86
|
+
assert collections.data[0].deprecated
|
|
87
|
+
collections = ansible_bindings.PulpAnsibleApiV3CollectionsApi.list(
|
|
88
|
+
second_distribution.base_path, namespace="pulp"
|
|
89
|
+
)
|
|
90
|
+
assert not collections.data[0].deprecated
|
|
91
|
+
|
|
92
|
+
# Change the deprecated status for the testing collection on the original repo to False
|
|
93
|
+
monitor_task(
|
|
94
|
+
ansible_bindings.PulpAnsibleApiV3CollectionsApi.update(
|
|
95
|
+
"k8s_demo_collection", "testing", first_distribution.base_path, {"deprecated": False}
|
|
96
|
+
).task
|
|
97
|
+
)
|
|
98
|
+
collections = ansible_bindings.PulpAnsibleApiV3CollectionsApi.list(
|
|
99
|
+
first_distribution.base_path, namespace="testing"
|
|
100
|
+
)
|
|
101
|
+
assert not collections.data[0].deprecated
|
|
102
|
+
|
|
103
|
+
# Sync the second repo again
|
|
104
|
+
second_repo = ansible_sync_factory(second_repo)
|
|
105
|
+
|
|
86
106
|
# Assert both collections show deprecated=False
|
|
87
107
|
collections = ansible_bindings.PulpAnsibleApiV3CollectionsApi.list(
|
|
88
108
|
second_distribution.base_path
|
|
89
109
|
)
|
|
110
|
+
assert len(collections.data) == 2, collections
|
|
90
111
|
for collection in collections.data:
|
|
91
112
|
assert not collection.deprecated
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pulp-ansible
|
|
3
|
-
Version: 0.25.
|
|
3
|
+
Version: 0.25.3
|
|
4
4
|
Summary: Pulp plugin to manage Ansible content, e.g. roles
|
|
5
5
|
Author-email: Pulp Ansible Plugin Project Developers <pulp-dev@redhat.com>
|
|
6
6
|
Project-URL: Homepage, https://pulpproject.org
|
|
@@ -18,13 +18,12 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
18
18
|
Requires-Python: >=3.9
|
|
19
19
|
Description-Content-Type: text/x-rst
|
|
20
20
|
License-File: LICENSE
|
|
21
|
-
Requires-Dist:
|
|
22
|
-
Requires-Dist: galaxy_importer<0.5,>=0.4.5
|
|
21
|
+
Requires-Dist: galaxy_importer<0.5,>=0.4.27
|
|
23
22
|
Requires-Dist: GitPython<3.2,>=3.1.24
|
|
24
|
-
Requires-Dist: jsonschema<4.
|
|
25
|
-
Requires-Dist: Pillow<11.
|
|
23
|
+
Requires-Dist: jsonschema<4.26,>=4.9
|
|
24
|
+
Requires-Dist: Pillow<11.4,>=10.3
|
|
26
25
|
Requires-Dist: pulpcore<3.85,>=3.49.0
|
|
27
|
-
Requires-Dist: PyYAML<7.0,>=
|
|
26
|
+
Requires-Dist: PyYAML<7.0,>=6.0.1
|
|
28
27
|
Requires-Dist: semantic_version<2.11,>=2.9
|
|
29
28
|
Dynamic: license-file
|
|
30
29
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pulp_ansible/__init__.py,sha256=WXL3JyOZzh4tALN4OBNgqj412O2LXVr8iYSn14Av0_o,67
|
|
2
2
|
pulp_ansible/pytest_plugin.py,sha256=1x_Nb39VqNmI8KMEA4RS0fkvOPpkpp-7Dwzo9mjvyJQ,8763
|
|
3
|
-
pulp_ansible/app/__init__.py,sha256=
|
|
3
|
+
pulp_ansible/app/__init__.py,sha256=6nupvvxeUj_IdcAjY2Tjb7CjgrdojFb3_6x2PjStQj0,279
|
|
4
4
|
pulp_ansible/app/constants.py,sha256=cRrWMbJU-v1nCY2CjmGTOFGzz5aggn4ZKIs4-zeYADQ,78
|
|
5
5
|
pulp_ansible/app/downloaders.py,sha256=0xrAXzhTMJ6ywvs6XyYNAKvRzYb_UZR6A5_WgaHvuiE,8331
|
|
6
6
|
pulp_ansible/app/fields.py,sha256=yV3FL-47K-nxbiBxgzWi1hk3tfLsRdgic59LiwT2Fvs,624
|
|
@@ -8,7 +8,7 @@ pulp_ansible/app/global_access_conditions.py,sha256=ayV_LbQIa4ReuDYQycdDY7WysfGk
|
|
|
8
8
|
pulp_ansible/app/logutils.py,sha256=My4XivZMYLKeJqPHFwk3pIZSGlspALhI93vZxwWzlSY,846
|
|
9
9
|
pulp_ansible/app/modelresource.py,sha256=Kqhmhd53JPfOthoOzLPxN1TnGERJBFckvwQoJfaIKnw,7839
|
|
10
10
|
pulp_ansible/app/models.py,sha256=N-xlOAKttNzC6_3XifFSDIlUssURy8tr3EVxvQVpqe0,22648
|
|
11
|
-
pulp_ansible/app/serializers.py,sha256=
|
|
11
|
+
pulp_ansible/app/serializers.py,sha256=T2egPZ2a1ZsCGMCxfg72BB0o9wfFhGOd7jVZm8-QNe4,36139
|
|
12
12
|
pulp_ansible/app/settings.py,sha256=M9OdR-EiHjmskVCGHBjHkBMts66kx4F22sS2dgb7YAw,1186
|
|
13
13
|
pulp_ansible/app/urls.py,sha256=PM-kGhBX7eQTJgsomUCxbIUHpIhyVZCyDUywQ21KAxU,8443
|
|
14
14
|
pulp_ansible/app/utils.py,sha256=ClI_WvVy73kOZQB2mS8Gftf80Vye7pOxrbodTmPQgmk,1686
|
|
@@ -22,7 +22,7 @@ pulp_ansible/app/galaxy/v3/exceptions.py,sha256=uKTj_LteLusc9Wu0iSYO-U8Gq9UK1eVR
|
|
|
22
22
|
pulp_ansible/app/galaxy/v3/filters.py,sha256=xkb_m8chPZBJzElwuFkhLV5Hx8I1A7ZnxdTqGYFnpyc,8087
|
|
23
23
|
pulp_ansible/app/galaxy/v3/pagination.py,sha256=FhFKtPfljjmKrnB_EWz7FxAhKPX9gkuvbOx-2KU9V4Y,4042
|
|
24
24
|
pulp_ansible/app/galaxy/v3/serializers.py,sha256=se2BpCnU-Pa0HfxNwu1My78qyloR_ahbuXNBlJM_VjU,13030
|
|
25
|
-
pulp_ansible/app/galaxy/v3/views.py,sha256=
|
|
25
|
+
pulp_ansible/app/galaxy/v3/views.py,sha256=hk9UbE7z3dBzlJA4Z_gAO6qxWNwn5CBAKQRgECDuCU4,47199
|
|
26
26
|
pulp_ansible/app/galaxy/v3/viewsets.py,sha256=z6vuXnROCraS5OLonHe4iPRt7jvI0bjDbeiLi_infjs,3858
|
|
27
27
|
pulp_ansible/app/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
28
28
|
pulp_ansible/app/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -84,14 +84,14 @@ pulp_ansible/app/migrations/0053_collectiondownloadcount.py,sha256=XK3FJf2P9XX7i
|
|
|
84
84
|
pulp_ansible/app/migrations/0054_split_collection_version_numbers.py,sha256=QdJkJ48A_ZcgD2WyyRrAdPMmQe8OWkEgs4FtMFa-rDg,2369
|
|
85
85
|
pulp_ansible/app/migrations/0055_alter_collectionversion_version_alter_role_version.py,sha256=g_4p81Owqi6sY0-KAv_mSPya1MNlj8pbJkMpHZ4hLpc,904
|
|
86
86
|
pulp_ansible/app/migrations/0056_collectionversion_sha256.py,sha256=6gLDAEcULJag1_L3jpbmrA42EeP5wsWhlq0VALK_Lpg,487
|
|
87
|
-
pulp_ansible/app/migrations/0057_collectionversion_sha256_migrate.py,sha256=
|
|
87
|
+
pulp_ansible/app/migrations/0057_collectionversion_sha256_migrate.py,sha256=MoMVQ2wnSmJqu826yqCKf3VGXmJCCiAXbUluWO8wMAY,2414
|
|
88
88
|
pulp_ansible/app/migrations/0058_fix_0056_regression.py,sha256=VTmiG_VlpC6wXb4R28TDC4rbd1KLTuPItgzcrq0R28w,1021
|
|
89
89
|
pulp_ansible/app/migrations/0059_collectionversion_unique_sha256.py,sha256=_4MAElCkv5OUjv7v9eL43h9PhtLl85vOfMiTZKEGD-Q,1291
|
|
90
90
|
pulp_ansible/app/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
91
91
|
pulp_ansible/app/schema/__init__.py,sha256=eGi2QVJ0kJgXwiDNjQsvqWDSr3EA8TFqFZATTY8xwT8,207
|
|
92
92
|
pulp_ansible/app/schema/copy_config.json,sha256=AJz8riON7_rh-L7jM5iev2Imc5UKjlNvGfwF7KrFH7Y,568
|
|
93
93
|
pulp_ansible/app/tasks/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
94
|
-
pulp_ansible/app/tasks/collections.py,sha256=
|
|
94
|
+
pulp_ansible/app/tasks/collections.py,sha256=_cDqU7_fhNmAJCboDDbth-QNu1n11-fXyQa3VPYg4cY,51932
|
|
95
95
|
pulp_ansible/app/tasks/collectionversion_index.py,sha256=_cnQeWRpFXrreTJM9aaEc2HuknTksZa0lLZ7XUfmQ_c,9401
|
|
96
96
|
pulp_ansible/app/tasks/copy.py,sha256=dO_DnLT5hck6GpZomEHurrDHJM0Wkh2B0_wgHHOtbuY,5563
|
|
97
97
|
pulp_ansible/app/tasks/deletion.py,sha256=f-VNTMzDMQiJpCLN34fY5gwhAGlh3RACj4VWc_tOaP0,3579
|
|
@@ -121,12 +121,12 @@ pulp_ansible/tests/functional/api/collection/test_signatures.py,sha256=Oos2kyUMz
|
|
|
121
121
|
pulp_ansible/tests/functional/api/collection/test_sync.py,sha256=ITeATktxSpXbV4emJ8xXl4EGuebBRmrBdDRarVmSgJ8,14818
|
|
122
122
|
pulp_ansible/tests/functional/api/collection/v3/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
123
123
|
pulp_ansible/tests/functional/api/collection/v3/test_client_configuration.py,sha256=Tk8PdvG2Y1mZs5pUm2z91Tlpxaw0_8TvhshNbioMkKg,648
|
|
124
|
-
pulp_ansible/tests/functional/api/collection/v3/test_collection.py,sha256=
|
|
124
|
+
pulp_ansible/tests/functional/api/collection/v3/test_collection.py,sha256=NAkW8SDhgHsVg88Fy0FTBzaOgDB2hHei-uFBMRZops0,13524
|
|
125
125
|
pulp_ansible/tests/functional/api/collection/v3/test_collection_naming_edgecases.py,sha256=qPYFf-lWg2-i8xRvRGMKmGHbg00Cyd0EqGjzXF4ET-8,5492
|
|
126
126
|
pulp_ansible/tests/functional/api/collection/v3/test_collection_version_search.py,sha256=uQGWs5ny0R-dP9YjZcspFRPDQTA-_OSuy6Dm4TLPrT8,48422
|
|
127
127
|
pulp_ansible/tests/functional/api/collection/v3/test_content_guard.py,sha256=lid_TY4ANaU1fmoDAn6xuApOcP8UnrBuXlYfaqGXyvA,3311
|
|
128
128
|
pulp_ansible/tests/functional/api/collection/v3/test_deletion.py,sha256=V25G4VgJybNeMbElunE1cOSm_6KRk8FkYBAW-L1ZLWc,11620
|
|
129
|
-
pulp_ansible/tests/functional/api/collection/v3/test_deprecation.py,sha256=
|
|
129
|
+
pulp_ansible/tests/functional/api/collection/v3/test_deprecation.py,sha256=n8khr_JySTXWDl-SiSDt4SLcdncMBGa5lBMIGyItyak,4185
|
|
130
130
|
pulp_ansible/tests/functional/api/collection/v3/test_namespace.py,sha256=6eOazDAQXnx3rxwhSX1cpSjWMrqj63DFtNnsHrswpGA,8828
|
|
131
131
|
pulp_ansible/tests/functional/api/collection/v3/test_proxy.py,sha256=Yb6BOZu0bnSGcy9zC57W5a-ohh7MpKRVJhZ6tgSS_LY,2817
|
|
132
132
|
pulp_ansible/tests/functional/api/collection/v3/test_redirects.py,sha256=ffolGXCJUp20YWk-ix_dWmCxtLN5yePHVywys3yDEZI,5170
|
|
@@ -160,9 +160,9 @@ pulp_ansible/tests/unit/migrations/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQe
|
|
|
160
160
|
pulp_ansible/tests/unit/migrations/conftest.py,sha256=61n56aJuwEuAcUS27xnmnEDx6Ie7M5zxh851doWm7Ok,438
|
|
161
161
|
pulp_ansible/tests/unit/migrations/test_0057_collection_version_sha256_migrate.py,sha256=NmU-frZNO1qHvhdxfw-bwPTEkqN4Wdc3dEs9dWME47o,2174
|
|
162
162
|
pulp_ansible/tests/unit/migrations/test_x_repo_search_migration.py,sha256=IrC_V_Gss4nqhkdL-60upcEzGcz8IsiI2d-MfUtFY-g,657
|
|
163
|
-
pulp_ansible-0.25.
|
|
164
|
-
pulp_ansible-0.25.
|
|
165
|
-
pulp_ansible-0.25.
|
|
166
|
-
pulp_ansible-0.25.
|
|
167
|
-
pulp_ansible-0.25.
|
|
168
|
-
pulp_ansible-0.25.
|
|
163
|
+
pulp_ansible-0.25.3.dist-info/licenses/LICENSE,sha256=2ylvL381vKOhdO-w6zkrOxe9lLNBhRQpo9_0EbHC_HM,18046
|
|
164
|
+
pulp_ansible-0.25.3.dist-info/METADATA,sha256=I-i1_OORETYhkS2s21gyaOMLzJaprA7NWAoD4-1YKzA,3173
|
|
165
|
+
pulp_ansible-0.25.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
166
|
+
pulp_ansible-0.25.3.dist-info/entry_points.txt,sha256=6PFqCdT7Yn7U5MlioYRsyyWbJDcZI2aAJuzc7yXHqGk,119
|
|
167
|
+
pulp_ansible-0.25.3.dist-info/top_level.txt,sha256=5Rrg5DSM_F9wH8vu8Fxjb5EmroC-I8RVKp05fhXH6kQ,13
|
|
168
|
+
pulp_ansible-0.25.3.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|