Пример #1
0
class Check(WithDatabase, InstallUninstall):
    name = 'check'
    description = N_("run a distribution's test")

    def _inun(self, pdir):
        logger.info(_("checking extension"))
        upenv = self.get_psql_env()
        logger.debug("additional env: %s", upenv)
        env = os.environ.copy()
        env.update(upenv)

        cmd = ['installcheck']
        if 'PGDATABASE' in upenv:
            cmd.append("CONTRIB_TESTDB=" + env['PGDATABASE'])

        try:
            self.run_make(cmd, dir=pdir, env=env)
        except PgxnClientException:
            # if the test failed, copy locally the regression result
            for ext in ('out', 'diffs'):
                fn = os.path.join(pdir, 'regression.' + ext)
                if os.path.exists(fn):
                    dest = './regression.' + ext
                    if not os.path.exists(dest) or not os.path.samefile(
                        fn, dest
                    ):
                        logger.info(_('copying regression.%s'), ext)
                        shutil.copy(fn, dest)
            raise
Пример #2
0
class Download(WithSpecUrl, Command):
    name = 'download'
    description = N_("download a distribution from the network")

    @classmethod
    def customize_parser(self, parser, subparsers, **kwargs):
        subp = super(Download, self).customize_parser(parser, subparsers,
                                                      **kwargs)
        subp.add_argument('--target',
                          metavar='PATH',
                          default='.',
                          help=_('Target directory and/or filename to save'))

        return subp

    def run(self):
        spec = self.get_spec()
        assert not spec.is_local()

        if spec.is_url():
            return self._run_url(spec)

        data = self.get_meta(spec)

        try:
            chk = data['sha1']
        except KeyError:
            raise PgxnClientException(
                "sha1 missing from the distribution meta")

        with self.api.download(data['name'], SemVer(data['version'])) as fin:
            fn = network.download(fin, self.opts.target)

        self.verify_checksum(fn, chk)
        return fn

    def _run_url(self, spec):
        with network.get_file(spec.url) as fin:
            fn = network.download(fin, self.opts.target)

        return fn

    def verify_checksum(self, fn, chk):
        """Verify that a downloaded file has the expected sha1."""
        sha = sha1()
        logger.debug(_("checking sha1 of '%s'"), fn)
        f = open(fn, "rb")
        try:
            while 1:
                data = f.read(8192)
                if not data: break
                sha.update(data)
        finally:
            f.close()

        sha = sha.hexdigest()
        if sha != chk:
            os.unlink(fn)
            logger.error(_("file %s has sha1 %s instead of %s"), fn, sha, chk)
            raise BadChecksum(_("bad sha1 in downloaded file"))
Пример #3
0
class Uninstall(SudoInstallUninstall):
    name = 'uninstall'
    description = N_("remove a distribution from the system")

    def _inun(self, pdir):
        logger.info(_("removing extension"))
        self.run_make('uninstall', dir=pdir, sudo=self.get_sudo_prog())
Пример #4
0
class Unload(LoadUnload):
    name = 'unload'
    description = N_("unload a distribution's extensions from a database")

    def run(self):
        items = self._get_extensions()

        if not self.opts.extensions:
            items.reverse()

        for (name, sql) in items:
            self.unload_ext(name, sql)

    def unload_ext(self, name, sqlfile):
        logger.debug(_("unloading extension '%s' with file: %s"), name,
                     sqlfile)

        if sqlfile and not sqlfile.endswith('.sql'):
            logger.info(
                _("the specified file '%s' doesn't seem SQL:"
                  " assuming '%s' is not a PostgreSQL extension"),
                sqlfile,
                name,
            )
            return

        pgver = self.get_pg_version()

        if pgver >= (9, 1, 0):
            if self.is_extension(name):
                self.drop_extension(name)
                return
            else:
                self.confirm(
                    _("""\
The extension '%s' doesn't contain a control file:
will look for an SQL script to unload the objects.
Do you want to continue?""") % name)

        if not sqlfile:
            sqlfile = name + '.sql'

        tmp = os.path.split(sqlfile)
        sqlfile = os.path.join(tmp[0], 'uninstall_' + tmp[1])

        fn = self.find_sql_file(name, sqlfile)
        self.confirm(
            _("""\
In order to unload the extension '%s' looks like you will have
to load the file '%s'.
Do you want to execute it?""") % (name, fn))

        data = self.patch_for_schema(fn)
        self.load_sql(data=data)

    def drop_extension(self, name):
        # TODO: cascade
        cmd = "DROP EXTENSION %s;" % Identifier(name)
        self.load_sql(data=cmd)
Пример #5
0
class Install(SudoInstallUninstall):
    name = 'install'
    description = N_("download, build and install a distribution")

    def _inun(self, pdir):
        logger.info(_("building extension"))
        self.run_make('all', dir=pdir)

        logger.info(_("installing extension"))
        self.run_make('install', dir=pdir, sudo=self.get_sudo_prog())
