Merge ppa-dev-tools:fix-lp1990244-create-for-owner into ppa-dev-tools:main

Proposed by Bryce Harrington
Status: Merged
Merged at revision: 9dbaa6f713ae958ad415607c771549434fa1db93
Proposed branch: ppa-dev-tools:fix-lp1990244-create-for-owner
Merge into: ppa-dev-tools:main
Diff against target: 732 lines (+207/-111)
8 files modified
NEWS.md (+9/-0)
ppa/ppa.py (+35/-29)
ppa/ppa_group.py (+9/-9)
scripts/ppa (+38/-18)
tests/helpers.py (+3/-2)
tests/test_ppa.py (+32/-32)
tests/test_ppa_group.py (+5/-5)
tests/test_scripts_ppa.py (+76/-16)
Reviewer Review Type Date Requested Status
Lena Voytek (community) Approve
Canonical Server packageset reviewers Pending
PpaDevTools Developers Pending
Canonical Server Reporter Pending
Review via email: mp+443452@code.launchpad.net

Description of the change

This adds the ability to create PPAs against other teams that your LP user account has access to, fixing LP: #1990244. You can specify the team by a command line argument, config file variable, or the ppa address.
For example:

$ ./scripts/ppa create --team ppa-dev-tools-devs foobar
PPA 'foobar' created for the following architectures:

  i386, amd64, armhf, ppc64el, s390x, arm64, powerpc

The PPA can be viewed at:

  https://launchpad.net/~ppa-dev-tools-devs/+archive/ubuntu/foobar

...

$ ./scripts/ppa create ppa-dev-tools-devs/example
PPA 'example' created for the following architectures:

  i386, amd64, armhf, ppc64el, s390x, arm64, powerpc

The PPA can be viewed at:

  https://launchpad.net/~ppa-dev-tools-devs/+archive/ubuntu/example

...

To post a comment you must log in.
Revision history for this message
Christian Ehrhardt  (paelzer) wrote :

I like this, I do not feel I was able to do a full review - but for whatever it is worth it LGTM.
Furthermore I have left a few small comments inline for you to think about.

Revision history for this message
Lena Voytek (lvoytek) wrote :

LGTM, added a few comments below for formatting/descriptions

review: Approve
Revision history for this message
Bryce Harrington (bryce) wrote :

Thanks, I've updated with the changes as suggested and landed:

Total 0 (delta 0), reused 0 (delta 0), pack-reused 0
To git+ssh://git.launchpad.net/ppa-dev-tools
   2a8e2ef..9dbaa6f main -> main

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
diff --git a/NEWS.md b/NEWS.md
index 61c117e..e6bdaeb 100644
--- a/NEWS.md
+++ b/NEWS.md
@@ -9,6 +9,15 @@ jobs that it is monitoring are failed builds. These options are aimed
9to facilitate CI/CD integration, but can also improve performance of the9to facilitate CI/CD integration, but can also improve performance of the
10waiting operation on larger PPAs.10waiting operation on larger PPAs.
1111
12It is now possible to create PPAs under a different team's ownership via
13the --owner option:
14
15 $ ppa create --owner foobar my-ppa
16
17As a convenience, this can also be specified in ppa address form, i.e.:
18
19 $ ppa create ppa:foobar/my-ppa
20
1221
13# 0.4.0 #22# 0.4.0 #
1423
diff --git a/ppa/ppa.py b/ppa/ppa.py
index 6e3a071..0cccb10 100755
--- a/ppa/ppa.py
+++ b/ppa/ppa.py
@@ -36,14 +36,15 @@ class PendingReason(enum.Enum):
36class PpaDoesNotExist(BaseException):36class PpaDoesNotExist(BaseException):
37 """Exception indicating a requested PPA could not be found."""37 """Exception indicating a requested PPA could not be found."""
3838
39 def __init__(self, ppa_name, team_name, message=None):39 def __init__(self, ppa_name, owner_name, message=None):
40 """Initializes the exception object.40 """Initializes the exception object.
4141
42 :param str ppa_name: The name of the missing PPA.42 :param str ppa_name: The name of the missing PPA.
43 :param str owner_name: The person or team the PPA belongs to.
43 :param str message: An error message.44 :param str message: An error message.
44 """45 """
45 self.ppa_name = ppa_name46 self.ppa_name = ppa_name
46 self.team_name = team_name47 self.owner_name = owner_name
47 self.message = message48 self.message = message
4849
49 def __str__(self):50 def __str__(self):
@@ -54,7 +55,7 @@ class PpaDoesNotExist(BaseException):
54 """55 """
55 if self.message:56 if self.message:
56 return self.message57 return self.message
57 return f"The PPA '{self.ppa_name}' does not exist for team or user '{self.team_name}'"58 return f"The PPA '{self.ppa_name}' does not exist for person or team '{self.owner_name}'"
5859
5960
60class Ppa:61class Ppa:
@@ -70,25 +71,27 @@ class Ppa:
70 " + Launchpad Build Page: {build_page}\n"71 " + Launchpad Build Page: {build_page}\n"
71 )72 )
7273
73 def __init__(self, ppa_name, team_name, ppa_description=None, service=None):74 def __init__(self, ppa_name, owner_name, ppa_description=None, service=None):
74 """Initializes a new Ppa object for a given PPA.75 """Initializes a new Ppa object for a given PPA.
7576
76 This creates only the local representation of the PPA, it does77 This creates only the local representation of the PPA, it does
77 not cause a new PPA to be created in Launchpad. For that, see78 not cause a new PPA to be created in Launchpad. For that, see
78 PpaGroup.create()79 PpaGroup.create()
7980
80 :param str ppa_name: The name of the PPA within the team's namespace.81 :param str ppa_name: The name of the PPA within the owning
81 :param str team_name: The name of the team or user that owns the PPA.82 person or team's namespace.
83 :param str owner_name: The name of the person or team the PPA
84 belongs to.
82 :param str ppa_description: Optional description text for the PPA.85 :param str ppa_description: Optional description text for the PPA.
83 :param launchpadlib.service service: The Launchpad service object.86 :param launchpadlib.service service: The Launchpad service object.
84 """87 """
85 if not ppa_name:88 if not ppa_name:
86 raise ValueError("undefined ppa_name.")89 raise ValueError("undefined ppa_name.")
87 if not team_name:90 if not owner_name:
88 raise ValueError("undefined team_name.")91 raise ValueError("undefined owner_name.")
8992
90 self.ppa_name = ppa_name93 self.ppa_name = ppa_name
91 self.team_name = team_name94 self.owner_name = owner_name
92 if ppa_description is None:95 if ppa_description is None:
93 self.ppa_description = ''96 self.ppa_description = ''
94 else:97 else:
@@ -102,7 +105,7 @@ class Ppa:
102 :returns: Official string representation of the object.105 :returns: Official string representation of the object.
103 """106 """
104 return (f'{self.__class__.__name__}('107 return (f'{self.__class__.__name__}('
105 f'ppa_name={self.ppa_name!r}, team_name={self.team_name!r})')108 f'ppa_name={self.ppa_name!r}, owner_name={self.owner_name!r})')
106109
107 def __str__(self) -> str:110 def __str__(self) -> str:
108 """Returns a displayable string identifying the PPA.111 """Returns a displayable string identifying the PPA.
@@ -110,7 +113,7 @@ class Ppa:
110 :rtype: str113 :rtype: str
111 :returns: Displayable representation of the PPA.114 :returns: Displayable representation of the PPA.
112 """115 """
113 return f"{self.team_name}/{self.name}"116 return f"{self.owner_name}/{self.name}"
114117
115 @property118 @property
116 @lru_cache119 @lru_cache
@@ -124,10 +127,10 @@ class Ppa:
124 if not self._service:127 if not self._service:
125 raise AttributeError("Ppa object not connected to the Launchpad service")128 raise AttributeError("Ppa object not connected to the Launchpad service")
126 try:129 try:
127 owner = self._service.people[self.team_name]130 owner = self._service.people[self.owner_name]
128 return owner.getPPAByName(name=self.ppa_name)131 return owner.getPPAByName(name=self.ppa_name)
129 except NotFound:132 except NotFound:
130 raise PpaDoesNotExist(self.ppa_name, self.team_name)133 raise PpaDoesNotExist(self.ppa_name, self.owner_name)
131134
132 @lru_cache135 @lru_cache
133 def exists(self) -> bool:136 def exists(self) -> bool:
@@ -146,7 +149,7 @@ class Ppa:
146 :rtype: str149 :rtype: str
147 :returns: The full identification string for the PPA.150 :returns: The full identification string for the PPA.
148 """151 """
149 return "ppa:{}/{}".format(self.team_name, self.ppa_name)152 return "ppa:{}/{}".format(self.owner_name, self.ppa_name)
150153
151 @property154 @property
152 def name(self):155 def name(self):
@@ -269,8 +272,8 @@ class Ppa:
269 base = self._service.API_ROOT_URL.rstrip('/')272 base = self._service.API_ROOT_URL.rstrip('/')
270 new_ppa_deps = []273 new_ppa_deps = []
271 for ppa_address in ppa_addresses:274 for ppa_address in ppa_addresses:
272 team_name, ppa_name = ppa_address_split(ppa_address)275 owner_name, ppa_name = ppa_address_split(ppa_address)
273 new_ppa_dep = f'{base}/~{team_name}/+archive/ubuntu/{ppa_name}'276 new_ppa_dep = f'{base}/~{owner_name}/+archive/ubuntu/{ppa_name}'
274 new_ppa_deps.append(new_ppa_dep)277 new_ppa_deps.append(new_ppa_dep)
275278
276 # TODO: Remove all existing dependencies279 # TODO: Remove all existing dependencies
@@ -529,44 +532,47 @@ class Ppa:
529 return []532 return []
530533
531534
532def ppa_address_split(ppa_address, default_team=None):535def ppa_address_split(ppa_address):
533 """Parse an address for a PPA into its team and name components.536 """Parse an address for a PPA into its owner and name components.
534537
535 :param str ppa_address: A ppa name or address.538 :param str ppa_address: A ppa name or address.
536 :param str default_team: (Optional) name of team to use if missing.
537 :rtype: tuple(str, str)539 :rtype: tuple(str, str)
538 :returns: The team name and ppa name as a tuple, or (None, None) on error.540 :returns: The owner name and ppa name as a tuple, or (None, None) on error.
539 """541 """
542 owner_name = None
543
540 if not ppa_address or len(ppa_address) < 2:544 if not ppa_address or len(ppa_address) < 2:
541 return (None, None)545 return (None, None)
542 if ppa_address.startswith('ppa:'):546 if ppa_address.startswith('ppa:'):
543 if '/' not in ppa_address:547 if '/' not in ppa_address:
544 return (None, None)548 return (None, None)
545 rem = ppa_address.split('ppa:', 1)[1]549 rem = ppa_address.split('ppa:', 1)[1]
546 team_name = rem.split('/', 1)[0]550 owner_name = rem.split('/', 1)[0]
547 ppa_name = rem.split('/', 1)[1]551 ppa_name = rem.split('/', 1)[1]
548 elif ppa_address.startswith('http'):552 elif ppa_address.startswith('http'):
549 # Only launchpad PPA urls are supported553 # Only launchpad PPA urls are supported
550 m = re.search(r'https:\/\/launchpad\.net\/~([^/]+)\/\+archive\/ubuntu\/(.+)$', ppa_address)554 m = re.search(r'https:\/\/launchpad\.net\/~([^/]+)\/\+archive\/ubuntu\/(.+)$', ppa_address)
551 if not m:555 if not m:
552 return (None, None)556 return (None, None)
553 team_name = m.group(1)557 owner_name = m.group(1)
554 ppa_name = m.group(2)558 ppa_name = m.group(2)
555 elif '/' in ppa_address:559 elif '/' in ppa_address:
556 team_name = ppa_address.split('/', 1)[0]560 owner_name = ppa_address.split('/', 1)[0]
557 ppa_name = ppa_address.split('/', 1)[1]561 ppa_name = ppa_address.split('/', 1)[1]
558 else:562 else:
559 team_name = default_team
560 ppa_name = ppa_address563 ppa_name = ppa_address
561564
562 if (team_name565 if owner_name is not None:
563 and ppa_name566 if len(owner_name) < 1:
564 and not (any(x.isupper() for x in team_name))567 return (None, None)
568 owner_name = owner_name.lower()
569
570 if (ppa_name
565 and not (any(x.isupper() for x in ppa_name))571 and not (any(x.isupper() for x in ppa_name))
566 and ppa_name.isascii()572 and ppa_name.isascii()
567 and '/' not in ppa_name573 and '/' not in ppa_name
568 and len(ppa_name) > 1):574 and len(ppa_name) > 1):
569 return (team_name, ppa_name)575 return (owner_name, ppa_name)
570576
571 return (None, None)577 return (None, None)
572578
@@ -600,7 +606,7 @@ def get_ppa(lp, config):
600 """606 """
601 return Ppa(607 return Ppa(
602 ppa_name=config.get('ppa_name', None),608 ppa_name=config.get('ppa_name', None),
603 team_name=config.get('team_name', None),609 owner_name=config.get('owner_name', None),
604 service=lp)610 service=lp)
605611
606612
diff --git a/ppa/ppa_group.py b/ppa/ppa_group.py
index 59011ea..c9747a5 100755
--- a/ppa/ppa_group.py
+++ b/ppa/ppa_group.py
@@ -8,7 +8,7 @@
8# Released under GNU GPLv2 or later, read the file 'LICENSE.GPLv2+' for8# Released under GNU GPLv2 or later, read the file 'LICENSE.GPLv2+' for
9# more information.9# more information.
1010
11"""A team or person that owns one or more PPAs in Launchpad."""11"""A person or team that owns one or more PPAs in Launchpad."""
1212
13from functools import lru_cache13from functools import lru_cache
14from lazr.restfulclient.errors import BadRequest14from lazr.restfulclient.errors import BadRequest
@@ -42,7 +42,7 @@ class PpaAlreadyExists(BaseException):
4242
4343
44class PpaGroup:44class PpaGroup:
45 """Represents a team or person that owns one or more PPAs.45 """Represents a person or team that owns one or more PPAs.
4646
47 This class provides a proxy object for interacting with collections47 This class provides a proxy object for interacting with collections
48 of PPA.48 of PPA.
@@ -51,7 +51,7 @@ class PpaGroup:
51 """Initializes a new PpaGroup object for a named person or team.51 """Initializes a new PpaGroup object for a named person or team.
5252
53 :param launchpadlib.service service: The Launchpad service object.53 :param launchpadlib.service service: The Launchpad service object.
54 :param str name: Launchpad username or team name.54 :param str name: Launchpad username for a person or team.
55 """55 """
56 if not service:56 if not service:
57 raise ValueError("undefined service.")57 raise ValueError("undefined service.")
@@ -80,8 +80,8 @@ class PpaGroup:
8080
81 @property81 @property
82 @lru_cache82 @lru_cache
83 def team(self):83 def owner(self):
84 """The team that owns this collection of PPAs.84 """The person or team that owns this collection of PPAs.
8585
86 :rtype: launchpadlib.person86 :rtype: launchpadlib.person
87 :returns: Launchpad person object that owns this PPA.87 :returns: Launchpad person object that owns this PPA.
@@ -106,11 +106,11 @@ class PpaGroup:
106 }106 }
107107
108 try:108 try:
109 self.team.createPPA(109 self.owner.createPPA(
110 name=ppa_name,110 name=ppa_name,
111 **ppa_settings111 **ppa_settings
112 )112 )
113 self.team.lp_save()113 self.owner.lp_save()
114 except BadRequest as e:114 except BadRequest as e:
115 if "You already have a PPA" in o2str(e.content):115 if "You already have a PPA" in o2str(e.content):
116 raise PpaAlreadyExists(ppa_name, e.content)116 raise PpaAlreadyExists(ppa_name, e.content)
@@ -127,7 +127,7 @@ class PpaGroup:
127 :rtype: Iterator[ppa.Ppa]127 :rtype: Iterator[ppa.Ppa]
128 :returns: Each PPA in the group as a ppa.Ppa object.128 :returns: Each PPA in the group as a ppa.Ppa object.
129 """129 """
130 for lp_ppa in self.team.ppas:130 for lp_ppa in self.owner.ppas:
131 if '-deletedppa' in lp_ppa.name:131 if '-deletedppa' in lp_ppa.name:
132 continue132 continue
133 yield Ppa(lp_ppa.name, self.name,133 yield Ppa(lp_ppa.name, self.name,
@@ -140,7 +140,7 @@ class PpaGroup:
140 :rtype: ppa.Ppa140 :rtype: ppa.Ppa
141 :returns: A Ppa object describing the named ppa.141 :returns: A Ppa object describing the named ppa.
142 """142 """
143 lp_ppa = self.team.getPPAByName(name=ppa_name)143 lp_ppa = self.owner.getPPAByName(name=ppa_name)
144 if not lp_ppa:144 if not lp_ppa:
145 return None145 return None
146 return Ppa(lp_ppa.name, self.name,146 return Ppa(lp_ppa.name, self.name,
diff --git a/scripts/ppa b/scripts/ppa
index b6aebbb..ee97f4d 100755
--- a/scripts/ppa
+++ b/scripts/ppa
@@ -254,6 +254,10 @@ def create_arg_parser() -> argparse.ArgumentParser:
254 create_parser.add_argument('ppa_name', metavar='ppa-name',254 create_parser.add_argument('ppa_name', metavar='ppa-name',
255 action='store',255 action='store',
256 help="Name of the PPA to be created")256 help="Name of the PPA to be created")
257 create_parser.add_argument('--owner-name', '--owner', '--team-name', '--team', metavar='NAME',
258 action='store',
259 default=None,
260 help="Person or team to create PPA under, if not specified via the ppa address (defaults to current LP user)")
257 add_basic_config_options(create_parser)261 add_basic_config_options(create_parser)
258262
259 # Desc Command263 # Desc Command
@@ -395,7 +399,7 @@ def create_arg_parser() -> argparse.ArgumentParser:
395DEFAULT_CONFIG = {399DEFAULT_CONFIG = {
396 'debug': False,400 'debug': False,
397 'ppa_name': None,401 'ppa_name': None,
398 'team_name': None,402 'owner_name': None,
399 'wait_seconds': 10.0403 'wait_seconds': 10.0
400 }404 }
401405
@@ -430,16 +434,32 @@ def create_config(lp: Lp, args: argparse.Namespace) -> dict[str, Any]:
430 for k, v in DEFAULT_CONFIG.items():434 for k, v in DEFAULT_CONFIG.items():
431 config.setdefault(k, v)435 config.setdefault(k, v)
432436
433 lp_username = None
434 if lp.me:
435 lp_username = lp.me.name
436 if not hasattr(args, 'ppa_name'):437 if not hasattr(args, 'ppa_name'):
437 warn("No ppa name given")438 warn("No ppa name given")
438 return None439 return None
439440
440 config['team_name'], config['ppa_name'] = ppa_address_split(args.ppa_name, lp_username)441 owner_name, ppa_name = ppa_address_split(args.ppa_name)
441 if not config['team_name'] or not config['ppa_name']:442 if owner_name:
443 # First use the owner if present in the PPA address itself,
444 # overriding any configured defaults or specified arguments.
445 config['owner_name'] = owner_name
446 elif config.get('owner_name'):
447 # Next use any owner name from config file or cli args.
448 pass
449 elif config.get('team_name'):
450 # Support legacy config term 'team_name' as alias for 'owner_name'
451 config['owner_name'] = config['team_name']
452 del config['team_name']
453 elif lp.me:
454 # Lastly, fallback to the current Launchpad username, if available.
455 config['owner_name'] = lp.me.name
456 else:
457 warn("No owning person or team identified for the PPA")
458 return None
459
460 if not ppa_name:
442 raise ValueError("Invalid ppa name '{}'".format(args.ppa_name))461 raise ValueError("Invalid ppa name '{}'".format(args.ppa_name))
462 config['ppa_name'] = ppa_name
443463
444 if args.dry_run:464 if args.dry_run:
445 config['dry_run'] = True465 config['dry_run'] = True
@@ -472,9 +492,9 @@ def command_create(lp: Lp, config: dict[str, str]) -> int:
472 warn("Could not determine PPA name")492 warn("Could not determine PPA name")
473 return os.EX_USAGE493 return os.EX_USAGE
474494
475 team_name = config.get('team_name')495 owner_name = config.get('owner_name')
476 if not team_name:496 if not owner_name:
477 warn("Could not determine team name")497 warn("Could not determine owning person or team LP username")
478 return os.EX_USAGE498 return os.EX_USAGE
479499
480 publish = config.get('publish', None)500 publish = config.get('publish', None)
@@ -485,7 +505,7 @@ def command_create(lp: Lp, config: dict[str, str]) -> int:
485505
486 try:506 try:
487 if not config.get('dry_run', False):507 if not config.get('dry_run', False):
488 ppa_group = PpaGroup(service=lp, name=team_name)508 ppa_group = PpaGroup(service=lp, name=owner_name)
489 the_ppa = ppa_group.create(ppa_name, ppa_description=description)509 the_ppa = ppa_group.create(ppa_name, ppa_description=description)
490 the_ppa.set_publish(publish)510 the_ppa.set_publish(publish)
491 if architectures:511 if architectures:
@@ -498,7 +518,7 @@ def command_create(lp: Lp, config: dict[str, str]) -> int:
498 the_ppa.set_dependencies(ppa_addresses)518 the_ppa.set_dependencies(ppa_addresses)
499519
500 else:520 else:
501 the_ppa = Ppa(ppa_name, team_name, description)521 the_ppa = Ppa(ppa_name, owner_name, description)
502 arch_str = ', '.join(architectures)522 arch_str = ', '.join(architectures)
503 if not config.get('quiet', False):523 if not config.get('quiet', False):
504 print("PPA '{}' created for the following architectures:\n".format(the_ppa.ppa_name))524 print("PPA '{}' created for the following architectures:\n".format(the_ppa.ppa_name))
@@ -590,16 +610,16 @@ def command_list(lp: Lp, config: dict[str, str], filter_func=None) -> int:
590 if not lp:610 if not lp:
591 return 1611 return 1
592612
593 team_name = config.get('team_name')613 owner_name = config.get('owner_name')
594 if not team_name:614 if not owner_name:
595 if lp.me:615 if lp.me:
596 team_name = lp.me.name616 owner_name = lp.me.name
597 else:617 else:
598 warn("Could not determine team name")618 warn("Could not determine owning person or team name")
599 return os.EX_USAGE619 return os.EX_USAGE
600620
601 try:621 try:
602 ppa_group = PpaGroup(service=lp, name=team_name)622 ppa_group = PpaGroup(service=lp, name=owner_name)
603 for p in ppa_group.ppas:623 for p in ppa_group.ppas:
604 print(p.address)624 print(p.address)
605 return os.EX_OK625 return os.EX_OK
@@ -826,7 +846,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
826846
827 the_ppa = get_ppa(lp, config)847 the_ppa = get_ppa(lp, config)
828 if not the_ppa.exists():848 if not the_ppa.exists():
829 error(f"PPA {the_ppa.name} does not exist for user {the_ppa.team_name}")849 error(f"PPA {the_ppa.name} does not exist for user {the_ppa.owner_name}")
830 return 1850 return 1
831851
832 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)852 architectures = config.get('architectures', ARCHES_AUTOPKGTEST)
@@ -909,7 +929,7 @@ def command_tests(lp: Lp, config: dict[str, str]) -> int:
909 results = []929 results = []
910 for release in releases:930 for release in releases:
911 base_results_fmt = f"{URL_AUTOPKGTEST}/results/autopkgtest-%s-%s-%s/"931 base_results_fmt = f"{URL_AUTOPKGTEST}/results/autopkgtest-%s-%s-%s/"
912 base_results_url = base_results_fmt % (release, the_ppa.team_name, the_ppa.name)932 base_results_url = base_results_fmt % (release, the_ppa.owner_name, the_ppa.name)
913 url = f"{base_results_url}?format=plain"933 url = f"{base_results_url}?format=plain"
914 response = open_url(url)934 response = open_url(url)
915 if response:935 if response:
diff --git a/tests/helpers.py b/tests/helpers.py
index 72a965e..c607d3c 100644
--- a/tests/helpers.py
+++ b/tests/helpers.py
@@ -40,9 +40,10 @@ class ProcessorMock:
4040
41class ArchiveMock:41class ArchiveMock:
42 """A stand-in for a Launchpad Archive object."""42 """A stand-in for a Launchpad Archive object."""
43 def __init__(self, name, description):43 def __init__(self, name, description, owner):
44 self.displayname = name44 self.displayname = name
45 self.description = description45 self.description = description
46 self.owner = owner
46 self.publish = True47 self.publish = True
47 self.processors = [ProcessorMock(proc_name) for proc_name in ARCHES_PPA_DEFAULT]48 self.processors = [ProcessorMock(proc_name) for proc_name in ARCHES_PPA_DEFAULT]
48 self.published_sources = []49 self.published_sources = []
@@ -68,7 +69,7 @@ class PersonMock:
68 if ppa.name == name:69 if ppa.name == name:
69 raise PpaAlreadyExists(name)70 raise PpaAlreadyExists(name)
70 ppa = Ppa(name, self.name, description)71 ppa = Ppa(name, self.name, description)
71 Ppa.archive = ArchiveMock(ppa.name, ppa.description)72 Ppa.archive = ArchiveMock(ppa.name, ppa.description, self)
72 self._ppas.append(ppa)73 self._ppas.append(ppa)
73 return True74 return True
7475
diff --git a/tests/test_ppa.py b/tests/test_ppa.py
index 2410f83..9bb76e9 100644
--- a/tests/test_ppa.py
+++ b/tests/test_ppa.py
@@ -21,59 +21,59 @@ from ppa.ppa import Ppa, ppa_address_split, get_ppa
2121
22def test_object():22def test_object():
23 """Check that PPA objects can be instantiated."""23 """Check that PPA objects can be instantiated."""
24 ppa = Ppa('test-ppa-name', 'test-team-name')24 ppa = Ppa('test-ppa-name', 'test-owner-name')
25 assert ppa25 assert ppa
2626
2727
28def test_description():28def test_description():
29 """Check specifying a description when creating a PPA."""29 """Check specifying a description when creating a PPA."""
30 ppa = Ppa('test-ppa-name', 'test-team-name', 'test-description')30 ppa = Ppa('test-ppa-name', 'test-owner-name', 'test-description')
3131
32 assert 'test-description' in ppa.ppa_description32 assert 'test-description' in ppa.ppa_description
3333
3434
35def test_address():35def test_address():
36 """Check getting the PPA address."""36 """Check getting the PPA address."""
37 ppa = Ppa('test', 'team')37 ppa = Ppa('test', 'owner')
38 assert ppa.address == "ppa:team/test"38 assert ppa.address == "ppa:owner/test"
3939
4040
41@pytest.mark.parametrize('address, default_team, expected', [41@pytest.mark.parametrize('address, expected', [
42 # Successful cases42 # Successful cases
43 ('bb', 'me', ('me', 'bb')),43 ('bb', (None, 'bb')),
44 ('123', 'me', ('me', '123')),44 ('123', (None, '123')),
45 ('a/123', 'me', ('a', '123')),45 ('a/123', ('a', '123')),
46 ('ppa:a/bb', None, ('a', 'bb')),46 ('ppa:A/bb', ('a', 'bb')),
47 ('ppa:ç/bb', None, ('ç', 'bb')),47 ('ppa:a/bb', ('a', 'bb')),
48 ('https://launchpad.net/~a/+archive/ubuntu/bb', None, ('a', 'bb')),48 ('ppa:ç/bb', ('ç', 'bb')),
49 ('https://launchpad.net/~a/+archive/ubuntu/bb', ('a', 'bb')),
4950
50 # Expected failure cases51 # Expected failure cases
51 ('ppa:', None, (None, None)),52 ('ppa:', (None, None)),
52 (None, None, (None, None)),53 (None, (None, None)),
53 ('', None, (None, None)),54 ('', (None, None)),
54 ('/', None, (None, None)),55 ('/', (None, None)),
55 (':/', None, (None, None)),56 (':/', (None, None)),
56 ('////', None, (None, None)),57 ('////', (None, None)),
57 ('ppa:/', None, (None, None)),58 ('ppa:/', (None, None)),
58 ('ppa:a/', None, (None, None)),59 ('ppa:a/', (None, None)),
59 ('ppa:/bb', None, (None, None)),60 ('ppa:/bb', (None, None)),
60 ('ppa:a/bç', None, (None, None)),61 ('ppa:a/bç', (None, None)),
61 ('ppa:A/bb', None, (None, None)),62 ('ppa/a/bb', (None, None)),
62 ('ppa/a/bb', None, (None, None)),63 ('ppa:a/bb/c', (None, None)),
63 ('ppa:a/bb/c', None, (None, None)),64 ('ppa:a/bB', (None, None)),
64 ('ppa:a/bB', None, (None, None)),65 ('http://launchpad.net/~a/+archive/ubuntu/bb', (None, None)),
65 ('http://launchpad.net/~a/+archive/ubuntu/bb', None, (None, None)),66 ('https://example.com/~a/+archive/ubuntu/bb', (None, None)),
66 ('https://example.com/~a/+archive/ubuntu/bb', None, (None, None)),67 ('https://launchpad.net/~a/+archive/nobuntu/bb', (None, None)),
67 ('https://launchpad.net/~a/+archive/nobuntu/bb', None, (None, None)),
68])68])
69def test_ppa_address_split(address, default_team, expected):69def test_ppa_address_split(address, expected):
70 """Check ppa address input strings can be parsed properly."""70 """Check ppa address input strings can be parsed properly."""
71 result = ppa_address_split(address, default_team=default_team)71 result = ppa_address_split(address)
72 assert result == expected72 assert result == expected
7373
7474
75def test_get_ppa():75def test_get_ppa():
76 ppa = get_ppa(None, {'team_name': 'a', 'ppa_name': 'bb'})76 ppa = get_ppa(None, {'owner_name': 'a', 'ppa_name': 'bb'})
77 assert type(ppa) is Ppa77 assert type(ppa) is Ppa
78 assert ppa.team_name == 'a'78 assert ppa.owner_name == 'a'
79 assert ppa.ppa_name == 'bb'79 assert ppa.ppa_name == 'bb'
diff --git a/tests/test_ppa_group.py b/tests/test_ppa_group.py
index d9d83de..f53799f 100644
--- a/tests/test_ppa_group.py
+++ b/tests/test_ppa_group.py
@@ -59,14 +59,14 @@ def test_create_with_description():
59 assert ppa.description == description59 assert ppa.description == description
6060
6161
62def test_create_with_team():62def test_create_with_owner():
63 """Check creating a PPA for a particular team."""63 """Check creating a PPA for a particular owner."""
64 lp = LpServiceMock()64 lp = LpServiceMock()
65 lp.launchpad.add_person('test_team_name')65 lp.launchpad.add_person('test_owner_name')
66 ppa_group = PpaGroup(service=lp, name='test_team_name')66 ppa_group = PpaGroup(service=lp, name='test_owner_name')
67 ppa = ppa_group.create('ppa_test_name')67 ppa = ppa_group.create('ppa_test_name')
68 assert ppa is not None68 assert ppa is not None
69 assert ppa.address == 'ppa:test_team_name/ppa_test_name'69 assert ppa.address == 'ppa:test_owner_name/ppa_test_name'
7070
7171
72def test_list_ppas():72def test_list_ppas():
diff --git a/tests/test_scripts_ppa.py b/tests/test_scripts_ppa.py
index 80347ba..f82a9e4 100644
--- a/tests/test_scripts_ppa.py
+++ b/tests/test_scripts_ppa.py
@@ -53,7 +53,7 @@ loader.exec_module(script)
53def fake_config():53def fake_config():
54 return {54 return {
55 'ppa_name': 'testing',55 'ppa_name': 'testing',
56 'team_name': 'me',56 'owner_name': 'me',
57 'wait_seconds': 0.1,57 'wait_seconds': 0.1,
58 'quiet': True58 'quiet': True
59 }59 }
@@ -257,6 +257,39 @@ def test_create_arg_parser_basic_config(command):
257 args.publish = None257 args.publish = None
258258
259259
260def test_create_arg_parser_create():
261 """Checks argument parsing for the 'create' command.
262
263 Most of the create command's args are covered by
264 test_create_arg_parser_basic_config(), this just verifies
265 the few that aren't.
266 """
267 parser = script.create_arg_parser()
268 command = 'create'
269
270 # Check ppa_name
271 args = parser.parse_args([command, 'test-ppa'])
272 assert args.ppa_name == 'test-ppa'
273 args = parser.parse_args([command, 'my-team/test-ppa'])
274 assert args.ppa_name == 'my-team/test-ppa'
275 args = parser.parse_args([command, 'ppa:my-team/test-ppa'])
276 assert args.ppa_name == 'ppa:my-team/test-ppa'
277
278 # Check --owner, --owner-name, --team, --team-name
279 args = parser.parse_args([command, 'test-ppa', '--owner', 'x'])
280 assert args.owner_name == 'x'
281 args.owner_name = None
282 args = parser.parse_args([command, 'test-ppa', '--owner-name', 'x'])
283 assert args.owner_name == 'x'
284 args.owner_name = None
285 args = parser.parse_args([command, 'test-ppa', '--team', 'x'])
286 assert args.owner_name == 'x'
287 args.owner_name = None
288 args = parser.parse_args([command, 'test-ppa', '--team-name', 'x'])
289 assert args.owner_name == 'x'
290 args.owner_name = None
291
292
260def test_create_arg_parser_show():293def test_create_arg_parser_show():
261 """Checks argument parsing for the 'show' command."""294 """Checks argument parsing for the 'show' command."""
262 parser = script.create_arg_parser()295 parser = script.create_arg_parser()
@@ -387,13 +420,19 @@ def test_create_arg_parser_tests():
387420
388@pytest.mark.parametrize('command_line_options, expected_config', [421@pytest.mark.parametrize('command_line_options, expected_config', [
389 pytest.param([], {}),422 pytest.param([], {}),
390 (['status', 'ppa:aa/bb'], {'command': 'status', 'team_name': 'aa', 'ppa_name': 'bb'}),423 (['status', 'ppa:aa/bb'], {'command': 'status', 'owner_name': 'aa', 'ppa_name': 'bb'}),
391 (['status', 'aa/bb'], {'team_name': 'aa', 'ppa_name': 'bb'}),424 (['status', 'aa/bb'], {'owner_name': 'aa', 'ppa_name': 'bb'}),
392 (['status', 'bb'], {'team_name': 'me', 'ppa_name': 'bb'}),425 (['status', 'bb'], {'owner_name': 'me', 'ppa_name': 'bb'}),
393 (['--debug', 'status', 'ppa:aa/bb'], {'debug': True}),426 (['--debug', 'status', 'ppa:aa/bb'], {'debug': True}),
394 (['--dry-run', 'status', 'ppa:aa/bb'], {'dry_run': True}),427 (['--dry-run', 'status', 'ppa:aa/bb'], {'dry_run': True}),
395 (['--verbose', 'status', 'ppa:aa/bb'], {'verbose': True}),428 (['--verbose', 'status', 'ppa:aa/bb'], {'verbose': True}),
396 (['--quiet', 'status', 'ppa:aa/bb'], {'quiet': True}),429 (['--quiet', 'status', 'ppa:aa/bb'], {'quiet': True}),
430 (['create', 'ppa:aa/bb'], {'command': 'create', 'owner_name': 'aa', 'ppa_name': 'bb'}),
431 (['create', 'aa/bb'], {'command': 'create', 'owner_name': 'aa', 'ppa_name': 'bb'}),
432 (['create', 'bb', '--owner', 'aa'], {'command': 'create', 'owner_name': 'aa', 'ppa_name': 'bb'}),
433 (['create', 'bb', '--owner-name', 'aa'], {'command': 'create', 'owner_name': 'aa', 'ppa_name': 'bb'}),
434 (['create', 'bb', '--team', 'aa'], {'command': 'create', 'owner_name': 'aa', 'ppa_name': 'bb'}),
435 (['create', 'bb', '--team-name', 'aa'], {'command': 'create', 'owner_name': 'aa', 'ppa_name': 'bb'}),
397 ])436 ])
398def test_create_config_from_args(command_line_options, expected_config):437def test_create_config_from_args(command_line_options, expected_config):
399 '''Checks creation of a config object from an argparser object.438 '''Checks creation of a config object from an argparser object.
@@ -462,8 +501,8 @@ def test_command_create(fake_config, monkeypatch, stdin, params, expected_ppa_co
462 assert script.command_create(lp, config) == 0501 assert script.command_create(lp, config) == 0
463502
464 # Retrieve the newly created PPA503 # Retrieve the newly created PPA
465 team = lp.people[config['team_name']]504 owner = lp.people[config['owner_name']]
466 lp_ppa = team.getPPAByName(config['ppa_name'])505 lp_ppa = owner.getPPAByName(config['ppa_name'])
467 assert lp_ppa506 assert lp_ppa
468507
469 # Verify the expected items are present in the new PPA508 # Verify the expected items are present in the new PPA
@@ -471,6 +510,27 @@ def test_command_create(fake_config, monkeypatch, stdin, params, expected_ppa_co
471 assert getattr(lp_ppa, key) == value510 assert getattr(lp_ppa, key) == value
472511
473512
513@pytest.mark.parametrize('params, owner_name', [
514 # Defaults
515 ({'owner_name': 'a', 'ppa_name': 'x'}, 'a'),
516])
517def test_command_create_with_owner(fake_config, monkeypatch, params, owner_name):
518 '''Checks create command produces a PPA for a specified owner.'''
519 lp = LpServiceMock()
520 lp.launchpad.add_person(owner_name)
521 monkeypatch.setattr("sys.stdin", io.StringIO('x'))
522
523 # Check success of the create command
524 config = {**fake_config, **params}
525 print(config)
526 assert script.command_create(lp, config) == 0
527
528 # Retrieve the newly created PPA
529 owner = lp.people[owner_name]
530 lp_ppa = owner.getPPAByName(config['ppa_name'])
531 assert lp_ppa
532
533
474@pytest.mark.parametrize('architectures, expected_processors', [534@pytest.mark.parametrize('architectures, expected_processors', [
475 (None, ARCHES_PPA_DEFAULT),535 (None, ARCHES_PPA_DEFAULT),
476 ('a', ['a']),536 ('a', ['a']),
@@ -487,8 +547,8 @@ def test_command_create_with_architectures(monkeypatch, fake_config, architectur
487 assert script.command_create(lp, config) == 0547 assert script.command_create(lp, config) == 0
488548
489 # Retrieve the newly created PPA549 # Retrieve the newly created PPA
490 team = lp.people[config['team_name']]550 owner = lp.people[config['owner_name']]
491 lp_ppa = team.getPPAByName(config['ppa_name'])551 lp_ppa = owner.getPPAByName(config['ppa_name'])
492552
493 # Check processor architectures553 # Check processor architectures
494 assert lp_ppa.processors554 assert lp_ppa.processors
@@ -549,15 +609,15 @@ def test_command_set(fake_config, params, expected_ppa_config):
549 lp = LpServiceMock()609 lp = LpServiceMock()
550610
551 # Create a default PPA, for modification later611 # Create a default PPA, for modification later
552 team = lp.people[fake_config['team_name']]612 owner = lp.people[fake_config['owner_name']]
553 team.createPPA(fake_config['ppa_name'], 'x', 'y')613 owner.createPPA(fake_config['ppa_name'], 'x', 'y')
554614
555 # Check success of the set command615 # Check success of the set command
556 config = {**fake_config, **params}616 config = {**fake_config, **params}
557 assert script.command_set(lp, config)617 assert script.command_set(lp, config)
558618
559 # Retrieve the PPA we created earlier619 # Retrieve the PPA we created earlier
560 lp_ppa = team.getPPAByName(fake_config['ppa_name'])620 lp_ppa = owner.getPPAByName(fake_config['ppa_name'])
561621
562 # Verify the expected items are present in the updated PPA622 # Verify the expected items are present in the updated PPA
563 for key, value in expected_ppa_config.items():623 for key, value in expected_ppa_config.items():
@@ -577,15 +637,15 @@ def test_command_set_architectures(fake_config, architectures, expected_processo
577 lp = LpServiceMock()637 lp = LpServiceMock()
578638
579 # Create a default PPA, for modification later639 # Create a default PPA, for modification later
580 team = lp.people[fake_config['team_name']]640 owner = lp.people[fake_config['owner_name']]
581 team.createPPA(fake_config['ppa_name'], 'x', 'y')641 owner.createPPA(fake_config['ppa_name'], 'x', 'y')
582642
583 # Check success of the set command643 # Check success of the set command
584 config = {**fake_config, **{'architectures': architectures}}644 config = {**fake_config, **{'architectures': architectures}}
585 assert script.command_set(lp, config)645 assert script.command_set(lp, config)
586646
587 # Retrieve the PPA we created earlier647 # Retrieve the PPA we created earlier
588 lp_ppa = team.getPPAByName(fake_config['ppa_name'])648 lp_ppa = owner.getPPAByName(fake_config['ppa_name'])
589649
590 # Check processor architectures650 # Check processor architectures
591 assert lp_ppa.processors651 assert lp_ppa.processors
@@ -629,8 +689,8 @@ def test_command_tests(urlopen_mock,
629 Ppa.get_autopkgtest_waiting = lambda x, y: []689 Ppa.get_autopkgtest_waiting = lambda x, y: []
630690
631 # Create a default PPA, for modification later691 # Create a default PPA, for modification later
632 team = lp.people[fake_config['team_name']]692 owner = lp.people[fake_config['owner_name']]
633 team.createPPA(fake_config['ppa_name'], 'x', 'y')693 owner.createPPA(fake_config['ppa_name'], 'x', 'y')
634 the_ppa = lp.me.getPPAByName(fake_config['ppa_name'])694 the_ppa = lp.me.getPPAByName(fake_config['ppa_name'])
635695
636 # Add some fake publications696 # Add some fake publications

Subscribers

People subscribed via source and target branches

to all changes: