| OLD | NEW |
| 1 # Copyright 2015 The LUCI Authors. All rights reserved. | 1 # Copyright 2015 The LUCI Authors. All rights reserved. |
| 2 # Use of this source code is governed under the Apache License, Version 2.0 | 2 # Use of this source code is governed under the Apache License, Version 2.0 |
| 3 # that can be found in the LICENSE file. | 3 # that can be found in the LICENSE file. |
| 4 | 4 |
| 5 """Adapter between config service client and the rest of auth_service. | 5 """Adapter between config service client and the rest of auth_service. |
| 6 | 6 |
| 7 Basically a cron job that each minute refetches config files from config service | 7 Basically a cron job that each minute refetches config files from config service |
| 8 and modifies auth service datastore state if anything changed. | 8 and modifies auth service datastore state if anything changed. |
| 9 | 9 |
| 10 Following files are fetched: | 10 Following files are fetched: |
| 11 imports.cfg - configuration for group importer cron job. | 11 imports.cfg - configuration for group importer cron job. |
| 12 ip_whitelist.cfg - IP whitelists. | 12 ip_whitelist.cfg - IP whitelists. |
| 13 oauth.cfg - OAuth client_id whitelist. | 13 oauth.cfg - OAuth client_id whitelist. |
| 14 | 14 |
| 15 Configs are ASCII serialized protocol buffer messages. The schema is defined in | 15 Configs are ASCII serialized protocol buffer messages. The schema is defined in |
| 16 proto/config.proto. | 16 proto/config.proto. |
| 17 | 17 |
| 18 Storing infrequently changing configuration in the config service (implemented | 18 Storing infrequently changing configuration in the config service (implemented |
| 19 on top of source control) allows to use code review workflow for configuration | 19 on top of source control) allows to use code review workflow for configuration |
| 20 changes as well as removes a need to write some UI for them. | 20 changes as well as removes a need to write some UI for them. |
| 21 """ | 21 """ |
| 22 | 22 |
| 23 import collections | 23 import collections |
| 24 import logging | 24 import logging |
| 25 import os | |
| 26 import posixpath | 25 import posixpath |
| 27 | 26 |
| 28 from google import protobuf | 27 from google import protobuf |
| 29 from google.appengine.ext import ndb | 28 from google.appengine.ext import ndb |
| 30 | 29 |
| 31 from components import config | 30 from components import config |
| 32 from components import datastore_utils | 31 from components import datastore_utils |
| 33 from components import gitiles | 32 from components import gitiles |
| 34 from components import utils | 33 from components import utils |
| 35 from components.auth import ipaddr | 34 from components.auth import ipaddr |
| 36 from components.auth import model | 35 from components.auth import model |
| 37 from components.config import validation | 36 from components.config import validation |
| 38 from components.config import validation_context | 37 from components.config import validation_context |
| 39 | 38 |
| 40 from proto import config_pb2 | 39 from proto import config_pb2 |
| 41 import importer | 40 import importer |
| 42 | 41 |
| 43 | 42 |
| 44 # Config file revision number and where it came from. | 43 # Config file revision number and where it came from. |
| 45 Revision = collections.namedtuple('Revision', ['revision', 'url']) | 44 Revision = collections.namedtuple('Revision', ['revision', 'url']) |
| 46 | 45 |
| 47 | 46 |
| 47 class CannotLoadConfigError(Exception): |
| 48 """Raised when fetching configs if they are missing or invalid.""" |
| 49 |
| 50 |
| 48 def is_remote_configured(): | 51 def is_remote_configured(): |
| 49 """True if config service backend URL is defined. | 52 """True if config service backend URL is defined. |
| 50 | 53 |
| 51 If config service backend URL is not set auth_service will use datastore | 54 If config service backend URL is not set auth_service will use datastore |
| 52 as source of truth for configuration (with some simple web UI to change it). | 55 as source of truth for configuration (with some simple web UI to change it). |
| 53 | 56 |
| 54 If config service backend URL is set, UI for config management will be read | 57 If config service backend URL is set, UI for config management will be read |
| 55 only and all config changes must be performed through the config service. | 58 only and all config changes must be performed through the config service. |
| 56 """ | 59 """ |
| 57 return bool(get_remote_url()) | 60 return bool(get_remote_url()) |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 97 | 100 |
| 98 Called as a cron job. | 101 Called as a cron job. |
| 99 """ | 102 """ |
| 100 if not is_remote_configured(): | 103 if not is_remote_configured(): |
| 101 logging.info('Config remote is not configured') | 104 logging.info('Config remote is not configured') |
| 102 return | 105 return |
| 103 | 106 |
| 104 # Grab and validate all new configs in parallel. | 107 # Grab and validate all new configs in parallel. |
| 105 try: | 108 try: |
| 106 configs = _fetch_configs(_CONFIG_SCHEMAS) | 109 configs = _fetch_configs(_CONFIG_SCHEMAS) |
| 107 except config.CannotLoadConfigError as exc: | 110 except CannotLoadConfigError as exc: |
| 108 logging.error('Failed to fetch configs\n%s', exc) | 111 logging.error('Failed to fetch configs\n%s', exc) |
| 109 return | 112 return |
| 110 | 113 |
| 111 # Figure out what needs to be updated. | 114 # Figure out what needs to be updated. |
| 112 dirty = {} | 115 dirty = {} |
| 113 dirty_in_authdb = {} | 116 dirty_in_authdb = {} |
| 114 for path, (new_rev, conf) in sorted(configs.iteritems()): | 117 for path, (new_rev, conf) in sorted(configs.iteritems()): |
| 115 assert path in _CONFIG_SCHEMAS, path | 118 assert path in _CONFIG_SCHEMAS, path |
| 116 cur_rev = get_config_revision(path) | 119 cur_rev = get_config_revision(path) |
| 117 if cur_rev != new_rev or force: | 120 if cur_rev != new_rev or force: |
| (...skipping 409 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 527 config.get_self_config_async( | 530 config.get_self_config_async( |
| 528 p, dest_type=_CONFIG_SCHEMAS[p]['proto_class'], store_last_good=False) | 531 p, dest_type=_CONFIG_SCHEMAS[p]['proto_class'], store_last_good=False) |
| 529 for p in paths | 532 for p in paths |
| 530 ] | 533 ] |
| 531 configs_url = _get_configs_url() | 534 configs_url = _get_configs_url() |
| 532 ndb.Future.wait_all(futures) | 535 ndb.Future.wait_all(futures) |
| 533 out = {} | 536 out = {} |
| 534 for path, future in zip(paths, futures): | 537 for path, future in zip(paths, futures): |
| 535 rev, conf = future.get_result() | 538 rev, conf = future.get_result() |
| 536 if conf is None: | 539 if conf is None: |
| 537 raise config.CannotLoadConfigError('Config %s is missing' % path) | 540 raise CannotLoadConfigError('Config %s is missing' % path) |
| 538 try: | 541 try: |
| 539 validation.validate(config.self_config_set(), path, conf) | 542 validation.validate(config.self_config_set(), path, conf) |
| 540 except ValueError as exc: | 543 except ValueError as exc: |
| 541 raise config.CannotLoadConfigError( | 544 raise CannotLoadConfigError( |
| 542 'Config %s at rev %s failed to pass validation: %s' % | 545 'Config %s at rev %s failed to pass validation: %s' % |
| 543 (path, rev, exc)) | 546 (path, rev, exc)) |
| 544 out[path] = (Revision(rev, _gitiles_url(configs_url, rev, path)), conf) | 547 out[path] = (Revision(rev, _gitiles_url(configs_url, rev, path)), conf) |
| 545 return out | 548 return out |
| 546 | 549 |
| 547 | 550 |
| 548 def _gitiles_url(configs_url, rev, path): | 551 def _gitiles_url(configs_url, rev, path): |
| 549 """URL to a directory in gitiles -> URL to a file at concrete revision.""" | 552 """URL to a directory in gitiles -> URL to a file at concrete revision.""" |
| 550 try: | 553 try: |
| 551 location = gitiles.Location.parse(configs_url) | 554 location = gitiles.Location.parse(configs_url) |
| 552 return str(gitiles.Location( | 555 return str(gitiles.Location( |
| 553 hostname=location.hostname, | 556 hostname=location.hostname, |
| 554 project=location.project, | 557 project=location.project, |
| 555 treeish=rev, | 558 treeish=rev, |
| 556 path=posixpath.join(location.path, path))) | 559 path=posixpath.join(location.path, path))) |
| 557 except ValueError: | 560 except ValueError: |
| 558 # Not a gitiles URL, return as is. | 561 # Not a gitiles URL, return as is. |
| 559 return configs_url | 562 return configs_url |
| OLD | NEW |