Useful Git Commands: Summarizing Lots of Log Entries

2 Apr 2014

When looking for a summary of a git repository’s history, the output of git log isn’t always as informative as one might like. It displays every commit in chronological order, which effectively hides the changes that merges bring in. It is also quite verbose, showing complete log messages, author info, commit hashes, and so on, drowning us with so much info that only a few commits will fit on the screen at once. After supplying the command with the right cocktail of options, though, its output becomes a significantly better summary:

Output of ``git graph''

The output above came from a command that is long enough that I made an alias, for it, git graph, in my ~/.gitconfig file:

[alias]
    graph = log --graph --abbrev-commit --date=relative --pretty=format:'%Cred%h%Creset -%C(yellow)%d%Creset %s %Cgreen(%cr) %C(blue)<%an>%Creset'

Don’t forget that git log accepts a list of things to show logs for as well, so if you want to look at the logs for branch-1 and branch-2 you can simply use git graph branch-1 branch-2 to make them both show up in the graph.


Making FileVault Use a Disk Password

31 Mar 2014

To unlock a disk that is encrypted with OS X’s FileVault feature one needs to type in the password that belongs to any user on the machine who is allowed to unlock the disk. The system then boots and helpfully logs you in as that user. In general that is probably a convenient little feature, but for me it just makes things awkward — I want to use different passwords for unlocking the disk and logging into my user account. To make that work I have to create a second account dedicated to unlocking the disk, get logged into that one when the system boots, then immediately log back out so I can log in as the user I actually want to use.

Or do I?

The system that powers FileVault, Core Storage, combines full disk encryption and some logical volume management features in a manner similar to LVM and LUKS on Linux. As a dedicated user of those features on my Linux-based machines, I jumped at the chance to read more about OS X’s version only to discover next to no official documentation whatsoever. On the bright side, after searching some more and then playing around with it for a while I finally figured out how to make it do everything I wanted it to do, including using a disk password. You just have to boot into recovery (or, in my case, the OS X 10.9 install DVD that I threw together) and use the command line.

Create a partition

Since this is a laptop I want to put everything on one huge partition. The utility that handles nearly everything related to disk management on OS X, diskutil, requires us to choose a type of filesystem for every partition we tell it to create, so for now we’ll just tell it to mark the partition as HFS+ and not bother to format it.

# diskutil partitiondisk disk0 1 gpt jhfs+ %noformat% 100%
Started partitioning on disk0
Unmounting disk
Creating the partition map
Waiting for the disks to reappear
Finished partitioning on disk0
/dev/disk0
   #:                       TYPE NAME    SIZE       IDENTIFIER
   0:      GUID_partition_scheme        *256.1 GB   disk0
   1:                        EFI EFI     209.7 MB   disk0s1
   2:                  Apple_HFS         255.7 GB   disk0s2

The system actually created two partitions for us. The first is a small EFI system partition, which helps the system start, and the second is the big partition for our data that spans the rest of the disk.

Or does it?

# gpt show disk0
      start        end  index  contents
          0          1         PMBR
          1          1         Pri GPT header
          2         32         Pri GPT header
         34          6
         40     409600      1  GPT part - C12A7328-F81F-11D2-BA4B-00A0C93EC93B
     409640  499446368      2  GPT part - 48465300-0000-11AA-AA11-00306543ECAC
  499856008     262151
  500118159         32         Sec GPT table
  500118191          1         Sec GPT header

It seems to have left a little empty space in between the last partition and the boilerplate bits at the end of the disk. More on that later.

Create a volume group

The next step is quite familiar to LVM users. Before we can add encryption we need to create a logical volume group that combines one or more partitions (physical volumes) into a single virtual “disk” that we can slice up.

Why more than one disk? That’s how Fusion Drive works.

# diskutil cs create vg_sodium disk0s2
Started CoreStorage operation
Touching partition type on disk0s2
Adding disk0s2 to Logical Volume Group
Creating Core Storage Logical Volume Group
Switching disk0s2 to Core Storage
Waiting for Logical Volume Group to appear
Discovered new Logical Volume Group "A642DBC3-644C-4C23-8337-ADBCDD9C85F2"
Core Storage LVG UUID: A642DBC3-644C-4C23-8337-ADBCDD9C85F2
Finished CoreStorage operation

# diskutil cs list
CoreStorage logical volume groups (1 found)
|
+-- Logical Volume Group A642DBC3-644C-4C23-8337-ADBCDD9C85F2
    =========================================================
    Name:         vg_sodium
    Status:       Online
    Size:         255716540416 B (255.7 GB)
    Free Space:   255380987904 B (255.4 GB)
    |
    +-< Physical Volume A39FA7E9-F52F-4FFA-9A70-F07304111115
        ----------------------------------------------------
        Index:    0
        Disk:     disk0s2
        Status:   Online
        Size:     255716540416 B (255.7 GB)

# diskutil list disk0
/dev/disk0
   #:                       TYPE NAME       SIZE       IDENTIFIER
   0:      GUID_partition_scheme           *256.1 GB   disk0
   1:                        EFI EFI        209.7 MB   disk0s1
   2:          Apple_CoreStorage            255.7 GB   disk0s2
   3:                 Apple_Boot Boot OS X  134.2 MB   disk0s3

This changed the type of the second partition and constructed a volume group out of it, but it also created a third partition on which it can put whatever minimal data are needed to allow the system to boot from a Core Storage volume. With a quick look at the partition table we can see that it carved this out of the space it reserved at the end of the disk earlier:

# gpt show disk0
      start        end  index  contents
          0          1         PMBR
          1          1         Pri GPT header
          2         32         Pri GPT header
         34          6
         40     409600      1  GPT part - C12A7328-F81F-11D2-BA4B-00A0C93EC93B
     409640  499446368      2  GPT part - 48465300-0000-11AA-AA11-00306543ECAC
  499856008     262144      3  GPT part - 426F6F74-0000-11AA-AA11-00306543ECAC
  500118152          7
  500118159         32         Sec GPT table
  500118191          1         Sec GPT header

Create a logical volume

LVM has three layers: physical volumes, volume groups, and logical volumes. Since Core Storage includes encryption it adds a fourth layer, the logical volume family, between the volume group and logical volume levels. When you boot a FileVault-enabled system it’s the volume family that your password unlocks. The final step before we can install OS X is to create an encrypted volume family and the logical volume that we will actually install onto.

# diskutil cs createvolume vg_sodium jhfs+ 'Macintosh HD' 100% -passphrase
Passphrase for new volume:
Confirm new passphrase:
Started CoreStorage operation
Waiting for Logical Volume to appear
Formatting file system for Logical Volume
Initialized /dev/rdisk13 as a 238 GB case-insensitive HFS Plus volume with a 24576k journal
Mounting disk
Core Storage LV UUID: 46A6CA83-9CDC-4978-ADF2-E4DC3F203DBD
Core Storage disk: disk13
Finished CoreStorage operation

# diskutil cs list
CoreStorage logical volume groups (1 found)
|
+-- Logical Volume Group A642DBC3-644C-4C23-8337-ADBCDD9C85F2
    =========================================================
    Name:         vg_sodium
    Status:       Online
    Size:         255716540416 B (255.7 GB)
    Free Space:   0 B (0 B)
    |
    +-< Physical Volume A39FA7E9-F52F-4FFA-9A70-F07304111115
    |   ----------------------------------------------------
    |   Index:    0
    |   Disk:     disk0s2
    |   Status:   Online
    |   Size:     255716540416 B (255.7 GB)
    |
    +-> Logical Volume Family AC7B483C-0524-4ACF-8083-9EFD963F81A5
        ----------------------------------------------------------
        Encryption Status:       Unlocked
        Encryption Type:         AES-XTS
        Conversion Status:       Complete
        Conversion Direction:    -none-
        Has Encrypted Extents:   Yes
        Fully Secure:            Yes
        Passphrase Required:     Yes
        |
        +-> Logical Volume 46A6CA83-9CDC-4978-ADF2-E4DC3F203DBD
            ---------------------------------------------------
            Disk:                  disk13
            Status:                Online
            Size (Total):          255380987904 B (255.4 GB)
            Conversion Progress:   -none-
            Revertable:            No
            LV Name:               Macintosh HD
            Volume Name:           Macintosh HD
            Content Hint:          Apple_HFS

Now we have a disk called “Macintosh HD” that is encrypted with its own non-user-specific password, formatted, and ready to go. Just switch back to the OS X installer and let it run.

Create a recovery key

After the installer finishes and the system reboots we see the familiar FileVault login screen with just one option: “Disk Password.” Success! Now we just enter that, run through the usual first-time boot stuff, and finally open up a terminal to create a recovery key:

$ sudo fdesetup changerecovery -personal
Enter a password for '/':
New recovery key = 'XPO6-E4OL-XQG6-TGV9-GFOZ-GB8M'

Add more users

Since I generally don’t log in with an administrative account I created a second, regular user, but then the next time I rebooted I discovered a problem: the FileVault password screen asked for the disk password or the new user’s password. Thankfully, removing that user from the list of choices is simple:

$ sudo fdesetup remove -user gholms

Open questions

Now that I have gone through all this my machine finally seems to be working exactly the way I want it to, but a few unanswered questions still remain in the back of my mind:

  • How am I supposed to do this on a newer Mac that ships with neither an install DVD nor an upgrade application that I can turn into one?
  • How do I make OS X stop automatically adding new users to FileVault’s password list?
  • Where is the official Core Storage documentation?

Do know the answers to any of these? Leave a comment!


A Useful rtadvd Configuration

20 Jan 2014

When most IPv6-capable computers join a network they attempt to automatically find a router on the network so they can figure out what addresses to use, how to set up routing, and so forth. On BSD systems like my router, the rtadvd(8) program manages the router’s side of this exchange. While rtadvd is rather flexible, its configuration file is frustratingly terse and its documentation assumes the reader has a fair amount of knowledge already.

For IPv4, my network uses DHCP to hand out addresses and DNS information. When rolling out IPv6, I wanted to set things up similarly, but without managing addresses centrally with DHCP since machines can configure themselves correctly without one. Configuration like this is the sort of thing that I tend to forget and have to re-learn periodically, so for reference, the rtadvd.conf file I used for that setup looked something like this:

vether0:\
    :addr="2001:db8:1221::":\
    :prefixlen#64:\
    :raflags#64:\
    :rdnss="2001:db8:1221::1,2001:4860:4860::8888,2001:4860:4860::8844":\
    :dnssl="internal.example.com":

This makes machines configure themselves for the network 2001:db8:1221::/64, DNS domain internal.example.com, and three DNS servers: a local one at 2001:db8:1221::1, and both of Google’s public servers.

The source of most of my confusion was figuring out the raflags option. raflags is a bit mask with two flags: M, which means a DHCP server manages addresses, and O, which means that non-address-related information (in this case, DNS information) is available that way even if addresses are not. This network setup requires the O flag to be set and the M flag to be unset, which means raflags has to be 64.

Now that I’ve rolled this out I get all the niceness of auto-configuration without the need for a central DHCP server to keep track of addresses. Well, for IPv6, at least.


Mr. TV

16 Dec 2013

I previously wrote about the big, Raspberry Pi-powered TV set at Eucalyptus HQ that displays the #eucalyptus-devel IRC channel so developers can always see what is going on and jump in if they need to. That setup has worked quite well for some time now, but I recently came up with a way to make it even better:

Mr. TV

Googly eyes have yet to fail me at improving a machine’s appearance.


Making a Less-Limited USB Stick

9 Dec 2013

The FAT32 filesystem is the closest thing we have to a universal standard for passing data around, but with the capacity of modern USB flash drives its 4 GB file size limitation has become problematic. exFAT is a popular contender for dealing with that, but the patent issues that surround it make true portability a pipe dream at best.

Enter UDF. As the filesystem of choice for DVDs and Blu-Ray disks, UDF support is ubiquitous. Appropriately-formatted disks are readable by operating systems dating back to the early 2000s. All that remains is figuring out how to format it. In general, there seem to be three important things to keep in mind:

  1. Remove all traces of previous filesystems. Different operating systems use different methods to detect what filesystems a disk contains, so ensure maximum reliability by eliminating potential sources of confusion.
  2. Format the entire disk, not just a partition. OS X seems to only look for UDF when the filesystem takes up the whole disk, as it does on DVDs, so overwrite the partition table before formatting the disk.
  3. Use the correct UDF version. UDF has a number of versions that add different features, but as you might expect, newer versions require newer operating systems. Windows XP will read up to version 2.01 out of the box, though some Blu-Ray drive manufacturers have released drivers for newer versions.

I formatted my most recently-purchased USB stick on Fedora. First off, I blew away the partition table and all traces of the FAT32 filesystem it came with:

# dd if=/dev/zero of=/dev/sdb bs=1M count=1
# wipefs -a /dev/sdb

Fedora uses the mkudffs command for creating UDF filesystems, which is part of the udftools package:

# yum install udftools
# mkudffs --revision=0x0201 --media-type=hd /dev/sdb

That’s it! Now the disk seems to be usable on Fedora, OS X, and Windows, which covers just about all of my computers. I will test OpenBSD one of these days.

There are undoubtedly ways to do this on other operating systems. Feel free to comment with instructions for your favorite operating system if you know them.


Every DNS Server Should Support Aliases

22 Jul 2013

Amazon’s Route53 DNS service, along with several content delivery networks and other DNS providers let one create an “alias” pseudo-record that causes the server to respond to requests for one name with results for another name. While the ways current implementations of this function vary a bit, the biggest difference between all of them and a CNAME is that while a CNAME gets applied to every query regardless of the type of record something is looking for, an alias is specific to just one type of record.

While this sounds like a trivial difference, the benefits are surprisingly enormous. The most obvious effect is that it lets you point a bare domain name (e.g. example.com) at something else (e.g. www.example.com). The reason you can’t normally do this is because the CNAME record you would normally use to do this would conflict with the SOA record at the top of your domain, but since the alias you would use for this only applies to A address records, this is no longer a problem.

Another property aliases have is that they don’t actually go over the wire. While a CNAME record returns to the machine looking up a DNS name, causing it to restart its search with a different name, the answer for an alias comes right out of the DNS server’s own database. This means that aliases can only be used for records for which the server is authoritative or at least has some means of reliably learning the answer it should return, but that’s good enough for a great deal of use cases, notably including those of most content delivery networks. The fact that servers look up what an alias points to before they send anything over the wire means that they can include this functionality without violating standards — no one else needs to change their servers or their clients to support it. If DNS standards evolve to support it in the future, this makes transitioning even easier as that change rolls out.

In short, aliases would solve one of the most commonly-encountered shortcomings of DNS, namely its inability to use a CNAME to point a bare domain at its www equivalent. Given that there are multiple proprietary systems out there that do this already, it’s about time we standardized on an approach.


Running a Text-based Kiosk with Systemd

9 Jul 2013

Eucalyptus HQ has a big TV on the wall that displays the #eucalyptus-devel IRC channel so developers can always see what is going on and jump in if they need to. Until recently, a laptop drove that display, but that seemed like overkill to me, so I went to employ my Raspberry Pi running the Raspberry Pi Fedora Remix to do that instead. Since the IRC program it’s using, irssi, is text-based I don’t need to use any of the Pi’s precious little memory to run anything graphical, so I just needed to figure out how to make systemd spawn irssi instead of a login prompt on tty1.

I would normally do this by copying /lib/systemd/system/getty@.service to /etc/systemd/system/getty@tty1.service and then editing that, but F18’s version of systemd let me do this in an even simpler manner. By creating a directory with the same name as that file, plus .d, I can add a config file to that directory that overrides only the parts of the original unit file that I need to change:

[Service]
After=network-online.target
Wants=network-online.target
ExecStartPre=/usr/bin/nm-online
ExecStart=
ExecStart=/usr/bin/irssi
KillSignal=SIGTERM
StandardInput=tty
StandardOutput=tty
User=kiosk

Now I can just plug the system in and have it automatically up and running irssi in less than a minute.

Unexpected lessons

I didn’t expect to have to run nm-online here because network-online.target is supposed to wait for a service that runs that itself, but for some reason systemd didn’t order things that way and irssi came up before the network connection did. Running that command as part of this unit worked around that problem.

Use the consoleblank=0 parameter to prevent Linux from blanking the screen after the usual ten minutes of inactivity.

