← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~cjwatson/launchpad/ppa-admins into lp:launchpad

 

Colin Watson has proposed merging lp:~cjwatson/launchpad/ppa-admins into lp:launchpad with lp:~cjwatson/launchpad/person-invalidate-team-cache as a prerequisite.

Commit message:
Give PPA administration permissions to a new launchpad-ppa-admins celebrity team.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)

For more details, see:
https://code.launchpad.net/~cjwatson/launchpad/ppa-admins/+merge/278720

Give PPA administration permissions to a new launchpad-ppa-admins celebrity team.  One of the main uses of commercial-admins these days is to satisfy requests for devirtualising PPAs and/or giving them access to different architectures; this doesn't require the large pile of private data access that commercial-admins has.

While this is somewhat related to bug 724920, I've steered clear of giving launchpad-ppa-admins access to anything private for now.  This could perhaps be done later.

I added a removeSecurityProxy call to livefs_modified because the most straightforward alternative was giving launchpad-ppa-admins launchpad.Edit access to LiveFS, which I don't want to do.  This also brings it into line with snap_modified.
-- 
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~cjwatson/launchpad/ppa-admins into lp:launchpad.
=== modified file 'database/sampledata/current-dev.sql'
--- database/sampledata/current-dev.sql	2015-09-28 12:56:16 +0000
+++ database/sampledata/current-dev.sql	2015-11-26 15:54:09 +0000
@@ -943,6 +943,7 @@
 INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243632, 'Ubuntu Technical Board', 243631, NULL, 'techboard', NULL, NULL, NULL, NULL, 3, NULL, '2009-08-04 10:50:39.370018', NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL, NULL);
 INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243651, 'Software-center-agent', NULL, NULL, 'software-center-agent', NULL, NULL, NULL, NULL, 1, NULL, '2010-07-12 09:48:27.198885', NULL, NULL, NULL, false, 1, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, 243637, NULL);
 INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243652, 'Launchpad PPA Self Admins', 243621, NULL, 'launchpad-ppa-self-admins', NULL, NULL, NULL, NULL, 3, NULL, '2013-05-10 05:40:34.64279', NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL, NULL);
+INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243653, 'Launchpad PPA Admins', 243621, NULL, 'launchpad-ppa-admins', NULL, NULL, NULL, NULL, 3, NULL, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL, NULL);
 
 
 ALTER TABLE person ENABLE TRIGGER ALL;
@@ -4929,6 +4930,7 @@
 INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243632, 'Ubuntu Technical Board', 243631, NULL, 'techboard', NULL, NULL, NULL, NULL, 3, NULL, '2009-08-04 10:50:39.370018', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL);
 INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243651, 'Software-center-agent', NULL, NULL, 'software-center-agent', NULL, NULL, NULL, NULL, 1, NULL, '2010-07-12 09:48:27.198885', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, 1, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, 243637);
 INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243652, 'Launchpad PPA Self Admins', 243621, NULL, 'launchpad-ppa-self-admins', NULL, NULL, NULL, NULL, 3, NULL, '2013-05-10 05:40:34.64279', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL);
+INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243653, 'Launchpad PPA Admins', 243621, NULL, 'launchpad-ppa-admins', NULL, NULL, NULL, NULL, 3, NULL, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL);
 
 
 ALTER TABLE lp_person ENABLE TRIGGER ALL;
@@ -5149,6 +5151,9 @@
 INSERT INTO lp_teamparticipation (id, team, person) VALUES (254, 243652, 243652);
 INSERT INTO lp_teamparticipation (id, team, person) VALUES (256, 243652, 243621);
 INSERT INTO lp_teamparticipation (id, team, person) VALUES (257, 243652, 243622);
+INSERT INTO lp_teamparticipation (id, team, person) VALUES (258, 243653, 243653);
+INSERT INTO lp_teamparticipation (id, team, person) VALUES (259, 243653, 243621);
+INSERT INTO lp_teamparticipation (id, team, person) VALUES (260, 243653, 243622);
 
 
 ALTER TABLE lp_teamparticipation ENABLE TRIGGER ALL;
