diff --git a/CHANGES/3008.feature b/CHANGES/3008.feature new file mode 100644 index 0000000000..56c7c5eba2 --- /dev/null +++ b/CHANGES/3008.feature @@ -0,0 +1 @@ +Added support for Domains. diff --git a/pulp_rpm/app/__init__.py b/pulp_rpm/app/__init__.py index 7d579dcf94..593103479f 100644 --- a/pulp_rpm/app/__init__.py +++ b/pulp_rpm/app/__init__.py @@ -10,3 +10,4 @@ class PulpRpmPluginAppConfig(PulpPluginAppConfig): label = "rpm" version = "3.20.0.dev" python_package_name = "pulp-rpm" + domain_compatible = True diff --git a/pulp_rpm/app/migrations/0049_domains.py b/pulp_rpm/app/migrations/0049_domains.py new file mode 100644 index 0000000000..19ff1940e2 --- /dev/null +++ b/pulp_rpm/app/migrations/0049_domains.py @@ -0,0 +1,150 @@ +# Generated by Django 3.2.18 on 2023-04-19 00:43 + +from django.db import migrations, models +import django.db.models.deletion +import pulpcore.app.util + + +class Migration(migrations.Migration): + + dependencies = [ + ('core', '0102_add_domain_relations'), + ('rpm', '0048_artifacts_dependencies_fix'), + ] + + operations = [ + migrations.AddField( + model_name='distributiontree', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_distributiontree', to='core.domain'), + ), + migrations.AddField( + model_name='modulemd', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_modulemd', to='core.domain'), + ), + migrations.AddField( + model_name='modulemddefaults', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_modulemddefaults', to='core.domain'), + ), + migrations.AddField( + model_name='modulemdobsolete', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_modulemdobsolete', to='core.domain'), + ), + migrations.AddField( + model_name='package', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_package', to='core.domain'), + ), + migrations.AddField( + model_name='packagecategory', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_packagecategory', to='core.domain'), + ), + migrations.AddField( + model_name='packageenvironment', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_packageenvironment', to='core.domain'), + ), + migrations.AddField( + model_name='packagegroup', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_packagegroup', to='core.domain'), + ), + migrations.AddField( + model_name='packagelangpacks', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_packagelangpacks', to='core.domain'), + ), + migrations.AddField( + model_name='repometadatafile', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_repometadatafile', to='core.domain'), + ), + migrations.AddField( + model_name='updaterecord', + name='_pulp_domain', + field=models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.PROTECT, related_name='rpm_updaterecord', to='core.domain'), + ), + migrations.AlterField( + model_name='modulemddefaults', + name='digest', + field=models.TextField(db_index=True), + ), + migrations.AlterField( + model_name='package', + name='pkgId', + field=models.TextField(db_index=True), + ), + migrations.AlterField( + model_name='packagecategory', + name='digest', + field=models.TextField(db_index=True), + ), + migrations.AlterField( + model_name='packageenvironment', + name='digest', + field=models.TextField(db_index=True), + ), + migrations.AlterField( + model_name='packagegroup', + name='digest', + field=models.TextField(db_index=True), + ), + migrations.AlterField( + model_name='packagelangpacks', + name='digest', + field=models.TextField(db_index=True), + ), + migrations.AlterField( + model_name='updaterecord', + name='digest', + field=models.TextField(db_index=True), + ), + migrations.AlterUniqueTogether( + name='distributiontree', + unique_together={('_pulp_domain', 'digest')}, + ), + migrations.AlterUniqueTogether( + name='modulemd', + unique_together={('_pulp_domain', 'name', 'stream', 'version', 'context', 'arch')}, + ), + migrations.AlterUniqueTogether( + name='modulemddefaults', + unique_together={('_pulp_domain', 'digest')}, + ), + migrations.AlterUniqueTogether( + name='modulemdobsolete', + unique_together={('_pulp_domain', 'modified', 'module_name', 'module_stream')}, + ), + migrations.AlterUniqueTogether( + name='package', + unique_together={('_pulp_domain', 'name', 'epoch', 'version', 'release', 'arch', 'checksum_type', 'pkgId')}, + ), + migrations.AlterUniqueTogether( + name='packagecategory', + unique_together={('_pulp_domain', 'digest')}, + ), + migrations.AlterUniqueTogether( + name='packageenvironment', + unique_together={('_pulp_domain', 'digest')}, + ), + migrations.AlterUniqueTogether( + name='packagegroup', + unique_together={('_pulp_domain', 'digest')}, + ), + migrations.AlterUniqueTogether( + name='packagelangpacks', + unique_together={('_pulp_domain', 'digest')}, + ), + migrations.AlterUniqueTogether( + name='repometadatafile', + unique_together={('_pulp_domain', 'data_type', 'checksum', 'relative_path')}, + ), + migrations.AlterUniqueTogether( + name='updaterecord', + unique_together={('_pulp_domain', 'digest')}, + ), + ] diff --git a/pulp_rpm/app/models/advisory.py b/pulp_rpm/app/models/advisory.py index ac87d5f5eb..37790adec8 100644 --- a/pulp_rpm/app/models/advisory.py +++ b/pulp_rpm/app/models/advisory.py @@ -8,6 +8,7 @@ BaseModel, Content, ) +from pulpcore.plugin.util import get_domain_pk from pulp_rpm.app.constants import ( CR_UPDATE_COLLECTION_ATTRS, @@ -100,7 +101,9 @@ class UpdateRecord(Content): # A field that represents the hash digest of the update record. Used to track differences # between two UpdateRecord objects without having to examine the associations like # UpdateCollection or UpdateCollectionPackage. - digest = models.TextField(unique=True) + digest = models.TextField(db_index=True) + + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) @classmethod def natural_key_fields(cls): @@ -228,6 +231,7 @@ def get_module_list(self): class Meta: default_related_name = "%(app_label)s_%(model_name)s" + unique_together = ("_pulp_domain", "digest") class UpdateCollection(BaseModel): diff --git a/pulp_rpm/app/models/comps.py b/pulp_rpm/app/models/comps.py index 0641498f3b..1b582d3b8d 100644 --- a/pulp_rpm/app/models/comps.py +++ b/pulp_rpm/app/models/comps.py @@ -5,6 +5,7 @@ from django.db import models from pulpcore.plugin.models import Content +from pulpcore.plugin.util import get_domain_pk from pulp_rpm.app.constants import ( LIBCOMPS_CATEGORY_ATTRS, @@ -73,12 +74,15 @@ class PackageGroup(Content): desc_by_lang = models.JSONField(default=dict) name_by_lang = models.JSONField(default=dict) - digest = models.TextField(unique=True) + digest = models.TextField(db_index=True) repo_key_fields = ("id",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + class Meta: default_related_name = "%(app_label)s_%(model_name)s" + unique_together = ("_pulp_domain", "digest") @classmethod def natural_key_fields(cls): @@ -230,12 +234,15 @@ class PackageCategory(Content): desc_by_lang = models.JSONField(default=dict) name_by_lang = models.JSONField(default=dict) - digest = models.TextField(unique=True) + digest = models.TextField(db_index=True) repo_key_fields = ("id",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + class Meta: default_related_name = "%(app_label)s_%(model_name)s" + unique_together = ("_pulp_domain", "digest") @classmethod def natural_key_fields(cls): @@ -357,12 +364,15 @@ class PackageEnvironment(Content): desc_by_lang = models.JSONField(default=dict) name_by_lang = models.JSONField(default=dict) - digest = models.TextField(unique=True) + digest = models.TextField(db_index=True) repo_key_fields = ("id",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + class Meta: default_related_name = "%(app_label)s_%(model_name)s" + unique_together = ("_pulp_domain", "digest") @classmethod def natural_key_fields(cls): @@ -463,10 +473,13 @@ class PackageLangpacks(Content): matches = models.JSONField(default=dict) - digest = models.TextField(unique=True) + digest = models.TextField(db_index=True) + + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) class Meta: default_related_name = "%(app_label)s_%(model_name)s" + unique_together = ("_pulp_domain", "digest") @classmethod def natural_key_fields(cls): diff --git a/pulp_rpm/app/models/custom_metadata.py b/pulp_rpm/app/models/custom_metadata.py index f1263aaa23..dd317008d3 100644 --- a/pulp_rpm/app/models/custom_metadata.py +++ b/pulp_rpm/app/models/custom_metadata.py @@ -3,6 +3,7 @@ from django.db import models from pulpcore.plugin.models import Content +from pulpcore.plugin.util import get_domain_pk from pulp_rpm.app.constants import CHECKSUM_CHOICES log = getLogger(__name__) @@ -32,9 +33,11 @@ class RepoMetadataFile(Content): repo_key_fields = ("data_type",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = ("data_type", "checksum", "relative_path") + unique_together = ("_pulp_domain", "data_type", "checksum", "relative_path") @property def unsupported_metadata_type(self): diff --git a/pulp_rpm/app/models/distribution.py b/pulp_rpm/app/models/distribution.py index b322d0df57..cf222e7da0 100644 --- a/pulp_rpm/app/models/distribution.py +++ b/pulp_rpm/app/models/distribution.py @@ -12,6 +12,7 @@ ContentArtifact, Repository, ) +from pulpcore.plugin.util import get_domain_pk log = getLogger(__name__) @@ -94,6 +95,8 @@ class DistributionTree(Content): digest = models.TextField(null=False) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + def repositories(self): """ Return the subrepos in this DistributionTree. @@ -134,7 +137,7 @@ def artifacts(self): class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = ("digest",) + unique_together = ("_pulp_domain", "digest") class Checksum(BaseModel): diff --git a/pulp_rpm/app/models/modulemd.py b/pulp_rpm/app/models/modulemd.py index eda89a44ec..f42eb53803 100644 --- a/pulp_rpm/app/models/modulemd.py +++ b/pulp_rpm/app/models/modulemd.py @@ -3,6 +3,7 @@ from django.db import models from pulpcore.plugin.models import Content +from pulpcore.plugin.util import get_domain_pk from pulp_rpm.app.models.package import Package @@ -65,9 +66,11 @@ class Modulemd(Content): snippet = models.TextField() + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = ("name", "stream", "version", "context", "arch") + unique_together = ("_pulp_domain", "name", "stream", "version", "context", "arch") class ModulemdDefaults(Content): @@ -92,12 +95,14 @@ class ModulemdDefaults(Content): module = models.TextField() stream = models.TextField() profiles = models.JSONField(default=list) - digest = models.TextField(unique=True) + digest = models.TextField(db_index=True) snippet = models.TextField() repo_key_fields = ("module",) + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + @classmethod def natural_key_fields(cls): """ @@ -107,6 +112,7 @@ def natural_key_fields(cls): class Meta: default_related_name = "%(app_label)s_%(model_name)s" + unique_together = ("_pulp_domain", "digest") class ModulemdObsolete(Content): @@ -151,6 +157,8 @@ class ModulemdObsolete(Content): snippet = models.TextField() + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = ("modified", "module_name", "module_stream") + unique_together = ("_pulp_domain", "modified", "module_name", "module_stream") diff --git a/pulp_rpm/app/models/package.py b/pulp_rpm/app/models/package.py index 3f82a89851..4bc4e9381c 100644 --- a/pulp_rpm/app/models/package.py +++ b/pulp_rpm/app/models/package.py @@ -8,6 +8,7 @@ from django.db.models.functions import RowNumber from pulpcore.plugin.models import Content, ContentManager +from pulpcore.plugin.util import get_domain_pk from pulp_rpm.app.constants import ( CHECKSUM_CHOICES, @@ -167,7 +168,7 @@ class Package(Content): # Currently filled by a database trigger - consider eventually switching to generated column evr = RpmVersionField() - pkgId = models.TextField(unique=True) # formerly "checksum" in Pulp 2 + pkgId = models.TextField(db_index=True) # formerly "checksum" in Pulp 2 checksum_type = models.TextField(choices=CHECKSUM_CHOICES) # Optional metadata @@ -236,6 +237,8 @@ class Package(Content): # E.g. glibc-2.26.11.3.2.nosrc.rpm vs glibc-2.26.11.3.2.src.rpm repo_key_fields = ("name", "epoch", "version", "release", "arch", "location_href") + _pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT) + @property def filename(self): """ @@ -283,7 +286,17 @@ def nevra_short(self): class Meta: default_related_name = "%(app_label)s_%(model_name)s" - unique_together = ("name", "epoch", "version", "release", "arch", "checksum_type", "pkgId") + unique_together = ("_pulp_domain", "pkgId") + unique_together = ( + "_pulp_domain", + "name", + "epoch", + "version", + "release", + "arch", + "checksum_type", + "pkgId", + ) class ReadonlyMeta: readonly = ["evr"] diff --git a/pulp_rpm/app/models/repository.py b/pulp_rpm/app/models/repository.py index 85ee6da3f7..d9b6ed3472 100644 --- a/pulp_rpm/app/models/repository.py +++ b/pulp_rpm/app/models/repository.py @@ -452,11 +452,23 @@ def content_handler(self, path): repository, publication = self.get_repository_and_publication() if not publication: return - base_url = "{}/".format( - urlpath_sanitize( - settings.CONTENT_ORIGIN, settings.CONTENT_PATH_PREFIX, self.base_path + if settings.DOMAIN_ENABLED: + base_url = "{}/".format( + urlpath_sanitize( + settings.CONTENT_ORIGIN, + settings.CONTENT_PATH_PREFIX, + self.pulp_domain.name, + self.base_path, + ) + ) + else: + base_url = "{}/".format( + urlpath_sanitize( + settings.CONTENT_ORIGIN, + settings.CONTENT_PATH_PREFIX, + self.base_path, + ) ) - ) val = textwrap.dedent( f"""\ [{self.name}] diff --git a/pulp_rpm/app/serializers/advisory.py b/pulp_rpm/app/serializers/advisory.py index d060343448..86e3e701c1 100644 --- a/pulp_rpm/app/serializers/advisory.py +++ b/pulp_rpm/app/serializers/advisory.py @@ -10,6 +10,7 @@ ModelSerializer, NoArtifactContentUploadSerializer, ) +from pulpcore.plugin.util import get_domain from pulp_rpm.app.advisory import hash_update_record from pulp_rpm.app.fields import ( @@ -231,6 +232,16 @@ def validate(self, data): validated_data = super().validate(update_record_data) return validated_data + # pulpcore 3.22 feature + # TODO upload has to be able to "find" a unique advisory, and id is Not It? + # when uploading content second time I don't get error but already existing content + def retrieve(self, validated_data): + content = UpdateRecord.objects.filter( + id=validated_data["id"], + pulp_domain=get_domain(), + ) + return content.first() + class Meta: fields = NoArtifactContentUploadSerializer.Meta.fields + ( "id", diff --git a/pulp_rpm/app/serializers/comps.py b/pulp_rpm/app/serializers/comps.py index 4b94ea9c5d..4380118131 100644 --- a/pulp_rpm/app/serializers/comps.py +++ b/pulp_rpm/app/serializers/comps.py @@ -177,5 +177,13 @@ class CompsXmlSerializer(serializers.Serializer): write_only=True, ) + # TODO: determine what comps is uploaded + # def retrieve(self, validated_data): + # content = UpdateRecord.objects.filter( + # digest=validated_data["id"], + # pulp_domain=get_domain(), + # ) + # return content.first() + class Meta: fields = ("file", "repository", "replace") diff --git a/pulp_rpm/app/serializers/custom_metadata.py b/pulp_rpm/app/serializers/custom_metadata.py index fbe5014e76..f035f9c80f 100644 --- a/pulp_rpm/app/serializers/custom_metadata.py +++ b/pulp_rpm/app/serializers/custom_metadata.py @@ -5,6 +5,7 @@ ContentChecksumSerializer, SingleArtifactContentUploadSerializer, ) +from pulpcore.plugin.util import get_domain from pulp_rpm.app.models import RepoMetadataFile @@ -19,6 +20,16 @@ class RepoMetadataFileSerializer(SingleArtifactContentUploadSerializer, ContentC checksum = serializers.CharField(help_text=_("Checksum for the file.")) relative_path = serializers.CharField(help_text=_("Relative path of the file.")) + # Mirror unique_together from the Model + def retrieve(self, validated_data): + content = RepoMetadataFile.objects.filter( + data_type=validated_data["data_type"], + checksum=validated_data["checksum"], + relative_path=validated_data["relative_path"], + pulp_domain=get_domain(), + ) + return content.first() + class Meta: fields = ( ContentChecksumSerializer.Meta.fields diff --git a/pulp_rpm/app/serializers/package.py b/pulp_rpm/app/serializers/package.py index cbf841d675..04f5b031c2 100644 --- a/pulp_rpm/app/serializers/package.py +++ b/pulp_rpm/app/serializers/package.py @@ -10,6 +10,7 @@ ContentChecksumSerializer, SingleArtifactContentUploadSerializer, ) +from pulpcore.plugin.util import get_domain_pk from pulp_rpm.app.models import Package from pulp_rpm.app.shared_utils import read_crpackage_from_artifact, format_nvra @@ -270,6 +271,7 @@ def deferred_validate(self, data): data.update(new_pkg) return data + # TODO: add domain check def retrieve(self, data): try: pkg = Package.createrepo_to_dict(read_crpackage_from_artifact(data["artifact"])) @@ -278,8 +280,7 @@ def retrieve(self, data): raise NotAcceptable(detail="RPM file cannot be parsed for metadata") package = Package(**pkg) - - return Package.objects.filter(package.q()).first() + return Package.objects.filter(pkgId=package.pkgId, pulp_domain=get_domain_pk()).first() class Meta: fields = ( diff --git a/pulp_rpm/app/tasks/comps.py b/pulp_rpm/app/tasks/comps.py index 57ef437a16..b0e8afc9a0 100644 --- a/pulp_rpm/app/tasks/comps.py +++ b/pulp_rpm/app/tasks/comps.py @@ -8,6 +8,8 @@ from pulpcore.plugin.models import PulpTemporaryFile, CreatedResource from pulpcore.plugin.models import Content +from pulpcore.plugin.util import get_domain + from pulp_rpm.app.comps import strdict_to_dict, dict_digest from pulp_rpm.app.models import ( @@ -42,7 +44,9 @@ def parse_comps_components(comps_file): if comps.langpacks: langpack_dict = PackageLangpacks.libcomps_to_dict(comps.langpacks) langpack, created = PackageLangpacks.objects.get_or_create( - matches=strdict_to_dict(comps.langpacks), digest=dict_digest(langpack_dict) + matches=strdict_to_dict(comps.langpacks), + digest=dict_digest(langpack_dict), + _pulp_domain=get_domain(), ) if created: created_objects.append(langpack) @@ -52,6 +56,7 @@ def parse_comps_components(comps_file): for category in comps.categories: category_dict = PackageCategory.libcomps_to_dict(category) category_dict["digest"] = dict_digest(category_dict) + category_dict["_pulp_domain"] = get_domain() packagecategory, created = PackageCategory.objects.get_or_create(**category_dict) if created: created_objects.append(packagecategory) @@ -61,6 +66,7 @@ def parse_comps_components(comps_file): for environment in comps.environments: environment_dict = PackageEnvironment.libcomps_to_dict(environment) environment_dict["digest"] = dict_digest(environment_dict) + environment_dict["_pulp_domain"] = get_domain() packageenvironment, created = PackageEnvironment.objects.get_or_create( **environment_dict ) @@ -72,6 +78,7 @@ def parse_comps_components(comps_file): for group in comps.groups: group_dict = PackageGroup.libcomps_to_dict(group) group_dict["digest"] = dict_digest(group_dict) + group_dict["_pulp_domain"] = get_domain() packagegroup, created = PackageGroup.objects.get_or_create(**group_dict) if created: created_objects.append(packagegroup) diff --git a/pulp_rpm/app/tasks/copy.py b/pulp_rpm/app/tasks/copy.py index e8902ae294..be2e56778d 100644 --- a/pulp_rpm/app/tasks/copy.py +++ b/pulp_rpm/app/tasks/copy.py @@ -2,6 +2,7 @@ from django.db.models import Q from pulpcore.plugin.models import Content, RepositoryVersion +from pulpcore.plugin.util import get_domain from pulp_rpm.app.depsolving import Solver from pulp_rpm.app.models import ( @@ -145,6 +146,8 @@ def process_entry(entry): else: content_filter = Q() + content_filter &= Q(pulp_domain=get_domain()) + return ( source_repo_version, dest_repo_version, diff --git a/pulp_rpm/app/urls.py b/pulp_rpm/app/urls.py index f4686280ad..e90889e2dd 100644 --- a/pulp_rpm/app/urls.py +++ b/pulp_rpm/app/urls.py @@ -3,7 +3,10 @@ from .viewsets import CopyViewSet, CompsXmlViewSet -V3_API_ROOT = settings.V3_API_ROOT_NO_FRONT_SLASH +if settings.DOMAIN_ENABLED: + V3_API_ROOT = settings.V3_DOMAIN_API_ROOT_NO_FRONT_SLASH +else: + V3_API_ROOT = settings.V3_API_ROOT_NO_FRONT_SLASH urlpatterns = [ path(f"{V3_API_ROOT}rpm/copy/", CopyViewSet.as_view({"post": "create"})), diff --git a/pulp_rpm/app/viewsets/acs.py b/pulp_rpm/app/viewsets/acs.py index 88b9b79183..2411e369fc 100644 --- a/pulp_rpm/app/viewsets/acs.py +++ b/pulp_rpm/app/viewsets/acs.py @@ -49,15 +49,15 @@ class RpmAlternateContentSourceViewSet(AlternateContentSourceViewSet, RolesMixin "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.view_rpmalternatecontentsource", + "condition": "has_model_or_domain_or_obj_perms:rpm.view_rpmalternatecontentsource", }, { "action": ["create"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_remote_param_model_or_obj_perms:rpm.view_rpmremote", - "has_model_perms:rpm.add_rpmalternatecontentsource", + "has_remote_param_model_or_domain_or_obj_perms:rpm.view_rpmremote", + "has_model_or_domain_perms:rpm.add_rpmalternatecontentsource", ], }, { @@ -65,8 +65,8 @@ class RpmAlternateContentSourceViewSet(AlternateContentSourceViewSet, RolesMixin "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.view_rpmalternatecontentsource", - "has_model_perms:rpm.refresh_rpmalternatecontentsource", + "has_model_or_domain_or_obj_perms:rpm.view_rpmalternatecontentsource", + "has_model_or_domain_perms:rpm.refresh_rpmalternatecontentsource", ], }, { @@ -74,9 +74,9 @@ class RpmAlternateContentSourceViewSet(AlternateContentSourceViewSet, RolesMixin "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.change_rpmalternatecontentsource", - "has_model_or_obj_perms:rpm.view_rpmalternatecontentsource", - "has_remote_param_model_or_obj_perms:rpm.view_rpmremote", + "has_model_or_domain_or_obj_perms:rpm.change_rpmalternatecontentsource", + "has_model_or_domain_or_obj_perms:rpm.view_rpmalternatecontentsource", + "has_remote_param_model_or_domain_or_obj_perms:rpm.view_rpmremote", ], }, { @@ -84,15 +84,16 @@ class RpmAlternateContentSourceViewSet(AlternateContentSourceViewSet, RolesMixin "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.delete_rpmalternatecontentsource", - "has_model_or_obj_perms:rpm.view_rpmalternatecontentsource", + "has_model_or_domain_or_obj_perms:rpm.delete_rpmalternatecontentsource", + "has_model_or_domain_or_obj_perms:rpm.view_rpmalternatecontentsource", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.manage_roles_rpmalternatecontentsource", + "condition": "has_model_or_domain_or_obj_perms:" + "rpm.manage_roles_rpmalternatecontentsource", }, ], "queryset_scoping": {"function": "scope_queryset"}, diff --git a/pulp_rpm/app/viewsets/repository.py b/pulp_rpm/app/viewsets/repository.py index 0bb401a654..74edf00ab3 100644 --- a/pulp_rpm/app/viewsets/repository.py +++ b/pulp_rpm/app/viewsets/repository.py @@ -65,15 +65,15 @@ class RpmRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin, Roles "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.view_rpmrepository", + "condition": "has_model_or_domain_or_obj_perms:rpm.view_rpmrepository", }, { "action": ["create"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_remote_param_model_or_obj_perms:rpm.view_rpmremote", - "has_model_perms:rpm.add_rpmrepository", + "has_remote_param_model_or_domain_or_obj_perms:rpm.view_rpmremote", + "has_model_or_domain_perms:rpm.add_rpmrepository", ], }, { @@ -81,9 +81,9 @@ class RpmRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin, Roles "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.change_rpmrepository", - "has_model_or_obj_perms:rpm.view_rpmrepository", - "has_remote_param_model_or_obj_perms:rpm.view_rpmremote", + "has_model_or_domain_or_obj_perms:rpm.change_rpmrepository", + "has_model_or_domain_or_obj_perms:rpm.view_rpmrepository", + "has_remote_param_model_or_domain_or_obj_perms:rpm.view_rpmremote", ], }, { @@ -91,8 +91,8 @@ class RpmRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin, Roles "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.modify_content_rpmrepository", - "has_model_or_obj_perms:rpm.view_rpmrepository", + "has_model_or_domain_or_obj_perms:rpm.modify_content_rpmrepository", + "has_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, { @@ -100,8 +100,8 @@ class RpmRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin, Roles "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.delete_rpmrepository", - "has_model_or_obj_perms:rpm.view_rpmrepository", + "has_model_or_domain_or_obj_perms:rpm.delete_rpmrepository", + "has_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, { @@ -109,16 +109,16 @@ class RpmRepositoryViewSet(RepositoryViewSet, ModifyRepositoryActionMixin, Roles "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.sync_rpmrepository", - "has_model_or_obj_perms:rpm.view_rpmrepository", - "has_remote_param_model_or_obj_perms:rpm.view_rpmremote", + "has_model_or_domain_or_obj_perms:rpm.sync_rpmrepository", + "has_model_or_domain_or_obj_perms:rpm.view_rpmrepository", + "has_remote_param_model_or_domain_or_obj_perms:rpm.view_rpmremote", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.manage_roles_rpmrepository", + "condition": "has_model_or_domain_or_obj_perms:rpm.manage_roles_rpmrepository", }, ], "queryset_scoping": {"function": "scope_queryset"}, @@ -262,15 +262,15 @@ class RpmRepositoryVersionViewSet(RepositoryVersionViewSet): "action": ["list", "retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_repository_model_or_obj_perms:rpm.view_rpmrepository", + "condition": "has_repository_model_or_domain_or_obj_perms:rpm.view_rpmrepository", }, { "action": ["destroy"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_repository_model_or_obj_perms:rpm.delete_rpmrepository", - "has_repository_model_or_obj_perms:rpm.view_rpmrepository", + "has_repository_model_or_domain_or_obj_perms:rpm.delete_rpmrepository", + "has_repository_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, { @@ -278,8 +278,8 @@ class RpmRepositoryVersionViewSet(RepositoryVersionViewSet): "principal": "authenticated", "effect": "allow", "condition": [ - "has_repository_model_or_obj_perms:rpm.delete_rpmrepository_version", - "has_repository_model_or_obj_perms:rpm.view_rpmrepository", + "has_repository_model_or_domain_or_obj_perms:rpm.delete_rpmrepository_version", + "has_repository_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, { @@ -287,8 +287,8 @@ class RpmRepositoryVersionViewSet(RepositoryVersionViewSet): "principal": "authenticated", "effect": "allow", "condition": [ - "has_repository_model_or_obj_perms:rpm.repair_rpmrepository", - "has_repository_model_or_obj_perms:rpm.view_rpmrepository", + "has_repository_model_or_domain_or_obj_perms:rpm.repair_rpmrepository", + "has_repository_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, ], @@ -316,21 +316,21 @@ class RpmRemoteViewSet(RemoteViewSet, RolesMixin): "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.view_rpmremote", + "condition": "has_model_or_domain_or_obj_perms:rpm.view_rpmremote", }, { "action": ["create"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_perms:rpm.add_rpmremote", + "condition": "has_model_or_domain_perms:rpm.add_rpmremote", }, { "action": ["update", "partial_update"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.change_rpmremote", - "has_model_or_obj_perms:rpm.view_rpmremote", + "has_model_or_domain_or_obj_perms:rpm.change_rpmremote", + "has_model_or_domain_or_obj_perms:rpm.view_rpmremote", ], }, { @@ -338,15 +338,15 @@ class RpmRemoteViewSet(RemoteViewSet, RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.delete_rpmremote", - "has_model_or_obj_perms:rpm.view_rpmremote", + "has_model_or_domain_or_obj_perms:rpm.delete_rpmremote", + "has_model_or_domain_or_obj_perms:rpm.view_rpmremote", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.manage_roles_rpmremote", + "condition": "has_model_or_domain_or_obj_perms:rpm.manage_roles_rpmremote", }, ], "queryset_scoping": {"function": "scope_queryset"}, @@ -395,21 +395,21 @@ class UlnRemoteViewSet(RemoteViewSet, RolesMixin): "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.view_ulnremote", + "condition": "has_model_or_domain_or_obj_perms:rpm.view_ulnremote", }, { "action": ["create"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_perms:rpm.add_ulnremote", + "condition": "has_model_or_domain_perms:rpm.add_ulnremote", }, { "action": ["update", "partial_update"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.change_ulnremote", - "has_model_or_obj_perms:rpm.view_ulnremote", + "has_model_or_domain_or_obj_perms:rpm.change_ulnremote", + "has_model_or_domain_or_obj_perms:rpm.view_ulnremote", ], }, { @@ -417,15 +417,15 @@ class UlnRemoteViewSet(RemoteViewSet, RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.delete_ulnremote", - "has_model_or_obj_perms:rpm.view_ulnremote", + "has_model_or_domain_or_obj_perms:rpm.delete_ulnremote", + "has_model_or_domain_or_obj_perms:rpm.view_ulnremote", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.manage_roles_ulnremote", + "condition": "has_model_or_domain_or_obj_perms:rpm.manage_roles_ulnremote", }, ], "queryset_scoping": {"function": "scope_queryset"}, @@ -474,15 +474,15 @@ class RpmPublicationViewSet(PublicationViewSet, RolesMixin): "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.view_rpmpublication", + "condition": "has_model_or_domain_or_obj_perms:rpm.view_rpmpublication", }, { "action": ["create"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_perms:rpm.add_rpmpublication", - "has_repo_attr_model_or_obj_perms:rpm.view_rpmrepository", + "has_model_or_domain_perms:rpm.add_rpmpublication", + "has_repo_attr_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, { @@ -490,15 +490,15 @@ class RpmPublicationViewSet(PublicationViewSet, RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.delete_rpmpublication", - "has_model_or_obj_perms:rpm.view_rpmpublication", + "has_model_or_domain_or_obj_perms:rpm.delete_rpmpublication", + "has_model_or_domain_or_obj_perms:rpm.view_rpmpublication", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.manage_roles_rpmpublication", + "condition": "has_model_or_domain_or_obj_perms:rpm.manage_roles_rpmpublication", }, ], "queryset_scoping": {"function": "scope_queryset"}, @@ -600,16 +600,16 @@ class RpmDistributionViewSet(DistributionViewSet, RolesMixin): "action": ["retrieve"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.view_rpmdistribution", + "condition": "has_model_or_domain_or_obj_perms:rpm.view_rpmdistribution", }, { "action": ["create"], "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_perms:rpm.add_rpmdistribution", - "has_publication_param_model_or_obj_perms:rpm.view_rpmpublication", - "has_repo_attr_model_or_obj_perms:rpm.view_rpmrepository", + "has_model_or_domain_perms:rpm.add_rpmdistribution", + "has_publication_param_model_or_domain_or_obj_perms:rpm.view_rpmpublication", + "has_repo_attr_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, { @@ -617,10 +617,10 @@ class RpmDistributionViewSet(DistributionViewSet, RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.change_rpmdistribution", - "has_model_or_obj_perms:rpm.view_rpmdistribution", - "has_publication_param_model_or_obj_perms:rpm.view_rpmpublication", - "has_repo_attr_model_or_obj_perms:rpm.view_rpmrepository", + "has_model_or_domain_or_obj_perms:rpm.change_rpmdistribution", + "has_model_or_domain_or_obj_perms:rpm.view_rpmdistribution", + "has_publication_param_model_or_domain_or_obj_perms:rpm.view_rpmpublication", + "has_repo_attr_model_or_domain_or_obj_perms:rpm.view_rpmrepository", ], }, { @@ -628,15 +628,15 @@ class RpmDistributionViewSet(DistributionViewSet, RolesMixin): "principal": "authenticated", "effect": "allow", "condition": [ - "has_model_or_obj_perms:rpm.delete_rpmdistribution", - "has_model_or_obj_perms:rpm.view_rpmdistribution", + "has_model_or_domain_or_obj_perms:rpm.delete_rpmdistribution", + "has_model_or_domain_or_obj_perms:rpm.view_rpmdistribution", ], }, { "action": ["list_roles", "add_role", "remove_role"], "principal": "authenticated", "effect": "allow", - "condition": "has_model_or_obj_perms:rpm.manage_roles_rpmdistribution", + "condition": "has_model_or_domain_or_obj_perms:rpm.manage_roles_rpmdistribution", }, ], "queryset_scoping": {"function": "scope_queryset"}, diff --git a/pulp_rpm/tests/conftest.py b/pulp_rpm/tests/conftest.py index d1e9599edd..4450539dbf 100644 --- a/pulp_rpm/tests/conftest.py +++ b/pulp_rpm/tests/conftest.py @@ -63,10 +63,13 @@ def rpm_repository_api(rpm_client): def rpm_repository_factory(rpm_repository_api, gen_object_with_cleanup): """A factory to generate an RPM Repository with auto-deletion after the test run.""" - def _rpm_repository_factory(**kwargs): + def _rpm_repository_factory(pulp_domain=None, **body): data = {"name": str(uuid.uuid4())} - data.update(kwargs) - return gen_object_with_cleanup(rpm_repository_api, data) + data.update(body) + kwargs = {} + if pulp_domain: + kwargs["pulp_domain"] = pulp_domain + return gen_object_with_cleanup(rpm_repository_api, data, **kwargs) return _rpm_repository_factory @@ -75,10 +78,15 @@ def _rpm_repository_factory(**kwargs): def rpm_rpmremote_factory(rpm_rpmremote_api, gen_object_with_cleanup): """A factory to generate an RPM Remote with auto-deletion after the test run.""" - def _rpm_rpmremote_factory(*, url=RPM_UNSIGNED_FIXTURE_URL, policy="immediate", **kwargs): + def _rpm_rpmremote_factory( + *, url=RPM_UNSIGNED_FIXTURE_URL, policy="immediate", pulp_domain=None, **body + ): data = {"url": url, "policy": policy, "name": str(uuid.uuid4())} - data.update(kwargs) - return gen_object_with_cleanup(rpm_rpmremote_api, data) + data.update(body) + kwargs = {} + if pulp_domain: + kwargs["pulp_domain"] = pulp_domain + return gen_object_with_cleanup(rpm_rpmremote_api, data, **kwargs) return _rpm_rpmremote_factory @@ -87,10 +95,13 @@ def _rpm_rpmremote_factory(*, url=RPM_UNSIGNED_FIXTURE_URL, policy="immediate", def rpm_distribution_factory(rpm_distribution_api, gen_object_with_cleanup): """A factory to generate an RPM Distribution with auto-deletion after the test run.""" - def _rpm_distribution_factory(**kwargs): + def _rpm_distribution_factory(pulp_domain=None, **body): data = {"base_path": str(uuid.uuid4()), "name": str(uuid.uuid4())} - data.update(kwargs) - return gen_object_with_cleanup(rpm_distribution_api, data) + data.update(body) + kwargs = {} + if pulp_domain: + kwargs["pulp_domain"] = pulp_domain + return gen_object_with_cleanup(rpm_distribution_api, data, **kwargs) return _rpm_distribution_factory @@ -99,10 +110,13 @@ def _rpm_distribution_factory(**kwargs): def rpm_publication_factory(rpm_publication_api, gen_object_with_cleanup): """A factory to generate an RPM Publication with auto-deletion after the test run.""" - def _rpm_publication_factory(**kwargs): + def _rpm_publication_factory(pulp_domain=None, **body): # XOR check on repository and repository_version - assert bool("repository" in kwargs) ^ bool("repository_version" in kwargs) - return gen_object_with_cleanup(rpm_publication_api, kwargs) + assert bool("repository" in body) ^ bool("repository_version" in body) + kwargs = {} + if pulp_domain: + kwargs["pulp_domain"] = pulp_domain + return gen_object_with_cleanup(rpm_publication_api, body, **kwargs) return _rpm_publication_factory diff --git a/pulp_rpm/tests/functional/api/test_domains.py b/pulp_rpm/tests/functional/api/test_domains.py new file mode 100644 index 0000000000..e523c4ba02 --- /dev/null +++ b/pulp_rpm/tests/functional/api/test_domains.py @@ -0,0 +1,288 @@ +import json + +import pytest +import uuid + +from django.conf import settings + +from pulpcore.client.pulp_rpm import RpmRepositorySyncURL, ApiException +from pulpcore.client.pulpcore.exceptions import ApiException as CoreApiException + +from pulp_smash.pulp3.utils import gen_repo + +from pulp_rpm.tests.functional.utils import ( + gen_rpm_remote, + get_package_repo_path, +) +from pulp_rpm.tests.functional.constants import ( + RPM_SIGNED_FIXTURE_URL, +) + +if not settings.DOMAIN_ENABLED: + pytest.skip("Domains not enabled.", allow_module_level=True) + + +def test_domain_create( + domains_api_client, + gen_object_with_cleanup, + monitor_task, + rpm_repository_api, + rpm_rpmremote_api, +): + """Test domain creation.""" + new_domain = str(uuid.uuid4()) + body = { + "name": new_domain, + "storage_class": "pulpcore.app.models.storage.FileSystem", + "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"}, + } + domain = gen_object_with_cleanup(domains_api_client, body) + domain_name = domain.name + assert domain_name == new_domain + + # sync remote in default domain (not specified) + remote = gen_object_with_cleanup(rpm_rpmremote_api, gen_rpm_remote(RPM_SIGNED_FIXTURE_URL)) + repo = gen_object_with_cleanup(rpm_repository_api, gen_repo(remote=remote.pulp_href)) + sync_url = RpmRepositorySyncURL(remote=remote.pulp_href) + sync_response = rpm_repository_api.sync(repo.pulp_href, sync_url) + monitor_task(sync_response.task) + + assert rpm_repository_api.list(pulp_domain="default").count == 1 + # check that newly created domain doesn't have any repo + assert rpm_repository_api.list(pulp_domain=new_domain).count == 0 + + +@pytest.mark.parallel +def test_object_creation( + domains_api_client, gen_object_with_cleanup, rpm_repository_api, rpm_rpmremote_api +): + """Test basic object creation in a separate domain.""" + body = { + "name": str(uuid.uuid4()), + "storage_class": "pulpcore.app.models.storage.FileSystem", + "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"}, + } + domain = gen_object_with_cleanup(domains_api_client, body) + domain_name = domain.name + + repo_body = {"name": str(uuid.uuid4())} + repo = gen_object_with_cleanup(rpm_repository_api, repo_body, pulp_domain=domain_name) + assert f"{domain_name}/api/v3/" in repo.pulp_href + + repos = rpm_repository_api.list(pulp_domain=domain_name) + assert repos.count == 1 + assert repo.pulp_href == repos.results[0].pulp_href + + # Will list repos on default domain + default_repos = rpm_repository_api.list(name=repo.name) + assert default_repos.count == 0 + + # Try to create an object w/ cross domain relations + default_remote = gen_object_with_cleanup(rpm_rpmremote_api, gen_rpm_remote()) + with pytest.raises(ApiException) as e: + repo_body = {"name": str(uuid.uuid4()), "remote": default_remote.pulp_href} + rpm_repository_api.create(repo_body, pulp_domain=domain.name) + assert e.value.status == 400 + # What key should this error be under? non-field-errors seems wrong + assert json.loads(e.value.body) == { + "non_field_errors": [f"Objects must all be apart of the {domain_name} domain."] + } + + with pytest.raises(ApiException) as e: + sync_body = {"remote": default_remote.pulp_href} + rpm_repository_api.sync(repo.pulp_href, sync_body) + assert e.value.status == 400 + assert json.loads(e.value.body) == { + "non_field_errors": [f"Objects must all be apart of the {domain_name} domain."] + } + + +@pytest.mark.parallel +def test_artifact_from_file( + domains_api_client, + gen_object_with_cleanup, + rpm_artifact_factory, + artifacts_api_client, +): + """Test uploading artifacts in separate domains.""" + body = { + "name": str(uuid.uuid4()), + "storage_class": "pulpcore.app.models.storage.FileSystem", + "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"}, + } + domain1 = gen_object_with_cleanup(domains_api_client, body) + + body = { + "name": str(uuid.uuid4()), + "storage_class": "pulpcore.app.models.storage.FileSystem", + "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"}, + } + domain2 = gen_object_with_cleanup(domains_api_client, body) + + # Create as-artifact in domain1 + domain1_artifact = rpm_artifact_factory(pulp_domain=domain1.name) + artifacts = artifacts_api_client.list(pulp_domain=domain1.name) + assert artifacts.count == 1 + assert domain1_artifact["pulp_href"] == artifacts.results[0].pulp_href + + # Create as-artifact in domain2 + domain2_artifact = rpm_artifact_factory(pulp_domain=domain2.name) + artifacts = artifacts_api_client.list(pulp_domain=domain2.name) + assert artifacts.count == 1 + assert domain2_artifact["pulp_href"] == artifacts.results[0].pulp_href + + # Show same artifact, diff domains + assert domain1_artifact["pulp_href"] != domain2_artifact["pulp_href"] + assert domain1_artifact["sha256"] == domain2_artifact["sha256"] + + # Show that duplicate artifact can not be uploaded in same domain + with pytest.raises(CoreApiException) as e: + rpm_artifact_factory(pulp_domain=domain1.name) + assert e.value.status == 400 + assert json.loads(e.value.body) == { + "non_field_errors": [ + f"Artifact with sha256 checksum of '{domain1_artifact['sha256']}' already exists.", + ] + } + + +@pytest.mark.parallel +def test_rpm_from_file( + domains_api_client, + rpm_package_factory, + orphans_cleanup_api_client, + gen_object_with_cleanup, + rpm_package_api, + monitor_task, +): + """Test uploading of RPM content with domains.""" + body = { + "name": str(uuid.uuid4()), + "storage_class": "pulpcore.app.models.storage.FileSystem", + "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"}, + } + domain = gen_object_with_cleanup(domains_api_client, body) + + try: + default_content = rpm_package_factory() + domain_content = rpm_package_factory(pulp_domain=domain.name) + assert default_content["pulp_href"] != domain_content["pulp_href"] + assert default_content["sha256"] == domain_content["sha256"] + + domain_contents = rpm_package_api.list(pulp_domain=domain.name) + assert domain_contents.count == 1 + finally: + # Content needs to be deleted for the domain to be deleted + body = {"orphan_protection_time": 0} + task = orphans_cleanup_api_client.cleanup(body, pulp_domain=domain.name).task + monitor_task(task) + + domain_contents = rpm_package_api.list(pulp_domain=domain.name) + assert domain_contents.count == 0 + + +@pytest.mark.parallel +def test_content_promotion( + domains_api_client, + download_content_unit, + rpm_repository_api, + rpm_rpmremote_factory, + rpm_publication_api, + rpm_distribution_factory, + gen_object_with_cleanup, + orphans_cleanup_api_client, + monitor_task, +): + """Tests Content promotion path with domains: Sync->Publish->Distribute""" + body = { + "name": str(uuid.uuid4()), + "storage_class": "pulpcore.app.models.storage.FileSystem", + "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"}, + } + domain = gen_object_with_cleanup(domains_api_client, body) + + # Sync task + remote = rpm_rpmremote_factory(pulp_domain=domain.name) + repo_body = {"name": str(uuid.uuid4()), "remote": remote.pulp_href} + repo = rpm_repository_api.create(repo_body, pulp_domain=domain.name) + + task = rpm_repository_api.sync(repo.pulp_href, {}).task + response = monitor_task(task) + assert len(response.created_resources) == 1 + + repo = rpm_repository_api.read(repo.pulp_href) + assert repo.latest_version_href[-2] == "1" + + # Publish task + pub_body = {"repository": repo.pulp_href} + task = rpm_publication_api.create(pub_body, pulp_domain=domain.name).task + response = monitor_task(task) + assert len(response.created_resources) == 1 + pub_href = response.created_resources[0] + pub = rpm_publication_api.read(pub_href) + + assert pub.repository == repo.pulp_href + + # Distribute Task + distro = rpm_distribution_factory(publication=pub.pulp_href, pulp_domain=domain.name) + + assert distro.publication == pub.pulp_href + # Url structure should be host/CONTENT_ORIGIN/DOMAIN_PATH/BASE_PATH + assert domain.name == distro.base_url.rstrip("/").split("/")[-2] + + # Check that content can be downloaded from base_url + for pkg in ("bear-4.1-1.noarch.rpm", "pike-2.2-1.noarch.rpm"): + pkg_path = get_package_repo_path(pkg) + download_content_unit(distro.base_path, pkg_path) + + # Cleanup to delete the domain + task = rpm_repository_api.delete(repo.pulp_href).task + monitor_task(task) + body = {"orphan_protection_time": 0} + task = orphans_cleanup_api_client.cleanup(body, pulp_domain=domain.name).task + monitor_task(task) + + +@pytest.mark.parallel +def test_domain_rbac(domains_api_client, gen_user, gen_object_with_cleanup, rpm_repository_api): + """Test domain level-roles.""" + body = { + "name": str(uuid.uuid4()), + "storage_class": "pulpcore.app.models.storage.FileSystem", + "storage_settings": {"MEDIA_ROOT": "/var/lib/pulp/media/"}, + } + domain = gen_object_with_cleanup(domains_api_client, body) + + rpm_viewer = "rpm.rpmrepository_viewer" + rpm_creator = "rpm.rpmrepository_creator" + user_a = gen_user(username="a", domain_roles=[(rpm_viewer, domain.pulp_href)]) + user_b = gen_user(username="b", domain_roles=[(rpm_creator, domain.pulp_href)]) + + # Create two repos in different domains w/ admin user + gen_object_with_cleanup(rpm_repository_api, {"name": str(uuid.uuid4())}) + gen_object_with_cleanup( + rpm_repository_api, {"name": str(uuid.uuid4())}, pulp_domain=domain.name + ) + + with user_b: + repo = gen_object_with_cleanup( + rpm_repository_api, {"name": str(uuid.uuid4())}, pulp_domain=domain.name + ) + repos = rpm_repository_api.list(pulp_domain=domain.name) + assert repos.count == 1 + assert repos.results[0].pulp_href == repo.pulp_href + # Try to create a repository in default domain + with pytest.raises(ApiException) as e: + rpm_repository_api.create({"name": str(uuid.uuid4())}) + assert e.value.status == 403 + + with user_a: + repos = rpm_repository_api.list(pulp_domain=domain.name) + assert repos.count == 2 + # Try to read repos in the default domain + repos = rpm_repository_api.list() + assert repos.count == 0 + # Try to create a repo + with pytest.raises(ApiException) as e: + rpm_repository_api.create({"name": str(uuid.uuid4())}, pulp_domain=domain.name) + assert e.value.status == 403 diff --git a/pulp_rpm/tests/functional/api/test_pulpimport.py b/pulp_rpm/tests/functional/api/test_pulpimport.py index a5b0bb3a23..d54b726005 100644 --- a/pulp_rpm/tests/functional/api/test_pulpimport.py +++ b/pulp_rpm/tests/functional/api/test_pulpimport.py @@ -11,12 +11,17 @@ from pulp_rpm.tests.functional.constants import RPM_KICKSTART_FIXTURE_URL, RPM_UNSIGNED_FIXTURE_URL +from pulpcore.app import settings + from pulpcore.client.pulp_rpm import RpmRepositorySyncURL NUM_REPOS = 2 ExportFileInfo = namedtuple("ExportFileInfo", "output_file_info") +if settings.DOMAIN_ENABLED: + pytest.skip("Domains do not support import.", allow_module_level=True) + @pytest.fixture def import_export_repositories( diff --git a/pulp_rpm/tests/functional/conftest.py b/pulp_rpm/tests/functional/conftest.py index 4fad5e3478..0dbeb1f684 100644 --- a/pulp_rpm/tests/functional/conftest.py +++ b/pulp_rpm/tests/functional/conftest.py @@ -1,4 +1,5 @@ import uuid +from tempfile import NamedTemporaryFile import pytest @@ -24,6 +25,7 @@ ) from pulp_rpm.tests.functional.utils import init_signed_repo_configuration +from pulp_smash.pulp3.bindings import PulpTaskError @pytest.fixture(scope="session") @@ -109,6 +111,64 @@ def signed_artifact(http_get, artifacts_api_client, gen_object_with_cleanup, tmp return gen_object_with_cleanup(artifacts_api_client, temp_file).to_dict() +@pytest.fixture +def rpm_artifact_factory( + http_get, artifacts_api_client, gen_object_with_cleanup, tmp_path, pulp_domain_enabled +): + """Return an artifact created from uploading an RPM file.""" + + def _rpm_artifact_factory(url=RPM_SIGNED_URL, pulp_domain=None): + temp_file = tmp_path / str(uuid.uuid4()) + temp_file.write_bytes(http_get(url)) + kwargs = {} + if pulp_domain: + if not pulp_domain_enabled: + raise RuntimeError("Server does not have domains enabled.") + kwargs["pulp_domain"] = pulp_domain + return gen_object_with_cleanup(artifacts_api_client, temp_file, **kwargs).to_dict() + + return _rpm_artifact_factory + + +@pytest.fixture +def rpm_package_factory( + http_get, + rpm_package_api, + add_to_cleanup, + monitor_task, + pulp_domain_enabled, +): + """Return a Package created from uploading an RPM file.""" + + def _rpm_package_factory(url=RPM_SIGNED_URL, pulp_domain=None): + kwargs = {} + if pulp_domain: + if not pulp_domain_enabled: + raise RuntimeError("Server does not have domains enabled.") + kwargs["pulp_domain"] = pulp_domain + + with NamedTemporaryFile() as file_to_upload: + file_to_upload.write(http_get(url)) + upload_attrs = {"file": file_to_upload.name} + upload = rpm_package_api.create(**upload_attrs, **kwargs) + + try: + upload_task_data = monitor_task(upload.task) + except PulpTaskError: + pass + + for created_resource in upload_task_data.created_resources: + try: + new_obj = rpm_package_api.read(created_resource) + except Exception: + pass # This isn't the right created_resource for this api_client + else: + add_to_cleanup(rpm_package_api, new_obj.pulp_href) + return new_obj.to_dict() + + return _rpm_package_factory + + @pytest.fixture(scope="class") def rpm_unsigned_repo_immediate(init_and_sync): repo, _ = init_and_sync()