Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 # Copyright 2016 The LUCI Authors. All rights reserved. | 1 # Copyright 2016 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 """Functions to fetch and interpret bots.cfg file with list of bot groups.""" | 5 """Functions to fetch and interpret bots.cfg file with list of bot groups.""" |
| 6 | 6 |
| 7 import collections | 7 import collections |
| 8 import hashlib | 8 import hashlib |
| 9 import logging | 9 import logging |
| 10 import os | |
| 10 import re | 11 import re |
| 11 | 12 |
| 12 from components import auth | 13 from components import auth |
| 13 from components import config | 14 from components import config |
| 14 from components import utils | 15 from components import utils |
| 15 from components.config import validation | 16 from components.config import validation |
| 16 | 17 |
| 17 from proto import bots_pb2 | 18 from proto import bots_pb2 |
| 18 from server import config as local_config | 19 from server import config as local_config |
| 19 | 20 |
| (...skipping 20 matching lines...) Expand all Loading... | |
| 40 'ip_whitelist', | 41 'ip_whitelist', |
| 41 | 42 |
| 42 # Tuple with emails of bot owners. | 43 # Tuple with emails of bot owners. |
| 43 'owners', | 44 'owners', |
| 44 | 45 |
| 45 # Dict {key => list of values}. Always contains all the keys specified by | 46 # Dict {key => list of values}. Always contains all the keys specified by |
| 46 # 'trusted_dimensions' set in BotsCfg. If BotGroup doesn't define some | 47 # 'trusted_dimensions' set in BotsCfg. If BotGroup doesn't define some |
| 47 # dimension from that set, the list of value for it will be empty. Key and | 48 # dimension from that set, the list of value for it will be empty. Key and |
| 48 # values are unicode strings. | 49 # values are unicode strings. |
| 49 'dimensions', | 50 'dimensions', |
| 51 | |
| 52 # Name of the supplemental bot_config.py to inject to the bot during | |
| 53 # handshake. | |
| 54 'bot_config_script', | |
| 50 ]) | 55 ]) |
| 51 | 56 |
| 52 | 57 |
| 53 # Post-processed and validated read-only form of bots.cfg config. Its structure | 58 # Post-processed and validated read-only form of bots.cfg config. Its structure |
| 54 # is optimized for fast lookup of BotGroupConfig by bot_id. | 59 # is optimized for fast lookup of BotGroupConfig by bot_id. |
| 55 _BotGroups = collections.namedtuple('_BotGroups', [ | 60 _BotGroups = collections.namedtuple('_BotGroups', [ |
| 56 'direct_matches', # dict bot_id => BotGroupConfig | 61 'direct_matches', # dict bot_id => BotGroupConfig |
| 57 'prefix_matches', # list of pairs (bot_id_prefix, BotGroupConfig) | 62 'prefix_matches', # list of pairs (bot_id_prefix, BotGroupConfig) |
| 58 'machine_types', # dict machine_type.name => BotGroupConfig | 63 'machine_types', # dict machine_type.name => BotGroupConfig |
| 59 'default_group', # fallback BotGroupConfig or None if not defined | 64 'default_group', # fallback BotGroupConfig or None if not defined |
| 60 ]) | 65 ]) |
| 61 | 66 |
| 62 | 67 |
| 63 # Default config to use on unconfigured server. | 68 # Default config to use on unconfigured server. |
| 64 _DEFAULT_BOT_GROUPS = _BotGroups( | 69 _DEFAULT_BOT_GROUPS = _BotGroups( |
| 65 direct_matches={}, | 70 direct_matches={}, |
| 66 prefix_matches=[], | 71 prefix_matches=[], |
| 67 machine_types={}, | 72 machine_types={}, |
| 68 default_group=BotGroupConfig( | 73 default_group=BotGroupConfig( |
| 69 version='default', | 74 version='default', |
| 70 require_luci_machine_token=False, | 75 require_luci_machine_token=False, |
| 71 require_service_account=None, | 76 require_service_account=None, |
| 72 ip_whitelist=auth.BOTS_IP_WHITELIST, | 77 ip_whitelist=auth.BOTS_IP_WHITELIST, |
| 73 owners=(), | 78 owners=(), |
| 74 dimensions={})) | 79 dimensions={}, |
| 80 bot_config_script='')) | |
| 75 | 81 |
| 76 | 82 |
| 77 def _gen_version(fields): | 83 def _gen_version(fields): |
| 78 """Looks at BotGroupConfig fields and derives a digest that summarizes them. | 84 """Looks at BotGroupConfig fields and derives a digest that summarizes them. |
| 79 | 85 |
| 80 This digest is going to be sent to the bot in /handshake, and bot would | 86 This digest is going to be sent to the bot in /handshake, and bot would |
| 81 include it in its state (and thus send it with each /poll). If server detects | 87 include it in its state (and thus send it with each /poll). If server detects |
| 82 that the bot is using older version of the config, it would ask the bot | 88 that the bot is using older version of the config, it would ask the bot |
| 83 to restart. | 89 to restart. |
| 84 | 90 |
| (...skipping 53 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 138 k, v = parts[0], parts[1] | 144 k, v = parts[0], parts[1] |
| 139 dimensions.setdefault(k, set()).add(v) | 145 dimensions.setdefault(k, set()).add(v) |
| 140 | 146 |
| 141 auth_cfg = msg.auth or bots_pb2.BotAuth() | 147 auth_cfg = msg.auth or bots_pb2.BotAuth() |
| 142 | 148 |
| 143 return _make_bot_group_config( | 149 return _make_bot_group_config( |
| 144 require_luci_machine_token=auth_cfg.require_luci_machine_token, | 150 require_luci_machine_token=auth_cfg.require_luci_machine_token, |
| 145 require_service_account=auth_cfg.require_service_account, | 151 require_service_account=auth_cfg.require_service_account, |
| 146 ip_whitelist=auth_cfg.ip_whitelist, | 152 ip_whitelist=auth_cfg.ip_whitelist, |
| 147 owners=tuple(msg.owners), | 153 owners=tuple(msg.owners), |
| 148 dimensions={k: sorted(v) for k, v in dimensions.iteritems()}) | 154 dimensions={k: sorted(v) for k, v in dimensions.iteritems()}, |
| 155 bot_config_script=msg.bot_config_script) | |
| 149 | 156 |
| 150 | 157 |
| 151 def _expand_bot_id_expr(expr): | 158 def _expand_bot_id_expr(expr): |
| 152 """Expands string with bash-like sets (if they are there). | 159 """Expands string with bash-like sets (if they are there). |
| 153 | 160 |
| 154 E.g. takes "vm{1..3}-m1" and yields "vm1-m1", "vm2-m1", "vm3-m1". Also | 161 E.g. takes "vm{1..3}-m1" and yields "vm1-m1", "vm2-m1", "vm3-m1". Also |
| 155 supports list syntax ({1,2,3}). Either one should be used, but not both, e.g. | 162 supports list syntax ({1,2,3}). Either one should be used, but not both, e.g. |
| 156 following WILL NOT work: {1..3,4,5}. | 163 following WILL NOT work: {1..3,4,5}. |
| 157 | 164 |
| 158 Yields original string if it doesn't have '{...}' section. | 165 Yields original string if it doesn't have '{...}' section. |
| (...skipping 226 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 385 try: | 392 try: |
| 386 auth.Identity(auth.IDENTITY_USER, own) | 393 auth.Identity(auth.IDENTITY_USER, own) |
| 387 except ValueError: | 394 except ValueError: |
| 388 ctx.error('invalid owner email "%s"', own) | 395 ctx.error('invalid owner email "%s"', own) |
| 389 | 396 |
| 390 # Validate 'dimensions'. | 397 # Validate 'dimensions'. |
| 391 for dim in entry.dimensions: | 398 for dim in entry.dimensions: |
| 392 if not local_config.validate_flat_dimension(dim): | 399 if not local_config.validate_flat_dimension(dim): |
| 393 ctx.error('bad dimension %r', dim) | 400 ctx.error('bad dimension %r', dim) |
| 394 | 401 |
| 402 # Validate 'bot_config_script': the supplemental bot_config.py. | |
| 403 if entry.bot_config_script: | |
| 404 # Another check confirms that the script itself is valid python. | |
| 405 if not entry.bot_config_script.endswith('.py'): | |
| 406 ctx.error('Invalid bot_config_script name') | |
| 407 if os.path.basename(entry.bot_config_script) != entry.bot_config_script: | |
| 408 ctx.error('Invalid bot_config_script name') | |
| 409 # The file must exist and must be valid non empty python script. | |
| 410 path = 'scripts/' + entry.bot_config_script | |
| 411 if not config.get_self_config(path, store_last_good=True)[1]: | |
| 412 # Work around a problem in luci-config, this will require a full RPC | |
| 413 # but better be safe than sorry. | |
| 414 logging.warning('failed to find cached %s', path) | |
| 415 if not config.get_self_config(path)[1]: | |
|
Vadim Sh.
2017/02/09 22:22:00
I think this will not generally work. Luci config
M-A Ruel
2017/02/09 23:47:02
Nodir can confirm if what I do is wrong. It's not
M-A Ruel
2017/02/10 18:33:26
I tested and confirmed it doesn't. Just removed th
nodir
2017/02/10 18:41:43
Sorry for being late, Vadim's reasoning is correct
M-A Ruel
2017/02/10 19:53:19
No big deal, the latest patchset is confirmed to w
| |
| 416 ctx.error( | |
| 417 'Referenced bot_config_script %s must exist in scripts/' % | |
| 418 entry.bot_config_script) | |
| 419 | |
| 395 # Now verify bot_id_prefix is never a prefix of other prefix. It causes | 420 # Now verify bot_id_prefix is never a prefix of other prefix. It causes |
| 396 # ambiguities. | 421 # ambiguities. |
| 397 for smaller, s_idx in bot_id_prefixes.iteritems(): | 422 for smaller, s_idx in bot_id_prefixes.iteritems(): |
| 398 for larger, l_idx in bot_id_prefixes.iteritems(): | 423 for larger, l_idx in bot_id_prefixes.iteritems(): |
| 399 if smaller == larger: | 424 if smaller == larger: |
| 400 continue # we've already checked prefixes have no duplicated | 425 continue # we've already checked prefixes have no duplicated |
| 401 if larger.startswith(smaller): | 426 if larger.startswith(smaller): |
| 402 ctx.error( | 427 ctx.error( |
| 403 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", ' | 428 'bot_id_prefix "%s", defined in group #%d, is subprefix of "%s", ' |
| 404 'defined in group #%d; it makes group assigned for bots with ' | 429 'defined in group #%d; it makes group assigned for bots with ' |
| 405 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger) | 430 'prefix "%s" ambigious', smaller, s_idx, larger, l_idx, larger) |
| OLD | NEW |