@@ -10125,6 +10130,7 @@
 INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (104, 243623, 243630, 2, '2009-07-09 11:58:38.122886', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2009-07-09 11:58:38.122886');
 INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (105, 243631, 243632, 3, '2009-08-04 10:50:47.920683', NULL, NULL, NULL, 243631, NULL, 243631, '2009-08-04 10:50:47.920683', NULL, NULL, '2009-08-04 10:50:47.920683', NULL, NULL, NULL, '2009-08-04 10:50:39.370018');
 INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (107, 243621, 243652, 3, '2013-05-10 05:44:12.01517', NULL, NULL, NULL, 16, NULL, 16, '2013-05-10 05:44:12.01517', NULL, NULL, '2013-05-10 05:44:12.01517', NULL, NULL, NULL, '2013-05-10 05:44:11.87651');
+INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (108, 243621, 243653, 3, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, 16, NULL, 16, '2015-11-25 11:06:11.767934', NULL, NULL, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, '2015-11-25 11:06:11.767934');
 
 
 ALTER TABLE teammembership ENABLE TRIGGER ALL;
@@ -10335,6 +10341,9 @@
 INSERT INTO teamparticipation (id, team, person) VALUES (254, 243652, 243652);
 INSERT INTO teamparticipation (id, team, person) VALUES (256, 243652, 243621);
 INSERT INTO teamparticipation (id, team, person) VALUES (257, 243652, 243622);
+INSERT INTO teamparticipation (id, team, person) VALUES (258, 243653, 243653);
+INSERT INTO teamparticipation (id, team, person) VALUES (259, 243653, 243621);
+INSERT INTO teamparticipation (id, team, person) VALUES (260, 243653, 243622);
 
 
 ALTER TABLE teamparticipation ENABLE TRIGGER ALL;

=== modified file 'database/sampledata/current.sql'
--- database/sampledata/current.sql	2015-09-28 12:56:16 +0000
+++ database/sampledata/current.sql	2015-11-26 15:54:09 +0000
@@ -943,6 +943,8 @@
 INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243632, 'Ubuntu Technical Board', 243631, NULL, 'techboard', NULL, NULL, NULL, NULL, 3, NULL, '2009-08-04 10:50:39.370018', NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL, NULL);
 INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243651, 'Software-center-agent', NULL, NULL, 'software-center-agent', NULL, NULL, NULL, NULL, 1, NULL, '2010-07-12 09:48:27.198885', NULL, NULL, NULL, false, 1, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, 243637, NULL);
 INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243652, 'Launchpad PPA Self Admins', 243621, NULL, 'launchpad-ppa-self-admins', NULL, NULL, NULL, NULL, 3, NULL, '2013-05-10 05:43:04.018122', NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL, NULL);
+INSERT INTO person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account, description) VALUES (243653, 'Launchpad PPA Admins', 243621, NULL, 'launchpad-ppa-admins', NULL, NULL, NULL, NULL, 3, NULL, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL, NULL);
+
 
 
 ALTER TABLE person ENABLE TRIGGER ALL;
@@ -4844,6 +4846,7 @@
 INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243632, 'Ubuntu Technical Board', 243631, NULL, 'techboard', NULL, NULL, NULL, NULL, 3, NULL, '2009-08-04 10:50:39.370018', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL);
 INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243651, 'Software-center-agent', NULL, NULL, 'software-center-agent', NULL, NULL, NULL, NULL, 1, NULL, '2010-07-12 09:48:27.198885', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, 1, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, 243637);
 INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243652, 'Launchpad PPA Self Admins', 243621, NULL, 'launchpad-ppa-self-admins', NULL, NULL, NULL, NULL, 3, NULL, '2013-05-10 05:43:04.018122', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL);
+INSERT INTO lp_person (id, displayname, teamowner, teamdescription, name, language, fti, defaultmembershipperiod, defaultrenewalperiod, subscriptionpolicy, merged, datecreated, addressline1, addressline2, organization, city, province, country, postcode, phone, homepage_content, icon, mugshot, hide_email_addresses, creation_rationale, creation_comment, registrant, logo, renewal_policy, personal_standing, personal_standing_reason, mail_resumption_date, mailing_list_auto_subscribe_policy, mailing_list_receive_duplicates, visibility, verbose_bugnotifications, account) VALUES (243653, 'Launchpad PPA Admins', 243621, NULL, 'launchpad-ppa-admins', NULL, NULL, NULL, NULL, 3, NULL, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, false, NULL, NULL, NULL, NULL, 10, 0, NULL, NULL, 1, true, 1, true, NULL);
 
 
 ALTER TABLE lp_person ENABLE TRIGGER ALL;
@@ -5064,6 +5067,9 @@
 INSERT INTO lp_teamparticipation (id, team, person) VALUES (254, 243652, 243652);
 INSERT INTO lp_teamparticipation (id, team, person) VALUES (256, 243652, 243621);
 INSERT INTO lp_teamparticipation (id, team, person) VALUES (257, 243652, 243622);
+INSERT INTO lp_teamparticipation (id, team, person) VALUES (258, 243653, 243653);
+INSERT INTO lp_teamparticipation (id, team, person) VALUES (259, 243653, 243621);
+INSERT INTO lp_teamparticipation (id, team, person) VALUES (260, 243653, 243622);
 
 
 ALTER TABLE lp_teamparticipation ENABLE TRIGGER ALL;
@@ -10040,6 +10046,7 @@
 INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (104, 243623, 243630, 2, '2009-07-09 11:58:38.122886', NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, '2009-07-09 11:58:38.122886');
 INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (105, 243631, 243632, 3, '2009-08-04 10:50:47.920683', NULL, NULL, NULL, 243631, NULL, 243631, '2009-08-04 10:50:47.920683', NULL, NULL, '2009-08-04 10:50:47.920683', NULL, NULL, NULL, '2009-08-04 10:50:39.370018');
 INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (107, 243621, 243652, 3, '2013-05-10 05:43:32.395783', NULL, NULL, NULL, 16, NULL, 16, '2013-05-10 05:43:32.395783', NULL, NULL, '2013-05-10 05:43:32.395783', NULL, NULL, NULL, '2013-05-10 05:43:32.221684');
+INSERT INTO teammembership (id, person, team, status, date_joined, date_expires, last_changed_by, last_change_comment, proposed_by, acknowledged_by, reviewed_by, date_proposed, date_last_changed, date_acknowledged, date_reviewed, proponent_comment, acknowledger_comment, reviewer_comment, date_created) VALUES (108, 243621, 243653, 3, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, 16, NULL, 16, '2015-11-25 11:06:11.767934', NULL, NULL, '2015-11-25 11:06:11.767934', NULL, NULL, NULL, '2015-11-25 11:06:11.767934');
 
 
 ALTER TABLE teammembership ENABLE TRIGGER ALL;
