Colin Watson has proposed merging ~cjwatson/launchpad:anoint-team-member into launchpad:master.
Commit message: Add anoint-team-member script Requested reviews: Launchpad code reviewers (launchpad-reviewers) For more details, see: https://code.launchpad.net/~cjwatson/launchpad/+git/launchpad/+merge/448542 This is useful in development environments. It will also replace the local `anoint-dogfood-admin` script on our dogfood instance. -- Your team Launchpad code reviewers is requested to review the proposed merge of ~cjwatson/launchpad:anoint-team-member into launchpad:master.
diff --git a/doc/how-to/new-user.rst b/doc/how-to/new-user.rst index daff7a6..31529bc 100644 --- a/doc/how-to/new-user.rst +++ b/doc/how-to/new-user.rst @@ -1,5 +1,16 @@ Creating additional user accounts in the development environment ================================================================ -You can create a new account using the ``utilities/make-lp-user`` script and log -in to that account at ``https://launchpad.test``. +In environments that use the test OpenID provider, such as the development +appserver started via ``make run``, you can create a new account using the +``utilities/make-lp-user`` script and log into that account at +``https://launchpad.test/``. + +Some development environments, such as those deployed using the :doc:`Mojo +spec <../explanation/charms>`, use production Single Sign-On for +authentication. In these environments, you should not use +``utilities/make-lp-user``; instead, log into ``https://launchpad.test/`` +via SSO as you would on production to create your user, and then use +``utilities/anoint-team-member`` as needed to add yourself to teams. For +example, you might want to temporarily add yourself to the ``admins`` team +in order to edit feature rules. diff --git a/lib/lp/scripts/utilities/anointteammember.py b/lib/lp/scripts/utilities/anointteammember.py new file mode 100644 index 0000000..ec08084 --- /dev/null +++ b/lib/lp/scripts/utilities/anointteammember.py @@ -0,0 +1,70 @@ +# Copyright 2023 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +"""Script to add a user to a team.""" + +__all__ = ["AnointTeamMemberScript"] + +from textwrap import dedent + +import transaction +from zope.component import getUtility + +from lp.registry.interfaces.person import IPersonSet +from lp.services.config import config +from lp.services.scripts.base import ( + LaunchpadScript, + LaunchpadScriptFailure, + SilentLaunchpadScriptFailure, +) +from lp.services.webapp.publisher import canonical_url + + +class AnointTeamMemberScript(LaunchpadScript): + """ + Add a user to a team, bypassing the normal workflows. + + This is particularly useful for making a local user be a member of the + "admins" team in a development environment. + + This script is for testing purposes only. Do NOT use it in production + environments. + """ + + usage = "%(prog)s <person> <team>" + description = dedent(__doc__) + + def main(self): + if len(self.args) != 2: + self.parser.print_help() + raise SilentLaunchpadScriptFailure(2) + if config.vhost.mainsite.hostname == "launchpad.net": + raise LaunchpadScriptFailure( + "This script may not be used on production. Use normal team " + "processes instead, or ask an existing member of ~admins." + ) + + person_name, team_name = self.args + person = getUtility(IPersonSet).getByName(person_name) + if person is None: + raise LaunchpadScriptFailure( + "There is no person named '%s'." % person_name + ) + team = getUtility(IPersonSet).getByName(team_name) + if team is None: + raise LaunchpadScriptFailure( + "There is no team named '%s'." % team_name + ) + if not team.is_team: + raise LaunchpadScriptFailure( + "The person named '%s' is not a team." % team_name + ) + + team.addMember(person, person) + transaction.commit() + self.logger.info( + "Anointed ~%s as a member of ~%s.", person_name, team_name + ) + self.logger.info( + "Use %s to leave.", canonical_url(team, view_name="+leave") + ) diff --git a/lib/lp/scripts/utilities/tests/test_anointteammember.py b/lib/lp/scripts/utilities/tests/test_anointteammember.py new file mode 100644 index 0000000..01c209e --- /dev/null +++ b/lib/lp/scripts/utilities/tests/test_anointteammember.py @@ -0,0 +1,75 @@ +# Copyright 2023 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +import transaction + +from lp.scripts.utilities.anointteammember import AnointTeamMemberScript +from lp.services.log.logger import BufferLogger +from lp.services.scripts.base import LaunchpadScriptFailure +from lp.testing import TestCaseWithFactory +from lp.testing.layers import ZopelessDatabaseLayer + + +class TestAnointTeamMember(TestCaseWithFactory): + layer = ZopelessDatabaseLayer + + def makeScript(self, test_args): + script = AnointTeamMemberScript(test_args=test_args) + script.logger = BufferLogger() + script.txn = transaction + return script + + def test_refuses_on_production(self): + self.pushConfig("vhost.mainsite", hostname="launchpad.net") + script = self.makeScript(["person", "team"]) + self.assertRaisesWithContent( + LaunchpadScriptFailure, + "This script may not be used on production. Use normal team " + "processes instead, or ask an existing member of ~admins.", + script.main, + ) + self.assertEqual("", script.logger.getLogBuffer()) + + def test_no_such_person(self): + script = self.makeScript(["nonexistent-person", "admins"]) + self.assertRaisesWithContent( + LaunchpadScriptFailure, + "There is no person named 'nonexistent-person'.", + script.main, + ) + self.assertEqual("", script.logger.getLogBuffer()) + + def test_no_such_team(self): + script = self.makeScript( + [self.factory.makePerson().name, "nonexistent-team"] + ) + self.assertRaisesWithContent( + LaunchpadScriptFailure, + "There is no team named 'nonexistent-team'.", + script.main, + ) + self.assertEqual("", script.logger.getLogBuffer()) + + def test_person_not_a_team(self): + persons = [self.factory.makePerson() for _ in range(2)] + script = self.makeScript([persons[0].name, persons[1].name]) + self.assertRaisesWithContent( + LaunchpadScriptFailure, + "The person named '%s' is not a team." % persons[1].name, + script.main, + ) + self.assertEqual("", script.logger.getLogBuffer()) + + def test_success(self): + person = self.factory.makePerson() + team = self.factory.makeTeam() + self.assertFalse(person.inTeam(team)) + script = self.makeScript([person.name, team.name]) + script.main() + self.assertTrue(person.inTeam(team)) + self.assertEqual( + "INFO Anointed ~%s as a member of ~%s.\n" + "INFO Use http://launchpad.test/~%s/+leave to leave.\n" + % (person.name, team.name, team.name), + script.logger.getLogBuffer(), + ) diff --git a/utilities/anoint-team-member b/utilities/anoint-team-member new file mode 100755 index 0000000..ebc32c5 --- /dev/null +++ b/utilities/anoint-team-member @@ -0,0 +1,11 @@ +#! /usr/bin/python3 -S +# +# Copyright 2023 Canonical Ltd. This software is licensed under the +# GNU Affero General Public License version 3 (see the file LICENSE). + +import _pythonpath # noqa: F401 + +from lp.scripts.utilities.anointteammember import AnointTeamMemberScript + +if __name__ == "__main__": + AnointTeamMemberScript("anoint-team-member").run()
_______________________________________________ Mailing list: https://launchpad.net/~launchpad-reviewers Post to : launchpad-reviewers@lists.launchpad.net Unsubscribe : https://launchpad.net/~launchpad-reviewers More help : https://help.launchpad.net/ListHelp