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 |