@@ -10250,6 +10257,9 @@
 INSERT INTO teamparticipation (id, team, person) VALUES (254, 243652, 243652);
 INSERT INTO teamparticipation (id, team, person) VALUES (256, 243652, 243621);
 INSERT INTO teamparticipation (id, team, person) VALUES (257, 243652, 243622);
+INSERT INTO teamparticipation (id, team, person) VALUES (258, 243653, 243653);
+INSERT INTO teamparticipation (id, team, person) VALUES (259, 243653, 243621);
+INSERT INTO teamparticipation (id, team, person) VALUES (260, 243653, 243622);
 
 
 ALTER TABLE teamparticipation ENABLE TRIGGER ALL;

=== modified file 'lib/lp/app/interfaces/launchpad.py'
--- lib/lp/app/interfaces/launchpad.py	2015-06-30 01:07:40 +0000
+++ lib/lp/app/interfaces/launchpad.py	2015-11-26 15:54:09 +0000
@@ -56,6 +56,7 @@
     launchpad = Attribute("The Launchpad project.")
     launchpad_developers = Attribute("The Launchpad development team.")
     obsolete_junk = Attribute("The Obsolete Junk project.")
+    ppa_admin = Attribute("The Launchpad PPA Admins team.")
     ppa_key_guard = Attribute("The PPA signing keys owner.")
     ppa_self_admins = Attribute("The Launchpad PPA Self Admins team.")
     registry_experts = Attribute("The Registry Administrators team.")

=== modified file 'lib/lp/app/utilities/celebrities.py'
--- lib/lp/app/utilities/celebrities.py	2015-07-08 16:05:11 +0000
+++ lib/lp/app/utilities/celebrities.py	2015-11-26 15:54:09 +0000
@@ -144,6 +144,7 @@
     launchpad = CelebrityDescriptor(IProductSet, 'launchpad')
     launchpad_developers = PersonCelebrityDescriptor('launchpad')
     obsolete_junk = CelebrityDescriptor(IProductSet, 'obsolete-junk')
+    ppa_admin = PersonCelebrityDescriptor('launchpad-ppa-admins')
     ppa_key_guard = PersonCelebrityDescriptor('ppa-key-guard')
     ppa_self_admins = PersonCelebrityDescriptor('launchpad-ppa-self-admins')
     registry_experts = PersonCelebrityDescriptor('registry')

=== modified file 'lib/lp/registry/doc/vocabularies.txt'
--- lib/lp/registry/doc/vocabularies.txt	2015-06-26 14:15:12 +0000
+++ lib/lp/registry/doc/vocabularies.txt	2015-11-26 15:54:09 +0000
@@ -966,6 +966,7 @@
      (u'Launchpad Beta Testers', u'Launchpad Beta Testers Owner'),
      (u'Launchpad Buildd Admins', u'Foo Bar'),
      (u'Launchpad Developers', u'Foo Bar'),
+     (u'Launchpad PPA Admins', u'Commercial Subscription Admins'),
      (u'Launchpad PPA Self Admins', u'Commercial Subscription Admins'),
      (u'Launchpad Users', u'Sample Person'),
      (u'Mailing List Experts', u'Launchpad Administrators'),

=== modified file 'lib/lp/registry/interfaces/role.py'
--- lib/lp/registry/interfaces/role.py	2014-06-12 01:04:30 +0000
+++ lib/lp/registry/interfaces/role.py	2015-11-26 15:54:09 +0000
@@ -97,6 +97,9 @@
     in_launchpad_developers = Bool(
         title=_("True if this person is a Launchpad developer."),
         required=True, readonly=True)
+    in_ppa_admin = Bool(
+        title=_("True if this person is a PPA admin."),
+        required=True, readonly=True)
     in_ppa_key_guard = Bool(
         title=_("True if this person is the ppa key guard."),
         required=True, readonly=True)

=== modified file 'lib/lp/registry/stories/person/xx-people-search.txt'
--- lib/lp/registry/stories/person/xx-people-search.txt	2013-05-10 08:28:46 +0000
+++ lib/lp/registry/stories/person/xx-people-search.txt	2015-11-26 15:54:09 +0000
@@ -51,7 +51,7 @@
     Launchpad Beta Testers       launchpad-beta-testers
     Launchpad Buildd Admins      launchpad-buildd-admins
     Launchpad Developers         launchpad
-    Launchpad PPA Self Admins    launchpad-ppa-self-admins
+    Launchpad PPA Admins         launchpad-ppa-admins
 
 Restrict the search to people and only individuals are listed.
 

=== modified file 'lib/lp/registry/tests/test_user_vocabularies.py'
--- lib/lp/registry/tests/test_user_vocabularies.py	2013-06-20 05:50:00 +0000
+++ lib/lp/registry/tests/test_user_vocabularies.py	2015-11-26 15:54:09 +0000
@@ -180,9 +180,11 @@
         # The vocab does the membership check for commercial admins too.
         user = self.factory.makeCommercialAdmin()
         com_admins = getUtility(IPersonSet).getByName('commercial-admins')
-        ppa_admins = getUtility(IPersonSet).getByName(
+        ppa_admins = getUtility(IPersonSet).getByName('launchpad-ppa-admins')
+        ppa_self_admins = getUtility(IPersonSet).getByName(
             'launchpad-ppa-self-admins')
         team1 = self.factory.makeTeam(members=[user])
         login_person(user)
         self.assertContentEqual(
-            [com_admins, ppa_admins, team1], self._vocabTermValues())
+            [com_admins, ppa_admins, ppa_self_admins, team1],
+            self._vocabTermValues())

=== modified file 'lib/lp/security.py'
--- lib/lp/security.py	2015-07-23 16:02:58 +0000
+++ lib/lp/security.py	2015-11-26 15:54:09 +0000
@@ -2630,29 +2630,29 @@
 
     Buildd admins can change this, as a site-wide resource that requires
     arbitration, especially between distribution builds and builds in
-    non-virtualized PPAs.  Commercial admins can also change this since it
-    affects the relative priority of (private) PPAs.
+    non-virtualized PPAs.  PPA/commercial admins can also change this since
+    it affects the relative priority of (private) PPAs.
     """
     permission = 'launchpad.Moderate'
     usedfor = IArchive
 
     def checkAuthenticated(self, user):
-        return (user.in_buildd_admin or user.in_commercial_admin or
-                user.in_admin)
+        return (user.in_buildd_admin or user.in_ppa_admin or
+                user.in_commercial_admin or user.in_admin)
 
 
 class AdminArchive(AuthorizationBase):
     """Restrict changing privacy and build settings on archives.
 
     The security of the non-virtualised build farm depends on these
-    settings, so they can only be changed by commercial admins, or by
+    settings, so they can only be changed by PPA/commercial admins, or by
     PPA self admins on PPAs that they can already edit.
     """
     permission = 'launchpad.Admin'
     usedfor = IArchive
 
     def checkAuthenticated(self, user):
-        if user.in_commercial_admin or user.in_admin:
+        if user.in_ppa_admin or user.in_commercial_admin or user.in_admin:
             return True
         return (
             user.in_ppa_self_admins
@@ -3016,14 +3016,14 @@
     """Restrict changing build settings on live filesystems.
 
     The security of the non-virtualised build farm depends on these
-    settings, so they can only be changed by commercial admins, or by "PPA"
-    self admins on live filesystems that they can already edit.
+    settings, so they can only be changed by "PPA"/commercial admins, or by
+    "PPA" self admins on live filesystems that they can already edit.
     """
     permission = 'launchpad.Admin'
     usedfor = ILiveFS
 
     def checkAuthenticated(self, user):
-        if user.in_commercial_admin or user.in_admin:
+        if user.in_ppa_admin or user.in_commercial_admin or user.in_admin:
             return True
         return (
             user.in_ppa_self_admins
@@ -3105,14 +3105,14 @@
     """Restrict changing build settings on snap packages.
 
     The security of the non-virtualised build farm depends on these
-    settings, so they can only be changed by commercial admins, or by "PPA"
-    self admins on snap packages that they can already edit.
+    settings, so they can only be changed by "PPA"/commercial admins, or by
+    "PPA" self admins on snap packages that they can already edit.
     """
     permission = 'launchpad.Admin'
     usedfor = ISnap
 
     def checkAuthenticated(self, user):
-        if user.in_commercial_admin or user.in_admin:
+        if user.in_ppa_admin or user.in_commercial_admin or user.in_admin:
             return True
         return (
             user.in_ppa_self_admins

=== modified file 'lib/lp/snappy/browser/tests/test_snap.py'
--- lib/lp/snappy/browser/tests/test_snap.py	2015-10-08 14:13:24 +0000
+++ lib/lp/snappy/browser/tests/test_snap.py	2015-11-26 15:54:09 +0000
@@ -224,12 +224,12 @@
     def test_admin_snap(self):
         # Admins can change require_virtualized.
         login("admin@xxxxxxxxxxxxx")
-        commercial_admin = self.factory.makePerson(
-            member_of=[getUtility(ILaunchpadCelebrities).commercial_admin])
+        ppa_admin = self.factory.makePerson(
+            member_of=[getUtility(ILaunchpadCelebrities).ppa_admin])
         login_person(self.person)
         snap = self.factory.makeSnap(registrant=self.person)
         self.assertTrue(snap.require_virtualized)
-        browser = self.getViewBrowser(snap, user=commercial_admin)
+        browser = self.getViewBrowser(snap, user=ppa_admin)
         browser.getLink("Administer snap package").click()
         browser.getControl("Require virtualized builders").selected = False
         browser.getControl("Update snap package").click()
@@ -239,13 +239,13 @@
     def test_admin_snap_sets_date_last_modified(self):
         # Administering a snap package sets the date_last_modified property.
         login("admin@xxxxxxxxxxxxx")
-        commercial_admin = self.factory.makePerson(
-            member_of=[getUtility(ILaunchpadCelebrities).commercial_admin])
+        ppa_admin = self.factory.makePerson(
+            member_of=[getUtility(ILaunchpadCelebrities).ppa_admin])
         login_person(self.person)
         date_created = datetime(2000, 1, 1, tzinfo=pytz.UTC)
         snap = self.factory.makeSnap(
             registrant=self.person, date_created=date_created)
-        login_person(commercial_admin)
+        login_person(ppa_admin)
         view = SnapAdminView(snap, LaunchpadTestRequest())
         view.initialize()
         view.request_action.success({"require_virtualized": False})

=== modified file 'lib/lp/snappy/tests/test_snap.py'
--- lib/lp/snappy/tests/test_snap.py	2015-09-25 17:26:03 +0000
+++ lib/lp/snappy/tests/test_snap.py	2015-11-26 15:54:09 +0000
@@ -858,19 +858,19 @@
 
     def test_setProcessors_admin(self):
         """An admin can add a new processor to the enabled restricted set."""
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        commercial_admin = self.factory.makePerson(member_of=[commercial])
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
         self.factory.makeProcessor(
             "arm", "ARM", "ARM", restricted=True, build_by_default=False)
         snap = self.makeSnap()
-        self.assertProcessors(commercial_admin, snap, ["386", "hppa", "amd64"])
+        self.assertProcessors(ppa_admin, snap, ["386", "hppa", "amd64"])
 
-        response = self.setProcessors(commercial_admin, snap, ["386", "arm"])
+        response = self.setProcessors(ppa_admin, snap, ["386", "arm"])
         self.assertEqual(200, response.status)
-        self.assertProcessors(commercial_admin, snap, ["386", "arm"])
+        self.assertProcessors(ppa_admin, snap, ["386", "arm"])
 
     def test_setProcessors_non_owner_forbidden(self):
-        """Only commercial admins and snap owners can call setProcessors."""
+        """Only PPA admins and snap owners can call setProcessors."""
         self.factory.makeProcessor(
             "unrestricted", "Unrestricted", "Unrestricted", restricted=False,
             build_by_default=False)
@@ -895,8 +895,8 @@
 
     def test_setProcessors_owner_restricted_forbidden(self):
         """The snap owner cannot enable/disable restricted processors."""
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        commercial_admin = self.factory.makePerson(member_of=[commercial])
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
         self.factory.makeProcessor(
             "arm", "ARM", "ARM", restricted=True, build_by_default=False)
         snap = self.makeSnap()
@@ -904,9 +904,8 @@
         response = self.setProcessors(self.person, snap, ["386", "arm"])
         self.assertEqual(403, response.status)
 
-        # If a commercial admin enables arm, the owner cannot disable it.
-        response = self.setProcessors(
-            commercial_admin, snap, ["386", "arm"])
+        # If a PPA admin enables arm, the owner cannot disable it.
+        response = self.setProcessors(ppa_admin, snap, ["386", "arm"])
         self.assertEqual(200, response.status)
         self.assertProcessors(self.person, snap, ["386", "arm"])
 

=== modified file 'lib/lp/soyuz/browser/tests/test_archive_webservice.py'
--- lib/lp/soyuz/browser/tests/test_archive_webservice.py	2015-10-21 09:37:08 +0000
+++ lib/lp/soyuz/browser/tests/test_archive_webservice.py	2015-11-26 15:54:09 +0000
@@ -164,11 +164,11 @@
         with ExpectedException(LRUnauthorized, '.*'):
             ws_archive.lp_save()
 
-    def test_external_dependencies_commercial_owner_invalid(self):
-        """Commercial admins can look and touch."""
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        owner = self.factory.makePerson(member_of=[commercial])
-        archive = self.factory.makeArchive(owner=owner)
+    def test_external_dependencies_ppa_owner_invalid(self):
+        """PPA admins can look and touch."""
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
+        archive = self.factory.makeArchive(owner=ppa_admin)
         transaction.commit()
         ws_archive = self.wsObject(archive, archive.owner)
         self.assertIs(None, ws_archive.external_dependencies)
@@ -177,11 +177,11 @@
         with ExpectedException(BadRequest, regex):
             ws_archive.lp_save()
 
-    def test_external_dependencies_commercial_owner_valid(self):
-        """Commercial admins can look and touch."""
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        owner = self.factory.makePerson(member_of=[commercial])
-        archive = self.factory.makeArchive(owner=owner)
+    def test_external_dependencies_ppa_owner_valid(self):
+        """PPA admins can look and touch."""
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
+        archive = self.factory.makeArchive(owner=ppa_admin)
         transaction.commit()
         ws_archive = self.wsObject(archive, archive.owner)
         self.assertIs(None, ws_archive.external_dependencies)
@@ -270,10 +270,10 @@
         """The enabled_restricted_processors property is not in beta."""
         self.ws_version = 'beta'
         archive = self.factory.makeArchive()
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        commercial_admin = self.factory.makePerson(member_of=[commercial])
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
         transaction.commit()
-        ws_archive = self.wsObject(archive, user=commercial_admin)
+        ws_archive = self.wsObject(archive, user=ppa_admin)
         expected_re = (
             "(.|\n)*object has no attribute "
             "'enabled_restricted_processors'(.|\n)*")
@@ -284,10 +284,10 @@
         """The enabled_restricted_processors property is in devel."""
         self.ws_version = 'devel'
         archive = self.factory.makeArchive()
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        commercial_admin = self.factory.makePerson(member_of=[commercial])
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
         transaction.commit()
-        ws_archive = self.wsObject(archive, user=commercial_admin)
+        ws_archive = self.wsObject(archive, user=ppa_admin)
         self.assertContentEqual([], ws_archive.enabled_restricted_processors)
 
     def test_processors(self):
@@ -317,21 +317,19 @@
 
     def test_setProcessors_admin(self):
         """An admin can add a new processor to the enabled restricted set."""
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        commercial_admin = self.factory.makePerson(member_of=[commercial])
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
         self.factory.makeProcessor(
             'arm', 'ARM', 'ARM', restricted=True, build_by_default=False)
         ppa_url = api_url(self.factory.makeArchive(purpose=ArchivePurpose.PPA))
-        self.assertProcessors(
-            commercial_admin, ppa_url, ['386', 'hppa', 'amd64'])
+        self.assertProcessors(ppa_admin, ppa_url, ['386', 'hppa', 'amd64'])
 
-        response = self.setProcessors(
-            commercial_admin, ppa_url, ['386', 'arm'])
+        response = self.setProcessors(ppa_admin, ppa_url, ['386', 'arm'])
         self.assertEqual(200, response.status)
-        self.assertProcessors(commercial_admin, ppa_url, ['386', 'arm'])
+        self.assertProcessors(ppa_admin, ppa_url, ['386', 'arm'])
 
     def test_setProcessors_non_owner_forbidden(self):
-        """Only commercial admins and archive owners can call setProcessors."""
+        """Only PPA admins and archive owners can call setProcessors."""
         self.factory.makeProcessor(
             'unrestricted', 'Unrestricted', 'Unrestricted', restricted=False,
             build_by_default=False)
@@ -358,8 +356,8 @@
 
     def test_setProcessors_owner_restricted_forbidden(self):
         """The archive owner cannot enable/disable restricted processors."""
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        commercial_admin = self.factory.makePerson(member_of=[commercial])
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
         self.factory.makeProcessor(
             'arm', 'ARM', 'ARM', restricted=True, build_by_default=False)
         archive = self.factory.makeArchive(purpose=ArchivePurpose.PPA)
@@ -369,9 +367,8 @@
         response = self.setProcessors(owner, ppa_url, ['386', 'arm'])
         self.assertEqual(403, response.status)
 
-        # If a commercial admin enables arm, the owner cannot disable it.
-        response = self.setProcessors(
-            commercial_admin, ppa_url, ['386', 'arm'])
+        # If a PPA admin enables arm, the owner cannot disable it.
+        response = self.setProcessors(ppa_admin, ppa_url, ['386', 'arm'])
         self.assertEqual(200, response.status)
         self.assertProcessors(owner, ppa_url, ['386', 'arm'])
 
@@ -384,11 +381,11 @@
         archive = self.factory.makeArchive()
         self.factory.makeProcessor(
             name='arm', restricted=True, build_by_default=False)
-        commercial = getUtility(ILaunchpadCelebrities).commercial_admin
-        commercial_admin = self.factory.makePerson(member_of=[commercial])
+        ppa_admin_team = getUtility(ILaunchpadCelebrities).ppa_admin
+        ppa_admin = self.factory.makePerson(member_of=[ppa_admin_team])
         transaction.commit()
         ws_arm = self.service.processors.getByName(name='arm')
-        ws_archive = self.wsObject(archive, user=commercial_admin)
+        ws_archive = self.wsObject(archive, user=ppa_admin)
         self.assertContentEqual([], ws_archive.enabled_restricted_processors)
         ws_archive.enableRestrictedProcessor(processor=ws_arm)
         self.assertContentEqual(

=== modified file 'lib/lp/soyuz/browser/tests/test_livefs.py'
--- lib/lp/soyuz/browser/tests/test_livefs.py	2015-05-07 10:52:10 +0000
+++ lib/lp/soyuz/browser/tests/test_livefs.py	2015-11-26 15:54:09 +0000
@@ -208,12 +208,12 @@
     def test_admin_livefs(self):
         # Admins can change require_virtualized.
         login("admin@xxxxxxxxxxxxx")
-        commercial_admin = self.factory.makePerson(
-            member_of=[getUtility(ILaunchpadCelebrities).commercial_admin])
+        ppa_admin = self.factory.makePerson(
+            member_of=[getUtility(ILaunchpadCelebrities).ppa_admin])
         login_person(self.person)
         livefs = self.factory.makeLiveFS(registrant=self.person)
         self.assertTrue(livefs.require_virtualized)
-        browser = self.getViewBrowser(livefs, user=commercial_admin)
+        browser = self.getViewBrowser(livefs, user=ppa_admin)
         browser.getLink("Administer live filesystem").click()
         browser.getControl("Require virtualized builders").selected = False
         browser.getControl("Update live filesystem").click()
@@ -223,13 +223,13 @@
     def test_admin_livefs_sets_date_last_modified(self):
         # Administering a live filesystem sets the date_last_modified property.
         login("admin@xxxxxxxxxxxxx")
-        commercial_admin = self.factory.makePerson(
-            member_of=[getUtility(ILaunchpadCelebrities).commercial_admin])
+        ppa_admin = self.factory.makePerson(
+            member_of=[getUtility(ILaunchpadCelebrities).ppa_admin])
         login_person(self.person)
         date_created = datetime(2000, 1, 1, tzinfo=pytz.UTC)
         livefs = self.factory.makeLiveFS(
             registrant=self.person, date_created=date_created)
-        login_person(commercial_admin)
+        login_person(ppa_admin)
         view = LiveFSAdminView(livefs, LaunchpadTestRequest())
         view.initialize()
         view.request_action.success({"require_virtualized": False})

=== modified file 'lib/lp/soyuz/configure.zcml'
--- lib/lp/soyuz/configure.zcml	2015-09-25 13:54:46 +0000
+++ lib/lp/soyuz/configure.zcml	2015-11-26 15:54:09 +0000
@@ -370,17 +370,19 @@
         <!--
            NOTE: The 'private' permission controls who can turn a public
            archive into a private one, and vice versa. The logic that
-           says this requires launchpad.Admin permissions is duplicated
+           says this requires launchpad.Commercial permissions is duplicated
            in validate_ppa.
           -->
         <require
+            permission="launchpad.Commercial"
+            set_attributes="buildd_secret private"/>
+        <require
             permission="launchpad.Admin"
             interface="lp.soyuz.interfaces.archive.IArchiveAdmin"
-            set_attributes="authorized_size buildd_secret
-                            enabled_restricted_processors
+            set_attributes="authorized_size enabled_restricted_processors
                             external_dependencies name
                             permit_obsolete_series_uploads
-                            private require_virtualized"/>
+                            require_virtualized"/>
         <require
             permission="launchpad.Moderate"
             set_schema="lp.soyuz.interfaces.archive.IArchiveRestricted"/>
@@ -969,8 +971,7 @@
         <require
             permission="launchpad.Edit"
             interface=".interfaces.livefs.ILiveFSEdit"
-            set_schema=".interfaces.livefs.ILiveFSEditableAttributes"
-            set_attributes="date_last_modified"/>
+            set_schema=".interfaces.livefs.ILiveFSEditableAttributes"/>
         <require
             permission="launchpad.Admin"
             set_schema=".interfaces.livefs.ILiveFSAdminAttributes"/>

=== modified file 'lib/lp/soyuz/doc/archive.txt'
--- lib/lp/soyuz/doc/archive.txt	2015-04-09 05:16:37 +0000
+++ lib/lp/soyuz/doc/archive.txt	2015-11-26 15:54:09 +0000
@@ -1711,17 +1711,25 @@
     >>> login('foo.bar@xxxxxxxxxxxxx')
     >>> cprov_archive.enable()
 
-Commercial admins can manage the privacy and build settings of any PPA.
-Additionally, a member of launchpad-ppa-self-admins can manage those
+PPA or commercial admins can manage the privacy and build settings of any
+PPA.  Additionally, a member of launchpad-ppa-self-admins can manage those
 settings on PPAs that they can otherwise edit.
 
     >>> login('celso.providelo@xxxxxxxxxxxxx')
     >>> check_permission('launchpad.Admin', cprov_archive)
     False
 
+    >>> ppa_admin = getUtility(IPersonSet).getByName('launchpad-ppa-admins')
+    >>> ppa_admin_member = factory.makePerson(
+    ...     email='ppa-member@xxxxxxxxxxxxx', member_of=[ppa_admin])
+    >>> login('ppa-member@xxxxxxxxxxxxx')
+    >>> check_permission('launchpad.Admin', cprov_archive)
+    True
+
     >>> login('commercial-member@xxxxxxxxxxxxx')
     >>> check_permission('launchpad.Admin', cprov_archive)
     True
+
     >>> celeb = getUtility(IPersonSet).getByName('launchpad-ppa-self-admins')
     >>> celeb.addMember(person=cprov, reviewer=celeb.teamowner)
     (True, ...)

=== modified file 'lib/lp/soyuz/model/archive.py'
--- lib/lp/soyuz/model/archive.py	2015-09-29 17:45:13 +0000
+++ lib/lp/soyuz/model/archive.py	2015-11-26 15:54:09 +0000
@@ -2313,7 +2313,7 @@
     creator = getUtility(ILaunchBag).user
     if private:
         # NOTE: This duplicates the policy in lp/soyuz/configure.zcml
-        # which says that one needs 'launchpad.Admin' permission to set
+        # which says that one needs 'launchpad.Commercial' permission to set
         # 'private', and the logic in `AdminArchive` which determines
         # who is granted launchpad.Admin permissions. The difference is
         # that here we grant ability to set 'private' to people with a

=== modified file 'lib/lp/soyuz/model/livefs.py'
--- lib/lp/soyuz/model/livefs.py	2015-09-27 23:00:53 +0000
+++ lib/lp/soyuz/model/livefs.py	2015-11-26 15:54:09 +0000
@@ -21,6 +21,7 @@
     )
 from zope.component import getUtility
 from zope.interface import implementer
+from zope.security.proxy import removeSecurityProxy
 
 from lp.buildmaster.enums import BuildStatus
 from lp.registry.errors import NoSuchDistroSeries
@@ -79,7 +80,7 @@
     This method is registered as a subscriber to `IObjectModifiedEvent`
     events on live filesystems.
     """
-    livefs.date_last_modified = UTC_NOW
+    removeSecurityProxy(livefs).date_last_modified = UTC_NOW
 
 
 @implementer(ILiveFS, IHasOwner)

=== modified file 'lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt'
--- lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt	2014-08-01 08:14:01 +0000
+++ lib/lp/soyuz/stories/ppa/xx-ppa-workflow.txt	2015-11-26 15:54:09 +0000
@@ -311,9 +311,10 @@
     >>> admin_browser.getLink("Change details") is not None
     True
 
-But more importantly, administering Personal Package Archives is
-restricted to LP administrators and LP commercial administrators, as
-they need to be able to make PPAs private.
+But more importantly, administering Personal Package Archives is restricted
+to LP administrators, LP commercial administrators, and LP PPA
+administrators, as they need to be able to make PPAs private, change their
+virtualisation settings, and so on.
 
     >>> sample_browser.open("http://launchpad.dev/~jblack/+archive";)
     >>> print sample_browser.getLink("Administer archive")
@@ -332,6 +333,17 @@
     >>> commercial_browser.getLink("Administer archive") is not None
     True
 
+    >>> login('admin@xxxxxxxxxxxxx')
+    >>> ppa_admin = getUtility(IPersonSet).getByName('launchpad-ppa-admins')
+    >>> ppa_admin_member = factory.makePerson(
+    ...     email='ppa-member@xxxxxxxxxxxxx', member_of=[ppa_admin])
+    >>> logout()
+    >>> ppa_admin_browser = setupBrowser(
+    ...     auth='Basic ppa-member@xxxxxxxxxxxxx:test')
+    >>> ppa_admin_browser.open("http://launchpad.dev/~jblack/+archive";)
+    >>> ppa_admin_browser.getLink("Administer archive") is not None
+    True
+
 
 Trying to shortcut the URL as a non-privileged user does not work:
 

