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 MigrationPlanSerializer(ModelSerializer): """Serializer for migration plan model.""" _href = IdentityField( view_name='migration-plans-detail' ) plan = serializers.JSONField( help_text=_('Migration Plan in JSON format'), required=True, ) class Meta: fields = ModelSerializer.Meta.fields + ('plan', ) model = MigrationPlan def validate(self, data): """ Validate that the Serializer contains valid data. Validates JSON structure of migration_plan. Checks pulp2 and pulp3 plugins are installed. """ schema = json.loads(SCHEMA) validator = Draft7Validator(schema) loaded_plan = json.loads(data['plan']) err = [] for error in sorted(validator.iter_errors(loaded_plan), key=str): err.append(error.message) if err: raise serializers.ValidationError( _("Provided Migration Plan format is invalid:'{}'".format(err)) ) plugins_to_migrate = set() for plugin_type in loaded_plan['plugins']: plugins_to_migrate.add(plugin_type['type']) if len(loaded_plan['plugins']) != len(plugins_to_migrate): raise serializers.ValidationError( _("Provided Migration Plan contains same plugin type specified more that once.") ) # MongoDB connection initialization connection.initialize() db = connection.get_database() for plugin in plugins_to_migrate: if PULP_2TO3_PLUGIN_MAP.get(plugin) not in INSTALLED_PULP_PLUGINS: raise serializers.ValidationError( _("Plugin {} is not installed in pulp3.".format(plugin)) ) try: collection = PULP2_COLLECTION_MAP.get(plugin) db.command("collstats", collection) except OperationFailure: raise serializers.ValidationError( _("Plugin {} is not installed in pulp2.".format(plugin)) ) data['plan'] = loaded_plan return data
class ContainerNamespaceSerializer(ModelSerializer, _GetOrCreateMixin): """ Serializer for ContainerNamespaces. """ pulp_href = IdentityField(view_name="pulp_container/namespaces-detail") class Meta: fields = ModelSerializer.Meta.fields + ("name",) model = models.ContainerNamespace
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 AnsibleRoleSerializer(ContentSerializer): """ A serializer for Ansible Roles. """ name = serializers.CharField() namespace = serializers.CharField() _versions_href = IdentityField( view_name='versions-list', lookup_url_kwarg='role_pk', ) version_count = serializers.IntegerField(source='versions.count', read_only=True) class Meta: fields = ('_href', 'name', 'namespace', '_versions_href', 'version_count') model = AnsibleRole
class MigrationPlanSerializer(ModelSerializer): _href = IdentityField(view_name='migration-plans-detail') plan = serializers.CharField( help_text=_('Migration Plan in JSON format'), required=True, ) class Meta: fields = ModelSerializer.Meta.fields + ('plan', ) model = MigrationPlan def validate(self, data): """ Validate that the Serializer contains valid data. TODO: Validate JSON structure of migration_plan. Check validity of the JSON content: - migration for requested plugins is supported """ return data
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 Pulp2RepositoriesSerializer(ModelSerializer): """ A serializer for the Pulp2Repositories """ pulp_href = IdentityField(view_name='pulp2repositories-detail') pulp2_object_id = serializers.CharField(max_length=255) pulp2_repo_id = serializers.CharField() pulp2_repo_type = serializers.CharField() is_migrated = serializers.BooleanField(default=False) not_in_plan = serializers.BooleanField(default=False) pulp3_repository_version = RepositoryVersionRelatedField( required=False, help_text=_('RepositoryVersion to be served'), allow_null=True, ) pulp3_remote_href = serializers.SerializerMethodField(read_only=True) pulp3_publication_href = serializers.SerializerMethodField(read_only=True) pulp3_distribution_hrefs = serializers.SerializerMethodField( read_only=True) pulp3_repository_href = serializers.SerializerMethodField(read_only=True) @extend_schema_serializer(many=False) def get_pulp3_repository_href(self, obj): """ Get pulp3_repository_href from pulp2repo """ rv = obj.pulp3_repository_version if rv: return get_pulp_href(rv.repository) @extend_schema_serializer(many=False) def get_pulp3_remote_href(self, obj): """ Get pulp3_remote_href from pulp2repo """ remote = obj.pulp3_repository_remote if remote: return get_pulp_href(remote) @extend_schema_serializer(many=False) def get_pulp3_publication_href(self, obj): """ Get pulp3_publication_href from pulp3_repository_version """ rv = obj.pulp3_repository_version if rv: return get_pulp_href(rv.publication_set.first()) @extend_schema_serializer(many=True) def get_pulp3_distribution_hrefs(self, obj): """ Get pulp3_distribution_hrefs from pulp3_repository_version """ dist_list = [] rv = obj.pulp3_repository_version if not rv: return dist_list result_qs = rv.publication_set.all() if result_qs: # repo_version has publication, therefore is a PublicationDistribution for publication in result_qs: distribution_type = f'{publication.pulp_type.replace(".", "_")}distribution' plugin_distribution = getattr(publication, distribution_type) dist_list.extend(plugin_distribution.all()) else: # empty result_qs means that repo_version does not need publication, # or repo_version was not published/distributed pulp_type = rv.repository.pulp_type distribution_type = f'{pulp_type.replace(".", "_")}distribution' if hasattr(rv, distribution_type): # repo_version is distributed directly trough RepositoryDistribution plugin_distribution = getattr(rv, distribution_type) dist_list = plugin_distribution.all() else: # repo_version was not published/distributed return dist_list return [get_pulp_href(dist) for dist in dist_list] class Meta: fields = ModelSerializer.Meta.fields + ( "pulp2_object_id", "pulp2_repo_id", "pulp2_repo_type", "is_migrated", "not_in_plan", "pulp3_repository_version", "pulp3_remote_href", "pulp3_publication_href", "pulp3_distribution_hrefs", "pulp3_repository_href", ) model = Pulp2Repository
class Pulp2RepositoriesSerializer(ModelSerializer): """ A serializer for the Pulp2Repositories """ pulp_href = IdentityField(view_name='pulp2repositories-detail') pulp2_object_id = serializers.CharField(max_length=255) pulp2_repo_id = serializers.CharField() is_migrated = serializers.BooleanField(default=False) pulp3_repository_version = NestedRelatedField( view_name='versions-detail', lookup_field='number', parent_lookup_kwargs={'repository_pk': 'repository__pk'}, queryset=RepositoryVersion.objects.all(), required=False, ) pulp3_remote_href = serializers.SerializerMethodField(read_only=True) pulp3_publisher_href = serializers.SerializerMethodField(read_only=True) pulp3_publication_href = serializers.SerializerMethodField(read_only=True) pulp3_distribution_hrefs = serializers.SerializerMethodField( read_only=True) def get_pulp3_remote_href(self, obj): """ Get pulp3_remote_href from Pulp2Importer """ importer = Pulp2Importer.objects.filter(pulp2_repository=obj).first() return get_pulp_href(importer.pulp3_remote) def get_pulp3_publisher_href(self, obj): """ Get pulp3_publisher_href from Pulp2Distributor """ distributors = getattr(self, "_distributors", None) if not distributors: self._distributors = Pulp2Distributor.objects.filter( pulp2_repository=obj).all() distributors = self._distributors return [ get_pulp_href(d.pulp3_publisher) for d in distributors if d.pulp3_publisher ] def get_pulp3_publication_href(self, obj): """ Get pulp3_publication_href from Pulp2Distributor """ distributors = getattr(self, "_distributors", None) if not distributors: self._distributors = Pulp2Distributor.objects.filter( pulp2_repository=obj).all() distributors = self._distributors return [ get_pulp_href(d.pulp3_publication) for d in distributors if d.pulp3_publication ] def get_pulp3_distribution_hrefs(self, obj): """ Get pulp3_distribution_hrefs from Pulp2Distributor """ distributors = getattr(self, "_distributors", None) if not distributors: self._distributors = Pulp2Distributor.objects.filter( pulp2_repository=obj).all() distributors = self._distributors return [ get_pulp_href(d.pulp3_distribution) for d in distributors if d.pulp3_distribution ] class Meta: fields = ModelSerializer.Meta.fields + ( "pulp2_object_id", "pulp2_repo_id", "is_migrated", "pulp3_repository_version", "pulp3_remote_href", "pulp3_publisher_href", "pulp3_publication_href", "pulp3_distribution_hrefs", ) model = Pulp2Repository
class Pulp2RepositoriesSerializer(ModelSerializer): """ A serializer for the Pulp2Repositories """ pulp_href = IdentityField( view_name='pulp2repositories-detail' ) pulp2_object_id = serializers.CharField(max_length=255) pulp2_repo_id = serializers.CharField() pulp2_repo_type = serializers.CharField() is_migrated = serializers.BooleanField(default=False) not_in_plan = serializers.BooleanField(default=False) pulp3_repository_version = RepositoryVersionRelatedField( required=False, help_text=_('RepositoryVersion to be served'), allow_null=True, ) pulp3_remote_href = serializers.SerializerMethodField(read_only=True) pulp3_publication_href = serializers.SerializerMethodField(read_only=True) pulp3_distribution_hrefs = serializers.SerializerMethodField(read_only=True) pulp3_repository_href = serializers.SerializerMethodField(read_only=True) @extend_schema_field(field=serializers.CharField) def get_pulp3_repository_href(self, obj): """ Get pulp3_repository_href from pulp2repo """ rv = obj.pulp3_repository_version if rv: return get_pulp_href(rv.repository) @extend_schema_field(field=serializers.CharField) def get_pulp3_remote_href(self, obj): """ Get pulp3_remote_href from pulp2repo """ remote = obj.pulp3_repository_remote if remote: return get_pulp_href(remote) @extend_schema_field(field=serializers.CharField) def get_pulp3_publication_href(self, obj): """ Get pulp3_publication_href from pulp3_repository_version """ rv = obj.pulp3_repository_version if rv: return get_pulp_href(rv.publication_set.filter(complete=True).first()) @extend_schema_field(field=serializers.ListField(child=serializers.CharField())) def get_pulp3_distribution_hrefs(self, obj): """ Get pulp3_distribution_hrefs from pulp3_repository_version """ pulp2dists = obj.pulp2_dists.filter(not_in_plan=False, is_migrated=True) return [get_pulp_href(dist.pulp3_distribution) for dist in pulp2dists] class Meta: fields = ModelSerializer.Meta.fields + ( "pulp2_object_id", "pulp2_repo_id", "pulp2_repo_type", "is_migrated", "not_in_plan", "pulp3_repository_version", "pulp3_remote_href", "pulp3_publication_href", "pulp3_distribution_hrefs", "pulp3_repository_href", ) model = Pulp2Repository
class MigrationPlanSerializer(ModelSerializer): """Serializer for migration plan model.""" pulp_href = IdentityField(view_name="migration-plans-detail") plan = serializers.JSONField( help_text=_("Migration Plan in JSON format"), required=True, ) class Meta: fields = ModelSerializer.Meta.fields + ("plan", ) model = MigrationPlan def validate(self, data): """ Validate that the Serializer contains valid data. Validates JSON structure of migration_plan. Checks pulp2 and pulp3 plugins are installed. """ schema = json.loads(SCHEMA) validator = Draft7Validator(schema) if isinstance(data["plan"], str): loaded_plan = json.loads(data["plan"]) elif isinstance(data["plan"], dict): loaded_plan = data["plan"] else: raise serializers.ValidationError( _("Must provide a (JSON-encoded) string or dict for 'plan', not list" )) err = [] for error in sorted(validator.iter_errors(loaded_plan), key=str): err.append(error.message) if err: raise serializers.ValidationError( _("Provided Migration Plan format is invalid:'{}'".format( err))) plugins_to_migrate = set() for plugin_type in loaded_plan["plugins"]: plugins_to_migrate.add(plugin_type["type"]) if len(loaded_plan["plugins"]) != len(plugins_to_migrate): raise serializers.ValidationError( _("Provided Migration Plan contains same plugin type specified more that once." )) # MongoDB connection initialization connection.initialize() db = connection.get_database() for plugin in plugins_to_migrate: plugin_migrator = PLUGIN_MIGRATORS.get(plugin) if not plugin_migrator: raise serializers.ValidationError( _("Migration of {} plugin is not supported.".format( plugin))) if plugin_migrator.pulp3_plugin not in INSTALLED_PULP_PLUGINS: raise serializers.ValidationError( _("Plugin {} is not installed in pulp3.".format(plugin))) try: db.command("collstats", plugin_migrator.pulp2_collection) except OperationFailure: raise serializers.ValidationError( _("Plugin {} is not installed in pulp2.".format(plugin))) data["plan"] = loaded_plan return data