XBuddy: Map base versions into fully qualified build version.

This uses LATEST-x.y.z markers in GS to map base version numbers (e.g.
6801.0.0) to fully qualified build versions including a release number
(e.g. R42-6801.0.0) for a given board. This is needed in order to fetch
SDK images corresponding to an SDK version, which does not include
a release number.

BUG=brillo:336
TEST=Unit tests.
TEST=Manual test with cros flash --project-sdk.

Change-Id: I59dd6294d52bbb4247e8c3c5066a5a2ff753508e
Reviewed-on: https://chromium-review.googlesource.com/251468
Trybot-Ready: Gilad Arnold <garnold@chromium.org>
Tested-by: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Chris Sosa <sosa@chromium.org>
Commit-Queue: Gilad Arnold <garnold@chromium.org>
Reviewed-by: Don Garrett <dgarrett@chromium.org>
diff --git a/xbuddy.py b/xbuddy.py
index 0bab714..5e7dfcb 100644
--- a/xbuddy.py
+++ b/xbuddy.py
@@ -329,6 +329,7 @@
     return devserver_constants.IMAGE_DIR % {'board':board,
                                             'suffix':suffix,
                                             'version':version}
+
   def _LookupChannel(self, board, channel='stable', image_dir=None):
     """Check the channel folder for the version number of interest."""
     # Get all names in channel dir. Get 10 highest directories by version.
@@ -398,6 +399,21 @@
     raise XBuddyException('Could not find remote build_id for %s %s' % (
         board, version))
 
+  def _ResolveBuildVersion(self, board, base_version):
+    """Check LATEST-<base_version> and returns a full build version."""
+    _Log('Checking gs for full version for %s of %s', base_version, board)
+    # TODO(garnold) We might want to accommodate version prefixes and pick the
+    # most recent found, as done in _LookupVersion().
+    latest_addr = (devserver_constants.GS_LATEST_BASE_VERSION %
+                   {'image_dir': devserver_constants.GS_IMAGE_DIR,
+                    'board': board,
+                    'suffix': RELEASE,
+                    'base_version': base_version})
+    cmd = 'gsutil cat %s' % latest_addr
+    msg = 'Failed to find build at %s' % latest_addr
+    # Full release + version is in the LATEST file.
+    return gsutil_util.GSUtilRun(cmd, msg)
+
   def _ResolveVersionToBuildId(self, board, version, image_dir=None):
     """Handle version aliases for remote payloads in GS.
 
@@ -405,10 +421,11 @@
       board: as specified in the original call. (i.e. x86-generic, parrot)
       version: as entered in the original call. can be
         {TBD, 0. some custom alias as defined in a config file}
-        1. latest
-        2. latest-{channel}
-        3. latest-official-{board suffix}
-        4. version prefix (i.e. RX-Y.X, RX-Y, RX)
+        1. fully qualified build version or base version.
+        2. latest
+        3. latest-{channel}
+        4. latest-official-{board suffix}
+        5. version prefix (i.e. RX-Y.X, RX-Y, RX)
       image_dir: image directory to check in Google Storage. If none,
         the default bucket is used.
 
@@ -423,6 +440,9 @@
 
     if re.match(devserver_constants.VERSION_RE, version):
       return self._RemoteBuildId(board, version)
+    elif re.match(devserver_constants.VERSION, version):
+      return self._RemoteBuildId(board,
+                                 self._ResolveBuildVersion(board, version))
     elif version == LATEST_OFFICIAL:
       # latest-official --> LATEST build in board-release
       return self._LookupOfficial(board, image_dir=image_dir)