=== modified file 'lib/lp/soyuz/stories/webservice/xx-archive.txt'
--- lib/lp/soyuz/stories/webservice/xx-archive.txt	2015-05-18 06:55:24 +0000
+++ lib/lp/soyuz/stories/webservice/xx-archive.txt	2015-11-26 15:54:09 +0000
@@ -1103,7 +1103,7 @@
 
 
 Modifying the require_virtualized flag through the API is not allowed except
-for admins and commercial admins.
+for admins, commercial admins, and PPA admins.
 
     >>> import simplejson
     >>> def modify_archive(service, archive):
@@ -1144,7 +1144,7 @@
     1024
 
 Modifying the authorized_size attribute through the API is not allowed except
-for admins and commercial admins.
+for admins, commercial admins, and PPA admins.
 
     >>> mark_archive['authorized_size'] = 4096
     >>> response = modify_archive(admin_webservice, mark_archive)
@@ -1330,14 +1330,25 @@
 ~~~~~~~~~~~~~~~~~
 
 Modifying the privacy flag through the API is not allowed except for
-admins and commercial admins.
+admins and commercial admins.  Mere PPA admins can't do it.
 
     >>> mark_archive = webservice.get("/~mark/+archive/ubuntu/ppa").jsonBody()
     >>> mark_archive['private'] = True
     >>> print modify_archive(user_webservice, mark_archive)
     HTTP/1.1 401 Unauthorized
     ...