I’m using the TV’s USB “service” port to power the raspberry pi. That usually works just fine, but when the TV turns off it cuts the power to that port as well, abruptly shutting the raspberry pi off. I don’t have any data loss in particular to worry about, but turning the system back on causes some annoyance: when the TV turns on the raspberry pi also powers on and attempts to detect what kind of screen it is plugged into. At that point the TV hasn’t figured out what it wants to display yet, so the detection fails and I’m left with a blank screen until I reboot the computer.


Managing a DNS Domain from One Place

8 Jun 2013

Taking a DNS name and resolving it to the address of a machine is easy to understand and easy to implement if you’re an administrator. Doing a reverse lookup from an address back to a name, however, is more difficult due to the way addresses are divided up. I won’t attempt to describe the details here (I recommend Liu and Albitz’s DNS and BIND for the gory details), but in short, the way this works is by breaking an IP address into its four octets and handling them from there like regular hierarchical names in the special in-addr.arpa zone:

1.2.0.192.in-addr-arpa. PTR foo.example.com.

This is problematic for two main reasons:

  • You have to change two zones every time you change a DNS name.
  • If you have fewer than 256 addresses, your ISP can’t delegate the appropriate subset of the in-addr.arpa zone to you so you can maintain it yourself. This usually forces you to log into a web page provided by your ISP every time you change a DNS name.

RFC 2317 notes that you can work around this by filling up your subset of the in-addr.arpa zone with CNAME records instead of the usual PTR records like this:

$ORIGIN 2.0.192.in-addr.arpa.

1   CNAME 1.ip4.example.com.
2   CNAME 2.ip4.example.com.
...
253 CNAME 253.ip4.example.com.
254 CNAME 254.ip4.example.com.

After you set this up you can control your forward and reverse DNS records from the same place without needing to change the reverse zone you just set up:

$ORIGIN example.com.

foo A 192.0.2.1
bar A 192.0.2.2
...
baz A 192.0.2.253
bop A 192.0.2.254

1.ip4   PTR foo
2.ip4   PTR bar
...
253.ip4 PTR baz
254.ip4 PTR bop

Of course, if you rely on your ISP to create reverse DNS names for you they have to be willing to create non-PTR records before you can take advantage of this.

If you’re lucky enough to have an entire /24 block of addresses all to yourself you can simplify the reverse DNS zone by simply mapping the entire set of addresses with a single DNAME instead of a long list of CNAMEs:

$ORIGIN 2.0.192.in-addr.arpa.

@ DNAME ip4.example.com.

This has the same net effect as the list of CNAMEs, but it shortens things significantly.


What’s New in Euca2ools 3, Part 2: A Developer’s Perspective

3 Apr 2013

The upcoming version of euca2ools, version 3, completely reworks the command line suite to make it both easier to write and easier to use. Part 1 of this series discussed the user-facing changes version 3 has to offer, and today we’re going to take a look at how things improve on the developer’s side of the fence.

A change in philosophy: declarative programming

The developer is very much in the driver’s seat in version 1 of euca2ools. To use a car analogy, the developer directly controls the code’s direction, speed, and gearbox manually. Version 2 adds a cruise control by centralizing a lot of boilerplate code in the form of boto’s roboto module. Version 3 opts to let the developer give the requestbuilder framework a destination, step aside completely, and let it do the driving for the boring parts of the trip.

Requestbuilder offers a set of base classes and a domain-specific language based on python’s standard argparse library that allows the developer to say exactly how something should look at the command line in addition to how it should look when given to the server all in the same place.

What makes this so powerful is that it lets anybody with a service’s documentation and knowledge of how to use argparse write a command line tool quickly and painlessly. For instance, it took me around a day to write highly-customized command line tools for every operation Amazon’s Elastic Load Balancing service supports. Here’s the code from one of them:

class CreateLBCookieStickinessPolicy(ELBRequest):
    DESCRIPTION = ('Create a new stickiness policy for a load balancer, '
                   'whereby the load balancer automatically generates cookies '
                   'that it uses to route requests from each user to the same '
                   'back end instance. This type of policy can only be '
                   'associated with HTTP or HTTPS listeners.')
    ARGS = [Arg('LoadBalancerName', metavar='ELB',
                help='name of the load balancer to modify (required)'),
            Arg('-e', '--expiration-period', dest='CookieExpirationPeriod',
                metavar='SECONDS', type=int, required=True,
                help='''time period after which cookies should be considered
                stale (default: user's session length) (required)'''),
            Arg('-p', '--policy-name', dest='PolicyName', metavar='POLICY',
                required=True, help='name of the new policy (required)')]

The framework hands everything inside each Arg in this code to argparse to gather input from the command line and then send the results directly to the web server using whatever name argparse gives the input it gets. For instance, whatever a user supplies using the -e option ends up getting sent to the server as a CookieExpirationPeriod parameter. With a small amount of practice it becomes quite easy to write a bunch of commands this way very quickly.

One request, one command

Euca2ools are built around a “one request, one command” tenet. This means that, in general, there is a dedicated command for each thing a web service can do. This philosophy naturally lends itself to the tight coupling between command line options and what gets sent to the server discussed earlier, but it also lends itself to reversing the usual relationship between web services and web service requests. Whereas one typically writes an object that represents the service and uses methods on it to send requests, in euca2ools it is the commands, and thus the requests, which are the first-class citizens. Each command that represents a request instead points to a service, rather than the other way around.

The way this works in practice is by defining a base class for each service and a base class that all methods which use that service share:

class CloudWatch(requestbuilder.service.BaseService):
    NAME = 'monitoring'
    DESCRIPTION = 'Instance monitoring service'
    API_VERSION = '2010-08-01'
    AUTH_CLASS = requestbuilder.auth.QuerySigV2Auth
    URL_ENVVAR = 'AWS_CLOUDWATCH_URL'

    ARGS = [MutuallyExclusiveArgList(
                Arg('--region', dest='userregion', metavar='USER@REGION',
                    route_to=SERVICE, help='''name of the region and/or user
                    in config files to use to connect to the service'''),
                Arg('-U', '--url', metavar='URL', route_to=SERVICE,
                    help='instance monitoring service endpoint URL'))]

class CloudWatchRequest(requestbuilder.request.AWSQueryRequest):
    SERVICE_CLASS = CloudWatch

Services can supply their own command line options in the same way as requests. After it gathers options from the command line, requestbuilder uses route_to to choose where to send it. This also provides a convenient way to tell the framework not to send an option to the server at all when a command needs to process it specially: just use route_to=None.

Convention over configuration

The oft-quoted programming paradigm for frameworks is just as true for euca2ools 3 as it is elsewhere. Want to make a command print something? Just write a print_result method. The result from the server gets passed in as a dictionary.

class TerminateInstances(EucalyptusRequest):
    DESCRIPTION = 'Terminate one or more instances'
    ARGS = [Arg('InstanceId', metavar='INSTANCE', nargs='+',
                help='ID(s) of the instance(s) to terminate')]
    LIST_TAGS = ['instancesSet']

    def print_result(self, result):
        for instance in result.get('instancesSet', []):
            print self.tabify(('INSTANCE', instance.get('instanceId'),
                               instance.get('previousState', {}).get('name'),
                               instance.get('currentState', {}).get('name')))

Want to make a request do fancier preparations than argparse can do on its own? Just write a preprocess method that takes things from self.args and adds things to self.params to be sent to the server.

class DescribeSecurityGroups(EucalyptusRequest):
    DESCRIPTION = ('Show information about security groups\n\nNote that '
                   'filters are matched on literal strings only, so '
                   '"--filter ip-permission.from-port=22" will *not* match a '
                   'group with a port range of 20 to 30.')
    ARGS = [Arg('group', metavar='GROUP', nargs='*', route_to=None,
                default=[], help='limit results to specific security groups')]
    ...
    def preprocess(self):
        for group in self.args['group']:
            if group.startswith('sg-'):
                self.params.setdefault('GroupId', [])
                self.params['GroupId'].append(group)
            else:
                self.params.setdefault('GroupName', [])
                self.params['GroupName'].append(group)

There are also a few other methods one can plug in, such as postprocess, and, for especially early-running code, configure. Expect documentation for requestbuilder that covers this in detail in the future.

Scratching the surface

