1# Copyright 2014 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5
6""" Utilities for dealing with builder names. This module obtains its attributes
7dynamically from builder_name_schema.json. """
8
9
10import json
11import os
12
13
14# All of these global variables are filled in by _LoadSchema().
15
16# The full schema.
17BUILDER_NAME_SCHEMA = None
18
19# Character which separates parts of a builder name.
20BUILDER_NAME_SEP = None
21
22# Builder roles.
23BUILDER_ROLE_CANARY = 'Canary'
24BUILDER_ROLE_BUILD = 'Build'
25BUILDER_ROLE_HOUSEKEEPER = 'Housekeeper'
26BUILDER_ROLE_INFRA = 'Infra'
27BUILDER_ROLE_PERF = 'Perf'
28BUILDER_ROLE_TEST = 'Test'
29BUILDER_ROLES = (BUILDER_ROLE_CANARY,
30                 BUILDER_ROLE_BUILD,
31                 BUILDER_ROLE_HOUSEKEEPER,
32                 BUILDER_ROLE_INFRA,
33                 BUILDER_ROLE_PERF,
34                 BUILDER_ROLE_TEST)
35
36# Suffix which distinguishes trybots from normal bots.
37TRYBOT_NAME_SUFFIX = None
38
39
40def _LoadSchema():
41  """ Load the builder naming schema from the JSON file. """
42
43  def _UnicodeToStr(obj):
44    """ Convert all unicode strings in obj to Python strings. """
45    if isinstance(obj, unicode):
46      return str(obj)
47    elif isinstance(obj, dict):
48      return dict(map(_UnicodeToStr, obj.iteritems()))
49    elif isinstance(obj, list):
50      return list(map(_UnicodeToStr, obj))
51    elif isinstance(obj, tuple):
52      return tuple(map(_UnicodeToStr, obj))
53    else:
54      return obj  # pragma: no cover
55
56  builder_name_json_filename = os.path.join(
57      os.path.dirname(__file__), 'builder_name_schema.json')
58  builder_name_schema_json = json.load(open(builder_name_json_filename))
59
60  global BUILDER_NAME_SCHEMA
61  BUILDER_NAME_SCHEMA = _UnicodeToStr(
62      builder_name_schema_json['builder_name_schema'])
63
64  global BUILDER_NAME_SEP
65  BUILDER_NAME_SEP = _UnicodeToStr(
66      builder_name_schema_json['builder_name_sep'])
67
68  global TRYBOT_NAME_SUFFIX
69  TRYBOT_NAME_SUFFIX = _UnicodeToStr(
70      builder_name_schema_json['trybot_name_suffix'])
71
72  # Since the builder roles are dictionary keys, just assert that the global
73  # variables above account for all of them.
74  assert len(BUILDER_ROLES) == len(BUILDER_NAME_SCHEMA)
75  for role in BUILDER_ROLES:
76    assert role in BUILDER_NAME_SCHEMA
77
78
79_LoadSchema()
80
81
82def MakeBuilderName(role, extra_config=None, is_trybot=False,
83                    **kwargs):  # pragma: no cover
84  schema = BUILDER_NAME_SCHEMA.get(role)
85  if not schema:  # pragma: no cover
86    raise ValueError('%s is not a recognized role.' % role)
87  for k, v in kwargs.iteritems():
88    if BUILDER_NAME_SEP in v:  # pragma: no cover
89      raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP, v))
90    if not k in schema:  # pragma: no cover
91      raise ValueError('Schema does not contain "%s": %s' %(k, schema))
92  if extra_config and BUILDER_NAME_SEP in extra_config:  # pragma: no cover
93    raise ValueError('%s not allowed in %s.' % (BUILDER_NAME_SEP,
94                                                extra_config))
95  name_parts = [role]
96  name_parts.extend([kwargs[attribute] for attribute in schema])
97  if extra_config:
98    name_parts.append(extra_config)
99  if is_trybot:
100    name_parts.append(TRYBOT_NAME_SUFFIX)
101  return BUILDER_NAME_SEP.join(name_parts)
102
103
104def IsTrybot(builder_name):  # pragma: no cover
105  """ Returns true if builder_name refers to a trybot (as opposed to a
106  waterfall bot). """
107  return builder_name.endswith(TRYBOT_NAME_SUFFIX)
108
109
110def GetWaterfallBot(builder_name):  # pragma: no cover
111  """Returns the name of the waterfall bot for this builder. If it is not a
112  trybot, builder_name is returned unchanged. If it is a trybot the name is
113  returned without the trybot suffix."""
114  if not IsTrybot(builder_name):
115    return builder_name
116  return _WithoutSuffix(builder_name, BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX)
117
118
119def TrybotName(builder_name):  # pragma: no cover
120  """Returns the name of the trybot clone of this builder.
121
122  If the given builder is a trybot, the name is returned unchanged. If not, the
123  TRYBOT_NAME_SUFFIX is appended.
124  """
125  if builder_name.endswith(TRYBOT_NAME_SUFFIX):
126    return builder_name
127  return builder_name + BUILDER_NAME_SEP + TRYBOT_NAME_SUFFIX
128
129
130def _WithoutSuffix(string, suffix):  # pragma: no cover
131  """ Returns a copy of string 'string', but with suffix 'suffix' removed.
132  Raises ValueError if string does not end with suffix. """
133  if not string.endswith(suffix):
134    raise ValueError('_WithoutSuffix: string %s does not end with suffix %s' % (
135        string, suffix))
136  return string[:-len(suffix)]
137
138
139def DictForBuilderName(builder_name):
140  """Makes a dictionary containing details about the builder from its name."""
141  split_name = builder_name.split(BUILDER_NAME_SEP)
142
143  def pop_front():
144    try:
145      return split_name.pop(0)
146    except:  # pragma: no cover
147      raise ValueError('Invalid builder name: %s' % builder_name)
148
149  result = {'is_trybot': False}
150
151  if split_name[-1] == TRYBOT_NAME_SUFFIX:
152    result['is_trybot'] = True
153    split_name.pop()
154
155  if split_name[0] in BUILDER_NAME_SCHEMA.keys():
156    key_list = BUILDER_NAME_SCHEMA[split_name[0]]
157    result['role'] = pop_front()
158    for key in key_list:
159      result[key] = pop_front()
160    if split_name:
161      result['extra_config'] = pop_front()
162    if split_name:  # pragma: no cover
163      raise ValueError('Invalid builder name: %s' % builder_name)
164  else:  # pragma: no cover
165    raise ValueError('Invalid builder name: %s' % builder_name)
166  return result
167
168
169