← Back to team overview

launchpad-reviewers team mailing list archive

[Merge] lp:~allenap/launchpad/update-ec2-image into lp:launchpad/devel

 

Gavin Panella has proposed merging lp:~allenap/launchpad/update-ec2-image into lp:launchpad/devel.

Requested reviews:
  Launchpad code reviewers (launchpad-reviewers)


Add a new command to display all the usable test images for bin/ec2:

{{{
$ bin/ec2 help images
Purpose: Display all available images.
Usage:   ec2 images

Options:
  --usage        Show usage message and options.
  -v, --verbose  Display more information.
  -q, --quiet    Only display errors and warnings.
  -h, --help     Show help message.

Description:
  The first in the list is the default image.

$ bin/ec2 images
  Rev  AMI           Owner ID      Owner
  499  ami-0288626b  366009196755  salgado
  120  ami-217f9448  038531743404  jelmer
  119  ami-2d8e6044  366009196755  salgado
  117  ami-a9749bc0  036590675370  jml
  116  ami-5d1df134  366009196755  salgado
  114  ami-35806c5c  889698597288  henninge
  113  ami-a909e4c0  200337130613  mwhudson
  112  ami-9212f0fb  200337130613  mwhudson
  111  ami-d228cabb  200337130613  mwhudson
  102  ami-748f6d1d  255383312499  gary
  100  ami-22a1424b  200337130613  mwhudson
}}}

It also adds me to VALID_AMI_OWNERS.

-- 
https://code.launchpad.net/~allenap/launchpad/update-ec2-image/+merge/41485
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/update-ec2-image into lp:launchpad/devel.
=== modified file 'lib/devscripts/ec2test/account.py'
--- lib/devscripts/ec2test/account.py	2010-07-30 15:52:05 +0000
+++ lib/devscripts/ec2test/account.py	2010-11-22 16:29:05 +0000
@@ -5,31 +5,34 @@
 
 __metaclass__ = type
 __all__ = [
+    'VALID_AMI_OWNERS',
     'EC2Account',
     ]
 
 import cStringIO
+from datetime import datetime
+from itertools import groupby
+from operator import itemgetter
 import re
 import sys
 import urllib
 
-from datetime import datetime
-
 from boto.exception import EC2ResponseError
 from devscripts.ec2test.session import EC2SessionName
-
 import paramiko
 
-VALID_AMI_OWNERS = (
-    '255383312499', # gary
-    '559320013529', # flacoste
-    '200337130613', # mwhudson
-    '889698597288', # henninge
-    '366009196755', # salgado
-    '036590675370', # jml
-    '038531743404', # jelmer
+
+VALID_AMI_OWNERS = {
+    '255383312499': 'gary',
+    '559320013529': 'flacoste',
+    '200337130613': 'mwhudson',
+    '889698597288': 'henninge',
+    '366009196755': 'salgado',
+    '036590675370': 'jml',
+    '038531743404': 'jelmer',
+    '444667466231': 'allenap',
     # ...anyone else want in on the fun?
-    )
+    }
 
 AUTH_FAILURE_MESSAGE = """\
 POSSIBLE CAUSES OF ERROR:
@@ -160,6 +163,32 @@
         self.delete_previous_security_groups()
         self.delete_previous_key_pairs()
 
+    def find_images(self):
+        # We are trying to find an image that has a location that matches a
+        # regex (see definition of _image_match, above). Part of that regex is
+        # expected to be an integer with the semantics of a revision number.
+        # The image location with the highest revision number is the one that
+        # should be chosen. Because AWS does not guarantee that two images
+        # cannot share a location string, we need to make sure that the search
+        # result for this image is unique, or throw an error because the
+        # choice of image is ambiguous.
+        search_results = []
+
+        # Find the images with the highest revision numbers and locations that
+        # match the regex.
+        images = self.conn.get_all_images(owners=tuple(VALID_AMI_OWNERS))
+        for image in images:
+            match = self._image_match(image.location)
+            if match is not None:
+                revision = int(match.group(1))
+                search_results.append((revision, image))
+
+        # Sort and group by revision.
+        get_revision = itemgetter(0)
+        search_results.sort(key=get_revision, reverse=True)
+        for revision, group in groupby(search_results, get_revision):
+            yield revision, [image for (revision, image) in group]
+
     def acquire_image(self, machine_id):
         """Get the image.
 
@@ -181,48 +210,23 @@
             # they can deal with it.
             return self.conn.get_image(machine_id)
 
