class TestRecipeURIDB(object): def setup(self): self.filename = os.path.join(tc.STAGING, 'rdb.yml') self.rdb = RecipeURIDB(self.filename) def teardown(self): tc.delete_it(self.filename) def test_add_local(self): self.rdb.add('local', 'local', False) assert self.rdb['local']['path'] == 'local' assert self.rdb['local']['is_vcs'] is False def test_add_remote(self): self.rdb.add('remote_uri', 'remote_path', True) assert self.rdb['remote_uri']['path'] == 'remote_path' assert self.rdb['remote_uri']['is_vcs'] def test_add_remote_kwargs(self): kwargs = {'tag': '0.30.0'} self.rdb.add('remote_uri', 'remote_path', True, kwargs) assert self.rdb['remote_uri']['path'] == 'remote_path' assert self.rdb['remote_uri']['is_vcs'] assert self.rdb['remote_uri']['kwargs'] == kwargs def test_update_time(self): self.rdb.add('local', 'local', False) old_time = self.rdb['local']['time'] self.rdb.update_time('local') assert self.rdb['local']['time'] > old_time def test_need_updates(self): key = 'remote_uri' self.rdb.add(key, 'remote_path', True) self.rdb[key]['time'] = self.rdb[key]['time'] - 10000 assert self.rdb.need_updates(5000) == ['remote_uri'] def test_select_path_preferred(self): preferred = '/tmp/first' assert self.rdb.select_path(preferred) == preferred def test_select_path_collision(self): preferred = '/tmp/first' self.rdb.add('first', preferred, False) assert self.rdb.select_path(preferred) == preferred + '_1'
class RecipeManager(object): """ Manage the retrieval and updating of recipe sources remote and local. This class works in conjunction with RecipeDB to provide recipes to pakit. Attributes: active_kwargs: Indexed on uri, value is kwargs for the factory. active_uris: Actively configured sources. root: Where all recipes will be downloaded and stored. uri_db: A database to help keep track of recipe sources. Indexed based on uri. """ def __init__(self, config): """ Initialize the state of the Recipe manager based on configuration. Args: config: The pakit configuration. """ self.interval = config.get('pakit.recipe.update_interval') self.root = config.path_to('recipes') self.uri_db = RecipeURIDB(os.path.join(self.root, 'uris.yml')) self.active_kwargs = {} self.active_uris = [] for kwargs in copy.deepcopy(config.get('pakit.recipe.uris')): uri = kwargs.pop('uri') self.active_uris.append(uri) if len(kwargs): self.active_kwargs[uri] = kwargs @property def paths(self): """ Returns the paths to all active recipe locations on the system. """ return [self.uri_db[uri]['path'] for uri in self.active_uris] def check_for_deletions(self): """ Check if any entries in the uri_db are stale. Purge any uri_db entries that have been deleted from their path. """ to_remove = [] for uri in self.uri_db: if not os.path.exists(self.uri_db[uri]['path']): to_remove.append(uri) for uri in to_remove: del self.uri_db[uri] self.uri_db.write() def check_for_updates(self): """ Check if any of the active URIs needs updating. A recipe remote will be update if it is version controlled and ... - it has not been updated since interval - the kwargs between uri_db and active_kwargs differ """ need_updates = self.uri_db.need_updates(self.interval) vcs_uris = [uri for uri in self.uri_db if self.uri_db[uri]['is_vcs']] for uri in set(self.active_uris).intersection(vcs_uris): db_kwargs = self.uri_db[uri].get('kwargs', {}) kwargs = self.active_kwargs.get(uri, {}) repo = vcs_factory(uri, **kwargs) repo.target = self.uri_db[uri]['path'] if uri in need_updates or db_kwargs != kwargs: PLOG('Updating recipes from: %s.', uri) with repo: self.uri_db.update_time(uri) self.uri_db[uri]['kwargs'] = kwargs self.uri_db.write() def init_new_uris(self): """ For new uris not present in the uri_db: - Select a unique name inside recipes folder - Add an entry to the uri_db - If the uri is local, create the folder at the path. - If the uri is remote, clone the version repository with optional kwargs. Raises: PakitError: User attempted to use an unsupported URI. """ for uri in set(self.active_uris).difference(self.uri_db.keys()): repo = None kwargs = self.active_kwargs.get(uri, {}) try: repo = vcs_factory(uri, **kwargs) except PakitError: if uri.find('/') != -1 or uri.find('.') != -1: raise preferred = os.path.join(self.root, os.path.basename(uri)) path = self.uri_db.select_path(preferred) self.uri_db.add(uri, path, repo is not None, kwargs) if repo: repo.target = path PLOG('Downloading new recipes: %s', uri) with repo: pass else: PLOG('Indexing local recipes from: %s', path) try: os.makedirs(path) except OSError: pass self.uri_db.write()