class FileSystemExporterSerializer(ModelSerializer): """ Base serializer for FileSystemExporters. """ pulp_href = DetailIdentityField() name = serializers.CharField( help_text=_("Unique name of the file system exporter."), validators=[ validators.MaxLengthValidator( models.FileSystemExporter._meta.get_field('name').max_length, message=_( '`name` length must be less than {} characters').format( models.FileSystemExporter._meta.get_field( 'name').max_length)), UniqueValidator(queryset=models.BaseDistribution.objects.all()) ]) path = serializers.CharField( help_text=_("File system location to export to.")) class Meta: model = models.FileSystemExporter fields = ModelSerializer.Meta.fields + ( 'path', 'name', )
class PublisherSerializer(MasterModelSerializer): """ Every publisher defined by a plugin should have an Publisher serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ _href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this publisher.'), validators=[UniqueValidator(queryset=models.Publisher.objects.all())]) _last_updated = serializers.DateTimeField(help_text=_( 'Timestamp of the most recent update of the publisher configuration.'), read_only=True) distributions = serializers.HyperlinkedRelatedField( many=True, read_only=True, view_name='distributions-detail', ) class Meta: abstract = True model = models.Publisher fields = MasterModelSerializer.Meta.fields + ( 'name', '_last_updated', 'distributions', )
class ImporterSerializer(ModelSerializer): """Base serializer for Importers.""" pulp_href = DetailIdentityField() name = serializers.CharField( help_text=_("Unique name of the Importer."), validators=[UniqueValidator(queryset=models.Importer.objects.all())]) class Meta: model = models.Importer fields = ModelSerializer.Meta.fields + ('name', )
class ImporterSerializer(ModelSerializer): """Base serializer for Importers.""" pulp_href = DetailIdentityField(view_name_pattern=r"importer(-.*/.*)-detail") name = serializers.CharField( help_text=_("Unique name of the Importer."), validators=[UniqueValidator(queryset=models.Importer.objects.all())], ) class Meta: model = models.Importer fields = ModelSerializer.Meta.fields + ("name",)
class ContentGuardSerializer(ModelSerializer): pulp_href = DetailIdentityField() name = serializers.CharField(help_text=_('The unique name.')) description = serializers.CharField( help_text=_('An optional description.'), allow_null=True, required=False) class Meta: model = models.ContentGuard fields = ModelSerializer.Meta.fields + ('name', 'description')
class ContentGuardSerializer(MasterModelSerializer): _href = DetailIdentityField() name = serializers.CharField( help_text=_('The unique name.'), validators=[ UniqueValidator(queryset=models.ContentGuard.objects.all()) ], ) class Meta: abstract = True model = models.ContentGuard fields = MasterModelSerializer.Meta.fields + ('name', )
class ContentGuardSerializer(ModelSerializer): pulp_href = DetailIdentityField( view_name_pattern=r"contentguards(-.*/.*)-detail") name = serializers.CharField(help_text=_("The unique name.")) description = serializers.CharField( help_text=_("An optional description."), allow_null=True, required=False) class Meta: model = models.ContentGuard fields = ModelSerializer.Meta.fields + ("name", "description")
class PublicationSerializer(MasterModelSerializer): _href = DetailIdentityField() repository_version = NestedRelatedField( view_name='versions-detail', lookup_field='number', parent_lookup_kwargs={'repository_pk': 'repository__pk'}, queryset=models.RepositoryVersion.objects.all(), required=False, ) repository = RelatedField( help_text=_('A URI of the repository to be published.'), required=False, label=_('Repository'), queryset=models.Repository.objects.all(), view_name='repositories-detail', write_only=True ) def validate(self, data): if hasattr(self, 'initial_data'): validate_unknown_fields(self.initial_data, self.fields) repository = data.pop('repository', None) # not an actual field on publication repository_version = data.get('repository_version') if not repository and not repository_version: raise serializers.ValidationError( _("Either the 'repository' or 'repository_version' need to be specified")) elif not repository and repository_version: return data elif repository and not repository_version: version = models.RepositoryVersion.latest(repository) if version: new_data = {'repository_version': version} new_data.update(data) return new_data else: raise serializers.ValidationError( detail=_('Repository has no version available to create Publication from')) raise serializers.ValidationError( _("Either the 'repository' or 'repository_version' need to be specified " "but not both.") ) class Meta: abstract = True model = models.Publication fields = MasterModelSerializer.Meta.fields + ( 'publisher', 'repository_version', 'repository' )
class RepositorySerializer(ModelSerializer): pulp_href = DetailIdentityField( view_name_pattern=r"repositories(-.*/.*)-detail") pulp_labels = LabelsField(required=False) versions_href = RepositoryVersionsIdentityFromRepositoryField() latest_version_href = LatestVersionField() name = serializers.CharField( help_text=_("A unique name for this repository."), validators=[UniqueValidator(queryset=models.Repository.objects.all())], ) description = serializers.CharField( help_text=_("An optional description."), required=False, allow_null=True) retain_repo_versions = serializers.IntegerField( help_text= _("Retain X versions of the repository. Default is null which retains all versions." " This is provided as a tech preview in Pulp 3 and may change in the future." ), allow_null=True, required=False, min_value=1, ) remote = DetailRelatedField( help_text=_("An optional remote to use by default when syncing."), view_name_pattern=r"remotes(-.*/.*)-detail", queryset=models.Remote.objects.all(), required=False, allow_null=True, ) def validate_remote(self, value): if value and type(value) not in self.Meta.model.REMOTE_TYPES: raise serializers.ValidationError( detail=_("Type for Remote '{}' does not match Repository." ).format(value.name)) return value class Meta: model = models.Repository fields = ModelSerializer.Meta.fields + ( "versions_href", "pulp_labels", "latest_version_href", "name", "description", "retain_repo_versions", "remote", )
class RepositorySerializer(ModelSerializer): pulp_href = DetailIdentityField() versions_href = RepositoryVersionsIdentityFromRepositoryField() latest_version_href = LatestVersionField() name = serializers.CharField( help_text=_('A unique name for this repository.'), validators=[UniqueValidator(queryset=models.Repository.objects.all())]) description = serializers.CharField( help_text=_('An optional description.'), required=False, allow_null=True) class Meta: model = models.Repository fields = ModelSerializer.Meta.fields + ( 'versions_href', 'latest_version_href', 'name', 'description')
class ExporterSerializer(ModelSerializer): """ Base serializer for Exporters. """ pulp_href = DetailIdentityField( view_name_pattern=r"exporter(-.*/.*)-detail") name = serializers.CharField( help_text=_("Unique name of the file system exporter."), validators=[UniqueValidator(queryset=models.Exporter.objects.all())], ) @staticmethod def validate_path(value, check_is_dir=False): """ Check if path is in ALLOWED_EXPORT_PATHS. Args: value: The user-provided value path to be validated. check_is_dir: If true, insure path ends with a directory Raises: ValidationError: When path is not in the ALLOWED_EXPORT_PATHS setting. Returns: The validated value. """ for allowed_path in settings.ALLOWED_EXPORT_PATHS: user_provided_realpath = os.path.realpath(value) if user_provided_realpath.startswith(allowed_path): if check_is_dir: # fail if exists and not-directory if os.path.exists( user_provided_realpath ) and not os.path.isdir(user_provided_realpath): raise serializers.ValidationError( _("Path '{}' must be a directory " "path").format(value)) return value raise serializers.ValidationError( _("Path '{}' is not an allowed export " "path").format(value)) class Meta: model = models.Exporter fields = ModelSerializer.Meta.fields + ("name", )
class ExporterSerializer(MasterModelSerializer): _href = DetailIdentityField() name = serializers.CharField( help_text=_('The exporter unique name.'), validators=[UniqueValidator(queryset=models.Exporter.objects.all())]) _last_updated = serializers.DateTimeField( help_text=_('Timestamp of the last update.'), read_only=True) last_export = serializers.DateTimeField( help_text=_('Timestamp of the last export.'), read_only=True) class Meta: abstract = True model = models.Exporter fields = MasterModelSerializer.Meta.fields + ( 'name', '_last_updated', 'last_export', )
class PublicationSerializer(ModelSerializer): pulp_href = DetailIdentityField( view_name_pattern=r"publications(-.*/.*)-detail") repository_version = RepositoryVersionRelatedField(required=False) repository = DetailRelatedField( help_text=_("A URI of the repository to be published."), required=False, label=_("Repository"), view_name_pattern=r"repositories(-.*/.*)?-detail", queryset=models.Repository.objects.all(), ) def validate(self, data): if hasattr(self, "initial_data"): validate_unknown_fields(self.initial_data, self.fields) repository = data.pop("repository", None) # not an actual field on publication repository_version = data.get("repository_version") if not repository and not repository_version: raise serializers.ValidationError( _("Either the 'repository' or 'repository_version' need to be specified" )) elif not repository and repository_version: return data elif repository and not repository_version: version = repository.latest_version() if version: new_data = {"repository_version": version} new_data.update(data) return new_data else: raise serializers.ValidationError(detail=_( "Repository has no version available to create Publication from" )) raise serializers.ValidationError( _("Either the 'repository' or 'repository_version' need to be specified " "but not both.")) class Meta: abstract = True model = models.Publication fields = ModelSerializer.Meta.fields + ("repository_version", "repository")
class RepositorySerializer(ModelSerializer): pulp_href = DetailIdentityField(view_name_pattern=r"repositories(-.*/.*)-detail",) versions_href = RepositoryVersionsIdentityFromRepositoryField() latest_version_href = LatestVersionField() name = serializers.CharField( help_text=_("A unique name for this repository."), validators=[UniqueValidator(queryset=models.Repository.objects.all())], ) description = serializers.CharField( help_text=_("An optional description."), required=False, allow_null=True ) class Meta: model = models.Repository fields = ModelSerializer.Meta.fields + ( "versions_href", "latest_version_href", "name", "description", )
class RepositorySerializer(ModelSerializer): pulp_href = DetailIdentityField( view_name_pattern=r"repositories(-.*/.*)-detail") pulp_labels = LabelsField(required=False) versions_href = RepositoryVersionsIdentityFromRepositoryField() latest_version_href = LatestVersionField() name = serializers.CharField( help_text=_("A unique name for this repository."), validators=[UniqueValidator(queryset=models.Repository.objects.all())], ) description = serializers.CharField( help_text=_("An optional description."), required=False, allow_null=True) remote = DetailRelatedField( view_name_pattern=r"remotes(-.*/.*)-detail", queryset=models.Remote.objects.all(), required=False, allow_null=True, ) def validate_remote(self, value): if value and type(value) not in self.Meta.model.REMOTE_TYPES: raise serializers.ValidationError( detail=_("Type for Remote '{}' does not match Repository." ).format(value.name)) return value class Meta: model = models.Repository fields = ModelSerializer.Meta.fields + ( "versions_href", "pulp_labels", "latest_version_href", "name", "description", "remote", )
class BaseDistributionSerializer(ModelSerializer): """ The Serializer for the BaseDistribution model. The serializer deliberately omits the "remote" field, which is used for pull-through caching only. Plugins implementing pull-through caching will have to add the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField() 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.BaseDistribution._meta.get_field( 'base_path').max_length, message=_('`base_path` length must be less than {} characters' ).format( models.BaseDistribution._meta.get_field( 'base_path').max_length)), UniqueValidator(queryset=models.BaseDistribution.objects.all()), ]) base_url = BaseURLField( source='base_path', read_only=True, help_text= _('The URL for accessing the publication as defined by this distribution.' )) content_guard = DetailRelatedField( required=False, help_text=_('An optional content-guard.'), queryset=models.ContentGuard.objects.all(), allow_null=True) name = serializers.CharField( help_text=_('A unique name. Ex, `rawhide` and `stable`.'), validators=[ validators.MaxLengthValidator( models.BaseDistribution._meta.get_field('name').max_length, message=_( '`name` length must be less than {} characters').format( models.BaseDistribution._meta.get_field( 'name').max_length)), UniqueValidator(queryset=models.BaseDistribution.objects.all()) ]) class Meta: abstract = True model = models.BaseDistribution fields = ModelSerializer.Meta.fields + ( 'base_path', 'base_url', 'content_guard', 'name', ) 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.BaseDistribution.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): self._validate_relative_path(path) return self._validate_path_overlap(path)
class BaseDistributionSerializer(ModelSerializer, BasePathOverlapMixin): """ The Serializer for the BaseDistribution model. The serializer deliberately omits the "remote" field, which is used for pull-through caching only. Plugins implementing pull-through caching will have to add the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) 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=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()) ], ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()), UniqueValidator(queryset=models.Distribution.objects.all()), ], ) def __init__(self, *args, **kwargs): """Initialize a BaseDistributionSerializer and emit deprecation warnings""" deprecation_logger.warn( _("BaseDistributionSerializer is deprecated and could be removed as early as " "pulpcore==3.13; use pulpcore.plugin.serializers.DistributionSerializer instead." )) return super().__init__(*args, **kwargs) class Meta: abstract = True model = models.BaseDistribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", )
class DistributionSerializer(ModelSerializer, BasePathOverlapMixin): """ The Serializer for the Distribution model. The serializer deliberately omits the the `publication` and `repository_version` field due to plugins typically requiring one or the other but not both. To include the ``publication`` field, it is recommended plugins define the field:: 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, ) To include the ``repository_version`` field, it is recommended plugins define the field:: repository_version = RepositoryVersionRelatedField( required=False, help_text=_("RepositoryVersion to be served"), allow_null=True ) Additionally, the serializer omits the ``remote`` field, which is used for pull-through caching feature and only by plugins which use publications. Plugins implementing a pull-through caching should define the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) 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")' ), ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()), UniqueValidator(queryset=models.Distribution.objects.all()), ], ) repository = DetailRelatedField( required=False, help_text=_( "The latest RepositoryVersion for this Repository will be served." ), view_name_pattern=r"repositories(-.*/.*)?-detail", queryset=models.Repository.objects.all(), allow_null=True, ) class Meta: abstract = True model = models.BaseDistribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", "repository", ) def validate(self, data): super().validate(data) publication_in_data = "publication" in data repository_version_in_data = "repository_version" in data publication_in_instance = self.instance.publication if self.instance else None repository_version_in_instance = self.instance.repository_version if self.instance else None if publication_in_data and repository_version_in_data: error = True elif publication_in_data and repository_version_in_instance: error = True elif publication_in_instance and repository_version_in_data: error = True else: error = False if error: msg = _( "Only one of the attributes 'publication' and 'repository_version' may be used " "simultaneously.") raise serializers.ValidationError(msg) return data
class RemoteSerializer(ModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ pulp_href = DetailIdentityField( view_name_pattern=r"remotes(-.*/.*)-detail") pulp_labels = LabelsField(required=False) name = serializers.CharField( help_text=_("A unique name for this remote."), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField( help_text="The URL of an external content source.") ca_cert = serializers.CharField( help_text="A PEM encoded CA certificate used to validate the server " "certificate presented by the remote server.", required=False, allow_null=True, ) client_cert = serializers.CharField( help_text="A PEM encoded client certificate used for authentication.", required=False, allow_null=True, ) client_key = serializers.CharField( help_text="A PEM encoded private key used for authentication.", required=False, allow_null=True, write_only=True, ) tls_validation = serializers.BooleanField( help_text="If True, TLS peer validation must be performed.", required=False) proxy_url = serializers.CharField( help_text="The proxy URL. Format: scheme://host:port", required=False, allow_null=True, ) proxy_username = serializers.CharField( help_text="The username to authenticte to the proxy.", required=False, allow_null=True, write_only=True, ) proxy_password = serializers.CharField( help_text="The password to authenticte to the proxy.", required=False, allow_null=True, write_only=True, style={"input_type": "password"}, ) username = serializers.CharField( help_text="The username to be used for authentication when syncing.", required=False, allow_null=True, write_only=True, ) password = serializers.CharField( help_text="The password to be used for authentication when syncing.", required=False, allow_null=True, write_only=True, style={"input_type": "password"}, ) pulp_last_updated = serializers.DateTimeField( help_text="Timestamp of the most recent update of the remote.", read_only=True) download_concurrency = serializers.IntegerField( help_text= ("Total number of simultaneous connections. If not set then the default " "value will be used."), allow_null=True, required=False, min_value=1, ) max_retries = serializers.IntegerField( help_text= ("Maximum number of retry attempts after a download failure. If not set then the " "default value (3) will be used."), required=False, allow_null=True, ) policy = serializers.ChoiceField( help_text="The policy to use when downloading content.", choices=((models.Remote.IMMEDIATE, "When syncing, download all metadata and content now.")), default=models.Remote.IMMEDIATE, ) total_timeout = serializers.FloatField( allow_null=True, required=False, help_text= ("aiohttp.ClientTimeout.total (q.v.) for download-connections. The default is null, " "which will cause the default from the aiohttp library to be used."), min_value=0.0, ) connect_timeout = serializers.FloatField( allow_null=True, required=False, help_text= ("aiohttp.ClientTimeout.connect (q.v.) for download-connections. The default is null, " "which will cause the default from the aiohttp library to be used."), min_value=0.0, ) sock_connect_timeout = serializers.FloatField( allow_null=True, required=False, help_text= ("aiohttp.ClientTimeout.sock_connect (q.v.) for download-connections. The default is " "null, which will cause the default from the aiohttp library to be used." ), min_value=0.0, ) sock_read_timeout = serializers.FloatField( allow_null=True, required=False, help_text= ("aiohttp.ClientTimeout.sock_read (q.v.) for download-connections. The default is " "null, which will cause the default from the aiohttp library to be used." ), min_value=0.0, ) headers = serializers.ListField( child=serializers.DictField(), help_text=_("Headers for aiohttp.Clientsession"), required=False, ) rate_limit = serializers.IntegerField( help_text=_( "Limits requests per second for each concurrent downloader"), allow_null=True, required=False, ) def validate_url(self, url): """ Check if the 'url' is a ``file://`` path, and if so, ensure it's an ALLOWED_IMPORT_PATH. The ALLOWED_IMPORT_PATH is specified as a Pulp setting. Args: url: The user-provided value for 'url' to be validated. Raises: ValidationError: When the url starts with `file://`, but is not a subfolder of a path in the ALLOWED_IMPORT_PATH setting. Returns: The validated value. """ parsed_url = urlparse(url) if parsed_url.username or parsed_url.password: raise serializers.ValidationError( _("The remote url contains username or password. Please use remote username or " "password instead.")) if not url.lower().startswith("file://"): return url user_path = url[7:] if not os.path.isabs(user_path): raise serializers.ValidationError( _("The path '{}' needs to be an absolute pathname.").format( user_path)) user_provided_realpath = os.path.realpath(user_path) for allowed_path in settings.ALLOWED_IMPORT_PATHS: if user_provided_realpath.startswith(allowed_path): return url raise serializers.ValidationError( _("The path '{}' does not start with any of the allowed import paths" ).format(user_path)) def validate_proxy_url(self, value): """ Check, that the proxy_url does not contain credentials. """ if value and "@" in value: raise serializers.ValidationError( _("proxy_url must not contain credentials")) return value def validate(self, data): """ Check, that proxy credentials are only provided completely and if a proxy is configured. """ data = super().validate(data) proxy_url = self.instance.proxy_url if self.partial else None proxy_url = data.get("proxy_url", proxy_url) proxy_username = self.instance.proxy_username if self.partial else None proxy_username = data.get("proxy_username", proxy_username) proxy_password = self.instance.proxy_password if self.partial else None proxy_password = data.get("proxy_password", proxy_password) if (proxy_username or proxy_password) and not proxy_url: raise serializers.ValidationError( _("proxy credentials cannot be specified without a proxy")) if bool(proxy_username) is not bool(proxy_password): raise serializers.ValidationError( _("proxy username and password can only be specified together") ) return data class Meta: abstract = True model = models.Remote fields = ModelSerializer.Meta.fields + ( "name", "url", "ca_cert", "client_cert", "client_key", "tls_validation", "proxy_url", "proxy_username", "proxy_password", "username", "password", "pulp_labels", "pulp_last_updated", "download_concurrency", "max_retries", "policy", "total_timeout", "connect_timeout", "sock_connect_timeout", "sock_read_timeout", "headers", "rate_limit", )
class RemoteSerializer(ModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ pulp_href = DetailIdentityField(view_name_pattern=r"remotes(-.*/.*)-detail") pulp_labels = LabelsField(required=False) name = serializers.CharField( help_text=_("A unique name for this remote."), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField(help_text="The URL of an external content source.") ca_cert = serializers.CharField( help_text="A PEM encoded CA certificate used to validate the server " "certificate presented by the remote server.", required=False, allow_null=True, ) client_cert = serializers.CharField( help_text="A PEM encoded client certificate used for authentication.", required=False, allow_null=True, ) client_key = serializers.CharField( help_text="A PEM encoded private key used for authentication.", required=False, allow_null=True, ) tls_validation = serializers.BooleanField( help_text="If True, TLS peer validation must be performed.", required=False ) proxy_url = serializers.CharField( help_text="The proxy URL. Format: scheme://user:password@host:port", required=False, allow_null=True, ) username = serializers.CharField( help_text="The username to be used for authentication when syncing.", required=False, allow_null=True, ) password = serializers.CharField( help_text="The password to be used for authentication when syncing.", required=False, allow_null=True, ) pulp_last_updated = serializers.DateTimeField( help_text="Timestamp of the most recent update of the remote.", read_only=True ) download_concurrency = serializers.IntegerField( help_text="Total number of simultaneous connections.", required=False, min_value=1 ) policy = serializers.ChoiceField( help_text="The policy to use when downloading content.", choices=((models.Remote.IMMEDIATE, "When syncing, download all metadata and content now.")), default=models.Remote.IMMEDIATE, ) total_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.total (q.v.) for download-connections.", min_value=0.0, ) connect_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.connect (q.v.) for download-connections.", min_value=0.0, ) sock_connect_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.sock_connect (q.v.) for download-connections.", min_value=0.0, ) sock_read_timeout = serializers.FloatField( allow_null=True, required=False, help_text="aiohttp.ClientTimeout.sock_read (q.v.) for download-connections.", min_value=0.0, ) headers = serializers.ListField( child=serializers.DictField(), help_text=_("Headers for aiohttp.Clientsession"), ) rate_limit = serializers.IntegerField( help_text=_("Limits total download rate in requests per second"), allow_null=True, required=False, ) def validate_url(self, value): """ Check if the 'url' is a ``file://`` path, and if so, ensure it's an ALLOWED_IMPORT_PATH. The ALLOWED_IMPORT_PATH is specified as a Pulp setting. Args: value: The user-provided value for 'url' to be validated. Raises: ValidationError: When the url starts with `file://`, but is not a subfolder of a path in the ALLOWED_IMPORT_PATH setting. Returns: The validated value. """ if not value.lower().startswith("file://"): return value user_path = value[7:] for allowed_path in settings.ALLOWED_IMPORT_PATHS: user_provided_realpath = os.path.realpath(user_path) if user_provided_realpath.startswith(allowed_path): return value raise serializers.ValidationError(_("url '{}' is not an allowed import path").format(value)) class Meta: abstract = True model = models.Remote fields = ModelSerializer.Meta.fields + ( "name", "url", "ca_cert", "client_cert", "client_key", "tls_validation", "proxy_url", "username", "password", "pulp_labels", "pulp_last_updated", "download_concurrency", "policy", "total_timeout", "connect_timeout", "sock_connect_timeout", "sock_read_timeout", "rate_limit", )
class RemoteSerializer(MasterModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ _href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField( help_text='The URL of an external content source.', ) validate = serializers.BooleanField( help_text='If True, the plugin will validate imported artifacts.', required=False, ) ssl_ca_certificate = serializers.FileField( help_text='A PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server.', write_only=True, required=False, ) ssl_client_certificate = serializers.FileField( help_text='A PEM encoded client certificate used for authentication.', write_only=True, required=False, ) ssl_client_key = serializers.FileField( help_text='A PEM encoded private key used for authentication.', write_only=True, required=False, ) ssl_validation = serializers.BooleanField( help_text='If True, SSL peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, allow_blank=True, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, allow_blank=True, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, allow_blank=True, ) _last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True) download_concurrency = serializers.IntegerField( help_text='Total number of simultaneous connections.', required=False, min_value=1) policy = serializers.ChoiceField( help_text= "The policy to use when downloading content. The possible values include: " "'immediate', 'on_demand', and 'cache_only'. 'immediate' is the default.", choices=models.Remote.POLICY_CHOICES, default=models.Remote.IMMEDIATE) class Meta: abstract = True model = models.Remote fields = MasterModelSerializer.Meta.fields + ( 'name', 'url', 'validate', 'ssl_ca_certificate', 'ssl_client_certificate', 'ssl_client_key', 'ssl_validation', 'proxy_url', 'username', 'password', '_last_updated', 'download_concurrency', 'policy')
class RemoteSerializer(ModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ pulp_href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField( help_text='The URL of an external content source.', ) ca_cert = SecretCharField( help_text='A string containing the PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server. All new line characters must be ' 'escaped. Returns SHA256 checksum of the certificate file on GET.', write_only=False, required=False, allow_null=True, ) client_cert = SecretCharField( help_text='A string containing the PEM encoded client certificate used for authentication. ' 'All new line characters must be escaped. Returns SHA256 checksum of the ' 'certificate file on GET.', write_only=False, required=False, allow_null=True, ) client_key = SecretCharField( help_text='A PEM encoded private key used for authentication. Returns SHA256 checksum of ' 'the certificate file on GET.', write_only=False, required=False, allow_null=True, ) tls_validation = serializers.BooleanField( help_text='If True, TLS peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, allow_null=True, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) pulp_last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True ) download_concurrency = serializers.IntegerField( help_text='Total number of simultaneous connections.', required=False, min_value=1 ) policy = serializers.ChoiceField( help_text="The policy to use when downloading content.", choices=( (models.Remote.IMMEDIATE, 'When syncing, download all metadata and content now.') ), default=models.Remote.IMMEDIATE ) def validate_url(self, value): """ Check if the 'url' is a ``file://`` path, and if so, ensure it's an ALLOWED_IMPORT_PATH. The ALLOWED_IMPORT_PATH is specified as a Pulp setting. Args: value: The user-provided value for 'url' to be validated. Raises: ValidationError: When the url starts with `file://`, but is not a subfolder of a path in the ALLOWED_IMPORT_PATH setting. Returns: The validated value. """ if not value.lower().startswith('file://'): return value user_path = value[7:] for allowed_path in settings.ALLOWED_IMPORT_PATHS: user_provided_realpath = os.path.realpath(user_path) if user_provided_realpath.startswith(allowed_path): return value raise serializers.ValidationError(_("url '{}' is not an allowed import path").format(value)) class Meta: abstract = True model = models.Remote fields = ModelSerializer.Meta.fields + ( 'name', 'url', 'ca_cert', 'client_cert', 'client_key', 'tls_validation', 'proxy_url', 'username', 'password', 'pulp_last_updated', 'download_concurrency', 'policy' )
class RemoteSerializer(ModelSerializer): """ Every remote defined by a plugin should have a Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ pulp_href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())], ) url = serializers.CharField( help_text='The URL of an external content source.', ) ssl_ca_certificate = SecretCharField( help_text= 'A string containing the PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server. All new line characters must be ' 'escaped. Returns SHA256 sum on GET.', write_only=False, required=False, allow_null=True, ) ssl_client_certificate = SecretCharField( help_text= 'A string containing the PEM encoded client certificate used for authentication. ' 'All new line characters must be escaped. Returns SHA256 sum on GET.', write_only=False, required=False, allow_null=True, ) ssl_client_key = SecretCharField( help_text= 'A PEM encoded private key used for authentication. Returns SHA256 sum on GET.', write_only=False, required=False, allow_null=True, ) ssl_validation = serializers.BooleanField( help_text='If True, SSL peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, allow_null=True, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, allow_null=True, ) pulp_last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True) download_concurrency = serializers.IntegerField( help_text='Total number of simultaneous connections.', required=False, min_value=1) policy = serializers.ChoiceField( help_text="The policy to use when downloading content.", choices=((models.Remote.IMMEDIATE, 'When syncing, download all metadata and content now.')), default=models.Remote.IMMEDIATE) class Meta: abstract = True model = models.Remote fields = ModelSerializer.Meta.fields + ( 'name', 'url', 'ssl_ca_certificate', 'ssl_client_certificate', 'ssl_client_key', 'ssl_validation', 'proxy_url', 'username', 'password', 'pulp_last_updated', 'download_concurrency', 'policy')
class RemoteSerializer(MasterModelSerializer): """ Every remote defined by a plugin should have an Remote serializer that inherits from this class. Please import from `pulpcore.plugin.serializers` rather than from this module directly. """ _href = DetailIdentityField() name = serializers.CharField( help_text=_('A unique name for this remote.'), validators=[UniqueValidator(queryset=models.Remote.objects.all())]) url = serializers.CharField( help_text='The URL of an external content source.', ) validate = serializers.BooleanField( help_text='If True, the plugin will validate imported artifacts.', required=False, ) ssl_ca_certificate = serializers.FileField( help_text='A PEM encoded CA certificate used to validate the server ' 'certificate presented by the remote server.', write_only=True, required=False, ) ssl_client_certificate = serializers.FileField( help_text='A PEM encoded client certificate used for authentication.', write_only=True, required=False, ) ssl_client_key = serializers.FileField( help_text='A PEM encoded private key used for authentication.', write_only=True, required=False, ) ssl_validation = serializers.BooleanField( help_text='If True, SSL peer validation must be performed.', required=False, ) proxy_url = serializers.CharField( help_text='The proxy URL. Format: scheme://user:password@host:port', required=False, ) username = serializers.CharField( help_text='The username to be used for authentication when syncing.', write_only=True, required=False, ) password = serializers.CharField( help_text='The password to be used for authentication when syncing.', write_only=True, required=False, ) last_synced = serializers.DateTimeField( help_text='Timestamp of the most recent successful sync.', read_only=True) last_updated = serializers.DateTimeField( help_text='Timestamp of the most recent update of the remote.', read_only=True) class Meta: abstract = True model = models.Remote fields = MasterModelSerializer.Meta.fields + ( 'name', 'url', 'validate', 'ssl_ca_certificate', 'ssl_client_certificate', 'ssl_client_key', 'ssl_validation', 'proxy_url', 'username', 'password', 'last_synced', 'last_updated', )
class DistributionSerializer(ModelSerializer): """ The Serializer for the Distribution model. The serializer deliberately omits the the `publication` and `repository_version` field due to plugins typically requiring one or the other but not both. To include the ``publication`` field, it is recommended plugins define the field:: 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, ) To include the ``repository_version`` field, it is recommended plugins define the field:: repository_version = RepositoryVersionRelatedField( required=False, help_text=_("RepositoryVersion to be served"), allow_null=True ) Additionally, the serializer omits the ``remote`` field, which is used for pull-through caching feature and only by plugins which use publications. Plugins implementing a pull-through caching should define the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) 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=[ UniqueValidator(queryset=models.Distribution.objects.all()) ], ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.Distribution.objects.all()), ], ) repository = DetailRelatedField( required=False, help_text=_( "The latest RepositoryVersion for this Repository will be served." ), view_name_pattern=r"repositories(-.*/.*)?-detail", queryset=models.Repository.objects.all(), allow_null=True, ) class Meta: abstract = True model = models.Distribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", "repository", ) 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.Distribution.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): self._validate_relative_path(path) return self._validate_path_overlap(path) def validate(self, data): super().validate(data) repository_provided = data.get( "repository", (self.instance and self.instance.repository) or None) repository_version_provided = data.get( "repository_version", (self.instance and self.instance.repository_version) or None) publication_provided = data.get( "publication", (self.instance and self.instance.publication) or None) if publication_provided and repository_version_provided: raise serializers.ValidationError( _("Only one of the attributes 'publication' and 'repository_version' " "may be used simultaneously.")) elif repository_provided and repository_version_provided: raise serializers.ValidationError( _("Only one of the attributes 'repository' and 'repository_version' " "may be used simultaneously.")) elif repository_provided and publication_provided: raise serializers.ValidationError( _("Only one of the attributes 'repository' and 'publication' " "may be used simultaneously.")) return data
class BaseDistributionSerializer(ModelSerializer): """ The Serializer for the BaseDistribution model. The serializer deliberately omits the "remote" field, which is used for pull-through caching only. Plugins implementing pull-through caching will have to add the field in their derived serializer class like this:: remote = DetailRelatedField( required=False, help_text=_('Remote that can be used to fetch content when using pull-through caching.'), queryset=models.Remote.objects.all(), allow_null=True ) """ pulp_href = DetailIdentityField( view_name_pattern=r"distributions(-.*/.*)-detail") pulp_labels = LabelsField(required=False) 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=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()) ], ) base_url = BaseURLField( source="base_path", read_only=True, help_text= _("The URL for accessing the publication as defined by this distribution." ), ) content_guard = DetailRelatedField( required=False, help_text=_("An optional content-guard."), view_name_pattern=r"contentguards(-.*/.*)?-detail", queryset=models.ContentGuard.objects.all(), allow_null=True, ) name = serializers.CharField( help_text=_("A unique name. Ex, `rawhide` and `stable`."), validators=[ UniqueValidator(queryset=models.BaseDistribution.objects.all()) ], ) class Meta: abstract = True model = models.BaseDistribution fields = ModelSerializer.Meta.fields + ( "base_path", "base_url", "content_guard", "pulp_labels", "name", ) 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.BaseDistribution.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): self._validate_relative_path(path) return self._validate_path_overlap(path)