3
3
4
4
from django .conf import settings
5
5
from jsonschema import Draft7Validator
6
- from pulpcore .plugin .models import AsciiArmoredDetachedSigningService , Publication , Remote
6
+ from pulpcore .plugin .models import (
7
+ AsciiArmoredDetachedSigningService ,
8
+ Publication ,
9
+ Remote ,
10
+ Content ,
11
+ RepositoryVersion ,
12
+ )
7
13
from pulpcore .plugin .serializers import (
8
14
DetailRelatedField ,
9
15
DistributionSerializer ,
14
20
RepositorySyncURLSerializer ,
15
21
ValidateFieldsMixin ,
16
22
)
17
- from pulpcore .plugin .util import get_domain
23
+ from pulpcore .plugin .util import get_domain , extract_pk
18
24
from rest_framework import serializers
19
25
from pulp_rpm .app .fields import CustomJSONField
20
26
37
43
)
38
44
from pulp_rpm .app .schema import COPY_CONFIG_SCHEMA
39
45
from urllib .parse import urlparse
46
+ from textwrap import dedent
40
47
41
48
42
49
class RpmRepositorySerializer (RepositorySerializer ):
@@ -543,7 +550,32 @@ class CopySerializer(ValidateFieldsMixin, serializers.Serializer):
543
550
"""
544
551
545
552
config = CustomJSONField (
546
- help_text = _ ("A JSON document describing sources, destinations, and content to be copied" ),
553
+ help_text = _ (
554
+ dedent (
555
+ """\
556
+ A JSON document describing sources, destinations, and content to be copied.
557
+
558
+ Its a list of dictionaries with the following available fields:
559
+
560
+ ```json
561
+ [
562
+ {
563
+ "source_repo_version": <RepositoryVersion [pulp_href|prn]>,
564
+ "dest_repo": <RpmRepository [pulp_href|prn]>,
565
+ "dest_base_version": <int>,
566
+ "content": [<Content [pulp_href|prn]>, ...]
567
+ },
568
+ ...
569
+ ]
570
+ ```
571
+
572
+ If domains are enabled, the refered pulp objects must be part of the current domain.
573
+
574
+ For usage examples, refer to the advanced copy guide:
575
+ <https://pulpproject.org/pulp_rpm/docs/user/guides/modify/#advanced-copy-workflow>
576
+ """
577
+ )
578
+ ),
547
579
)
548
580
549
581
dependency_solving = serializers .BooleanField (
@@ -558,29 +590,70 @@ def validate(self, data):
558
590
Check for cross-domain references (if domain-enabled).
559
591
"""
560
592
561
- def check_domain (domain , href , name ):
562
- # We're doing just a string-check here rather than checking objects
563
- # because there can be A LOT of objects, and this is happening in the view-layer
564
- # where we have strictly-limited timescales to work with
565
- if href and domain not in href :
593
+ def _extract_pk (ref ):
594
+ """Workaround for expensive extract_pk(href) calls."""
595
+ if ref .startswith ("prn:" ):
596
+ return extract_pk (ref )
597
+ url = urlparse (ref ).path .strip ("/" ).split ("/" )
598
+ # RepositoryVersion resources doesnt have their pk in the href.
599
+ # Nevertheless, they have the associated Repository pk, which is really what
600
+ # we need to determine it's domain.
601
+ # .../{repository<uuid>}/versions/{version<int>}/
602
+ is_repover_href = url [- 1 ].isdigit () and url [- 2 ] == "versions"
603
+ uuid = url [- 1 ] if not is_repover_href else url [- 3 ]
604
+
605
+ if len (uuid ) < 32 :
566
606
raise serializers .ValidationError (
567
- _ ("{} must be a part of the {} domain." ).format (name , domain )
607
+ _ ("The href path should end with a uuid pk: {}" .format (ref ))
608
+ )
609
+ return uuid
610
+
611
+ def raise_validation (field , domain , id = "" ):
612
+ id = f"\n { id } " if id else ""
613
+ raise serializers .ValidationError (
614
+ _ (f"The field { field !r} contains object(s) not in { domain !r} domain.{ id } " )
615
+ )
616
+
617
+ def check_domain (entry , name , curr_domain ):
618
+ href_or_prn = entry [name ]
619
+ resource_pk = _extract_pk (href_or_prn )
620
+ try :
621
+ resource_domain_pk = RpmRepository .objects .values ("pulp_domain" ).get (
622
+ pk = resource_pk
623
+ )["pulp_domain" ]
624
+ except Exception : # if prns was provided we'll get the repository version pk
625
+ resource_domain_pk = (
626
+ RepositoryVersion .objects .select_related ("repository" )
627
+ .only ("pulp_domain" )
628
+ .get (pk = resource_pk )["pulp_domain" ]
568
629
)
630
+ if resource_domain_pk != curr_domain .pk :
631
+ raise_validation (name , curr_domain .name , resource_domain_pk )
569
632
570
633
def check_cross_domain_config (cfg ):
571
634
"""Check that all config-elts are in 'our' domain."""
572
635
# copy-cfg is a list of dictionaries.
573
636
# source_repo_version and dest_repo are required fields.
574
637
# Insure curr-domain exists in src/dest/dest_base_version/content-list hrefs
575
- curr_domain_name = get_domain (). name
638
+ curr_domain = get_domain ()
576
639
for entry in cfg :
577
- check_domain (curr_domain_name , entry ["source_repo_version" ], "dest_repo" )
578
- check_domain (curr_domain_name , entry ["dest_repo" ], "dest_repo" )
579
- check_domain (
580
- curr_domain_name , entry .get ("dest_base_version" , None ), "dest_base_version"
581
- )
582
- for content_href in entry .get ("content" , []):
583
- check_domain (curr_domain_name , content_href , "content" )
640
+ # Check required fields individually
641
+ try :
642
+ check_domain (entry , "source_repo_version" , curr_domain )
643
+ check_domain (entry , "dest_repo" , curr_domain )
644
+ except RpmRepository .DoesNotExist :
645
+ raise
646
+
647
+ # Check content generically to avoid timeout of multiple calls
648
+ content_list = entry .get ("content" , None )
649
+ if content_list :
650
+ content_list = [_extract_pk (v ) for v in content_list ]
651
+ distinct = (
652
+ Content .objects .filter (pk__in = content_list ).values ("pulp_domain" ).distinct ()
653
+ )
654
+ domain_ok = len (distinct ) == 1 and distinct .name == curr_domain .name
655
+ if not domain_ok :
656
+ raise_validation ("content" , curr_domain .name )
584
657
585
658
super ().validate (data )
586
659
if "config" in data :
0 commit comments