The examples above cover only a fraction of what is possible with euca2ools 3’s new infrastructure. While you can look forward to some more advanced uses of it in later blog posts, you can also take a look at the current euca2ools code in development to see some of the interesting things one can do with it. Today’s pre-release of that code carries with it commands for all three of AWS’s “triangle” services: Auto Scaling, CloudWatch, and Elastic Load Balancing. Continuing what seems to have become a euca2ools tradition, just look for the commands that start with euscale (pronounced “you scale”) euwatch (“you watch”), and eulb (“you’ll be”).

Packages for Fedora and RHEL 6 are available here. If you’re using another OS or want to build the code yourself you can simply clone euca2ools’s git repository‘s requestbuilder branch. Requestbuilder itself is available on PyPI and GitHub. As always, I encourage you to test this code against AWS and Eucalyptus 3.3 and let me know what you think on the euca-users mailing list. If you encounter bugs, please file them in the project’s bug tracker.


What’s New in Euca2ools 3, Part 1: A User’s Perspective

21 Feb 2013

Version 3 of euca2ools, slated for release in just a couple months, gives the command line suite a much-needed refresh that makes it both easier to write and easier to use. Most of the innovation here involves changes to the platform upon which it is built. I will cover those changes from a developer’s perspective in future blog posts, but today I’m going to focus on what euca2ools 3 brings to the table for developers and other users alike. While there are too many small improvements to possibly cover them all, euca2ools 3 at last brings a few of the niceties power users have come to expect from their command line tools to cloud management.

A configuration file

Yes, you read that right: a configuration file. Both euca2ools and the command line tools provided by AWS themselves have astonishingly limited support for configuration, forcing people to resort to writing a separate shell script for each combination of users and clouds one might possibly want to access and then use them in place of one.

Your cries of anguish have been heard, so now we have this:

[user gholms]
key-id = AKIA93F29V0AEXAMPLE
secret-key = vcasd93cm1458un4vj84039vda78mDEXAMPLE

[user ecc-admin]
key-id = EVDB93F29V0AEXAMPLE
secret-key = 38fva93cm1458un4vj84039vda78mDEXAMPLE

[region us-east-1]
ec2-url = https://ec2.amazonaws.com/
iam-url = https://iam.amazonaws.com/
s3-url  = https://s3.amazonaws.com/
user = gholms

[region ecc]
ec2-url = https://communitycloud.eucalyptus.com:8773/services/Eucalyptus/
iam-url = https://communitycloud.eucalyptus.com:8773/services/Euare/
s3-url  = https://communitycloud.eucalyptus.com:8773/services/Walrus/
user = ecc-admin

[global]
default-region = us-east-1

A file like this, combined with the --region option that all tools share, mean you can mix and match users and clouds to you heart’s content. Just throw a file like this inside of ~/.euca, end it with .ini, and away you go! You can add as many files to ~/.euca as you want — they all get combined together.

Friendly error feedback

Another common complaint that people had with euca2ools 2 was its behavior in the face of input that didn’t match what it expected. Some of the worst offenders had error messages ranging from confusing to irrelevant to nonexistent. Euca2ools 3 overhauls the code that does this, replacing it with standard python tools and friendlier code that makes its behavior in the face of errors much better.

Here’s how it behaves in the face of the most common case of this:

% euca-describe-availability-zones
error: missing access key ID; please supply one with -I

Also included is special treatment for “pick one from multiple alternatives” options:

% euare-useraddcert
usage: euare-useraddcert (-c CERT | -f FILE) [-u USER]
                         [--as-account ACCOUNT] [--region REGION | -U URL]
                         [-I KEY_ID] [-S KEY]
euare-useraddcert: error: one of the arguments -c/--certificate-body -f/--certificate file is required

A lot of attention to detail went into dealing with some of the most common mistakes people make:

% euca-register -n myimage -b /dev/sda1=snap-12345678:false
euca-register: error: argument -b/--block-device-mapping: second element of EBS block device mapping "/dev/sda1=snap-00000000:false" must be an integer
% euca-authorize mygroup -p 8773:8777
euca-authorize: error: argument -p/--port-range: multi-port range must be separated by "-", not ":"

Tagging and filtering support

Euca2ools 3 at last offers full support for EC2’s massive sets of resource tags and filters:

% euca-describe-instances -h
usage: euca-describe-instances [-h] [--show-empty-fields]
...
  --filter NAME=VALUE   restrict results to those that meet criteria
...

allowed filter names:
  architecture          CPU architecture
  availability-zone
  block-device-mapping.attach-time
                        volume attachment time
  block-device-mapping.delete-on-termination
                        whether a volume is deleted upon instance
                        termination
  block-device-mapping.device-name
                        volume device name (e.g.  /dev/sdf)
  block-device-mapping.status
                        volume status
  block-device-mapping.volume-id
                        volume ID
  client-token          idempotency token provided at instance run
                        time
  dns-name              public DNS name
  group-id              security group membership
  hypervisor            hypervisor type
  image-id              machine image ID
  instance-id
  instance-lifecycle    whether this is a spot instance
  instance-state-code   numeric code identifying instance state
  instance-state-name   instance state
  instance-type
  ip-address            public IP address
  kernel-id             kernel image ID
  key-name              key pair name provided at instance launch time
  launch-index          launch index within a reservation
  launch-time           instance launch time
  monitoring-state      whether monitoring is enabled
  owner-id              instance owner's account ID
  placement-group-name
  platform              whether this is a Windows instance
  private-dns-name
  private-ip-address
  product-code
  ramdisk-id            ramdisk image ID
  reason                reason for the more recent state change
  requestor-id          ID of the entity that launched an instance
  reservation-id
  root-device-name      root device name (e.g.  /dev/sda1)
  root-device-type      root device type (ebs or instance-store)
  spot-instance-request-id
  state-reason-code     reason code for the most recent state change
  state-reason-message  
                        message for the most recent state change
  subnet-id             ID of the VPC subnet the instance is in
  tag-key               name of any tag assigned to the instance
  tag-value             value of any tag assigned to the instance
  tag:KEY               specific tag key/value combination
  virtualization-type
  vpc-id                ID of the VPC the instance is in

The new foundation this code is based upon makes it incredibly simple to extend support for these features as things change in the future.

What else?

Some other minor, but nonetheless noteworthy, changes include:

  • euca-* tools gained a --show-empty-fields option that tweaks their output to make it friendlier for running through the column command.
  • All tools that access web services use the same options (-I and -S) for access keys.
  • euare-* tools’ --delegate option for cloud administrators is now --as-account.
  • Multiple --filter options are handled correctly.
  • Machine image device mappings are now handled correctly.

A few tools have yet to be ported to the new framework, but will be in the near future. eustore-installimage is known to be broken. The bundle management tools should work correctly, though their testing to date has been minimal. Finally, do not install them on a system that runs a Eucalyptus node controller.

Isn’t aws-cli the future? Why continue developing euca2ools?

Aws-cli is a great project. Both it and euca2ools tie what the server sees very closely to what the user sees under the hood, but the euca2ools suite does so in a way that makes it trivial to customize tools to do more complicated things behind the hood or to make them easier to use. For instance, consider changing a security group’s permissions in EC2 with aws-cli:

% aws ec2 authorize-security-group-ingress --group-name MySecurityGroup --ip-permissions '{"from_port":22,"to_port":22,"ip_protocol":"tcp","ip_ranges":["0.0.0.0/0"]}'

The exact format we need to use to supply the info the tool needs requires relatively detailed knowledge of what EC2-the-server expects. Compared to that, the euca2ools version of that is easier to remember and much easier to type:

% euca-authorize MySecurityGroup --port 22 --source-subnet 0.0.0.0/0

Aws-cli is a very young project, so people haven’t yet had the chance to iron it out completely. Perhaps some day it will become as user-friendly as euca2ools and finally eclipse it. But we aren’t there yet.

How can I try it out?

If you’re interested in a preview of the next major version of euca2ools, an alpha release is available on GitHub. In addition to the dependencies required to run euca2ools 2, you will also need to install requests and the new requestbuilder framework that drives the new tools. It is still alpha-quality software, so be prepared to find bugs. If you encounter any, feel free to file them in the euca2ools project’s bug tracker.

If you’re interested in helping with development, we are happy to accept pull requests on GitHub. Please also consider joining the euca-users mailing list or stopping by in the #eucalyptus-devel IRC channel on Freenode. I look forward to hearing your feedback. 8^)