-        # We are trying to find an image that has a location that matches a
-        # regex (see definition of _image_match, above). Part of that regex is
-        # expected to be an integer with the semantics of a revision number.
-        # The image location with the highest revision number is the one that
-        # should be chosen. Because AWS does not guarantee that two images
-        # cannot share a location string, we need to make sure that the search
-        # result for this image is unique, or throw an error because the
-        # choice of image is ambiguous.
-        search_results = None
-
-        # Find the images with the highest revision numbers and locations that
-        # match the regex.
-        for image in self.conn.get_all_images(owners=VALID_AMI_OWNERS):
-            match = self._image_match(image.location)
-            if match:
-                revision = int(match.group(1))
-                if (search_results is None
-                    or search_results['revision'] < revision):
-                    # Then we have our first, highest match.
-                    search_results = {'revision': revision, 'images': [image]}
-                elif search_results['revision'] == revision:
-                    # Another image that matches and is equally high.
-                    search_results['images'].append(image)
-
-        # No matching image.
-        if search_results is None:
+        try:
+            revision, images = next(self.find_images())
+        except StopIteration:
+            # No matching image.
             raise RuntimeError(
                 "You don't have access to a test-runner image.\n"
                 "Request access and try again.\n")
-
-        # More than one matching image.
-        if len(search_results['images']) > 1:
-            raise ValueError(
-                ('more than one image of revision %(revision)d found: '
-                 '%(images)r') % search_results)
-
-        # We could put a minimum image version number check here.
-        image = search_results['images'][0]
-        self.log(
-            'Using machine image version %d\n'
-            % (search_results['revision'],))
-        return image
+        else:
+            self.log('Using machine image version %d\n' % revision)
+            try:
+                [image] = images
+            except ValueError:
+                raise ValueError(
+                    'More than one image of revision %d found: %r' % (
+                        revision, images))
+            else:
+                return image
 
     def get_instance(self, instance_id):
         """Look in all of our reservations for an instance with the given ID.

=== modified file 'lib/devscripts/ec2test/builtins.py'
--- lib/devscripts/ec2test/builtins.py	2010-11-05 16:54:24 +0000
+++ lib/devscripts/ec2test/builtins.py	2010-11-22 16:29:05 +0000
@@ -8,24 +8,30 @@
 
 import os
 import pdb
+import socket
 import subprocess
 
 from bzrlib.bzrdir import BzrDir
 from bzrlib.commands import Command
 from bzrlib.errors import BzrCommandError
 from bzrlib.help import help_commands
-from bzrlib.option import ListOption, Option
-
-import socket
-
+from bzrlib.option import (
+    ListOption,
+    Option,
+    )
 from devscripts import get_launchpad_root
-
+from devscripts.ec2test.account import VALID_AMI_OWNERS
 from devscripts.ec2test.credentials import EC2Credentials
 from devscripts.ec2test.instance import (
-    AVAILABLE_INSTANCE_TYPES, DEFAULT_INSTANCE_TYPE, EC2Instance)
+    AVAILABLE_INSTANCE_TYPES,
+    DEFAULT_INSTANCE_TYPE,
+    EC2Instance,
+    )
 from devscripts.ec2test.session import EC2SessionName
-from devscripts.ec2test.testrunner import EC2TestRunner, TRUNK_BRANCH
-
+from devscripts.ec2test.testrunner import (
+    EC2TestRunner,
+    TRUNK_BRANCH,
+    )
 
 # Options accepted by more than one command.
 
@@ -188,7 +194,6 @@
     return branches, test_branch
 
 
-
 DEFAULT_TEST_OPTIONS = '--subunit -vvv'
 
 
@@ -409,7 +414,8 @@
                 "Commit text not specified. Use --commit-text, or specify a "
                 "message on the merge proposal.")
         if rollback and (no_qa or incremental):
-            print "--rollback option used. Ignoring --no-qa and --incremental."
+            print (
+                "--rollback option used. Ignoring --no-qa and --incremental.")
         try:
             commit_message = mp.build_commit_message(
                 commit_text, testfix, no_qa, incremental, rollback=rollback)
@@ -613,6 +619,25 @@
         instance.bundle(ami_name, credentials)
 
 
+class cmd_images(EC2Command):
+    """Display all available images.
+
+    The first in the list is the default image.
+    """
+
+    def run(self):
+        credentials = EC2Credentials.load_from_file()
+        session_name = EC2SessionName.make(EC2TestRunner.name)
+        account = credentials.connect(session_name)
+        format = "%5s  %-12s  %-12s  %s\n"
+        self.outf.write(format % ("Rev", "AMI", "Owner ID", "Owner"))
+        for revision, images in account.find_images():
+            for image in images:
+                self.outf.write(format % (
+                        revision, image.id, image.ownerId,
+                        VALID_AMI_OWNERS.get(image.ownerId, "unknown")))
+
+
 class cmd_help(EC2Command):
     """Show general help or help for a command."""