Home | History | Annotate | Download | only in server2
      1 # Copyright 2013 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 import logging
      6 
      7 _APP_YAML_CONTAINER = '''
      8 application: chrome-apps-doc
      9 version: %s
     10 runtime: python27
     11 api_version: 1
     12 threadsafe: false
     13 '''
     14 
     15 class AppYamlHelper(object):
     16   '''Parses the app.yaml file, and is able to step back in the host file
     17   system's revision history to find when it changed to some given version.
     18   '''
     19   def __init__(self,
     20                app_yaml_path,
     21                file_system_at_head,
     22                object_store_creator,
     23                host_file_system_creator):
     24     self._app_yaml_path = app_yaml_path
     25     self._file_system_at_head = file_system_at_head
     26     self._store = object_store_creator.Create(
     27         AppYamlHelper,
     28         category=file_system_at_head.GetIdentity(),
     29         start_empty=False)
     30     self._host_file_system_creator = host_file_system_creator
     31 
     32   @staticmethod
     33   def ExtractVersion(app_yaml, key='version'):
     34     '''Extracts the 'version' key from the contents of an app.yaml file.
     35     Allow overriding the key to parse e.g. the cron file ('target').
     36     '''
     37     # We could properly parse this using a yaml library but Python doesn't have
     38     # one built in so whatevs.
     39     key_colon = '%s:' % key
     40     versions = [line.strip()[len(key_colon):].strip()
     41                 for line in app_yaml.split('\n')
     42                 if line.strip().startswith(key_colon)]
     43     if not versions:
     44       raise ValueError('No versions found for %s in %s' % (
     45           key, app_yaml))
     46     if len(set(versions)) > 1:
     47       raise ValueError('Inconsistent versions found for %s in %s: %s' % (
     48           key, app_yaml, versions))
     49     return versions[0]
     50 
     51   @staticmethod
     52   def IsGreater(lhs, rhs):
     53     '''Return whether the app.yaml version |lhs| > |rhs|. This is tricky
     54     because versions are typically not numbers but rather 2-0-9, 2-0-12,
     55     2-1-0, etc - and 2-1-0 > 2-0-10 > 2-0-9.
     56     '''
     57     lhs_parts = lhs.replace('-', '.').split('.')
     58     rhs_parts = rhs.replace('-', '.').split('.')
     59     while lhs_parts and rhs_parts:
     60       lhs_msb = int(lhs_parts.pop(0))
     61       rhs_msb = int(rhs_parts.pop(0))
     62       if lhs_msb != rhs_msb:
     63         return lhs_msb > rhs_msb
     64     return len(lhs) > len(rhs)
     65 
     66   @staticmethod
     67   def GenerateAppYaml(version):
     68     '''Probably only useful for tests.
     69     '''
     70     return _APP_YAML_CONTAINER % version
     71 
     72   def IsUpToDate(self, app_version):
     73     '''Returns True if the |app_version| is up to date with respect to the one
     74     checked into the host file system.
     75     '''
     76     checked_in_app_version = AppYamlHelper.ExtractVersion(
     77         self._file_system_at_head.ReadSingle(self._app_yaml_path))
     78     if app_version == checked_in_app_version:
     79       return True
     80     if AppYamlHelper.IsGreater(app_version, checked_in_app_version):
     81       logging.warning(
     82           'Server is too new! Checked in %s < currently running %s' % (
     83               checked_in_app_version, app_version))
     84       return True
     85     return False
     86 
     87   def GetFirstRevisionGreaterThan(self, app_version):
     88     '''Finds the first revision that the version in app.yaml was greater than
     89     |app_version|.
     90 
     91     WARNING: if there is no such revision (e.g. the app is up to date, or
     92     *oops* the app is even newer) then this will throw a ValueError. Use
     93     IsUpToDate to validate the input before calling this method.
     94     '''
     95     stored = self._store.Get(app_version).Get()
     96     if stored is None:
     97       stored = self._GetFirstRevisionGreaterThanImpl(app_version)
     98       assert stored is not None
     99       self._store.Set(app_version, stored)
    100     return stored
    101 
    102   def _GetFirstRevisionGreaterThanImpl(self, app_version):
    103     def get_app_yaml_revision(file_system):
    104       return int(file_system.Stat(self._app_yaml_path).version)
    105 
    106     def has_greater_app_version(file_system):
    107       app_version_in_file_system = AppYamlHelper.ExtractVersion(
    108           file_system.ReadSingle(self._app_yaml_path))
    109       return AppYamlHelper.IsGreater(app_version_in_file_system, app_version)
    110 
    111     found = None
    112     next_file_system = self._file_system_at_head
    113 
    114     while has_greater_app_version(next_file_system):
    115       found = get_app_yaml_revision(next_file_system)
    116       # Back up a revision then find when app.yaml was last updated before then.
    117       if found == 0:
    118         logging.warning('All revisions are greater than %s' % app_version)
    119         return 0
    120       next_file_system = self._host_file_system_creator.Create(
    121           revision=found - 1)
    122 
    123     if found is None:
    124       raise ValueError('All revisions are less than %s' % app_version)
    125     return found
    126