Re: Known working sets II [was: Eggification redux]

Tres Seaver <[email protected]> Thu, 27 Sep 2007 19:40:11 -0400
Newsgroups gmane.comp.web.zope.zope3
Message-ID <[email protected]>
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Tres Seaver wrote:

> Anybody running against the Cheeseshop today is *more* on the bleeding
> edge than a sysadmin whose production boxes are running 'sid':  Debian
> has cultural constraits, even for that distro, which are vastly more
> restricted than the Wild West which is PyPI.
> 
> The only solution I can see is to create filtered subsets / mirrors of PyPI.

<snip>

> 
> Exactly.  Without some way to impose a "gatekeeper" role on the package
> pool from which a given deployment draws, we can't have any
> deterministic outcomes when installing packages.

OK, here is a sample "gatekeeper" script, intended to be run from within
a directory full of source distributions.  E.g.:

  $ cd /path/to/dist.example.com
  $ ls
  abc-1.2.3.tar.gz  abc-1.2.4.tar.gz  ghijk-2.3.4.tar.gz
  $ python /tmp/makeindex.py *.gz
  Parsing: abc-1.2.3.tar.gz
  Parsing: abc-1.2.4.tar.gz
  Parsing: ghijk-2.3.4.tar.gz
  Project: abc
    --> 1.2.3  abc-1.2.3.tar.gz
    --> 1.2.4  abc-1.2.4.tar.gz
  Project: ghijk
    --> 2.3.4  ghijk-2.3.4.tar.gz

Assuming that the directory is the root of an Apache virtual domain,
'dist.example.com', the script creates a 'simple' subdirectory, with
an index listing the projects corresponding to the tarballs.  Each
project ('abc', 'ghijk') gets a subdirectory with an index pointing to
its tarballs.

At this point, from a fresh virtualenv, you can install those packages
without risk of pulling anything from the Cheeseshop:

  $ bin/easy_install --index-url=http://dist.example.com/simple ghijk

Total effort involved in maintaining the "gated community" then becomes
keeping a set of tarballs available at some web-downloadable location,
and re-running the script after adding / removing them to regenerate
the index.


Tres.
- --
===================================================================
Tres Seaver          +1 540-429-0999          [email protected]
Palladion Software   "Excellence by Design"    http://palladion.com
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.6 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org

iD8DBQFG/D9a+gerLs4ltQ4RAtZrAJwPrSe+vAaLTNF+XrrdyPY6bFXgTgCgzqOV
ssgeiDB9/whhld4DyylsQxA=
=f2tL
-----END PGP SIGNATURE-----
makeindex.py (application/x-httpd-cgi, 2.9 KB)
import os
import setuptools
import shutil
import subprocess
import sys
import tarfile
import tempfile

_tempdir = None

def _extractNameVersion(filename):
    global _tempdir
    if _tempdir is None:
        _tempdir = tempfile.mkdtemp()
    print 'Parsing:', filename
    tgz = tarfile.TarFile.gzopen(filename, 'r')
    try:
        names = tgz.getnames()
        for name in names:

            if name.endswith('PKG-INFO'):

                project, version = None, None

                for line in tgz.extractfile(name).readlines():
                    key, value = line.split(':', 1)

                    if key == 'Name':
                        project = value.strip()
                        if version is not None:
                            return project, version

                    elif key == 'Version':
                        version = value.strip()
                        if project is not None:
                            return project, version


            elif name == 'setup.py':
                tgz.extract(name, _tempdir)

        # no PKG-INFO found, do it the hard way.
        command = ('cd %s/%s && %s setup.py --name --version'
                                % (_tempdir, names[0], sys.executable))
        popen = subprocess.Popen(command,
                                 stdout=subprocess.PIPE,
                                 shell=True,
                                )
        output = popen.communicate()[0]
        return output.splitlines()[:2]
    finally:
        tgz.close()


def main(argv=None):
    if argv is None:
        argv = sys.argv[1:]

    projects = {}
    for arg in argv:
        try:
            project, revision = _extractNameVersion(arg)
            projects.setdefault(project, []).append((revision, arg))
        except:
            continue

    items = projects.items()
    items.sort()

    os.makedirs('simple')
    top = open('simple/index.html', 'w')
    top.writelines(['<html>\n',
                    '<body>\n',
                    '<h1>Package Index</h1>\n',
                    '<ul>\n'])

    for key, value in items:
        print 'Project: %s' % key
        dirname = 'simple/%s' % key
        os.makedirs(dirname)
        top.write('<li><a href="%s">%s</a>\n' % (key, key))

        sub = open('simple/%s/index.html' % key, 'w')
        sub.writelines(['<html>\n',
                        '<body>\n',
                        '<h1>%s Distributions</h1>\n' % key,
                        '<ul>\n'])

        for revision, archive in value:
            print '  -> %s, %s' % (revision, archive)
            sub.write('<li><a href="/%s">%s</a>\n' % (archive, archive))

        sub.writelines(['</ul>\n',
                        '</body>\n',
                        '</html>\n'])

    top.writelines(['</ul>\n',
                    '</body>\n',
                    '</html>\n'])
    top.close()

if __name__ == '__main__':
    main()
    if _tempdir is not None:
        shutil.rmtree(_tempdir)