Currently, in addition to running my own hardware, I use both the Amazon and Rackspace clouds. Rackspace unfortunately hasn’t added new images in a while. Therefore, in order to run Ubuntu 11.04 (Natty) I had to start with 10.10 (Maverick).
Ubuntu’s upgrade instructions are pretty clear. In fact, of the three steps given only two are necessary:
- sudo apt-get install update-manager-core
- sudo do-release-upgrade
As long as you start with Ubuntu 10.10, Prompt is already set to normal, so there is no need to edit any files.
These two commands are easy enough to execute, but you have to confirm many steps. That’s okay if you only have to do the upgrade once. But I’m constantly bringing up and taking down servers, so the whole process needed to be automated.
Installing the update-manager-core can be easily automated:
apt-get install --assume-yes update-manager-core
It’s the do-release-upgrade part that is harder to automate. There are two types of prompts presented when you do the upgrade. The first ones come from the upgrade program itself, the others come from the dpkg/apt subsystem.
You can quiet all of the dpkg messages by calling do-release-upgrade like this:
DEBIAN_FRONTEND=noninteractive /usr/bin/do-release-upgrade
But that unfortunately won’t stop all the messages that you need to confirm.
My next thought was to combine that with the program “yes”, a small unix tool which outputs the string “y” over and over.
So we’d have something like:
yes | DEBIAN_FRONTEND=noninteractive /usr/bin/do-release-upgrade
But unfortunately that doesn’t work, instead the program complains: “Must be connected to a terminal.”
So finally I had to put together this little expect program in Python.
#!/usr/bin/python
import pexpect
import sys
child = pexpect.spawn(
'''/bin/bash -c "DEBIAN_FRONTEND=noninteractive /usr/bin/do-release-upgrade"''',
timeout = 10 * 60 # 10 minutes
)
child.logfile = sys.stdout
child.expect(r'''Continue running under SSH?.*Continue \[yN\]''')
child.sendline('y')
child.expect(r'''If you run a firewall.*To continue please press \[ENTER\]''')
child.sendline('')
child.expect(r'''Fetching and installing the upgrade.*Continue \[yN\] Details \[d\]''')
child.sendline('y')
child.expect(r'''Remove obsolete packages?.*Continue \[yN\] Details \[d\]''')
child.sendline('y')
# We prefer to do our own rebooting
child.expect(r'''If you select 'y' the system will be restarted.*Continue \[yN\]''')
child.sendline('N')
Save the above file with the name “upgrade_release.py”. You can then run it directly. Or, if you use fabric to manage the creation of all of your new servers, you can add it to your fabfile like so:
def upgrade_dist():
output = run("lsb_release -r")
version = output.partition(":")[2].strip()
if version == "10.10":
print("Going to upgrade Ubuntu...")
sudo("aptitude install --assume-yes update-manager-core python-pexpect")
put("fabfile/upgrade_release.py", "/tmp")
sudo("/usr/bin/python /tmp/upgrade_release.py")
print("Done upgrading Ubuntu - Need to sleep while rebooting")
reboot(60)
# Line below necessary because of ssh caching bug in fabric
fabric.state.connections = fabric.state.HostConnectionCache()
else:
print("Not going to upgrade Ubuntu")