def test_bad_ip_whitelist_name(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(auth=bots_pb2.BotAuth( ip_whitelist='bad ## name')) ]) self.validator_test( cfg, ['bot_group #0: invalid ip_whitelist name "bad ## name"'])
def test_bad_owners(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup( bot_id=['blah'], auth=DEFAULT_AUTH_CFG, owners=['bad email']), ]) self.validator_test(cfg, ['bot_group #0: invalid owner email "bad email"'])
def test_bad_auth_cfg_no_ip_whitelist(self): cfg = bots_pb2.BotsCfg( bot_group=[bots_pb2.BotGroup(auth=bots_pb2.BotAuth())]) self.validator_test(cfg, [ 'bot_group #0: if both require_luci_machine_token and ' 'require_service_account are unset, ip_whitelist is required' ])
def test_bad_required_service_account(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(auth=bots_pb2.BotAuth( require_service_account='not-an-email')) ]) self.validator_test( cfg, ['bot_group #0: invalid service account email "not-an-email"'])
def test_duplicate_prefixes(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(bot_id_prefix=['abc-'], auth=DEFAULT_AUTH_CFG), bots_pb2.BotGroup(bot_id_prefix=['abc-'], auth=DEFAULT_AUTH_CFG), ]) self.validator_test(cfg, [ 'bot_group #1: bot_id_prefix "abc-" is already specified in group #0' ])
def test_bot_id_duplication(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(bot_id=['b{0..5}'], auth=DEFAULT_AUTH_CFG), bots_pb2.BotGroup(bot_id=['b5'], auth=DEFAULT_AUTH_CFG), ]) self.validator_test( cfg, ['bot_group #1: bot_id "b5" was already mentioned in group #0'])
def test_bad_bot_id(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(bot_id=['blah{}'], auth=DEFAULT_AUTH_CFG), ]) self.validator_test(cfg, [ 'bot_group #0: bad bot_id expression "blah{}" - Invalid set "", ' 'not a list and not a range' ])
def test_bad_dimension_not_kv(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(bot_id=['blah'], auth=DEFAULT_AUTH_CFG, dimensions=['not_kv_pair']), ]) self.validator_test(cfg, [ 'bot_group #0: bad dimension "not_kv_pair", not a key:value pair' ])
def test_bad_dimension_bad_dim_key(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(bot_id=['blah'], auth=DEFAULT_AUTH_CFG, dimensions=['blah####key:value:value']), ]) self.validator_test(cfg, [ 'bot_group #0: bad dimension key in "blah####key:value:value", ' 'should match ^[a-zA-Z\\-\\_\\.]+$' ])
def test_bad_auth_cfg_two_methods(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(auth=bots_pb2.BotAuth( require_luci_machine_token=True, require_service_account='*****@*****.**', )) ]) self.validator_test(cfg, [ 'bot_group #0: require_luci_machine_token and require_service_account ' 'can\'t both be used at the same time' ])
def _get_expanded_bots_cfg(known_digest=None): """Fetches expanded bots.cfg from the datastore cache. If the cache is not there (may happen right after deploying the service or after changing _BOT_CFG_CACHE_VER), falls back to fetching the config directly right here. This situation is rare. Args: known_digest: digest of ExpandedBotsCfg already known to the caller, to skip fetching it from the cache if nothing has changed. Returns: (True, ExpandedBotsCfg) if fetched some new version from the cache. (True, None) if there's no bots.cfg config at all. (False, None) if the cached version has digest matching 'known_digest'. Raises: BadConfigError if there's no cached config and the current config at HEAD is not passing validation. """ head = _bots_cfg_head_key().get() if not head: # This branch is hit when we deploy the service the first time, before # the fetch cron runs, or after changing _BOT_CFG_CACHE_VER. We manually # refresh the cache in this case, not waiting for the cron. logging.warning('No bots.cfg cached for code v%d, forcing the refresh', _BOT_CFG_CACHE_VER) expanded = refetch_from_config_service( ) # raises BadConfigError on errors if expanded and known_digest and expanded.digest == known_digest: return False, None return True, expanded if known_digest and head.digest == known_digest: return False, None if head.empty: return True, None # At this point we know there's something newer stored in the cache. Grab it. # Since this happens outside of a transaction, we may fetch a version that is # ever newer than pointed to by 'head'. This is fine. body = _bots_cfg_body_key().get() if not body: raise AssertionError( 'BotsCfgBody is missing, this should not be possible') if known_digest and body.digest == known_digest: return False, None # the body was sneakily reverted back just now if body.empty: return True, None bots = bots_pb2.BotsCfg() bots.ParseFromString(body.bots_cfg) return True, ExpandedBotsCfg(bots, body.bots_cfg_rev, body.digest)
def test_intersecting_prefixes(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(bot_id_prefix=['abc-'], auth=DEFAULT_AUTH_CFG), bots_pb2.BotGroup(bot_id_prefix=['abc-def-'], auth=DEFAULT_AUTH_CFG), ]) self.validator_test(cfg, [ 'bot_id_prefix "abc-", defined in group #0, is subprefix of "abc-def-", ' 'defined in group #1; it makes group assigned for bots with prefix ' '"abc-def-" ambigious' ])
TEST_CONFIG = bots_pb2.BotsCfg( trusted_dimensions=['pool'], bot_group=[ bots_pb2.BotGroup( bot_id=['bot_with_token'], auth=bots_pb2.BotAuth(require_luci_machine_token=True), dimensions=['pool:with_token']), bots_pb2.BotGroup(bot_id=['bot_with_service_account'], auth=bots_pb2.BotAuth(require_service_account=[ '*****@*****.**', '*****@*****.**', ]), dimensions=['pool:with_service_account']), bots_pb2.BotGroup(bot_id=['bot_with_ip_whitelist'], auth=bots_pb2.BotAuth(ip_whitelist='ip_whitelist'), dimensions=['pool:with_ip_whitelist']), bots_pb2.BotGroup( bot_id=['bot_with_service_account_and_ip_whitelist'], auth=bots_pb2.BotAuth( require_service_account=['*****@*****.**'], ip_whitelist='ip_whitelist', ), dimensions=['pool:with_service_account_and_ip_whitelist']), bots_pb2.BotGroup(bot_id=['bot_with_token_and_ip_whitelist'], auth=bots_pb2.BotAuth( require_luci_machine_token=True, ip_whitelist='ip_whitelist'), dimensions=['pool:with_token_and_ip_whitelist']), ], )
def test_empty_config_is_valid(self): self.validator_test(bots_pb2.BotsCfg(), [])
def test_trusted_dimensions_valid(self): cfg = bots_pb2.BotsCfg(trusted_dimensions=['pool', 'project']) self.validator_test(cfg, [])
def test_trusted_dimensions_invalid(self): cfg = bots_pb2.BotsCfg(trusted_dimensions=['pool:blah']) self.validator_test(cfg, [ 'trusted_dimensions: invalid dimension key - "pool:blah", ' 'must match ^[a-zA-Z\\-\\_\\.]+$' ])
def test_empty_prefix(self): cfg = bots_pb2.BotsCfg(bot_group=[ bots_pb2.BotGroup(bot_id_prefix=[''], auth=DEFAULT_AUTH_CFG) ]) self.validator_test( cfg, ['bot_group #0: empty bot_id_prefix is not allowed'])
from components import utils from components.config import validation from test_support import test_case from proto import bots_pb2 from server import bot_groups_config TEST_CONFIG = bots_pb2.BotsCfg( trusted_dimensions=['pool'], bot_group=[ bots_pb2.BotGroup( bot_id=['bot1', 'bot{2..3}'], auth=bots_pb2.BotAuth(require_luci_machine_token=True), owners=['*****@*****.**'], dimensions=['pool:A', 'pool:B', 'other:D'], ), bots_pb2.BotGroup( bot_id=['other_bot'], bot_id_prefix=['bot'], auth=bots_pb2.BotAuth(require_service_account='*****@*****.**')), bots_pb2.BotGroup(auth=bots_pb2.BotAuth(ip_whitelist='bots'), dimensions=['pool:default']), ], ) EXPECTED_GROUP_1 = bot_groups_config._make_bot_group_config( require_luci_machine_token=True, require_service_account=u'', ip_whitelist=u'', owners=(u'*****@*****.**', ), dimensions={