diff options
author | Antonin Kral <a.kral@bobek.cz> | 2010-08-11 12:38:57 +0200 |
---|---|---|
committer | Antonin Kral <a.kral@bobek.cz> | 2010-08-11 12:38:57 +0200 |
commit | 7645618fd3914cb8a20561625913c20d49504a49 (patch) | |
tree | 8370f846f58f6d71165b7a0e2eda04648584ec76 /buildscripts/makedist.py | |
parent | 68c73c3c7608b4c87f07440dc3232801720b1168 (diff) | |
download | mongodb-7645618fd3914cb8a20561625913c20d49504a49.tar.gz |
Imported Upstream version 1.6.0
Diffstat (limited to 'buildscripts/makedist.py')
-rw-r--r-- | buildscripts/makedist.py | 645 |
1 files changed, 387 insertions, 258 deletions
diff --git a/buildscripts/makedist.py b/buildscripts/makedist.py index 35383b9..1928b76 100644 --- a/buildscripts/makedist.py +++ b/buildscripts/makedist.py @@ -1,23 +1,13 @@ #!/usr/bin/env python -# makedist.py: make a distro package (on an EC2 instance) +# makedist.py: make a distro package (on an EC2 (or sometimes +# RackSpace) instance) # For ease of use, put a file called settings.py someplace in your # sys.path, containing something like the following: # makedist = { -# # ec2-api-tools needs the following two set in the process -# # environment. -# "EC2_HOME": "/path/to/ec2-api-tools", -# # The EC2 tools won't run at all unless this variable is set to a directory -# # relative to which a "bin/java" exists. -# "JAVA_HOME" : "/usr", -# # All the ec2-api-tools take these two as arguments. -# # Alternatively, you can set the environment variables EC2_PRIVATE_KEY and EC2_CERT -# # respectively, leave these two out of settings.py, and let the ec2 tools default. -# "ec2_pkey": "/path/to/pk-file.pem" -# "ec2_cert" : "/path/to/cert-file.pem" -# # This gets supplied to ec2-run-instances to rig up an ssh key for +# # This gets supplied to EC2 to rig up an ssh key for # # the remote user. # "ec2_sshkey" : "key-id", # # And so we need to tell our ssh processes where to find the @@ -54,6 +44,14 @@ import socket import time import os.path import tempfile +import string +import settings + +from libcloud.types import Provider +from libcloud.providers import get_driver +from libcloud.drivers.ec2 import EC2NodeDriver, NodeImage +from libcloud.base import Node, NodeImage, NodeSize, NodeState +from libcloud.ssh import ParamikoSSHClient # For the moment, we don't handle any of the errors we raise, so it # suffices to have a simple subclass of Exception that just @@ -141,139 +139,125 @@ class EC2InstanceConfigurator(BaseConfigurator): (("centos", "5.4", "x86_64"), "ami-ccb35ea5"), (("fedora", "8", "x86_64"), "ami-2547a34c"), (("fedora", "8", "x86"), "ami-5647a33f"))), + ("rackspace_imgname", + ((("fedora", "11", "x86_64"), "Fedora 11"), + (("fedora", "12", "x86_64"), "Fedora 12"), + (("fedora", "13", "x86_64"), "Fedora 13"))), ("ec2_mtype", ((("*", "*", "x86"), "m1.small"), (("*", "*", "x86_64"), "m1.large"))), ] +class nodeWrapper(object): + def __init__(self, configurator, **kwargs): + self.terminate = False if "no_terminate" in kwargs else True + self.use_internal_name = False + + def getHostname(self): + internal_name=self.node.private_ip[0] + public_name=self.node.public_ip[0] + if not (internal_name or external_name): + raise Exception('host has no name?') + if self.use_internal_name: + # FIXME: by inspection, it seems this is sometimes the + # empty string. Dunno if that's EC2 or libcloud being + # stupid, but it's not good. + if internal_name: + return internal_name + else: + return public_name + else: + return public_name + + def initwait(self): + print "waiting for node to spin up" + # Wait for EC2 to tell us the node is running. + while 1: + n=None + # EC2 sometimes takes a while to report a node. + for i in range(6): + nodes = [n for n in self.list_nodes() if (n.id==self.node.id)] + if len(nodes)>0: + n=nodes[0] + break + else: + time.sleep(10) + if not n: + raise Exception("couldn't find node with id %s" % self.node.id) + if n.state == NodeState.PENDING: + time.sleep(10) + else: + self.node = n + break + print "ok" + # Now wait for the node's sshd to be accepting connections. + print "waiting for ssh" + sshwait = True + if sshwait == False: + return + while sshwait: + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + try: + try: + s.connect((self.node.public_ip[0], 22)) + sshwait = False + print "connected on port 22 (ssh)" + time.sleep(15) # arbitrary timeout, in case the + # remote sshd is slow. + except socket.error, err: + pass + finally: + s.close() + time.sleep(3) # arbitrary timeout + print "ok" + + def __enter__(self): + self.start() + # Note: we don't do an initwait() in __enter__ because if an + # exception is raised during __enter__, __exit__ doesn't get + # run (and by inspection RackSpace doesn't let you kill a node + # that hasn't finished booting yet). + return self + + def __exit__(self, type, value, traceback): + self.stop() -class EC2Instance (object): + def stop(self): + if self.terminate: + print "Destroying node %s" % self.node.id + self.node.destroy() + else: + print "Not terminating EC2 instance %s." % self.node.id + + def setup(self): + pass + +class EC2Instance (nodeWrapper): def __init__(self, configurator, **kwargs): + super(EC2Instance, self).__init__(configurator, **kwargs) # Stuff we need to start an instance: AMI name, key and cert # files. AMI and mtype default to configuration in this file, # but can be overridden. self.ec2_ami = configurator.findOrDefault(kwargs, "ec2_ami") self.ec2_mtype = configurator.findOrDefault(kwargs, "ec2_mtype") - self.use_internal_name = True if "use_internal_name" in kwargs else False - - # Authentication stuff defaults according to the conventions - # of the ec2-api-tools. - self.ec2_cert=kwargs["ec2_cert"] - self.ec2_pkey=kwargs["ec2_pkey"] self.ec2_sshkey=kwargs["ec2_sshkey"] # FIXME: this needs to be a commandline option self.ec2_groups = ["default", "buildbot-slave", "dist-slave"] - self.terminate = False if "no_terminate" in kwargs else True - def parsedesc (self, hdl): - line1=hdl.readline() - splitline1=line1.split() - (_, reservation, unknown1, groupstr) = splitline1[:4] - groups = groupstr.split(',') - self.ec2_reservation = reservation - self.ec2_unknown1 = unknown1 - self.ec2_groups = groups - # I haven't seen more than 4 data fields in one of these - # descriptions, but what do I know? - if len(splitline1)>4: - print >> sys.stderr, "more than 4 fields in description line 1\n%s\n" % line1 - self.ec2_extras1 = splitline1[4:] - line2=hdl.readline() - splitline2=line2.split() - # The jerks make it tricky to parse line 2: the fields are - # dependent on the instance's state. - (_, instance, ami, status_or_hostname) = splitline2[:4] - self.ec2_instance = instance - if ami != self.ec2_ami: - print >> sys.stderr, "warning: AMI in description isn't AMI we invoked\nwe started %s, but got\n%s", (self.ec2_ami, line2) - # FIXME: are there other non-running statuses? - if status_or_hostname in ["pending", "terminated"]: - self.ec2_status = status_or_hostname - self.ec2_running = False - index = 4 - self.ec2_storage = splitline2[index+8] - else: - self.ec2_running = True - index = 6 - self.ec2_status = splitline2[5] - self.ec2_external_hostname = splitline2[3] - self.ec2_internal_hostname = splitline2[4] - self.ec2_external_ipaddr = splitline2[index+8] - self.ec2_internal_ipaddr = splitline2[index+9] - self.ec2_storage = splitline2[index+10] - (sshkey, unknown2, mtype, starttime, zone, unknown3, unknown4, monitoring) = splitline2[index:index+8] - # FIXME: potential disagreement with the supplied sshkey? - self.ec2_sshkey = sshkey - self.ec2_unknown2 = unknown2 - # FIXME: potential disagreement with the supplied mtype? - self.ec2_mtype = mtype - self.ec2_starttime = starttime - self.ec2_zone = zone - self.ec2_unknown3 = unknown3 - self.ec2_unknown4 = unknown4 - self.ec2_monitoring = monitoring def start(self): "Fire up a fresh EC2 instance." - groups = reduce(lambda x, y : x+y, [["-g", i] for i in self.ec2_groups], []) - argv = ["ec2-run-instances", - self.ec2_ami, "-K", self.ec2_pkey, "-C", self.ec2_cert, - "-k", self.ec2_sshkey, "-t", self.ec2_mtype] + groups - self.ec2_running = False - print "running %s" % argv - proc = subprocess.Popen(argv, stdout=subprocess.PIPE) - try: - self.parsedesc(proc.stdout) - if self.ec2_instance == "": - raise SimpleError("instance id is empty") - else: - print "Instance id: %s" % self.ec2_instance - finally: - r = proc.wait() - if r != 0: - raise SimpleError("ec2-run-instances exited %d", r) - - def initwait(self): - # poll the instance description until we get a hostname. - # Note: it seems there can be a time interval after - # ec2-run-instance finishes during which EC2 will tell us that - # the instance ID doesn't exist. This is sort of bad. - state = "pending" - numtries = 0 - giveup = 5 - - while not self.ec2_running: - time.sleep(15) # arbitrary - argv = ["ec2-describe-instances", "-K", self.ec2_pkey, "-C", self.ec2_cert, self.ec2_instance] - proc = subprocess.Popen(argv, stdout=subprocess.PIPE) - try: - self.parsedesc(proc.stdout) - except Exception, e: - r = proc.wait() - if r < giveup: - print sys.stderr, str(e) - continue - else: - raise SimpleError("ec2-describe-instances exited %d", r) - numtries+=1 - - def stop(self): - if self.terminate: - LocalHost.runLocally(["ec2-terminate-instances", "-K", self.ec2_pkey, "-C", self.ec2_cert, self.ec2_instance]) - else: - print "Not terminating EC2 instance %s." % self.ec2_instance - - def __enter__(self): - self.start() - return self - - def __exit__(self, type, value, traceback): - self.stop() - - def getHostname(self): - return self.ec2_internal_hostname if self.use_internal_name else self.ec2_external_hostname + EC2 = get_driver(Provider.EC2) + self.driver = EC2NodeDriver(settings.id, settings.key) + image = NodeImage(self.ec2_ami, self.ec2_ami, EC2) + size = NodeSize(self.ec2_mtype, self.ec2_mtype, None, None, None, None, EC2) + self.node = self.driver.create_node(image=image, name=self.ec2_ami, size=size, keyname=self.ec2_sshkey, securitygroup=self.ec2_groups) + print "Created node %s" % self.node.id + + def list_nodes(self): + return self.driver.list_nodes() class SshConnectionConfigurator (BaseConfigurator): def __init__(self, **kwargs): @@ -287,6 +271,7 @@ class SshConnectionConfigurator (BaseConfigurator): (("ubuntu", "9.4", "*"), "root"), (("ubuntu", "8.10", "*"), "root"), (("ubuntu", "8.4", "*"), "ubuntu"), + (("fedora", "*", "*"), "root"), (("centos", "*", "*"), "root"))), ] @@ -300,28 +285,7 @@ class SshConnection (object): # Gets set to False when we think we can ssh in. self.sshwait = True - def sshWait(self): - "Poll until somebody's listening on port 22" - - if self.sshwait == False: - return - while self.sshwait: - s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - try: - try: - s.connect((self.ssh_host, 22)) - self.sshwait = False - print "connected on port 22 (ssh)" - time.sleep(15) # arbitrary timeout, in case the - # remote sshd is slow. - except socket.error, err: - pass - finally: - s.close() - time.sleep(3) # arbitrary timeout - def initSsh(self): - self.sshWait() ctlpath="/tmp/ec2-ssh-%s-%s-%s" % (self.ssh_host, self.ssh_login, os.getpid()) argv = ["ssh", "-o", "StrictHostKeyChecking no", "-M", "-o", "ControlPath %s" % ctlpath, @@ -349,7 +313,6 @@ class SshConnection (object): self.ssh_host] + argv) def sendFiles(self, files): - self.sshWait() for (localfile, remotefile) in files: LocalHost.runLocally(["scp", "-o", "StrictHostKeyChecking no", "-o", "ControlMaster auto", @@ -360,8 +323,6 @@ class SshConnection (object): ("" if remotefile is None else remotefile) ]) def recvFiles(self, files): - self.sshWait() - print files for (remotefile, localfile) in files: LocalHost.runLocally(["scp", "-o", "StrictHostKeyChecking no", "-o", "ControlMaster auto", @@ -402,7 +363,8 @@ s/^Package:.*mongodb/Package: {pkg_name}{pkg_name_suffix}\\ Conflicts: {pkg_name_conflicts}/' debian/control; ) || exit 1 ( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i 's|$(CURDIR)/debian/mongodb/|$(CURDIR)/debian/{pkg_name}{pkg_name_suffix}/|g' debian/rules) || exit 1 ( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i 's|debian/mongodb.manpages|debian/{pkg_name}{pkg_name_suffix}.manpages|g' debian/rules) || exit 1 -( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i '/^Name:/s/.*/Name: {pkg_name}{pkg_name_suffix}/; /^Version:/s/.*/Version: {pkg_version}/;' rpm/mongo.spec ) +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i '/^Name:/s/.*/Name: {pkg_name}{pkg_name_suffix}\\ +Conflicts: {pkg_name_conflicts}/; /^Version:/s/.*/Version: {pkg_version}/; /Requires.*mongo/s/mongo/{pkg_name}{pkg_name_suffix}/;' rpm/mongo.spec ) # Debian systems require some ridiculous workarounds to get an init # script at /etc/init.d/mongodb when the packge name isn't the init # script name. Note: dh_installinit --name won't work, because that @@ -412,6 +374,22 @@ Conflicts: {pkg_name_conflicts}/' debian/control; ) || exit 1 ln debian/init.d debian/{pkg_name}{pkg_name_suffix}.mongodb.init && ln debian/mongodb.upstart debian/{pkg_name}{pkg_name_suffix}.mongodb.upstart && sed -i 's/dh_installinit/dh_installinit --name=mongodb/' debian/rules) || exit 1 +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && cat debian/rules) +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && cat rpm/mongo.spec) +""" + + # If we're just packaging up nightlies, do this: + nightly_build_mangle_files=""" +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i '/scons[[:space:]]*$/d; s^scons.*install^mkdir -p debian/{pkg_name}{pkg_name_suffix} \&\& wget http://downloads.mongodb.org/linux/mongodb-linux-{mongo_arch}-{mongo_pub_version}.tgz \&\& tar xzvf mongodb-linux-{mongo_arch}-{mongo_pub_version}.tgz \&\& find `tar tzf mongodb-linux-{mongo_arch}-{mongo_pub_version}.tgz | sed "s|/.*||" | sort -u | head -n1` -mindepth 1 -maxdepth 1 -type d | xargs -n1 -IARG mv -v ARG debian/{pkg_name}{pkg_name_suffix}/usr \&\& (rm debian/{pkg_name}{pkg_name_suffix}/usr/bin/mongosniff || true)^' debian/rules) +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i 's/^BuildRequires:.*//; s/scons.*\ -c//; s/scons.*\ all//; s^scons.*install^(mkdir -p $RPM_BUILD_ROOT/usr ; cd /tmp \&\& curl http://downloads.mongodb.org/linux/mongodb-linux-{mongo_arch}-{mongo_pub_version}.tgz > mongodb-linux-{mongo_arch}-{mongo_pub_version}.tgz \&\& tar xzvf mongodb-linux-{mongo_arch}-{mongo_pub_version}.tgz \&\& find `tar tzf mongodb-linux-{mongo_arch}-{mongo_pub_version}.tgz | sed "s|/.*||" | sort -u | head -n1` -mindepth 1 -maxdepth 1 -type d | xargs -n1 -IARG cp -pRv ARG $RPM_BUILD_ROOT/usr \&\& (rm -r $RPM_BUILD_ROOT/usr/bin/mongosniff $RPM_BUILD_ROOT/usr/lib64/libmongoclient.a $RPM_BUILD_ROOT/usr/lib/libmongoclient.a $RPM_BUILD_ROOT/usr/include/mongo || true))^' rpm/mongo.spec) +# Upstream nightlies no longer contain libmongoclient. +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i '/%package devel/{{N;N;d;}}; /%description devel/{{N;N;N;N;N;d;}}; /%files devel/{{N;N;N;d;}};' rpm/mongo.spec ) +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && cat debian/rules) +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && cat rpm/mongo.spec) +""" +#$RPM_BUILD_ROOT/usr/lib/libmongoclient.a $RPM_BUILD_ROOT/usr/lib64/libmongoclient.a + mangle_files_for_new_deb_xulrunner_commands = """ +( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}" && sed -i 's/xulrunner-dev/xulrunner-1.9.2-dev/g' debian/control ) """ mangle_files_for_ancient_redhat_commands = """ @@ -432,8 +410,10 @@ mkdir -p "{pkg_product_dir}/{distro_version}/10gen/binary-{distro_arch}" mkdir -p "{pkg_product_dir}/{distro_version}/10gen/source" ( cd "{pkg_name}{pkg_name_suffix}-{pkg_version}"; debuild ) || exit 1 # Try installing it -dpkg -i *.deb +dpkg -i {pkg_name}{pkg_name_suffix}*.deb ps ax | grep mongo || {{ echo "no running mongo" >/dev/stderr; exit 1; }} +dpkg --remove $(for f in {pkg_name}{pkg_name_suffix}*.deb ; do echo ${{f%%_*}}; done) +dpkg --purge $(for f in {pkg_name}{pkg_name_suffix}*.deb ; do echo ${{f%%_*}}; done) cp {pkg_name}{pkg_name_suffix}*.deb "{pkg_product_dir}/{distro_version}/10gen/binary-{distro_arch}" cp {pkg_name}{pkg_name_suffix}*.dsc "{pkg_product_dir}/{distro_version}/10gen/source" cp {pkg_name}{pkg_name_suffix}*.tar.gz "{pkg_product_dir}/{distro_version}/10gen/source" @@ -445,10 +425,11 @@ rpm -Uvh http://download.fedora.redhat.com/pub/epel/5/{distro_arch}/epel-release yum -y install {pkg_prereq_str} """ rpm_build_commands=""" -for d in BUILD BUILDROOT RPMS SOURCES SPECS SRPMS; do mkdir -p /usr/src/redhat/$d; done -cp -v "{pkg_name}{pkg_name_suffix}-{pkg_version}/rpm/mongo.spec" /usr/src/redhat/SPECS -tar -cpzf /usr/src/redhat/SOURCES/"{pkg_name}{pkg_name_suffix}-{pkg_version}".tar.gz "{pkg_name}{pkg_name_suffix}-{pkg_version}" -rpmbuild -ba /usr/src/redhat/SPECS/mongo.spec +for d in BUILD BUILDROOT RPMS SOURCES SPECS SRPMS; do mkdir -p {rpmbuild_dir}/$d; done +cp -v "{pkg_name}{pkg_name_suffix}-{pkg_version}/rpm/mongo.spec" {rpmbuild_dir}/SPECS/{pkg_name}{pkg_name_suffix}.spec +tar -cpzf {rpmbuild_dir}/SOURCES/"{pkg_name}{pkg_name_suffix}-{pkg_version}".tar.gz "{pkg_name}{pkg_name_suffix}-{pkg_version}" +rpmbuild -ba --target={distro_arch} {rpmbuild_dir}/SPECS/{pkg_name}{pkg_name_suffix}.spec +# FIXME: should install the rpms, check if mongod is running. """ # FIXME: this is clean, but adds 40 minutes or so to the build process. old_rpm_precommands = """ @@ -474,25 +455,28 @@ rpm -ivh /usr/src/redhat/RPMS/{distro_arch}/boost-devel-1.38.0-1.{distro_arch}.r # On very old Debianoids, libboost-<foo>-dev will be some old # boost that's not as thready as we want, but which Eliot says - # will work. - very_old_deb_prereqs = ["libboost-thread-dev", "libboost-filesystem-dev", "libboost-program-options-dev", "libboost-date-time-dev", "libboost-dev", "xulrunner1.9-dev"] + # will work; on very new Debianoids, libbost-<foo>-dev is what we + # want. + unversioned_deb_boost_prereqs = ["libboost-thread-dev", "libboost-filesystem-dev", "libboost-program-options-dev", "libboost-date-time-dev", "libboost-dev"] + # On some in-between Debianoids, libboost-<foo>-dev is still a + # 1.34, but 1.35 packages are available, so we want those. + versioned_deb_boost_prereqs = ["libboost-thread1.35-dev", "libboost-filesystem1.35-dev", "libboost-program-options1.35-dev", "libboost-date-time1.35-dev", "libboost1.35-dev"] - # On less old (but still old!) Debianoids, libboost-<foo>-dev is - # still a 1.34, but 1.35 packages are available, so we want those. - old_deb_prereqs = ["libboost-thread1.35-dev", "libboost-filesystem1.35-dev", "libboost-program-options1.35-dev", "libboost-date-time1.35-dev", "libboost1.35-dev", "xulrunner-dev"] + unversioned_deb_xulrunner_prereqs = ["xulrunner-dev"] - # On newer Debianoids, libbost-<foo>-dev is some sufficiently new - # thing. - new_deb_prereqs = [ "libboost-thread-dev", "libboost-filesystem-dev", "libboost-program-options-dev", "libboost-date-time-dev", "libboost-dev", "xulrunner-dev" ] + old_versioned_deb_xulrunner_prereqs = ["xulrunner-1.9-dev"] + new_versioned_deb_xulrunner_prereqs = ["xulrunner-1.9.2-dev"] common_deb_prereqs = [ "build-essential", "dpkg-dev", "libreadline-dev", "libpcap-dev", "libpcre3-dev", "git-core", "scons", "debhelper", "devscripts", "git-core" ] centos_preqres = ["js-devel", "readline-devel", "pcre-devel", "gcc-c++", "scons", "rpm-build", "git" ] - fedora_prereqs = ["js-devel", "readline-devel", "pcre-devel", "gcc-c++", "scons", "rpm-build", "git" ] + fedora_prereqs = ["js-devel", "readline-devel", "pcre-devel", "gcc-c++", "scons", "rpm-build", "git", "curl" ] def __init__(self, **kwargs): super(ScriptFileConfigurator, self).__init__(**kwargs) - if kwargs["mongo_version"][0] == 'r': + # FIXME: this method is disabled until we get back around to + # actually building from source. + if None: # kwargs["mongo_version"][0] == 'r': self.get_mongo_commands = """ wget -Otarball.tgz "http://github.com/mongodb/mongo/tarball/{mongo_version}"; tar xzf tarball.tgz @@ -502,7 +486,9 @@ mv "`tar tzf tarball.tgz | sed 's|/.*||' | sort -u | head -n1`" "{pkg_name}{pkg_ self.get_mongo_commands = """ git clone git://github.com/mongodb/mongo.git """ - if kwargs['mongo_version'][0] == 'v': + # This is disabled for the moment. it's for building the + # tip of some versioned branch. + if None: #kwargs['mongo_version'][0] == 'v': self.get_mongo_commands +=""" ( cd mongo && git archive --prefix="{pkg_name}{pkg_name_suffix}-{pkg_version}/" "`git log origin/{mongo_version} | sed -n '1s/^commit //p;q'`" ) | tar xf - """ @@ -518,81 +504,188 @@ git clone git://github.com/mongodb/mongo.git self.configuration += [("pkg_product_dir", ((("ubuntu", "*", "*"), self.deb_productdir), (("debian", "*", "*"), self.deb_productdir), - (("fedora", "*", "*"), self.rpm_productdir), - (("centos", "*", "*"), self.rpm_productdir))), + (("fedora", "*", "*"), "~/rpmbuild/RPMS"), + (("centos", "*", "*"), "/usr/src/redhat/RPMS"))), ("pkg_prereqs", ((("ubuntu", "9.4", "*"), - self.old_deb_prereqs + self.common_deb_prereqs), + self.versioned_deb_boost_prereqs + self.unversioned_deb_xulrunner_prereqs + self.common_deb_prereqs), (("ubuntu", "9.10", "*"), - self.new_deb_prereqs + self.common_deb_prereqs), + self.unversioned_deb_boost_prereqs + self.unversioned_deb_xulrunner_prereqs + self.common_deb_prereqs), (("ubuntu", "10.4", "*"), - self.new_deb_prereqs + self.common_deb_prereqs), + self.unversioned_deb_boost_prereqs + self.new_versioned_deb_xulrunner_prereqs + self.common_deb_prereqs), (("ubuntu", "8.10", "*"), - self.old_deb_prereqs + self.common_deb_prereqs), + self.versioned_deb_boost_prereqs + self.unversioned_deb_xulrunner_prereqs + self.common_deb_prereqs), (("ubuntu", "8.4", "*"), - self.very_old_deb_prereqs + self.common_deb_prereqs), + self.unversioned_deb_boost_prereqs + self.old_versioned_deb_xulrunner_prereqs + self.common_deb_prereqs), (("debian", "5.0", "*"), - self.old_deb_prereqs + self.common_deb_prereqs), - (("fedora", "8", "*"), + self.versioned_deb_boost_prereqs + self.unversioned_deb_xulrunner_prereqs + self.common_deb_prereqs), + (("fedora", "*", "*"), self.fedora_prereqs), (("centos", "5.4", "*"), self.centos_preqres))), + # FIXME: this is deprecated ("commands", ((("debian", "*", "*"), - self.preamble_commands + self.deb_prereq_commands + self.get_mongo_commands + self.mangle_files_commands + self.deb_build_commands), - (("ubuntu", "*", "*"), + self.deb_prereq_commands + self.get_mongo_commands + self.mangle_files_commands + self.deb_build_commands), + (("ubuntu", "10.4", "*"), + self.preamble_commands + self.deb_prereq_commands + self.get_mongo_commands + self.mangle_files_commands + self.mangle_files_for_new_deb_xulrunner_commands + self.deb_build_commands), + (("ubuntu", "*", "*"), self.preamble_commands + self.deb_prereq_commands + self.get_mongo_commands + self.mangle_files_commands + self.deb_build_commands), (("centos", "*", "*"), self.preamble_commands + self.old_rpm_precommands + self.rpm_prereq_commands + self.get_mongo_commands + self.mangle_files_commands + self.mangle_files_for_ancient_redhat_commands + self.rpm_build_commands), (("fedora", "*", "*"), self.preamble_commands + self.old_rpm_precommands + self.rpm_prereq_commands + self.get_mongo_commands + self.mangle_files_commands + self.rpm_build_commands))), + ("preamble_commands", + ((("*", "*", "*"), self.preamble_commands), + )), + ("install_prereqs", + ((("debian", "*", "*"), self.deb_prereq_commands), + (("ubuntu", "*", "*"), self.deb_prereq_commands), + (("centos", "*", "*"), self.rpm_prereq_commands), + (("fedora", "*", "*"), self.rpm_prereq_commands))), + ("get_mongo", + ((("*", "*", "*"), self.get_mongo_commands), + )), + ("mangle_mongo", + ((("debian", "*", "*"), self.mangle_files_commands), + (("ubuntu", "10.4", "*"), + self.mangle_files_commands + self.mangle_files_for_new_deb_xulrunner_commands), + (("ubuntu", "*", "*"), self.mangle_files_commands), + (("centos", "*", "*"), + self.mangle_files_commands + self.mangle_files_for_ancient_redhat_commands), + (("fedora", "*", "*"), + self.mangle_files_commands))), + ("build_prerequisites", + ((("fedora", "*", "*"), self.old_rpm_precommands), + (("centos", "*", "*"), self.old_rpm_precommands), + (("*", "*", "*"), ''))), + ("install_for_packaging", + ((("debian", "*", "*"),""), + (("ubuntu", "*", "*"),""), + (("fedora", "*", "*"), ""), + (("centos", "*", "*"),""))), + ("build_package", + ((("debian", "*", "*"), + self.deb_build_commands), + (("ubuntu", "*", "*"), + self.deb_build_commands), + (("fedora", "*", "*"), + self.rpm_build_commands), + (("centos", "*", "*"), + self.rpm_build_commands))), ("pkg_name", ((("debian", "*", "*"), "mongodb"), (("ubuntu", "*", "*"), "mongodb"), (("centos", "*", "*"), "mongo"), - - (("fedora", "*", "*"), "mongo") - )), + (("fedora", "*", "*"), "mongo"))), + # FIXME: there should be a command-line argument for this. ("pkg_name_conflicts", - ((("*", "*", "*"), ["", "-stable", "-unstable", "-snapshot"]), - )) - ] + ((("*", "*", "*"), ["", "-stable", "-unstable", "-snapshot", "-oldstable"]), + )), + ("rpmbuild_dir", + ((("fedora", "*", "*"), "~/rpmbuild"), + (("centos", "*", "*"), "/usr/src/redhat"), + (("*", "*","*"), ''), + )), + ] class ScriptFile(object): def __init__(self, configurator, **kwargs): - self.mongo_version = kwargs["mongo_version"] - self.pkg_version = kwargs["pkg_version"] - self.pkg_name_suffix = kwargs["pkg_name_suffix"] if "pkg_name_suffix" in kwargs else "" + self.configurator = configurator + self.mongo_version_spec = kwargs['mongo_version_spec'] + self.mongo_arch = kwargs["arch"] if kwargs["arch"] == "x86_64" else "i686" self.pkg_prereqs = configurator.default("pkg_prereqs") self.pkg_name = configurator.default("pkg_name") self.pkg_product_dir = configurator.default("pkg_product_dir") - self.pkg_name_conflicts = configurator.default("pkg_name_conflicts") if self.pkg_name_suffix else [] - self.pkg_name_conflicts.remove(self.pkg_name_suffix) if self.pkg_name_suffix and self.pkg_name_suffix in self.pkg_name_conflicts else [] - self.formatter = configurator.default("commands") + #self.formatter = configurator.default("commands") self.distro_name = configurator.default("distro_name") self.distro_version = configurator.default("distro_version") self.distro_arch = configurator.default("distro_arch") + def bogoformat(self, fmt, **kwargs): + r = '' + i = 0 + while True: + c = fmt[i] + if c in '{}': + i+=1 + c2=fmt[i] + if c2 == c: + r+=c + else: + j=i + while True: + p=fmt[j:].find('}') + if p == -1: + raise Exception("malformed format string starting at %d: no closing brace" % i) + else: + j+=p + if len(fmt) > (j+1) and fmt[j+1]=='}': + j+=2 + else: + break + key = fmt[i:j] + r+=kwargs[key] + i=j + else: + r+=c + i+=1 + if i==len(fmt): + return r + + def fmt(self, formatter, **kwargs): + try: + return string.Formatter.format(formatter, kwargs) + finally: + return self.bogoformat(formatter, **kwargs) + def genscript(self): - return self.formatter.format(mongo_version=self.mongo_version, - distro_name=self.distro_name, - distro_version=self.distro_version, - distro_arch=self.distro_arch, - pkg_prereq_str=" ".join(self.pkg_prereqs), - pkg_name=self.pkg_name, - pkg_name_suffix=self.pkg_name_suffix, - pkg_version=self.pkg_version, - pkg_product_dir=self.pkg_product_dir, - # KLUDGE: rpm specs and deb - # control files use - # comma-separated conflicts, - # but there's no reason to - # suppose this works elsewhere - pkg_name_conflicts = ", ".join([self.pkg_name+conflict for conflict in self.pkg_name_conflicts]) - ) + script='' + formatter = self.configurator.default("preamble_commands") + self.configurator.default("install_prereqs") + script+=self.fmt(formatter, + distro_name=self.distro_name, + distro_version=self.distro_version, + distro_arch=self.distro_arch, + pkg_name=self.pkg_name, + pkg_product_dir=self.pkg_product_dir, + mongo_arch=self.mongo_arch, + pkg_prereq_str=" ".join(self.pkg_prereqs), + ) + + specs=self.mongo_version_spec.split(',') + for spec in specs: + (version, pkg_name_suffix, pkg_version) = parse_mongo_version_spec(spec) + mongo_version = version if version[0] != 'n' else ('HEAD' if version == 'nlatest' else 'r'+version[1:]) #'HEAD' + mongo_pub_version = version.lstrip('n') if version[0] in 'n' else 'latest' + pkg_name_suffix = pkg_name_suffix if pkg_name_suffix else '' + pkg_version = pkg_version + pkg_name_conflicts = list(self.configurator.default("pkg_name_conflicts") if pkg_name_suffix else []) + pkg_name_conflicts.remove(pkg_name_suffix) if pkg_name_suffix and pkg_name_suffix in pkg_name_conflicts else [] + formatter = self.configurator.default("get_mongo") + self.configurator.default("mangle_mongo") + (self.configurator.nightly_build_mangle_files if version[0] == 'n' else '') +(self.configurator.default("build_prerequisites") if version[0] != 'n' else '') + self.configurator.default("install_for_packaging") + self.configurator.default("build_package") + script+=self.fmt(formatter, + mongo_version=mongo_version, + distro_name=self.distro_name, + distro_version=self.distro_version, + distro_arch=self.distro_arch, + pkg_prereq_str=" ".join(self.pkg_prereqs), + pkg_name=self.pkg_name, + pkg_name_suffix=pkg_name_suffix, + pkg_version=pkg_version, + pkg_product_dir=self.pkg_product_dir, + # KLUDGE: rpm specs and deb + # control files use + # comma-separated conflicts, + # but there's no reason to + # suppose this works elsewhere + pkg_name_conflicts = ", ".join([self.pkg_name+conflict for conflict in pkg_name_conflicts]), + mongo_arch=self.mongo_arch, + mongo_pub_version=mongo_pub_version, + rpmbuild_dir=self.configurator.default('rpmbuild_dir')) + script+='rm -rf mongo' + return script def __enter__(self): self.localscript=None @@ -614,6 +707,69 @@ class ScriptFile(object): class Configurator(SshConnectionConfigurator, EC2InstanceConfigurator, ScriptFileConfigurator, BaseHostConfigurator): def __init__(self, **kwargs): super(Configurator, self).__init__(**kwargs) + +class rackspaceInstance(nodeWrapper): + def __init__(self, configurator, **kwargs): + super(rackspaceInstance, self).__init__(configurator, **kwargs) + self.imgname=configurator.default('rackspace_imgname') + + def start(self): + driver = get_driver(Provider.RACKSPACE) + self.conn = driver(settings.rackspace_account, settings.rackspace_api_key) + name=self.imgname+'-'+str(os.getpid()) + images=filter(lambda x: (x.name.find(self.imgname) > -1), self.conn.list_images()) + sizes=self.conn.list_sizes() + sizes.sort(cmp=lambda x,y: int(x.ram)<int(y.ram)) + node = None + if len(images) > 1: + raise Exception("too many images with \"%s\" in the name" % self.imgname) + if len(images) < 1: + raise Exception("too few images with \"%s\" in the name" % self.imgname) + image = images[0] + self.node = self.conn.create_node(image=image, name=name, size=sizes[0]) + # Note: the password is available only in the response to the + # create_node request, not in subsequent list_nodes() + # requests; so although the node objects we get back from + # list_nodes() are usuable for most things, we must hold onto + # the initial password. + self.password = self.node.extra['password'] + print self.node + + def list_nodes(self): + return self.conn.list_nodes() + + def setup(self): + self.putSshKey() + + def putSshKey(self): + keyfile=settings.makedist['ssh_keyfile'] + ssh = ParamikoSSHClient(hostname = self.node.public_ip[0], password = self.password) + ssh.connect() + print "putting ssh public key" + ssh.put(".ssh/authorized_keys", contents=open(keyfile+'.pub').read(), chmod=0600) + print "ok" + +def parse_mongo_version_spec (spec): + foo = spec.split(":") + mongo_version = foo[0] # this can be a commit id, a + # release id "r1.2.2", or a branch name + # starting with v. + if len(foo) > 1: + pkg_name_suffix = foo[1] + if len(foo) > 2 and foo[2]: + pkg_version = foo[2] + else: + pkg_version = time.strftime("%Y%m%d") + if not pkg_name_suffix: + if mongo_version[0] in ["r", "v"]: + nums = mongo_version.split(".") + if int(nums[1]) % 2 == 0: + pkg_name_suffix = "-stable" + else: + pkg_name_suffix = "-unstable" + else: + pkg_name_suffix = "" + return (mongo_version, pkg_name_suffix, pkg_version) def main(): # checkEnvironment() @@ -629,59 +785,28 @@ def main(): try: import settings if "makedist" in dir ( settings ): - for key in ["EC2_HOME", "JAVA_HOME"]: - if key in settings.makedist: - os.environ[key] = settings.makedist[key] - for key in ["ec2_pkey", "ec2_cert", "ec2_sshkey", "ssh_keyfile" ]: + for key in ["ec2_sshkey", "ssh_keyfile", "gpg_homedir" ]: if key not in kwargs and key in settings.makedist: kwargs[key] = settings.makedist[key] except Exception, err: print "No settings: %s. Continuing anyway..." % err pass - # Ensure that PATH contains $EC2_HOME/bin - vars = ["EC2_HOME", "JAVA_HOME"] - for var in vars: - if os.getenv(var) == None: - raise SimpleError("Environment variable %s is unset; did you create a settings.py?", var) - - if len([True for x in os.environ["PATH"].split(":") if x.find(os.environ["EC2_HOME"]) > -1]) == 0: - os.environ["PATH"]=os.environ["EC2_HOME"]+"/bin:"+os.environ["PATH"] - - kwargs["distro_name"] = distro_name kwargs["distro_version"] = distro_version kwargs["arch"] = arch - - foo = mongo_version_spec.split(":") - kwargs["mongo_version"] = foo[0] # this can be a commit id, a - # release id "r1.2.2", or a - # branch name starting with v. - if len(foo) > 1: - kwargs["pkg_name_suffix"] = foo[1] - if len(foo) > 2 and foo[2]: - kwargs["pkg_version"] = foo[2] - else: - kwargs["pkg_version"] = time.strftime("%Y%m%d") - + kwargs['mongo_version_spec'] = mongo_version_spec + + kwargs["localdir"] = rootdir # FIXME: this should also include the mongo version or something. - if "subdirs" in kwargs: - kwargs["localdir"] = "%s/%s/%s/%s" % (rootdir, distro_name, distro_version, arch, kwargs["mongo_version"]) - else: - kwargs["localdir"] = rootdir +# if "subdirs" in kwargs: +# kwargs["localdir"] = "%s/%s/%s/%s/%s" % (rootdir, distro_name, distro_version, arch, kwargs["mongo_version"]) +# else: + - if "pkg_name_suffix" not in kwargs: - if kwargs["mongo_version"][0] in ["r", "v"]: - nums = kwargs["mongo_version"].split(".") - if int(nums[1]) % 2 == 0: - kwargs["pkg_name_suffix"] = "-stable" - else: - kwargs["pkg_name_suffix"] = "-unstable" - else: - kwargs["pkg_name_suffix"] = "" - kwargs['local_gpg_dir'] = kwargs["local_gpg_dir"] if "local_gpg_dir" in kwargs else os.path.expanduser("~/.gnupg") + kwargs['gpg_homedir'] = kwargs["gpg_homedir"] if "gpg_homedir" in kwargs else os.path.expanduser("~/.gnupg") configurator = Configurator(**kwargs) LocalHost.runLocally(["mkdir", "-p", kwargs["localdir"]]) with ScriptFile(configurator, **kwargs) as script: @@ -689,15 +814,18 @@ def main(): print """# Going to run the following on a fresh AMI:""" print f.read() time.sleep(10) - with EC2Instance(configurator, **kwargs) as ec2: - ec2.initwait() - kwargs["ssh_host"] = ec2.getHostname() + # FIXME: it's not the best to have two different pathways for + # the different hosting services, but... + with EC2Instance(configurator, **kwargs) if kwargs['distro_name'] != 'fedora' else rackspaceInstance(configurator, **kwargs) as host: + host.initwait() + host.setup() + kwargs["ssh_host"] = host.getHostname() with SshConnection(configurator, **kwargs) as ssh: ssh.runRemotely(["uname -a; ls /"]) ssh.runRemotely(["mkdir", "pkg"]) if "local_mongo_dir" in kwargs: ssh.sendFiles([(kwargs["local_mongo_dir"]+'/'+d, "pkg") for d in ["rpm", "debian"]]) - ssh.sendFiles([(kwargs['local_gpg_dir'], ".gnupg")]) + ssh.sendFiles([(kwargs['gpg_homedir'], ".gnupg")]) ssh.sendFiles([(script.localscript, "makedist.sh")]) ssh.runRemotely((["sudo"] if ssh.ssh_login != "root" else [])+ ["sh", "makedist.sh"]) ssh.recvFiles([(script.pkg_product_dir, kwargs['localdir'])]) @@ -709,7 +837,7 @@ def processArguments(): ("N", "no-terminate", False, "Leave the EC2 instance running at the end of the job", None), ("S", "subdirs", False, "Create subdirectories of the output directory based on distro name, version, and architecture", None), ("I", "use-internal-name", False, "Use the EC2 internal hostname for sshing", None), - (None, "local-gpg-dir", True, "Local directory of gpg junk", "STRING"), + (None, "gpg-homedir", True, "Local directory of gpg junk", "STRING"), (None, "local-mongo-dir", True, "Copy packaging files from local mongo checkout", "DIRECTORY"), ] shortopts = "".join([t[0] + (":" if t[2] else "") for t in flagspec if t[0] is not None]) @@ -746,11 +874,12 @@ def processArguments(): MONGO-VERSION-SPEC has the syntax Commit(:Pkg-Name-Suffix(:Pkg-Version)). If Commit starts with an 'r', -build from a tagged release; if Commit starts with a 'v', build from -the HEAD of a version branch; otherwise, build whatever git commit is -identified by Commit. Pkg-Name-Suffix gets appended to the package -name, and defaults to "-stable" and "-unstable" if Commit looks like -it designates a stable or unstable release/branch, respectively. +build from a tagged release; if Commit starts with an 'n', package up +a nightly build; if Commit starts with a 'v', build from the HEAD of a +version branch; otherwise, build whatever git commit is identified by +Commit. Pkg-Name-Suffix gets appended to the package name, and +defaults to "-stable" and "-unstable" if Commit looks like it +designates a stable or unstable release/branch, respectively. Pkg-Version is used as the package version, and defaults to YYYYMMDD. Examples: @@ -779,8 +908,7 @@ Options:""" print "%-20s\t%s." % ("%4s--%s%s:" % ("-%s, " % t[0] if t[0] else "", t[1], ("="+t[4]) if t[4] else ""), t[3]) print """ Mandatory arguments to long options are also mandatory for short -options. Some EC2 arguments default to (and override) environment -variables; see the ec2-api-tools documentation.""" +options.""" sys.exit(0) if "usage" in kwargs: @@ -796,4 +924,5 @@ if __name__ == "__main__": # Examples: -# ./makedist.py --local-gpg-dir=$HOME/10gen/dst/dist-gnupg /tmp/ubuntu ubuntu 8.10 x86_64 HEAD:-snapshot +# ./makedist.py /tmp/ubuntu ubuntu 8.10 x86_64 HEAD:-snapshot,v1.4:-stable,v1.5:-unstable +# ./makedist.py /tmp/ubuntu ubuntu 8.10 x86_64 nlatest:-snapshot,n1.4.2:-stable,n1.5.0:-unstable |