Пример #6
0
class Mirror(Command):
    name = 'mirror'
    description = N_("return information about the available mirrors")

    @classmethod
    def customize_parser(self, parser, subparsers, **kwargs):
        subp = super(Mirror, self).customize_parser(
            parser, subparsers, **kwargs
        )

        subp.add_argument(
            'uri',
            nargs='?',
            metavar="URI",
            help=_(
                "return detailed info about this mirror."
                " If not specified return a list of mirror URIs"
            ),
        )
        subp.add_argument(
            '--detailed',
            action="store_true",
            help=_("return full details for each mirror"),
        )

        return subp

    def run(self):
        data = self.api.mirrors()
        if self.opts.uri:
            detailed = True
            data = [d for d in data if d['uri'] == self.opts.uri]
            if not data:
                raise ResourceNotFound(
                    _('mirror not found: %s') % self.opts.uri
                )
        else:
            detailed = self.opts.detailed

        for i, d in enumerate(data):
            if not detailed:
                emit(d['uri'])
            else:
                for k in u"""
                    uri frequency location bandwidth organization email
                    timezone src rsync notes
                """.split():
                    emit("%s: %s" % (k, d.get(k, '')))

                emit()
Пример #7
0
class Help(Command):
    name = 'help'
    description = N_("display help and other program information")

    @classmethod
    def customize_parser(self, parser, subparsers, **kwargs):
        subp = super(Help, self).customize_parser(parser, subparsers, **kwargs)

        g = subp.add_mutually_exclusive_group()
        g.add_argument(
            '--all',
            action="store_true",
            help=_("list all the available commands"),
        )
        g.add_argument(
            '--libexec',
            action="store_true",
            help=_("print the location of the scripts directory"),
        )
        g.add_argument(
            'command',
            metavar='CMD',
            nargs='?',
            help=_("the command to get help about"),
        )

        # To print the basic help
        self._parser = parser

        return subp

    def run(self):
        if self.opts.command:
            from pgxnclient.cli import main

            main([self.opts.command, '--help'])
        elif self.opts.all:
            self.print_all_commands()
        elif self.opts.libexec:
            self.print_libexec()
        else:
            self._parser.print_help()

    def print_all_commands(self):
        cmds = self.find_all_commands()
        title = _("Available PGXN Client commands")
        emit(title)
        emit("-" * len(title))

        for cmd in cmds:
            emit("  " + cmd)

    def find_all_commands(self):
        rv = []
        path = os.environ.get('PATH', '').split(os.pathsep)
        path[0:0] = get_scripts_dirs()
        for p in path:
            try:
                files = os.listdir(p)
            except OSError:
                # Dir missing, or not readable
                continue

            for fn in files:
                if fn.startswith('pgxn-'):
                    rv.append(fn[5:])

        rv.sort()
        return rv

    def print_libexec(self):
        emit(get_public_scripts_dir())
Пример #8
0
class Load(LoadUnload):
    name = 'load'
    description = N_("load a distribution's extensions into a database")

    def run(self):
        items = self._get_extensions()
        for (name, sql) in items:
            self.load_ext(name, sql)

    def load_ext(self, name, sqlfile):
        logger.debug(_("loading extension '%s' with file: %s"), name, sqlfile)

        if sqlfile and not sqlfile.endswith('.sql'):
            logger.info(
                _(
                    "the specified file '%s' doesn't seem SQL:"
                    " assuming '%s' is not a PostgreSQL extension"
                ),
                sqlfile,
                name,
            )
            return

        pgver = self.get_pg_version()

        if pgver >= (9, 1, 0):
            if self.is_extension(name):
                self.create_extension(name)
                return
            else:
                self.confirm(
                    _(
                        """\
The extension '%s' doesn't contain a control file:
it will be installed as a loose set of objects.
Do you want to continue?"""
                    )
                    % name
                )

        confirm = False
        if not sqlfile:
            sqlfile = name + '.sql'
            confirm = True

        fn = self.find_sql_file(name, sqlfile)
        if confirm:
            self.confirm(
                _(
                    """\
The extension '%s' doesn't specify a SQL file.
'%s' is probably the right one.
Do you want to load it?"""
                )
                % (name, fn)
            )

        # TODO: is confirmation asked only once? Also, check for repetition
        # in unload.
        if self._is_loaded(fn):
            logger.info(_("file %s already loaded"), fn)
        else:
            data = self.patch_for_schema(fn)
            self.load_sql(data=data)
            self._register_loaded(fn)

    def create_extension(self, name):
        name = Identifier(name)
        schema = self.opts.schema
        cmd = ["CREATE EXTENSION", name]
        if schema:
            cmd.extend(["SCHEMA", schema])

        cmd = " ".join(cmd) + ';'
        self.load_sql(data=cmd)
