Skip to content

Commit

Permalink
As a user, I can remove all container content with ["*"]
Browse files Browse the repository at this point in the history
  • Loading branch information
ipanova committed Nov 20, 2019
1 parent 68400b9 commit c5945c2
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 64 deletions.
1 change: 1 addition & 0 deletions CHANGES/5756.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
As a user, I can remove all repository container content with ["*"]
2 changes: 1 addition & 1 deletion docs/_static/api.json

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions docs/workflows/manage-content.rst
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,10 @@ New Repository Version::
"number": 2
}

.. note::

Users can remove all content from the repo by specifying '*' in the content_units

Reference: `Container Recursive Remove Usage <../restapi.html#tag/container:-recursive-remove>`_

.. _tag-copy:
Expand Down
13 changes: 13 additions & 0 deletions pulp_container/app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -276,6 +276,19 @@ class RecursiveManageSerializer(serializers.Serializer):
required=False
)

def validate(self, data):
"""
Validate data passed through a request call.
"""
content_units = data.get('content_units', None)
if content_units:
if '*' in content_units and len(content_units) > 1:
raise serializers.ValidationError(
_("'*' should be the only item present in the {}"
.format(content_units))
)
return data


class CopySerializer(serializers.Serializer):
"""
Expand Down
131 changes: 68 additions & 63 deletions pulp_container/app/tasks/recursive_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,66 +32,71 @@ def recursive_remove_content(repository_pk, content_units):
repository = ContainerRepository.objects.get(pk=repository_pk)
latest_version = repository.latest_version()
latest_content = latest_version.content.all() if latest_version else Content.objects.none()

tags_in_repo = Q(pk__in=latest_content.filter(pulp_type='container.tag'))
manifests_in_repo = Q(pk__in=latest_content.filter(pulp_type='container.manifest'))
user_provided_content = Q(pk__in=content_units)
type_manifest_list = Q(media_type=MEDIA_TYPE.MANIFEST_LIST)
type_manifest = Q(media_type__in=[MEDIA_TYPE.MANIFEST_V1, MEDIA_TYPE.MANIFEST_V2])
blobs_in_repo = Q(pk__in=latest_content.filter(pulp_type='container.blob'))

# Tags do not have must_remain because they are the highest level content.
tags_to_remove = Tag.objects.filter(user_provided_content & tags_in_repo)
tags_to_remain = Tag.objects.filter(tags_in_repo).exclude(pk__in=tags_to_remove)
tagged_manifests_must_remain = Q(
pk__in=tags_to_remain.values_list("tagged_manifest", flat=True)
)
tagged_manifests_to_remove = Q(pk__in=tags_to_remove.values_list("tagged_manifest", flat=True))

manifest_lists_must_remain = Manifest.objects.filter(
manifests_in_repo & tagged_manifests_must_remain & type_manifest_list
)
manifest_lists_to_remove = Manifest.objects.filter(
user_provided_content | tagged_manifests_to_remove
).filter(
type_manifest_list & manifests_in_repo
).exclude(pk__in=manifest_lists_must_remain)

manifest_lists_to_remain = Manifest.objects.filter(
manifests_in_repo & type_manifest_list
).exclude(pk__in=manifest_lists_to_remove)

listed_manifests_must_remain = Q(
pk__in=manifest_lists_to_remain.values_list('listed_manifests', flat=True)
)
manifests_must_remain = Manifest.objects.filter(
tagged_manifests_must_remain | listed_manifests_must_remain
).filter(type_manifest & manifests_in_repo)

listed_manifests_to_remove = Q(
pk__in=manifest_lists_to_remove.values_list('listed_manifests', flat=True)
)
manifests_to_remove = Manifest.objects.filter(
user_provided_content | listed_manifests_to_remove | tagged_manifests_to_remove
).filter(type_manifest & manifests_in_repo).exclude(pk__in=manifests_must_remain)

manifests_to_remain = Manifest.objects.filter(
manifests_in_repo & type_manifest
).exclude(pk__in=manifests_to_remove)

listed_blobs_must_remain = Q(
pk__in=manifests_to_remain.values_list('blobs', flat=True)
) | Q(pk__in=manifests_to_remain.values_list('config_blob', flat=True))
listed_blobs_to_remove = Q(
pk__in=manifests_to_remove.values_list('blobs', flat=True)
) | Q(pk__in=manifests_to_remove.values_list('config_blob', flat=True))

blobs_to_remove = Blob.objects.filter(
user_provided_content | listed_blobs_to_remove
).filter(blobs_in_repo).exclude(listed_blobs_must_remain)

with repository.new_version() as new_version:
new_version.remove_content(tags_to_remove)
new_version.remove_content(manifest_lists_to_remove)
new_version.remove_content(manifests_to_remove)
new_version.remove_content(blobs_to_remove)
if '*' in content_units:
with repository.new_version() as new_version:
new_version.remove_content(latest_content)
else:
tags_in_repo = Q(pk__in=latest_content.filter(pulp_type='container.tag'))
manifests_in_repo = Q(pk__in=latest_content.filter(pulp_type='container.manifest'))
user_provided_content = Q(pk__in=content_units)
type_manifest_list = Q(media_type=MEDIA_TYPE.MANIFEST_LIST)
type_manifest = Q(media_type__in=[MEDIA_TYPE.MANIFEST_V1, MEDIA_TYPE.MANIFEST_V2])
blobs_in_repo = Q(pk__in=latest_content.filter(pulp_type='container.blob'))

# Tags do not have must_remain because they are the highest level content.
tags_to_remove = Tag.objects.filter(user_provided_content & tags_in_repo)
tags_to_remain = Tag.objects.filter(tags_in_repo).exclude(pk__in=tags_to_remove)
tagged_manifests_must_remain = Q(
pk__in=tags_to_remain.values_list("tagged_manifest", flat=True)
)
tagged_manifests_to_remove = Q(
pk__in=tags_to_remove.values_list("tagged_manifest", flat=True)
)

manifest_lists_must_remain = Manifest.objects.filter(
manifests_in_repo & tagged_manifests_must_remain & type_manifest_list
)
manifest_lists_to_remove = Manifest.objects.filter(
user_provided_content | tagged_manifests_to_remove
).filter(
type_manifest_list & manifests_in_repo
).exclude(pk__in=manifest_lists_must_remain)

manifest_lists_to_remain = Manifest.objects.filter(
manifests_in_repo & type_manifest_list
).exclude(pk__in=manifest_lists_to_remove)

listed_manifests_must_remain = Q(
pk__in=manifest_lists_to_remain.values_list('listed_manifests', flat=True)
)
manifests_must_remain = Manifest.objects.filter(
tagged_manifests_must_remain | listed_manifests_must_remain
).filter(type_manifest & manifests_in_repo)

listed_manifests_to_remove = Q(
pk__in=manifest_lists_to_remove.values_list('listed_manifests', flat=True)
)
manifests_to_remove = Manifest.objects.filter(
user_provided_content | listed_manifests_to_remove | tagged_manifests_to_remove
).filter(type_manifest & manifests_in_repo).exclude(pk__in=manifests_must_remain)

manifests_to_remain = Manifest.objects.filter(
manifests_in_repo & type_manifest
).exclude(pk__in=manifests_to_remove)

listed_blobs_must_remain = Q(
pk__in=manifests_to_remain.values_list('blobs', flat=True)
) | Q(pk__in=manifests_to_remain.values_list('config_blob', flat=True))
listed_blobs_to_remove = Q(
pk__in=manifests_to_remove.values_list('blobs', flat=True)
) | Q(pk__in=manifests_to_remove.values_list('config_blob', flat=True))

blobs_to_remove = Blob.objects.filter(
user_provided_content | listed_blobs_to_remove
).filter(blobs_in_repo).exclude(listed_blobs_must_remain)

with repository.new_version() as new_version:
new_version.remove_content(tags_to_remove)
new_version.remove_content(manifest_lists_to_remove)
new_version.remove_content(manifests_to_remove)
new_version.remove_content(blobs_to_remove)
4 changes: 4 additions & 0 deletions pulp_container/app/viewsets.py
Original file line number Diff line number Diff line change
Expand Up @@ -271,6 +271,10 @@ def remove(self, request, pk):

if 'content_units' in request.data:
for url in request.data['content_units']:
if url == '*':
remove_content_units = [url]
break

content = NamedModelViewSet.get_resource(url, Content)
remove_content_units.append(content.pk)

Expand Down
37 changes: 37 additions & 0 deletions pulp_container/tests/functional/api/test_recursive_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

from pulp_smash import api, config
from pulp_smash.pulp3.utils import gen_repo, sync
from requests.exceptions import HTTPError

from pulp_container.tests.functional.constants import (
CONTAINER_TAG_PATH,
Expand Down Expand Up @@ -53,6 +54,42 @@ def test_repository_only_no_latest_version(self):
latest_version_href = self.client.get(self.to_repo['pulp_href'])['latest_version_href']
self.assertIsNone(latest_version_href)

def test_remove_everything(self):
"""Add a manifest and its related blobs."""
manifest_a = self.client.get("{unit_path}?{filters}".format(
unit_path=CONTAINER_TAG_PATH,
filters="name=manifest_a&{v_filter}".format(v_filter=self.latest_from_version),
))['results'][0]['tagged_manifest']
self.client.post(
self.CONTAINER_RECURSIVE_ADD_PATH,
{'content_units': [manifest_a]})
latest_version_href = self.client.get(self.to_repo['pulp_href'])['latest_version_href']
latest = self.client.get(latest_version_href)

# Ensure test begins in the correct state
self.assertFalse('container.tag' in latest['content_summary']['added'])
self.assertEqual(latest['content_summary']['added']['container.manifest']['count'], 1)
self.assertEqual(latest['content_summary']['added']['container.blob']['count'], 2)

# Actual test
self.client.post(
self.CONTAINER_RECURSIVE_REMOVE_PATH,
{'content_units': ["*"]})
latest_version_href = self.client.get(self.to_repo['pulp_href'])['latest_version_href']
latest = self.client.get(latest_version_href)
self.assertEqual(latest['content_summary']['present'], {})
self.assertEqual(latest['content_summary']['removed']['container.blob']['count'], 2)
self.assertEqual(latest['content_summary']['removed']['container.manifest']['count'], 1)

def test_remove_invalid_content_units(self):
"""Ensure exception is raised when '*' is not the only item in the content_units."""
with self.assertRaises(HTTPError) as context:
self.client.post(
self.CONTAINER_RECURSIVE_REMOVE_PATH,
{'content_units': ["*", "some_href"]}
)
self.assertEqual(context.exception.response.status_code, 400)

def test_manifest_recursion(self):
"""Add a manifest and its related blobs."""
manifest_a = self.client.get("{unit_path}?{filters}".format(
Expand Down

0 comments on commit c5945c2

Please sign in to comment.