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 5import logging 6 7from extensions_paths import APP_YAML 8 9 10_APP_YAML_CONTAINER = ''' 11application: chrome-apps-doc 12version: %s 13runtime: python27 14api_version: 1 15threadsafe: false 16''' 17 18 19class AppYamlHelper(object): 20 '''Parses the app.yaml file, and is able to step back in the host file 21 system's revision history to find when it changed to some given version. 22 ''' 23 def __init__(self, 24 object_store_creator, 25 host_file_system_provider): 26 self._store = object_store_creator.Create( 27 AppYamlHelper, 28 category=host_file_system_provider.GetMaster().GetIdentity(), 29 start_empty=False) 30 self._host_file_system_provider = host_file_system_provider 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._host_file_system_provider.GetMaster().ReadSingle(APP_YAML).Get()) 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 # XXX(ahernandez): Tricky. The 'version' of app.yaml coming from 104 # GitilesFileSystem is a blob ID. 105 def get_app_yaml_revision(file_system): 106 return int(file_system.Stat(APP_YAML).version) 107 108 def has_greater_app_version(file_system): 109 app_version_in_file_system = AppYamlHelper.ExtractVersion( 110 file_system.ReadSingle(APP_YAML).Get()) 111 return AppYamlHelper.IsGreater(app_version_in_file_system, app_version) 112 113 found = None 114 next_file_system = self._host_file_system_provider.GetMaster() 115 116 while has_greater_app_version(next_file_system): 117 found = get_app_yaml_revision(next_file_system) 118 # Back up a revision then find when app.yaml was last updated before then. 119 if found == 0: 120 logging.warning('All revisions are greater than %s' % app_version) 121 return 0 122 next_file_system = self._host_file_system_provider.GetMaster( 123 commit=next_file_system.GetPreviousCommitID().Get()) 124 125 if found is None: 126 raise ValueError('All revisions are less than %s' % app_version) 127 return found 128