Пример #9
0
class Search(Command):
    name = 'search'
    description = N_("search in the available extensions")

    @classmethod
    def customize_parser(self, parser, subparsers, **kwargs):
        subp = super(Search, self).customize_parser(parser, subparsers,
                                                    **kwargs)

        g = subp.add_mutually_exclusive_group()
        g.add_argument(
            '--docs',
            dest='where',
            action='store_const',
            const='docs',
            default='docs',
            help=_("search in documentation [default]"),
        )
        g.add_argument(
            '--dist',
            dest='where',
            action='store_const',
            const="dists",
            help=_("search in distributions"),
        )
        g.add_argument(
            '--ext',
            dest='where',
            action='store_const',
            const='extensions',
            help=_("search in extensions"),
        )
        subp.add_argument('query',
                          metavar='TERM',
                          nargs='+',
                          help=_("a string to search"))

        return subp

    def run(self):
        data = self.api.search(self.opts.where, self.opts.query)

        for hit in data['hits']:
            emit("%s %s" % (hit['dist'], hit['version']))
            if 'excerpt' in hit:
                excerpt = self.clean_excerpt(hit['excerpt'])

                for line in textwrap.wrap(excerpt, 72):
                    emit("    " + line)
                emit()

    def clean_excerpt(self, excerpt):
        """Clean up the excerpt returned in the json result for output."""
        # replace ellipsis with three dots, as there's no chance
        # to have them printed on non-utf8 consoles.
        # Also, they suck obscenely on fixed-width output.
        excerpt = excerpt.replace('…', '...')

        # TODO: this apparently misses a few entities
        excerpt = saxutils.unescape(excerpt)
        excerpt = excerpt.replace('"', '"')

        # Convert numerical entities
        excerpt = re.sub(r'\&\#(\d+)\;', lambda c: six.unichr(int(c.group(1))),
                         excerpt)

        # Hilight found terms
        # TODO: use proper highlight with escape chars?
        excerpt = excerpt.replace('<strong></strong>', '')
        excerpt = excerpt.replace('<strong>', '*')
        excerpt = excerpt.replace('</strong>', '*')

        return excerpt
Пример #10
0
class Info(WithSpec, Command):
    name = 'info'
    description = N_("print information about a distribution")

    @classmethod
    def customize_parser(self, parser, subparsers, **kwargs):
        subp = super(Info, self).customize_parser(parser, subparsers, **kwargs)

        g = subp.add_mutually_exclusive_group()
        g.add_argument(
            '--details',
            dest='what',
            action='store_const',
            const='details',
            default='details',
            help=_("show details about the distribution [default]"),
        )
        g.add_argument(
            '--meta',
            dest='what',
            action='store_const',
            const='meta',
            help=_("show the distribution META.json"),
        )
        g.add_argument(
            '--readme',
            dest='what',
            action='store_const',
            const='readme',
            help=_("show the distribution README"),
        )
        g.add_argument(
            '--versions',
            dest='what',
            action='store_const',
            const='versions',
            help=_("show the list of available versions"),
        )

        return subp

    def run(self):
        spec = self.get_spec()
        getattr(self, 'print_' + self.opts.what)(spec)

    def print_meta(self, spec):
        data = self._get_dist_data(spec.name)
        ver = self.get_best_version(data, spec, quiet=True)
        emit(self.api.meta(spec.name, ver, as_json=False))

    def print_readme(self, spec):
        data = self._get_dist_data(spec.name)
        ver = self.get_best_version(data, spec, quiet=True)
        emit(self.api.readme(spec.name, ver))

    def print_details(self, spec):
        data = self._get_dist_data(spec.name)
        ver = self.get_best_version(data, spec, quiet=True)
        data = self.api.meta(spec.name, ver)
        for k in u"""
            name abstract description maintainer license release_status
            version date sha1
        """.split():
            try:
                v = data[k]
            except KeyError:
                logger.warning(_("data key '%s' not found"), k)
                continue

            if isinstance(v, list):
                for vv in v:
                    emit("%s: %s" % (k, vv))
            elif isinstance(v, dict):
                for kk, vv in v.items():
                    emit("%s: %s: %s" % (k, kk, vv))
            else:
                emit("%s: %s" % (k, v))

        k = 'provides'
        for ext, dext in data[k].items():
            emit("%s: %s: %s" % (k, ext, dext['version']))

        k = 'prereqs'
        if k in data:
            for phase, rels in data[k].items():
                for rel, pkgs in rels.items():
                    for pkg, ver in pkgs.items():
                        emit("%s: %s: %s %s" % (phase, rel, pkg, ver))

    def print_versions(self, spec):
        data = self._get_dist_data(spec.name)
        name = data['name']
        vs = [(SemVer(d['version']), s) for s, ds in data['releases'].items()
              for d in ds]
        vs = [(v, s) for v, s in vs if spec.accepted(v)]
        vs.sort(reverse=True)
        for v, s in vs:
            emit("%s %s %s" % (name, v, s))

    def _get_dist_data(self, name):
        try:
            return self.api.dist(name)
        except NotFound as e:
            # maybe the user was looking for an extension instead?
            try:
                ext = self.api.ext(name)
            except NotFound:
                pass
            else:
                vs = ext.get('versions', {})
                for extver, ds in vs.items():
                    for d in ds:
                        if 'dist' not in d:
                            continue
                        dist = d['dist']
                        distver = d.get('version', 'unknown')
                        logger.info(
                            _("extension %s %s found in distribution %s %s"),
                            name,
                            extver,
                            dist,
                            distver,
                        )

            raise e