summaryrefslogtreecommitdiff
path: root/buildscripts/packager_enterprise.py
diff options
context:
space:
mode:
Diffstat (limited to 'buildscripts/packager_enterprise.py')
-rwxr-xr-xbuildscripts/packager_enterprise.py383
1 files changed, 383 insertions, 0 deletions
diff --git a/buildscripts/packager_enterprise.py b/buildscripts/packager_enterprise.py
new file mode 100755
index 00000000000..0ef782862d9
--- /dev/null
+++ b/buildscripts/packager_enterprise.py
@@ -0,0 +1,383 @@
+#!/usr/bin/env python
+"""Packager Enterprise module."""
+
+# This program makes Debian and RPM repositories for MongoDB, by
+# downloading our tarballs of statically linked executables and
+# insinuating them into Linux packages. It must be run on a
+# Debianoid, since Debian provides tools to make RPMs, but RPM-based
+# systems don't provide debian packaging crud.
+
+# Notes:
+#
+# * Almost anything that you want to be able to influence about how a
+# package construction must be embedded in some file that the
+# packaging tool uses for input (e.g., debian/rules, debian/control,
+# debian/changelog; or the RPM specfile), and the precise details are
+# arbitrary and silly. So this program generates all the relevant
+# inputs to the packaging tools.
+#
+# * Once a .deb or .rpm package is made, there's a separate layer of
+# tools that makes a "repository" for use by the apt/yum layers of
+# package tools. The layouts of these repositories are arbitrary and
+# silly, too.
+#
+# * Before you run the program on a new host, these are the
+# prerequisites:
+#
+# apt-get install dpkg-dev rpm debhelper fakeroot ia32-libs createrepo git-core libsnmp15
+# echo "Now put the dist gnupg signing keys in ~root/.gnupg"
+
+import errno
+from glob import glob
+import os
+import re
+import shutil
+import sys
+import tempfile
+import time
+
+import packager # pylint: disable=relative-import
+
+# The MongoDB names for the architectures we support.
+ARCH_CHOICES = ["x86_64", "ppc64le", "s390x", "arm64"]
+
+# Made up names for the flavors of distribution we package for.
+DISTROS = ["suse", "debian", "redhat", "ubuntu", "amazon", "amazon2"]
+
+
+class EnterpriseSpec(packager.Spec):
+ """EnterpriseSpec class."""
+
+ def suffix(self):
+ """Suffix."""
+ return "-enterprise" if int(self.ver.split(".")[1]) % 2 == 0 else "-enterprise-unstable"
+
+
+class EnterpriseDistro(packager.Distro):
+ """EnterpriseDistro class."""
+
+ def repodir(self, arch, build_os, spec): # noqa: D406,D407,D412,D413
+ """Return the directory where we'll place the package files.
+
+ This is for (distro, distro_version) in that distro's preferred repository
+ layout (as distinct from where that distro's packaging building
+ tools place the package files).
+
+ Packages will go into repos corresponding to the major release
+ series (2.5, 2.6, 2.7, 2.8, etc.) except for RC's and nightlies
+ which will go into special separate "testing" directories
+
+ Examples:
+
+ repo/apt/ubuntu/dists/precise/mongodb-enterprise/testing/multiverse/binary-amd64
+ repo/apt/ubuntu/dists/precise/mongodb-enterprise/testing/multiverse/binary-i386
+
+ repo/apt/ubuntu/dists/precise/mongodb-enterprise/2.5/multiverse/binary-amd64
+ repo/apt/ubuntu/dists/precise/mongodb-enterprise/2.5/multiverse/binary-i386
+
+ repo/apt/ubuntu/dists/trusty/mongodb-enterprise/2.5/multiverse/binary-amd64
+ repo/apt/ubuntu/dists/trusty/mongodb-enterprise/2.5/multiverse/binary-i386
+
+ repo/apt/debian/dists/wheezy/mongodb-enterprise/2.5/main/binary-amd64
+ repo/apt/debian/dists/wheezy/mongodb-enterprise/2.5/main/binary-i386
+
+ repo/yum/redhat/6/mongodb-enterprise/2.5/x86_64
+ repo/yum/redhat/6/mongodb-enterprise/2.5/i386
+
+ repo/zypper/suse/11/mongodb-enterprise/2.5/x86_64
+ repo/zypper/suse/11/mongodb-enterprise/2.5/i386
+
+ repo/zypper/suse/11/mongodb-enterprise/testing/x86_64
+ repo/zypper/suse/11/mongodb-enterprise/testing/i386
+ """
+
+ repo_directory = ""
+
+ if spec.is_pre_release():
+ repo_directory = "testing"
+ else:
+ repo_directory = spec.branch()
+
+ if re.search("^(debian|ubuntu)", self.dname):
+ return "repo/apt/%s/dists/%s/mongodb-enterprise/%s/%s/binary-%s/" % (
+ self.dname, self.repo_os_version(build_os), repo_directory, self.repo_component(),
+ self.archname(arch))
+ elif re.search("(redhat|fedora|centos|amazon)", self.dname):
+ return "repo/yum/%s/%s/mongodb-enterprise/%s/%s/RPMS/" % (
+ self.dname, self.repo_os_version(build_os), repo_directory, self.archname(arch))
+ elif re.search("(suse)", self.dname):
+ return "repo/zypper/%s/%s/mongodb-enterprise/%s/%s/RPMS/" % (
+ self.dname, self.repo_os_version(build_os), repo_directory, self.archname(arch))
+ else:
+ raise Exception("BUG: unsupported platform?")
+
+ def build_os(self, arch): # pylint: disable=too-many-branches
+ """Return the build os label in the binary package to download.
+
+ The labels "rhel57", "rhel62", "rhel67" and "rhel70" are for redhat,
+ the others are delegated to the super class.
+ """
+ # pylint: disable=too-many-return-statements
+ if arch == "ppc64le":
+ if self.dname == 'ubuntu':
+ return ["ubuntu1604"]
+ if self.dname == 'redhat':
+ return ["rhel71"]
+ return []
+ if arch == "s390x":
+ if self.dname == 'redhat':
+ return ["rhel67", "rhel72"]
+ if self.dname == 'suse':
+ return ["suse11", "suse12"]
+ if self.dname == 'ubuntu':
+ return ["ubuntu1604"]
+ return []
+ if arch == "arm64":
+ if self.dname == 'ubuntu':
+ return ["ubuntu1604"]
+ return []
+
+ if re.search("(redhat|fedora|centos)", self.dname):
+ return ["rhel70", "rhel62", "rhel57"]
+ return super(EnterpriseDistro, self).build_os(arch)
+ # pylint: enable=too-many-return-statements
+
+
+def main():
+ """Execute Main program."""
+
+ distros = [EnterpriseDistro(distro) for distro in DISTROS]
+
+ args = packager.get_args(distros, ARCH_CHOICES)
+
+ spec = EnterpriseSpec(args.server_version, args.metadata_gitspec, args.release_number)
+
+ oldcwd = os.getcwd()
+ srcdir = oldcwd + "/../"
+
+ # Where to do all of our work. Use a randomly-created directory if one
+ # is not passed in.
+ prefix = args.prefix
+ if prefix is None:
+ prefix = tempfile.mkdtemp()
+
+ print "Working in directory %s" % prefix
+
+ os.chdir(prefix)
+ try:
+ made_pkg = False
+ # Build a package for each distro/spec/arch tuple, and
+ # accumulate the repository-layout directories.
+ for (distro, arch) in packager.crossproduct(distros, args.arches):
+
+ for build_os in distro.build_os(arch):
+ if build_os in args.distros or not args.distros:
+
+ filename = tarfile(build_os, arch, spec)
+ packager.ensure_dir(filename)
+ shutil.copyfile(args.tarball, filename)
+
+ repo = make_package(distro, build_os, arch, spec, srcdir)
+ make_repo(repo, distro, build_os)
+
+ made_pkg = True
+
+ if not made_pkg:
+ raise Exception("No valid combination of distro and arch selected")
+
+ finally:
+ os.chdir(oldcwd)
+
+
+def tarfile(build_os, arch, spec):
+ """Return the location where we store the downloaded tarball for this package."""
+ return "dl/mongodb-linux-%s-enterprise-%s-%s.tar.gz" % (spec.version(), build_os, arch)
+
+
+def setupdir(distro, build_os, arch, spec):
+ """Return the setup directory name."""
+ # The setupdir will be a directory containing all inputs to the
+ # distro's packaging tools (e.g., package metadata files, init
+ # scripts, etc, along with the already-built binaries). In case
+ # the following format string is unclear, an example setupdir
+ # would be dst/x86_64/debian-sysvinit/wheezy/mongodb-org-unstable/
+ # or dst/x86_64/redhat/rhel57/mongodb-org-unstable/
+ return "dst/%s/%s/%s/%s%s-%s/" % (arch, distro.name(), build_os, distro.pkgbase(),
+ spec.suffix(), spec.pversion(distro))
+
+
+def unpack_binaries_into(build_os, arch, spec, where):
+ """Unpack the tarfile for (build_os, arch, spec) into directory where."""
+ rootdir = os.getcwd()
+ packager.ensure_dir(where)
+ # Note: POSIX tar doesn't require support for gtar's "-C" option,
+ # and Python's tarfile module prior to Python 2.7 doesn't have the
+ # features to make this detail easy. So we'll just do the dumb
+ # thing and chdir into where and run tar there.
+ os.chdir(where)
+ try:
+ packager.sysassert(["tar", "xvzf", rootdir + "/" + tarfile(build_os, arch, spec)])
+ release_dir = glob('mongodb-linux-*')[0]
+ for releasefile in "bin", "snmp", "LICENSE.txt", "README", "THIRD-PARTY-NOTICES", "MPL-2":
+ os.rename("%s/%s" % (release_dir, releasefile), releasefile)
+ os.rmdir(release_dir)
+ except Exception:
+ exc = sys.exc_value
+ os.chdir(rootdir)
+ raise exc
+ os.chdir(rootdir)
+
+
+def make_package(distro, build_os, arch, spec, srcdir):
+ """Construct the package for (arch, distro, spec).
+
+ Get the packaging files from srcdir and any user-specified suffix from suffixes.
+ """
+
+ sdir = setupdir(distro, build_os, arch, spec)
+ packager.ensure_dir(sdir)
+ # Note that the RPM packages get their man pages from the debian
+ # directory, so the debian directory is needed in all cases (and
+ # innocuous in the debianoids' sdirs).
+ for pkgdir in ["debian", "rpm"]:
+ print "Copying packaging files from %s to %s" % ("%s/%s" % (srcdir, pkgdir), sdir)
+ # FIXME: sh-dash-cee is bad. See if tarfile can do this.
+ packager.sysassert([
+ "sh", "-c",
+ "(cd \"%s\" && git archive %s %s/ ) | (cd \"%s\" && tar xvf -)" %
+ (srcdir, spec.metadata_gitspec(), pkgdir, sdir)
+ ])
+ # Splat the binaries and snmp files under sdir. The "build" stages of the
+ # packaging infrastructure will move the files to wherever they
+ # need to go.
+ unpack_binaries_into(build_os, arch, spec, sdir)
+ # Remove the mongoreplay binary due to libpcap dynamic
+ # linkage.
+ if os.path.exists(sdir + "bin/mongoreplay"):
+ os.unlink(sdir + "bin/mongoreplay")
+ return distro.make_pkg(build_os, arch, spec, srcdir)
+
+
+def make_repo(repodir, distro, build_os):
+ """Make the repo."""
+ if re.search("(debian|ubuntu)", repodir):
+ make_deb_repo(repodir, distro, build_os)
+ elif re.search("(suse|centos|redhat|fedora|amazon)", repodir):
+ packager.make_rpm_repo(repodir)
+ else:
+ raise Exception("BUG: unsupported platform?")
+
+
+def make_deb_repo(repo, distro, build_os):
+ """Make the Debian repo."""
+ # Note: the Debian repository Packages files must be generated
+ # very carefully in order to be usable.
+ oldpwd = os.getcwd()
+ os.chdir(repo + "../../../../../../")
+ try:
+ dirs = set([
+ os.path.dirname(deb)[2:]
+ for deb in packager.backtick(["find", ".", "-name", "*.deb"]).split()
+ ])
+ for directory in dirs:
+ st = packager.backtick(["dpkg-scanpackages", directory, "/dev/null"])
+ with open(directory + "/Packages", "w") as fh:
+ fh.wmake_deb_reporite(st)
+ bt = packager.backtick(["gzip", "-9c", directory + "/Packages"])
+ with open(directory + "/Packages.gz", "wb") as fh:
+ fh.write(bt)
+ finally:
+ os.chdir(oldpwd)
+ # Notes: the Release{,.gpg} files must live in a special place,
+ # and must be created after all the Packages.gz files have been
+ # done.
+ s1 = """Origin: mongodb
+Label: mongodb
+Suite: %s
+Codename: %s/mongodb-enterprise
+Architectures: amd64 ppc64el s390x arm64
+Components: %s
+Description: MongoDB packages
+""" % (distro.repo_os_version(build_os), distro.repo_os_version(build_os), distro.repo_component())
+ if os.path.exists(repo + "../../Release"):
+ os.unlink(repo + "../../Release")
+ if os.path.exists(repo + "../../Release.gpg"):
+ os.unlink(repo + "../../Release.gpg")
+ oldpwd = os.getcwd()
+ os.chdir(repo + "../../")
+ s2 = packager.backtick(["apt-ftparchive", "release", "."])
+ try:
+ with open("Release", 'w') as fh:
+ fh.write(s1)
+ fh.write(s2)
+ finally:
+ os.chdir(oldpwd)
+
+
+def move_repos_into_place(src, dst): # pylint: disable=too-many-branches
+ """Move the repos into place."""
+ # Find all the stuff in src/*, move it to a freshly-created
+ # directory beside dst, then play some games with symlinks so that
+ # dst is a name the new stuff and dst+".old" names the previous
+ # one. This feels like a lot of hooey for something so trivial.
+
+ # First, make a crispy fresh new directory to put the stuff in.
+ idx = 0
+ while True:
+ date_suffix = time.strftime("%Y-%m-%d")
+ dname = dst + ".%s.%d" % (date_suffix, idx)
+ try:
+ os.mkdir(dname)
+ break
+ except OSError:
+ exc = sys.exc_value
+ if exc.errno == errno.EEXIST:
+ pass
+ else:
+ raise exc
+ idx = idx + 1
+
+ # Put the stuff in our new directory.
+ for src_file in os.listdir(src):
+ packager.sysassert(["cp", "-rv", src + "/" + src_file, dname])
+
+ # Make a symlink to the new directory; the symlink will be renamed
+ # to dst shortly.
+ idx = 0
+ while True:
+ tmpnam = dst + ".TMP.%d" % idx
+ try:
+ os.symlink(dname, tmpnam)
+ break
+ except OSError: # as exc: # Python >2.5
+ exc = sys.exc_value
+ if exc.errno == errno.EEXIST:
+ pass
+ else:
+ raise exc
+ idx = idx + 1
+
+ # Make a symlink to the old directory; this symlink will be
+ # renamed shortly, too.
+ oldnam = None
+ if os.path.exists(dst):
+ idx = 0
+ while True:
+ oldnam = dst + ".old.%d" % idx
+ try:
+ os.symlink(os.readlink(dst), oldnam)
+ break
+ except OSError: # as exc: # Python >2.5
+ exc = sys.exc_value
+ if exc.errno == errno.EEXIST:
+ pass
+ else:
+ raise exc
+
+ os.rename(tmpnam, dst)
+ if oldnam:
+ os.rename(oldnam, dst + ".old")
+
+
+if __name__ == "__main__":
+ main()