Skip to content
Browse files

Don't use a default sudo program

--sudo's argument is now optional, defaulting to 'sudo'. If not specified,
and the target directory is not writable, fail.

Fix issue #13

This is a behavioural change from previous version, so bumping the
minor version number.
  • Loading branch information...
1 parent ca4c0d8 commit 4a774836ddbd57b4adfaf20967a82f1c7b8b0465 @dvarrazzo committed Jun 27, 2012
@@ -3,12 +3,14 @@
PGXN Client changes log
-pgxnclient 1.0.4
+pgxnclient 1.1
+- ``sudo`` is not invoked automatically: the ``--sudo`` option must be
+ specified if the user has not permission to write into PostgreSQL's libdir
+ (ticket #13). The ``--sudo`` option can also be invoked without argument.
- Make sure the same ``pg_config`` is used both by the current user and by
- sudo (ticket #13).
-- Don't run ``sudo`` if not required (e.g. lib directory is writable).
+ sudo.
pgxnclient 1.0.3
24 docs/usage.rst
@@ -79,7 +79,7 @@ Usage:
:class: pgxn-install
pgxn install [--help] [--stable | --testing | --unstable]
- [--pg_config *PATH*] [--sudo *PROG* | --nosudo]
+ [--pg_config *PATH*] [--sudo [*PROG*] | --nosudo]
The program takes a `package specification`_ identifying the distribution to
@@ -97,17 +97,25 @@ commands are implemented.
.. _PGXS:
-The install phase usually requires root privileges in order to install a build
-library and other files in the PostgreSQL directories: by default
-:program:`sudo` will be invoked for the purpose. An alternative program can be
-specified with the option :samp:`--sudo {PROG}`; ``--nosudo`` can be used to
-avoid running any program.
If there are many PostgreSQL installations on the system, the extension will
be built and installed against the instance whose :program:`pg_config` is
first found on the :envvar:`PATH`. A different instance can be specified using
the option :samp:`--pg_config {PATH}`.
+If the extension is being installed into a system PostgreSQL installation, the
+install phase will likely require root privileges to be performed. In this
+case either run the command under :program:`sudo` or specify the ``--sudo``
+option: in the latter case :program:`sudo` will only be invoked during the
+"install" phase. An optional privilege elevation program :samp:`{PROG}` can be
+.. note::
+ If ``--sudo`` is the last option and no :samp:`{PROG}` is specified, a
+ ``--`` separator may be required to disambiguate the :samp:`{SPEC}`::
+ pgxn install --sudo -- foobar
.. _check:
@@ -170,7 +178,7 @@ Usage:
:class: pgxn-uninstall
pgxn uninstall [--help] [--stable | --testing | --unstable]
- [--pg_config *PATH*] [--sudo *PROG* | --nosudo]
+ [--pg_config *PATH*] [--sudo [*PROG*] | --nosudo]
The command does the opposite of the install_ command, removing a
2 pgxnclient/
@@ -6,7 +6,7 @@
# This file is part of the PGXN client
-__version__ = '1.0.4.dev0'
+__version__ = '1.1.dev0'
# Paths where to find the command executables.
# If relative, it's from the `pgxnclient` package directory.
7 pgxnclient/commands/
@@ -544,11 +544,12 @@ def customize_parser(self, parser, subparsers, **kwargs):
parser, subparsers, **kwargs)
g = subp.add_mutually_exclusive_group()
- g.add_argument('--sudo', metavar="PROG", default='sudo',
+ g.add_argument('--sudo', metavar="PROG", const='sudo', nargs="?",
help = _("run PROG to elevate privileges when required"
- " [default: %(default)s]"))
+ " [default: %(const)s]"))
g.add_argument('--nosudo', dest='sudo', action='store_false',
- help = _("never elevate privileges"))
+ help = _("never elevate privileges "
+ "(no more needed: for backward compatibility)"))
return subp
4 pgxnclient/commands/
@@ -18,7 +18,7 @@
from pgxnclient import SemVer
from pgxnclient.i18n import _, N_
from pgxnclient.utils import sha1
-from pgxnclient.errors import BadChecksum, PgxnClientException
+from pgxnclient.errors import BadChecksum, PgxnClientException, InsufficientPrivileges
from import download
from pgxnclient.commands import Command, WithDatabase, WithMake, WithPgConfig
from pgxnclient.commands import WithSpec, WithSpecLocal, WithSudo
@@ -134,7 +134,7 @@ class SudoInstallUninstall(WithSudo, InstallUninstall):
def run(self):
if not self.is_libdir_writable() and not self.opts.sudo:
dir = self.call_pg_config('libdir')
- raise PgxnClientException(_(
+ raise InsufficientPrivileges(_(
"PostgreSQL library directory (%s) not writable: "
"you should run the program as superuser, or specify "
"a 'sudo' program") % dir)
3 pgxnclient/
@@ -25,6 +25,9 @@ class BadSpecError(PgxnClientException):
class ProcessError(PgxnClientException):
"""An error raised calling an external program."""
+class InsufficientPrivileges(PgxnClientException):
+ """Operation will fail because the user is too lame."""
class NotFound(PgxnException):
"""Something requested by the user not found on PGXN"""
27 pgxnclient/tests/
@@ -6,7 +6,7 @@
from urllib import quote
from pgxnclient.utils import b
-from pgxnclient.errors import PgxnClientException, ResourceNotFound
+from pgxnclient.errors import PgxnClientException, ResourceNotFound, InsufficientPrivileges
from pgxnclient.tests import unittest
from pgxnclient.tests.testutils import ifunlink, get_test_filename
@@ -293,14 +293,31 @@ def tearDown(self):
def test_install_latest(self):
from pgxnclient.cli import main
- main(['install', 'foobar'])
+ main(['install', '--sudo', '--', 'foobar'])
self.assertEquals(self.mock_popen.call_count, 2)
self.assertEquals(['make'], self.mock_popen.call_args_list[0][0][0][:1])
self.assertEquals(['sudo', 'make'], self.mock_popen.call_args_list[1][0][0][:2])
+ def test_install_missing_sudo(self):
+ from pgxnclient.cli import main
+ self.assertRaises(InsufficientPrivileges, main, ['install', 'foobar'])
+ def test_install_local(self):
+ self.mock_pgconfig.side_effect = fake_pg_config(
+ libdir=os.environ['HOME'], bindir='/')
+ from pgxnclient.cli import main
+ main(['install', 'foobar'])
+ self.assertEquals(self.mock_popen.call_count, 2)
+ self.assertEquals(['make'], self.mock_popen.call_args_list[0][0][0][:1])
+ self.assertEquals(['make'], self.mock_popen.call_args_list[1][0][0][:1])
def test_install_fails(self):
self.mock_popen.return_value.returncode = 1
+ self.mock_pgconfig.side_effect = fake_pg_config(
+ libdir=os.environ['HOME'], bindir='/')
from pgxnclient.cli import main
self.assertRaises(PgxnClientException, main, ['install', 'foobar'])
@@ -318,7 +335,7 @@ def fakefake(url):
from pgxnclient.cli import main
from pgxnclient.errors import BadChecksum
- main, ['install', 'foobar'])
+ main, ['install', '--sudo', '--', 'foobar'])
def test_install_nosudo(self):
self.mock_pgconfig.side_effect = fake_pg_config(libdir=os.environ['HOME'])
@@ -345,7 +362,7 @@ def test_install_local_zip(self, mock_unpack):
mock_unpack.side_effect = unpack
from pgxnclient.cli import main
- main(['install', get_test_filename('')])
+ main(['install', '--sudo', '--', get_test_filename('')])
self.assertEquals(self.mock_popen.call_count, 2)
self.assertEquals(['make'], self.mock_popen.call_args_list[0][0][0][:1])
@@ -367,7 +384,7 @@ def test_install_local_dir(self):
dir = unpack(get_test_filename(''), tdir)
from pgxnclient.cli import main
- main(['install', dir])
+ main(['install', '--sudo', '--', dir])

0 comments on commit 4a77483

Please sign in to comment.
Something went wrong with that request. Please try again.