Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright 2014 The Chromium Authors. All rights reserved. | 2 # Copyright 2014 The Chromium Authors. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Loads gatekeeper configuration files for use with gatekeeper_ng.py. | 6 """Loads gatekeeper configuration files for use with gatekeeper_ng.py. |
| 7 | 7 |
| 8 The gatekeeper json configuration file has two main sections: 'masters' | 8 The gatekeeper json configuration file has two main sections: 'masters' |
| 9 and 'categories.' The following shows the breakdown of a possible config, | 9 and 'categories.' The following shows the breakdown of a possible config, |
| 10 but note that all nodes are optional (including the root 'masters' and | 10 but note that all nodes are optional (including the root 'masters' and |
| 11 'categories' nodes). | 11 'categories' nodes). |
| 12 | 12 |
| 13 A builder ultimately needs 4 lists (sets): | 13 A builder ultimately needs 4 lists (sets): |
| 14 closing_steps: steps which close the tree on failure or omission | 14 closing_steps: steps which close the tree on failure or omission |
| 15 forgiving_steps: steps which close the tree but don't email committers | 15 forgiving_steps: steps which close the tree but don't email committers |
| 16 tree_notify: any additional emails to notify on tree failure | 16 tree_notify: any additional emails to notify on tree failure |
| 17 sheriff_classes: classes of sheriffs to notify on build failure | 17 sheriff_classes: classes of sheriffs to notify on build failure |
| 18 | 18 |
| 19 Builders can inherit these properties from categories, they can inherit | 19 Builders can inherit these properties from categories, they can inherit |
| 20 tree_notify and sheriff_classes from their master, and they can have these | 20 tree_notify and sheriff_classes from their master, and they can have these |
| 21 properties assigned in the builder itself. Any property not specified | 21 properties assigned in the builder itself. Any property not specified |
| 22 is considered blank (empty set), and inheritance is always constructive (you | 22 is considered blank (empty set), and inheritance is always constructive (you |
| 23 can't remove a property by inheriting or overwriting it). Builders can inherit | 23 can't remove a property by inheriting or overwriting it). Builders can inherit |
| 24 categories from their master. | 24 categories from their master. |
| 25 | 25 |
| 26 A master consists of zero or more sections, which specify which builders are | 26 A master consists of zero or more sections, which specify which builders are |
| 27 watched by the section and what action should be taken. A section can specify | 27 watched by the section and what action should be taken. |
| 28 tree_closing to be false, which causes the section to only send out emails | |
| 29 instead of closing the tree. A section or builder can also specify to respect | |
| 30 a build's failure status with respect_build_status. | |
| 31 | 28 |
| 32 The 'excluded_builders' key is a list of builder names that will not be | 29 The 'excluded_builders' key is a list of builder names that will not be |
| 33 processed even if they match a configuration. This is useful when the builder | 30 processed even if they match a configuration. This is useful when the builder |
| 34 set is specified using the wildcard ('*'). Entries in this list may use | 31 set is specified using the wildcard ('*'). Entries in this list may use |
| 35 filename-style globbing (e.g., *mybuilder*) to specify builder name patterns. | 32 filename-style globbing (e.g., *mybuilder*) to specify builder name patterns. |
| 36 | 33 |
| 37 The 'subject_template' key is the template used for the email subjects. Its | 34 The 'subject_template' key is the template used for the email subjects. Its |
| 38 formatting arguments are found at https://chromium.googlesource.com/chromium/ | 35 formatting arguments are found at https://chromium.googlesource.com/chromium/ |
| 39 tools/chromium-build/+/master/gatekeeper_mailer.py, but the list is | 36 tools/chromium-build/+/master/gatekeeper_mailer.py, but the list is |
| 40 reproduced here: | 37 reproduced here: |
| (...skipping 12 matching lines...) Expand all Loading... | |
| 53 'forgive_all' converts all closing_steps to be forgiving_steps. Since | 50 'forgive_all' converts all closing_steps to be forgiving_steps. Since |
| 54 forgiving_steps only email sheriffs + watchlist (not the committer), this is a | 51 forgiving_steps only email sheriffs + watchlist (not the committer), this is a |
| 55 great way to set up experimental or informational builders without spamming | 52 great way to set up experimental or informational builders without spamming |
| 56 people. It is enabled by providing the string 'true'. | 53 people. It is enabled by providing the string 'true'. |
| 57 | 54 |
| 58 'forgiving_optional' and 'closing_optional' work just like 'forgiving_steps' | 55 'forgiving_optional' and 'closing_optional' work just like 'forgiving_steps' |
| 59 and 'closing_steps', but they won't close if the step is missing. This is like | 56 and 'closing_steps', but they won't close if the step is missing. This is like |
| 60 previous gatekeeper behavior. They can be set to '*', which will match all | 57 previous gatekeeper behavior. They can be set to '*', which will match all |
| 61 steps in the builder. | 58 steps in the builder. |
| 62 | 59 |
| 60 'respect_build_status' means to use the buildbot result of the entire build | |
| 61 as an additional way to close the tree. As an example, if a build's closing | |
| 62 steps succeeded but the overall build result was FAILURE, the tree would | |
| 63 close if respect_build_status is set to True. respect_build_status only checks | |
| 64 for FAILURE, not any of the other statuses (including EXCEPTION). A build | |
| 65 status of SUCCESS will not override failing closing or forgiving steps. | |
| 66 respect_build_status is a boolean (true or false in JSON) and defaults to | |
| 67 False. | |
|
Dirk Pranke
2016/03/16 22:29:52
This seems kinda confusing (the concept, not the d
ghost stip (do not use)
2016/03/16 22:56:16
it's the return code of the build. think of what y
| |
| 68 | |
| 69 'close_tree' allows masters or builders to disable the --set-status option | |
| 70 set in gatekeeper_trees.json. In particular, this would be useful for a specific | |
| 71 builder on a tree-closing master which should notify the blamelist about | |
| 72 failures but should not close the tree. close_tree is a boolean (true or false | |
| 73 in JSON) and defaults to True. | |
| 74 | |
| 63 Note that if a builder sets something as forgiving_optional which is set as | 75 Note that if a builder sets something as forgiving_optional which is set as |
| 64 closing_optional in the master config, this value will be removed from | 76 closing_optional in the master config, this value will be removed from |
| 65 closing_optional. This allows builders to override master configuration values. | 77 closing_optional. This allows builders to override master configuration values. |
| 66 | 78 |
| 67 The 'comment' key can be put anywhere and is ignored by the parser. | 79 The 'comment' key can be put anywhere and is ignored by the parser. |
| 68 | 80 |
| 69 # Python, not JSON. | 81 # Python, not JSON. |
| 70 { | 82 { |
| 71 'masters': { | 83 'masters': { |
| 72 'http://build.chromium.org/p/chromium.win': [ | 84 'http://build.chromium.org/p/chromium.win': [ |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 123 | 135 |
| 124 DATA_DIR = os.path.dirname(os.path.abspath(__file__)) | 136 DATA_DIR = os.path.dirname(os.path.abspath(__file__)) |
| 125 | 137 |
| 126 | 138 |
| 127 # Keys which have defaults besides None or set([]). | 139 # Keys which have defaults besides None or set([]). |
| 128 DEFAULTS = { | 140 DEFAULTS = { |
| 129 'status_template': ('Tree is closed (Automatic: "%(unsatisfied)s" on ' | 141 'status_template': ('Tree is closed (Automatic: "%(unsatisfied)s" on ' |
| 130 '"%(builder_name)s" %(blamelist)s)'), | 142 '"%(builder_name)s" %(blamelist)s)'), |
| 131 'subject_template': ('buildbot %(result)s in %(project_name)s on ' | 143 'subject_template': ('buildbot %(result)s in %(project_name)s on ' |
| 132 '%(builder_name)s, revision %(revision)s'), | 144 '%(builder_name)s, revision %(revision)s'), |
| 145 'respect_build_status': False, | |
| 133 } | 146 } |
| 134 | 147 |
| 135 | 148 |
| 136 def allowed_keys(test_dict, *keys): | 149 def allowed_keys(test_dict, *keys): |
| 137 keys = keys + ('comment',) | 150 keys = keys + ('comment',) |
| 138 assert all(k in keys for k in test_dict), ( | 151 assert all(k in keys for k in test_dict), ( |
| 139 'not valid: %s; allowed: %s' % ( | 152 'not valid: %s; allowed: %s' % ( |
| 140 ', '.join(set(test_dict.keys()) - set(keys)), | 153 ', '.join(set(test_dict.keys()) - set(keys)), |
| 141 ', '.join(keys))) | 154 ', '.join(keys))) |
| 142 | 155 |
| (...skipping 22 matching lines...) Expand all Loading... | |
| 165 if union: | 178 if union: |
| 166 raise ValueError( | 179 raise ValueError( |
| 167 "The builder categories have conflicting entries %s for keys %s " | 180 "The builder categories have conflicting entries %s for keys %s " |
| 168 "and %s." % (union, k, v)) | 181 "and %s." % (union, k, v)) |
| 169 | 182 |
| 170 | 183 |
| 171 def load_gatekeeper_config(filename): | 184 def load_gatekeeper_config(filename): |
| 172 """Loads and verifies config json, constructs builder config dict.""" | 185 """Loads and verifies config json, constructs builder config dict.""" |
| 173 | 186 |
| 174 # Keys which are allowed in a master or builder section. | 187 # Keys which are allowed in a master or builder section. |
| 175 master_keys = ['excluded_builders', | 188 master_keys = ['close_tree', |
| 189 'excluded_builders', | |
| 176 'excluded_steps', | 190 'excluded_steps', |
| 177 'forgive_all', | 191 'forgive_all', |
| 192 'respect_build_status', | |
| 178 'sheriff_classes', | 193 'sheriff_classes', |
| 179 'status_template', | 194 'status_template', |
| 180 'subject_template', | 195 'subject_template', |
| 181 'tree_notify', | 196 'tree_notify', |
| 182 ] | 197 ] |
| 183 | 198 |
| 184 builder_keys = ['closing_optional', | 199 builder_keys = ['close_tree', |
| 200 'closing_optional', | |
| 185 'closing_steps', | 201 'closing_steps', |
| 186 'excluded_builders', | 202 'excluded_builders', |
| 187 'excluded_steps', | 203 'excluded_steps', |
| 188 'forgive_all', | 204 'forgive_all', |
| 189 'forgiving_optional', | 205 'forgiving_optional', |
| 190 'forgiving_steps', | 206 'forgiving_steps', |
| 207 'respect_build_status', | |
| 191 'sheriff_classes', | 208 'sheriff_classes', |
| 192 'status_template', | 209 'status_template', |
| 193 'subject_template', | 210 'subject_template', |
| 194 'tree_notify', | 211 'tree_notify', |
| 195 ] | 212 ] |
| 196 | 213 |
| 197 # These keys are strings instead of sets. Strings can't be merged, | 214 # These keys are strings instead of sets. Strings can't be merged, |
| 198 # so more specific (master -> category -> builder) strings clobber | 215 # so more specific (master -> category -> builder) strings clobber |
| 199 # more generic ones. | 216 # more generic ones. |
| 200 strings = ['forgive_all', 'status_template', 'subject_template'] | 217 strings = ['forgive_all', 'status_template', 'subject_template'] |
| 201 | 218 |
| 219 # Bools also share the 'strings' clobbering logic. | |
| 220 bools = ['close_tree', 'respect_build_status'] | |
| 221 | |
| 202 with open(filename) as f: | 222 with open(filename) as f: |
| 203 raw_gatekeeper_config = json.load(f) | 223 raw_gatekeeper_config = json.load(f) |
| 204 | 224 |
| 205 allowed_keys(raw_gatekeeper_config, 'categories', 'masters') | 225 allowed_keys(raw_gatekeeper_config, 'categories', 'masters') |
| 206 | 226 |
| 207 categories = raw_gatekeeper_config.get('categories', {}) | 227 categories = raw_gatekeeper_config.get('categories', {}) |
| 208 masters = raw_gatekeeper_config.get('masters', {}) | 228 masters = raw_gatekeeper_config.get('masters', {}) |
| 209 | 229 |
| 210 for category in categories.values(): | 230 for category in categories.values(): |
| 211 allowed_keys(category, *builder_keys) | 231 allowed_keys(category, *builder_keys) |
| 212 | 232 |
| 213 gatekeeper_config = {} | 233 gatekeeper_config = {} |
| 214 for master_url, master_sections in masters.iteritems(): | 234 for master_url, master_sections in masters.iteritems(): |
| 215 for master_section in master_sections: | 235 for master_section in master_sections: |
| 216 gatekeeper_config.setdefault(master_url, []).append({}) | 236 gatekeeper_config.setdefault(master_url, []).append({}) |
| 217 allowed_keys(master_section, 'builders', 'categories', 'close_tree', | 237 allowed_keys(master_section, 'builders', 'categories', *master_keys) |
| 218 'respect_build_status', *master_keys) | |
| 219 | 238 |
| 220 builders = master_section.get('builders', {}) | 239 builders = master_section.get('builders', {}) |
| 221 for buildername, builder in builders.iteritems(): | 240 for buildername, builder in builders.iteritems(): |
| 222 allowed_keys(builder, 'categories', *builder_keys) | 241 allowed_keys(builder, 'categories', *builder_keys) |
| 223 for key, item in builder.iteritems(): | 242 for key, item in builder.iteritems(): |
| 224 if key in strings: | 243 if key in strings: |
| 225 assert isinstance(item, basestring) | 244 assert isinstance(item, basestring) |
| 245 elif key in bools: | |
| 246 assert isinstance(item, bool) | |
| 226 else: | 247 else: |
| 227 assert isinstance(item, list) | 248 assert isinstance(item, list) |
| 228 assert all(isinstance(elem, basestring) for elem in item) | 249 assert all(isinstance(elem, basestring) for elem in item) |
| 229 | 250 |
| 230 gatekeeper_config[master_url][-1].setdefault(buildername, {}) | 251 gatekeeper_config[master_url][-1].setdefault(buildername, {}) |
| 231 gatekeeper_builder = gatekeeper_config[master_url][-1][buildername] | 252 gatekeeper_builder = gatekeeper_config[master_url][-1][buildername] |
| 232 | 253 |
| 233 # Populate with specified defaults. | 254 # Populate with specified defaults. |
| 234 for k in builder_keys: | 255 for k in builder_keys: |
| 235 if k in DEFAULTS: | 256 if k in DEFAULTS: |
| 236 gatekeeper_builder.setdefault(k, DEFAULTS[k]) | 257 gatekeeper_builder.setdefault(k, DEFAULTS[k]) |
| 237 elif k in strings: | 258 elif k in strings: |
| 238 gatekeeper_builder.setdefault(k, '') | 259 gatekeeper_builder.setdefault(k, '') |
| 260 elif k in bools: | |
| 261 gatekeeper_builder.setdefault(k, True) | |
| 239 else: | 262 else: |
| 240 gatekeeper_builder.setdefault(k, set()) | 263 gatekeeper_builder.setdefault(k, set()) |
| 241 | 264 |
| 242 # Inherit any values from the master. | 265 # Inherit any values from the master. |
| 243 for k in master_keys: | 266 for k in master_keys: |
| 244 if k in strings: | 267 if k in strings or k in bools: |
| 245 if k in master_section: | 268 if k in master_section: |
| 246 gatekeeper_builder[k] = master_section[k] | 269 gatekeeper_builder[k] = master_section[k] |
| 247 else: | 270 else: |
| 248 gatekeeper_builder[k] |= set(master_section.get(k, [])) | 271 gatekeeper_builder[k] |= set(master_section.get(k, [])) |
| 249 | 272 |
| 250 gatekeeper_builder['close_tree'] = master_section.get('close_tree', | |
| 251 True) | |
| 252 gatekeeper_builder['respect_build_status'] = master_section.get( | |
| 253 'respect_build_status', False) | |
| 254 | |
| 255 # Inherit any values from the categories. | 273 # Inherit any values from the categories. |
| 256 for c in master_section.get('categories', []): | 274 for c in master_section.get('categories', []): |
| 257 for k in builder_keys: | 275 for k in builder_keys: |
| 258 if k in strings: | 276 if k in strings or k in bools: |
| 259 if k in categories[c]: | 277 if k in categories[c]: |
| 260 gatekeeper_builder[k] = categories[c][k] | 278 gatekeeper_builder[k] = categories[c][k] |
| 261 else: | 279 else: |
| 262 gatekeeper_builder[k] |= set(categories[c].get(k, [])) | 280 gatekeeper_builder[k] |= set(categories[c].get(k, [])) |
| 263 | 281 |
| 264 special_keys = { | 282 special_keys = { |
| 265 'forgiving': 'closing', | 283 'forgiving': 'closing', |
| 266 'forgiving_optional': 'closing_optional', | 284 'forgiving_optional': 'closing_optional', |
| 267 } | 285 } |
| 268 | 286 |
| 269 check_builder_conflicts( | 287 check_builder_conflicts( |
| 270 special_keys, builder.get('categories', []), categories) | 288 special_keys, builder.get('categories', []), categories) |
| 271 | 289 |
| 272 for c in builder.get('categories', []): | 290 for c in builder.get('categories', []): |
| 273 for k in builder_keys: | 291 for k in builder_keys: |
| 274 if k in strings: | 292 if k in strings or k in bools: |
| 275 if k in categories[c]: | 293 if k in categories[c]: |
| 276 gatekeeper_builder[k] = categories[c][k] | 294 gatekeeper_builder[k] = categories[c][k] |
| 277 else: | 295 else: |
| 278 gatekeeper_builder[k] |= set(categories[c].get(k, [])) | 296 gatekeeper_builder[k] |= set(categories[c].get(k, [])) |
| 279 | 297 |
| 280 # If we're forgiving something in the builder that we set as | 298 # If we're forgiving something in the builder that we set as |
| 281 # closing in the master config, then don't close on it. Builders | 299 # closing in the master config, then don't close on it. Builders |
| 282 # can override master configurations. | 300 # can override master configurations. |
| 283 for key, key_to_modify in special_keys.items(): | 301 for key, key_to_modify in special_keys.items(): |
| 284 if key_to_modify in gatekeeper_builder: | 302 if key_to_modify in gatekeeper_builder: |
| 285 gatekeeper_builder[key_to_modify] -= set( | 303 gatekeeper_builder[key_to_modify] -= set( |
| 286 gatekeeper_builder.get(key, [])) | 304 gatekeeper_builder.get(key, [])) |
| 287 | 305 |
| 288 # Add in any builder-specific values. | 306 # Add in any builder-specific values. |
| 289 for k in builder_keys: | 307 for k in builder_keys: |
| 290 if k in strings: | 308 if k in strings or k in bools: |
| 291 if k in builder: | 309 if k in builder: |
| 292 gatekeeper_builder[k] = builder[k] | 310 gatekeeper_builder[k] = builder[k] |
| 293 else: | 311 else: |
| 294 gatekeeper_builder[k] |= set(builder.get(k, [])) | 312 gatekeeper_builder[k] |= set(builder.get(k, [])) |
| 295 | 313 |
| 296 # Builder postprocessing. | 314 # Builder postprocessing. |
| 297 if gatekeeper_builder['forgive_all'] == 'true': | 315 if gatekeeper_builder['forgive_all'] == 'true': |
| 298 gatekeeper_builder['forgiving_steps'] |= gatekeeper_builder[ | 316 gatekeeper_builder['forgiving_steps'] |= gatekeeper_builder[ |
| 299 'closing_steps'] | 317 'closing_steps'] |
| 300 gatekeeper_builder['forgiving_optional'] |= gatekeeper_builder[ | 318 gatekeeper_builder['forgiving_optional'] |= gatekeeper_builder[ |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 395 gatekeeper_config = inject_hashes(gatekeeper_config) | 413 gatekeeper_config = inject_hashes(gatekeeper_config) |
| 396 | 414 |
| 397 flatten_to_json(gatekeeper_config, sys.stdout) | 415 flatten_to_json(gatekeeper_config, sys.stdout) |
| 398 print | 416 print |
| 399 | 417 |
| 400 return 0 | 418 return 0 |
| 401 | 419 |
| 402 | 420 |
| 403 if __name__ == '__main__': | 421 if __name__ == '__main__': |
| 404 sys.exit(main()) | 422 sys.exit(main()) |
| OLD | NEW |