class ManifestSerializer(SingleArtifactContentSerializer): """ Serializer for Manifests. """ digest = serializers.CharField(help_text="sha256 of the Manifest file") schema_version = serializers.IntegerField( help_text="Docker schema version") media_type = serializers.CharField( help_text="Docker media type of the file") blobs = DetailRelatedField( many=True, help_text="Blobs that are referenced by this Manifest", view_name='docker-blobs-detail', queryset=models.ManifestBlob.objects.all()) config_blob = DetailRelatedField( many=False, help_text="Blob that contains configuration for this Manifest", view_name='docker-blobs-detail', queryset=models.ManifestBlob.objects.all()) class Meta: fields = SingleArtifactContentSerializer.Meta.fields + ( 'digest', 'schema_version', 'media_type', 'blobs', 'config_blob', ) model = models.ImageManifest
class ManifestSignatureSerializer(NoArtifactContentSerializer): """ Serializer for image manifest signatures. """ name = serializers.CharField( help_text= "Signature name in the format of `digest_algo:manifest_digest@random_32_chars`" ) digest = serializers.CharField( help_text="sha256 digest of the signature blob") type = serializers.CharField( help_text="Container signature type, e.g. 'atomic'") key_id = serializers.CharField(help_text="Signing key ID") timestamp = serializers.IntegerField(help_text="Timestamp of a signature") creator = serializers.CharField(help_text="Signature creator") signed_manifest = DetailRelatedField( many=False, help_text="Manifest that is signed", view_name="container-manifests-detail", queryset=models.Manifest.objects.all(), ) class Meta: fields = NoArtifactContentSerializer.Meta.fields + ( "name", "digest", "type", "key_id", "timestamp", "creator", "signed_manifest", ) model = models.ManifestSignature
class ModulemdSerializer(SingleArtifactContentUploadSerializer, ContentChecksumSerializer): """ Modulemd serializer. """ name = serializers.CharField(help_text=_("Modulemd name."), ) stream = serializers.CharField(help_text=_("Stream name."), ) version = serializers.CharField(help_text=_("Modulemd version."), ) context = serializers.CharField(help_text=_("Modulemd context."), ) arch = serializers.CharField(help_text=_("Modulemd architecture."), ) artifacts = serializers.JSONField(help_text=_("Modulemd artifacts."), allow_null=True) dependencies = serializers.JSONField(help_text=_("Modulemd dependencies."), allow_null=True) # TODO: The performance of this is not great, there's a noticable difference in response # time before/after. Since this will only return Package content hrefs, we might benefit # from creating a specialized version of this Field that can skip some of the work. packages = DetailRelatedField(help_text=_("Modulemd artifacts' packages."), allow_null=True, required=False, queryset=Package.objects.all(), many=True) class Meta: fields = SingleArtifactContentUploadSerializer.Meta.fields + ( 'name', 'stream', 'version', 'context', 'arch', 'artifacts', 'dependencies', 'packages', 'sha256') model = Modulemd
class InstallerFileIndexSerializer(MultipleArtifactContentSerializer): """ A serializer for InstallerFileIndex. """ component = CharField( help_text="Component of the component - architecture combination.", required=True) architecture = CharField( help_text="Architecture of the component - architecture combination.", required=True) relative_path = CharField( help_text= "Path of directory containing MD5SUMS and SHA256SUMS relative to url.", required=False, ) release = DetailRelatedField( help_text="Release this index file belongs to.", many=False, queryset=ReleaseFile.objects.all(), view_name="deb-release-file-detail", ) class Meta: fields = MultipleArtifactContentSerializer.Meta.fields + ( "release", "component", "architecture", "relative_path", ) model = InstallerFileIndex
class CompsXmlSerializer(serializers.Serializer): """ A serializer for comps.xml Upload API. """ file = serializers.FileField( help_text= _("Full path of a comps.xml file that may be parsed into comps.xml Content units." ), required=True, ) repository = DetailRelatedField( help_text= _("URI of an RPM repository the comps.xml content units should be associated to." ), required=False, write_only=True, view_name_pattern=r"repositories(-.*/.*)-detail", queryset=Repository.objects.all(), ) replace = serializers.BooleanField( help_text=_( "If true, incoming comps.xml replaces existing comps-related ContentUnits in the " "specified repository."), required=False, write_only=True, ) class Meta: fields = ("file", "repository", "replace")
class FileDistributionSerializer(DistributionSerializer): """ Serializer for File Distributions. """ publication = DetailRelatedField( required=False, help_text=_("Publication to be served"), view_name_pattern=r"publications(-.*/.*)?-detail", queryset=models.Publication.objects.exclude(complete=False), allow_null=True, ) def validate(self, data): """ Ensure publication and repository are not set at the same time. This is needed here till https://pulp.plan.io/issues/8761 is resolved. """ data = super().validate(data) repository_provided = data.get("repository", None) publication_provided = data.get("publication", None) if repository_provided and publication_provided: raise serializers.ValidationError( _("Only one of the attributes 'repository' and 'publication' " "may be used simultaneously.")) if repository_provided or publication_provided: data["repository"] = repository_provided data["publication"] = publication_provided return data class Meta: fields = DistributionSerializer.Meta.fields + ("publication", ) model = FileDistribution
class FilePublicationSerializer(PublicationSerializer): """ Serializer for File Publications. """ distributions = DetailRelatedField( help_text= _("This publication is currently hosted as defined by these distributions." ), source="distribution_set", view_name="filedistributions-detail", many=True, read_only=True, ) manifest = serializers.CharField( help_text= _("Filename to use for manifest file containing metadata for all the files." ), default="PULP_MANIFEST", required=False, ) class Meta: model = FilePublication fields = PublicationSerializer.Meta.fields + ("distributions", "manifest")
class CollectionVersionSignatureSerializer(NoArtifactContentSerializer): """ A serializer for signature models. """ signed_collection = DetailRelatedField( help_text=_("The content this signature is pointing to."), view_name_pattern=r"content(-.*/.*)-detail", queryset=CollectionVersion.objects.all(), ) pubkey_fingerprint = serializers.CharField(help_text=_("The fingerprint of the public key.")) signing_service = RelatedField( help_text=_("The signing service used to create the signature."), view_name="signing-services-detail", queryset=SigningService.objects.all(), allow_null=True, ) class Meta: model = CollectionVersionSignature fields = NoArtifactContentSerializer.Meta.fields + ( "signed_collection", "pubkey_fingerprint", "signing_service", )
class Pulp2ContentSerializer(ModelSerializer): """ A serializer for the Pulp2Content model """ pulp_href = IdentityField(view_name="pulp2content-detail") pulp2_id = serializers.CharField(max_length=255) pulp2_content_type_id = serializers.CharField(max_length=255) pulp2_last_updated = serializers.IntegerField() pulp2_storage_path = serializers.CharField(allow_null=True) downloaded = serializers.BooleanField(default=False) pulp3_content = DetailRelatedField( required=False, allow_null=True, queryset=Pulp2Content.objects.all(), view_name_pattern=r"content(-.*/.*)?-detail", ) pulp3_repository_version = serializers.SerializerMethodField( read_only=True) @extend_schema_field(field=serializers.CharField) def get_pulp3_repository_version(self, obj): """ Get pulp3_repository_version href from pulp2repo if available """ pulp2_repo = obj.pulp2_repo if pulp2_repo and pulp2_repo.is_migrated: pulp3_repo_href = get_pulp_href(pulp2_repo.pulp3_repository) pulp3_repo_version = pulp2_repo.pulp3_repository_version return f"{pulp3_repo_href}versions/{pulp3_repo_version.number}/" def to_representation(self, instance): """ Do not serialize pulp3_repository_version if it is null. pulp3_repository_version is set only for content which can migrate from one pulp2_content unit into multiple pulp3_content units. Serializing pulp3_repository_version when it is not set can mislead users that migrated content doesn't belong to any pulp3_repository_version. """ result = super(Pulp2ContentSerializer, self).to_representation(instance) if not result.get("pulp3_repository_version"): result.pop("pulp3_repository_version", None) return result class Meta: fields = ModelSerializer.Meta.fields + ( "pulp2_id", "pulp2_content_type_id", "pulp2_last_updated", "pulp2_storage_path", "downloaded", "pulp3_content", "pulp3_repository_version", ) model = Pulp2Content
class ContainerDistributionSerializer(RepositoryVersionDistributionSerializer): """ A serializer for ContainerDistribution. """ registry_path = RegistryPathField( source="base_path", read_only=True, help_text=_( "The Registry hostame/name/ to use with docker pull command defined by " "this distribution."), ) content_guard = DetailRelatedField( required=False, help_text= _("An optional content-guard. If none is specified, a default one will be used." ), view_name=r"contentguards-container/content-redirect-detail", queryset=models.ContentRedirectContentGuard.objects.all(), allow_null=False, ) def validate(self, data): """ Validate the ContainterDistribution. Make sure there is an instance of ContentRedirectContentGuard always present in validated data. Validate that the distribution is not serving a repository versions of a push repository. """ validated_data = super().validate(data) if "content_guard" not in validated_data: try: validated_data[ "content_guard"] = models.ContentRedirectContentGuard.objects.get( name="content redirect") except ObjectDoesNotExist: cg_serializer = ContentRedirectContentGuardSerializer( data={"name": "content redirect"}) cg_serializer.is_valid(raise_exception=True) validated_data["content_guard"] = cg_serializer.create( cg_serializer.validated_data) if "repository_version" in validated_data: repository = validated_data["repository_version"].repository.cast() if repository.PUSH_ENABLED: raise serializers.ValidationError( _("A container push repository cannot be distributed by specifying a " "repository version.")) return validated_data class Meta: model = models.ContainerDistribution fields = tuple( set(RepositoryVersionDistributionSerializer.Meta.fields) - {"base_url"}) + ("registry_path", )
class PackageReleaseComponentSerializer(NoArtifactContentSerializer): """ A Serializer for PackageReleaseComponent. """ package = DetailRelatedField( help_text="Package that is contained in release_comonent.", many=False, queryset=ReleaseComponent.objects.all(), view_name="deb-release_component-detail", ) release_component = DetailRelatedField( help_text="ReleaseComponent this package is contained in.", many=False, queryset=ReleaseComponent.objects.all(), view_name="deb-release_component-detail", ) class Meta(NoArtifactContentSerializer.Meta): model = PackageReleaseComponent fields = NoArtifactContentSerializer.Meta.fields + ("package", "release_component")
class ManifestSerializer(SingleArtifactContentSerializer): """ Serializer for Manifests. """ digest = serializers.CharField(help_text="sha256 of the Manifest file") schema_version = serializers.IntegerField( help_text="Manifest schema version") media_type = serializers.CharField( help_text="Manifest media type of the file") listed_manifests = DetailRelatedField( many=True, help_text="Manifests that are referenced by this Manifest List", view_name="container-manifests-detail", queryset=models.Manifest.objects.all(), ) blobs = DetailRelatedField( many=True, help_text="Blobs that are referenced by this Manifest", view_name="container-blobs-detail", queryset=models.Blob.objects.all(), ) config_blob = DetailRelatedField( many=False, required=False, help_text="Blob that contains configuration for this Manifest", view_name="container-blobs-detail", queryset=models.Blob.objects.all(), ) class Meta: fields = SingleArtifactContentSerializer.Meta.fields + ( "digest", "schema_version", "media_type", "listed_manifests", "config_blob", "blobs", ) model = models.Manifest
class FilePublicationSerializer(PublicationSerializer): """ Serializer for File Publications. """ publisher = DetailRelatedField( help_text=_('The publisher that created this publication.'), queryset=FilePublisher.objects.all(), required=False, ) class Meta: fields = PublicationSerializer.Meta.fields + ('publisher',) model = FilePublication
class ReleaseArchitectureSerializer(NoArtifactContentSerializer): """ A Serializer for ReleaseArchitecture. """ architecture = CharField(help_text="Name of the architecture.") release = DetailRelatedField( help_text="Release this architecture is contained in.", many=False, queryset=Release.objects.all(), view_name="deb-release-detail", ) class Meta(NoArtifactContentSerializer.Meta): model = ReleaseArchitecture fields = NoArtifactContentSerializer.Meta.fields + ("architecture", "release")
class RpmDistributionSerializer(DistributionSerializer): """ Serializer for RPM Distributions. """ publication = DetailRelatedField( required=False, help_text=_("Publication to be served"), view_name_pattern=r"publications(-.*/.*)?-detail", queryset=Publication.objects.exclude(complete=False), allow_null=True, ) class Meta: fields = DistributionSerializer.Meta.fields + ("publication", ) model = RpmDistribution
class TagSerializer(NoArtifactContentSerializer): """ Serializer for Tags. """ name = serializers.CharField(help_text="Tag name") tagged_manifest = DetailRelatedField( many=False, help_text="Manifest that is tagged", view_name="container-manifests-detail", queryset=models.Manifest.objects.all(), ) class Meta: fields = NoArtifactContentSerializer.Meta.fields + ("name", "tagged_manifest") model = models.Tag
class ReleaseComponentSerializer(NoArtifactContentSerializer): """ A Serializer for ReleaseComponent. """ component = CharField(help_text="Name of the component.") release = DetailRelatedField( help_text="Release this component is contained in.", many=False, queryset=Release.objects.all(), view_name="deb-release-detail", ) class Meta(NoArtifactContentSerializer.Meta): model = ReleaseComponent fields = NoArtifactContentSerializer.Meta.fields + ("component", "release")
class ManifestTagSerializer(SingleArtifactContentSerializer): """ Serializer for ManifestTags. """ name = serializers.CharField(help_text="Tag name") manifest = DetailRelatedField(many=False, help_text="Manifest that is tagged", view_name='docker-manifests-detail', queryset=models.ImageManifest.objects.all()) class Meta: fields = SingleArtifactContentSerializer.Meta.fields + ( 'name', 'manifest', ) model = models.ManifestTag
class CollectionVersionSignatureSerializer(NoArtifactContentUploadSerializer): """ A serializer for signature models. """ signed_collection = DetailRelatedField( help_text=_("The content this signature is pointing to."), view_name_pattern=r"content(-.*/.*)-detail", queryset=CollectionVersion.objects.all(), ) pubkey_fingerprint = serializers.CharField( help_text=_("The fingerprint of the public key."), read_only=True, ) signing_service = RelatedField( help_text=_("The signing service used to create the signature."), view_name="signing-services-detail", read_only=True, allow_null=True, ) def __init__(self, *args, **kwargs): """Ensure `file` field is required.""" super().__init__(*args, **kwargs) self.fields["file"].required = True def validate(self, data): """ Verify the signature is valid before creating it. """ data = super().validate(data) if "request" not in self.context: # Validate is called twice, first on the viewset, and second on the create task # data should be set up properly on the second time, when request isn't in context data = verify_signature_upload(data) return data class Meta: model = CollectionVersionSignature fields = NoArtifactContentUploadSerializer.Meta.fields + ( "signed_collection", "pubkey_fingerprint", "signing_service", )
class ModulemdSerializer(NoArtifactContentSerializer): """ Modulemd serializer. """ name = serializers.CharField(help_text=_("Modulemd name."), ) stream = serializers.CharField(help_text=_("Stream name."), ) version = serializers.CharField(help_text=_("Modulemd version."), ) static_context = serializers.BooleanField( help_text=_("Modulemd static-context flag."), required=False, ) context = serializers.CharField(help_text=_("Modulemd context."), ) arch = serializers.CharField(help_text=_("Modulemd architecture."), ) artifacts = serializers.JSONField(help_text=_("Modulemd artifacts."), allow_null=True) dependencies = serializers.JSONField(help_text=_("Modulemd dependencies."), allow_null=True) # TODO: The performance of this is not great, there's a noticable difference in response # time before/after. Since this will only return Package content hrefs, we might benefit # from creating a specialized version of this Field that can skip some of the work. packages = DetailRelatedField( help_text=_("Modulemd artifacts' packages."), allow_null=True, required=False, queryset=Package.objects.all(), view_name="content-rpm/packages-detail", many=True, ) snippet = serializers.CharField(help_text=_("Modulemd snippet"), write_only=True) class Meta: fields = NoArtifactContentSerializer.Meta.fields + ( "name", "stream", "version", "static_context", "context", "arch", "artifacts", "dependencies", "packages", "snippet", ) model = Modulemd
class Pulp2ContentSerializer(ModelSerializer): """ A serializer for the Pulp2Content model """ pulp_href = IdentityField(view_name='pulp2content-detail') pulp2_id = serializers.CharField(max_length=255) pulp2_content_type_id = serializers.CharField(max_length=255) pulp2_last_updated = serializers.IntegerField() pulp2_storage_path = serializers.CharField() downloaded = serializers.BooleanField(default=False) pulp3_content = DetailRelatedField(required=False, allow_null=True, queryset=Pulp2Content.objects.all()) pulp3_repository_version = serializers.SerializerMethodField( read_only=True) @extend_schema_serializer(many=False) def get_pulp3_repository_version(self, obj): """ Get pulp3_repository_version href from pulp2repo if available """ pulp2_repo = obj.pulp2_repo if pulp2_repo and pulp2_repo.is_migrated: pulp3_repo_href = get_pulp_href(pulp2_repo.pulp3_repository) pulp3_repo_version = pulp2_repo.pulp3_repository_version return f"{pulp3_repo_href}versions/{pulp3_repo_version.number}/" def to_representation(self, instance): """ Do not serialize pulp3_repository_version if it is null. """ result = super(Pulp2ContentSerializer, self).to_representation(instance) if not result.get('pulp3_repository_version'): del result['pulp3_repository_version'] return result class Meta: fields = ModelSerializer.Meta.fields + ( 'pulp2_id', 'pulp2_content_type_id', 'pulp2_last_updated', 'pulp2_storage_path', 'downloaded', 'pulp3_content', 'pulp3_repository_version') model = Pulp2Content
class Pulp2ContentSerializer(ModelSerializer): """ A serializer for the Pulp2Content model """ pulp_href = IdentityField(view_name='migration-plans-detail') pulp2_id = serializers.CharField(max_length=255) pulp2_content_type_id = serializers.CharField(max_length=255) pulp2_last_updated = serializers.IntegerField() pulp2_storage_path = serializers.CharField() downloaded = serializers.BooleanField(default=False) pulp3_content = DetailRelatedField(required=False, allow_null=True, queryset=Pulp2Content.objects.all()) class Meta: fields = ModelSerializer.Meta.fields + ( 'pulp2_id', 'pulp2_content_type_id', 'pulp2_last_updated', 'pulp2_storage_path', 'downloaded', 'pulp3_content') model = Pulp2Content
class TagSerializer(SingleArtifactContentSerializer): """ Serializer for Tags. """ name = serializers.CharField(help_text="Tag name") tagged_manifest = DetailRelatedField( many=False, help_text="Manifest that is tagged", view_name='docker-manifests-detail', queryset=models.Manifest.objects.all()) class Meta: fields = SingleArtifactContentSerializer.Meta.fields + ( 'name', 'tagged_manifest', ) model = models.Tag ref_name = f'{model._meta.app_label}_{model._meta.model_name}'
class CookbookDistributionSerializer(DistributionSerializer): """Serializer for the Distribution.""" base_url = CookbookBaseURLField( source="base_path", read_only=True, help_text=_("The URL for accessing the universe API as defined by this distribution."), ) publication = DetailRelatedField( required=False, help_text=_("Publication to be served"), view_name_pattern=r"publications(-.*/.*)?-detail", queryset=models.Publication.objects.exclude(complete=False), allow_null=True, ) class Meta: fields = DistributionSerializer.Meta.fields + ("publication",) model = CookbookDistribution
class UploadSerializerFieldsMixin(Serializer): """A mixin class that contains fields and methods common to content upload serializers.""" file = FileField( help_text= _("An uploaded file that may be turned into the artifact of the content unit." ), required=False, write_only=True, ) repository = DetailRelatedField( help_text= _("A URI of a repository the new content unit should be associated with." ), required=False, write_only=True, view_name_pattern=r"repositories(-.*/.*)-detail", queryset=Repository.objects.all(), ) def create(self, validated_data): """ Save a GenericContent unit. This must be used inside a task that locks on the Artifact and if given, the repository. """ repository = validated_data.pop("repository", None) content = super().create(validated_data) if repository: repository.cast() content_to_add = self.Meta.model.objects.filter(pk=content.pk) # create new repo version with uploaded package with repository.new_version() as new_version: new_version.add_content(content_to_add) return content class Meta: fields = ("file", "repository")
class DockerDistributionSerializer(ModelSerializer): """ A serializer for DockerDistribution. """ _href = IdentityField(view_name='docker-distributions-detail') name = serializers.CharField( help_text=_('A unique distribution name. Ex, `rawhide` and `stable`.'), validators=[ validators.MaxLengthValidator( models.DockerDistribution._meta.get_field('name').max_length, message=_( 'Distribution name length must be less than {} characters' ).format( models.DockerDistribution._meta.get_field( 'name').max_length)), UniqueValidator(queryset=models.DockerDistribution.objects.all()) ]) base_path = serializers.CharField( help_text= _('The base (relative) path component of the published url. Avoid paths that \ overlap with other distribution base paths (e.g. "foo" and "foo/bar")' ), validators=[ validators.MaxLengthValidator( models.DockerDistribution._meta.get_field( 'base_path').max_length, message= _('Distribution base_path length must be less than {} characters' ).format( models.DockerDistribution._meta.get_field( 'base_path').max_length)), UniqueValidator(queryset=models.DockerDistribution.objects.all()), ]) publisher = DetailRelatedField( required=False, help_text=_( 'Publications created by this publisher and repository are automatically' 'served as defined by this distribution'), queryset=models.DockerPublisher.objects.all(), allow_null=True) publication = RelatedField( required=False, help_text=_( 'The publication being served as defined by this distribution'), queryset=Publication.objects.exclude(complete=False), view_name='publications-detail', allow_null=True) repository = RelatedField( required=False, help_text=_( 'Publications created by this repository and publisher are automatically' 'served as defined by this distribution'), queryset=Repository.objects.all(), view_name='repositories-detail', allow_null=True) registry_path = RegistryPathField( source='base_path', read_only=True, help_text=_( 'The Registry hostame:port/name/ to use with docker pull command defined by ' 'this distribution.')) class Meta: model = models.DockerDistribution fields = ModelSerializer.Meta.fields + ( 'name', 'base_path', 'publisher', 'publication', 'registry_path', 'repository', 'content_guard', ) def _validate_path_overlap(self, path): # look for any base paths nested in path search = path.split("/")[0] q = Q(base_path=search) for subdir in path.split("/")[1:]: search = "/".join((search, subdir)) q |= Q(base_path=search) # look for any base paths that nest path q |= Q(base_path__startswith='{}/'.format(path)) qs = models.DockerDistribution.objects.filter(q) if self.instance is not None: qs = qs.exclude(pk=self.instance.pk) match = qs.first() if match: raise serializers.ValidationError( detail=_("Overlaps with existing distribution '" "{}'").format(match.name)) return path def validate_base_path(self, path): """ Validate that path is valid. Args: path (str): the path at which the registry will be served at """ self._validate_relative_path(path) return self._validate_path_overlap(path) def validate(self, data): """ Validates that the data dict has valid DockerDistribution info. Args: data (dict): dict representing a DockerDistribution """ super().validate(data) if 'publisher' in data: publisher = data['publisher'] elif self.instance: publisher = self.instance.publisher else: publisher = None if 'repository' in data: repository = data['repository'] elif self.instance: repository = self.instance.repository else: repository = None if publisher and not repository: raise serializers.ValidationError({ 'repository': _("Repository must be set if " "publisher is set.") }) if repository and not publisher: raise serializers.ValidationError({ 'publisher': _("Publisher must be set if " "repository is set.") }) return data
class SingleArtifactContentUploadSerializer(SingleArtifactContentSerializer): """ A serializer for content_types with a single Artifact. The Artifact can either be specified via it's url, or a new file can be uploaded. Additionally a repository can be specified, to which the content unit will be added. When using this serializer, the creation of the real content must be wrapped in a task that locks the Artifact and when specified, the repository. """ file = FileField( help_text= _("An uploaded file that should be turned into the artifact of the content unit." ), required=False, write_only=True, ) repository = DetailRelatedField( help_text= _("A URI of a repository the new content unit should be associated with." ), required=False, write_only=True, queryset=Repository.objects.all(), ) def __init__(self, *args, **kwargs): """Initializer for SingleArtifactContentUploadSerializer.""" super().__init__(*args, **kwargs) if self.fields.get("artifact"): self.fields["artifact"].required = False def validate(self, data): """Validate that we have an Artifact or can create one.""" data = super().validate(data) if "file" in data: if "artifact" in data: raise ValidationError( _("Only one of 'file' and 'artifact' may be specified.")) data["artifact"] = Artifact.init_and_validate(data.pop("file")) elif "artifact" not in data: raise ValidationError( _("One of 'file' and 'artifact' must be specified.")) if "request" not in self.context: data = self.deferred_validate(data) return data def deferred_validate(self, data): """Validate the content unit by deeply analyzing the specified Artifact. This is only called when validating without a request context to prevent stalling an ongoing http request. It should be overwritten by plugins to extract metadata from the actual content in much the same way as `validate`. """ return data def create(self, validated_data): """Save the GenericContent unit. This must be used inside a task that locks on the Artifact and if given, the repository. """ repository = validated_data.pop("repository", None) content = super().create(validated_data) if repository: repository.cast() content_to_add = self.Meta.model.objects.filter(pk=content.pk) # create new repo version with uploaded package with repository.new_version() as new_version: new_version.add_content(content_to_add) return content class Meta(SingleArtifactContentSerializer.Meta): fields = SingleArtifactContentSerializer.Meta.fields + ("file", "repository")
class ContainerDistributionSerializer(DistributionSerializer): """ A serializer for ContainerDistribution. """ registry_path = RegistryPathField( source="base_path", read_only=True, help_text=_( "The Registry hostame/name/ to use with docker pull command defined by " "this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard. If none is specified, a default one will be used."), view_name=r"contentguards-container/content-redirect-detail", queryset=models.ContentRedirectContentGuard.objects.all(), allow_null=False, ) namespace = RelatedField( required=False, read_only=True, view_name="pulp_container/namespaces-detail", help_text=_("Namespace this distribution belongs to."), ) description = serializers.CharField( help_text=_("An optional description."), required=False, allow_null=True ) repository_version = RepositoryVersionRelatedField( required=False, help_text=_("RepositoryVersion to be served"), allow_null=True ) def validate(self, data): """ Validate the ContainterDistribution. Make sure there is an instance of ContentRedirectContentGuard always present in validated data. Validate that the distribution is not serving a repository versions of a push repository. """ validated_data = super().validate(data) if "content_guard" not in validated_data: validated_data["content_guard"] = ContentRedirectContentGuardSerializer.get_or_create( {"name": "content redirect"} ) if "repository_version" in validated_data: repository = validated_data["repository_version"].repository.cast() if repository.PUSH_ENABLED: raise serializers.ValidationError( _( "A container push repository cannot be distributed by specifying a " "repository version." ) ) if "base_path" in validated_data: base_path = validated_data["base_path"] namespace_name = base_path.split("/")[0] if "namespace" in validated_data: if validated_data["namespace"].name != namespace_name: raise serializers.ValidationError( _("Selected namespace does not match first component of base_path.") ) else: validated_data["namespace"] = ContainerNamespaceSerializer.get_or_create( {"name": namespace_name} ) return validated_data class Meta: model = models.ContainerDistribution fields = tuple(set(DistributionSerializer.Meta.fields) - {"base_url"}) + ( "repository_version", "registry_path", "namespace", "private", "description", )