-    (<Archive at ...>, 'private', 'launchpad.Admin')
+    (<Archive at ...>, 'private', 'launchpad.Commercial')
+
+    >>> login('foo.bar@xxxxxxxxxxxxx')
+    >>> ppa_admin = factory.makePerson(member_of=[
+    ...     getUtility(IPersonSet).getByName('launchpad-ppa-admins')])
+    >>> logout()
+    >>> ppa_admin_webservice = webservice_for_person(
+    ...     ppa_admin, permission=OAuthPermission.WRITE_PRIVATE)
+    >>> print modify_archive(ppa_admin_webservice, mark_archive)
+    HTTP/1.1 401 Unauthorized
+    ...
+    (<Archive at ...>, 'private', 'launchpad.Commercial')
 
 
 Copying private file to public archives

=== modified file 'lib/lp/soyuz/tests/test_archive.py'
--- lib/lp/soyuz/tests/test_archive.py	2015-09-29 01:38:34 +0000
+++ lib/lp/soyuz/tests/test_archive.py	2015-11-26 15:54:09 +0000
@@ -1211,7 +1211,7 @@
         login(ANONYMOUS)
         e = self.assertRaises(
             Unauthorized, setattr, self.archive, "buildd_secret", "boing")
-        self.assertEqual("launchpad.Admin", e.args[2])
+        self.assertEqual("launchpad.Commercial", e.args[2])
 
     def test_commercial_admin_can_set_buildd_secret(self):
         with celebrity_logged_in("commercial_admin"):

=== modified file 'lib/lp/soyuz/tests/test_binarypackagebuild.py'
--- lib/lp/soyuz/tests/test_binarypackagebuild.py	2015-07-31 00:50:08 +0000
+++ lib/lp/soyuz/tests/test_binarypackagebuild.py	2015-11-26 15:54:09 +0000
@@ -813,11 +813,11 @@
         archive = self.factory.makeArchive()
         self.assertScoreNotWriteableByOwner(archive)
 
-    def test_score_archive_allows_buildd_and_commercial_admin(self):
-        # Buildd and commercial admins can change an archive's build score.
+    def test_score_archive_allows_buildd_and_ppa_admin(self):
+        # Buildd and PPA admins can change an archive's build score.
         archive = self.factory.makeArchive()
         self.assertScoreWriteableByTeam(
             archive, getUtility(ILaunchpadCelebrities).buildd_admin)
         with anonymous_logged_in():
             self.assertScoreWriteableByTeam(
-                archive, getUtility(ILaunchpadCelebrities).commercial_admin)
+                archive, getUtility(ILaunchpadCelebrities).ppa_admin)


Follow ups