blob: be40b2e6f102857227fffdb5009d3ae851a92f3c [file] [log] [blame]
# Copyright 2015 The LUCI Authors. All rights reserved.
# Use of this source code is governed under the Apache License, Version 2.0
# that can be found in the LICENSE file.
"""fs.Provider reads configs from the filesystem."""
import logging
import os
from google.appengine.ext import ndb
from components.config import common
# SEPARATOR separates config set name and config file path.
# A config set name cannot contain 'CONFIGS' because of the capilization.
SEPARATOR = 'CONFIGS'
class Provider(object):
"""Filesystem-based configuration provider.
Some functions are made NDB tasklets because they follow common interface.
See api._get_config_provider for context.
"""
def __init__(self, root):
assert root
self.root = root
@ndb.tasklet
def get_async(self, config_set, path, dest_type=None, **kwargs):
"""Reads a (revision, config) from a file, where revision is always None.
Kwargs are not used, but reported as warnings.
"""
assert config_set
assert path
if kwargs:
logging.warning(
'config: parameters %r are ignored in the filesystem mode',
kwargs.keys())
filename = os.path.join(
self.root,
config_set.replace('/', os.path.sep),
SEPARATOR,
path.replace('/', os.path.sep))
filename = os.path.abspath(filename)
assert filename.startswith(os.path.abspath(self.root)), filename
content = None
if os.path.exists(filename):
with open(filename, 'rb') as f:
content = f.read()
config = common._convert_config(content, dest_type)
raise ndb.Return(None, config)
def get_project_ids(self):
# A project_id cannot contain a slash, so recursion is not needed.
projects_dir = os.path.join(self.root, 'projects')
if not os.path.isdir(projects_dir):
return
for pid in os.listdir(projects_dir):
if os.path.isdir(os.path.join(projects_dir, pid)):
yield pid
@ndb.tasklet
def get_projects_async(self):
projects = [{'id': pid} for pid in sorted(self.get_project_ids())]
# TODO(nodir): read project names from projects/<pid>:project.cfg
raise ndb.Return(projects)
def get_project_refs(self, project_id):
assert project_id
assert os.path.sep not in project_id, project_id
project_path = os.path.join(self.root, 'projects', project_id) + os.path.sep
refs_dir = project_path + 'refs'
if not os.path.isdir(refs_dir):
return
for dirpath, dirs, _ in os.walk(refs_dir):
if SEPARATOR in dirs:
# This is a leaf of the ref tree.
dirs.remove(SEPARATOR) # Do not go deeper.
yield dirpath[len(project_path):]
@ndb.tasklet
def get_project_configs_async(self, path):
"""Reads a config file in all projects.
Returns:
{config_set -> (revision, content)} map, where revision is always None.
"""
assert path
config_sets = ['projects/%s' % pid for pid in self.get_project_ids()]
result = {}
for config_set in config_sets:
rev, content = yield self.get_async(config_set, path)
if content is not None:
result[config_set] = (rev, content)
raise ndb.Return(result)
@ndb.tasklet
def get_config_set_location_async(self, _config_set):
"""Returns URL of where configs for given config set are stored.
Returns:
Always None for file system.
"""
raise ndb.Return(None)
def get_provider(): # pragma: no cover
return Provider(common.CONSTANTS.CONFIG_DIR)