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 from __future__ import absolute_import | 5 from __future__ import absolute_import |
| 6 import contextlib | 6 import contextlib |
| 7 import collections | 7 import collections |
| 8 import keyword | 8 import keyword |
| 9 import re | 9 import re |
| 10 import types | 10 import types |
| 11 | 11 |
| 12 from functools import wraps | 12 from functools import wraps |
| 13 | 13 |
| 14 from .recipe_test_api import DisabledTestData, ModuleTestData | 14 from .recipe_test_api import DisabledTestData, ModuleTestData |
| 15 from .config import Single | 15 from .config import Single |
| 16 | 16 |
| 17 from .util import ModuleInjectionSite | 17 from .util import ModuleInjectionSite |
| 18 | 18 |
| 19 from . import field_composer | 19 from . import field_composer |
| 20 | 20 |
| 21 | 21 |
| 22 _StepConfig = collections.namedtuple('StepConfig', | 22 _TriggerSpec = collections.namedtuple('_TriggerSpec', |
| 23 ('bucket', 'builder_name', 'properties', 'buildbot_changes', 'tags', | |
| 24 'critical')) | |
| 25 | |
| 26 class TriggerSpec(_TriggerSpec): | |
| 27 """ | |
| 28 TriggerSpec is the internal representation of a raw trigger step. You should | |
| 29 use the standard 'step' recipe module, which will construct trigger specs | |
| 30 via API. | |
| 31 """ | |
| 32 | |
| 33 @classmethod | |
| 34 def _make_from_api_dict(cls, trig): | |
|
iannucci
2016/09/12 22:35:16
could we **explode this and add docstring?
| |
| 35 builder_name = trig.pop('builder_name') | |
| 36 if not builder_name: | |
| 37 raise ValueError('Trigger spec: builder_name is not set') | |
| 38 | |
| 39 changes = trig.pop('buildbot_changes', None) | |
| 40 if changes is not None: | |
| 41 assert isinstance(changes, list), 'buildbot_changes must be a list' | |
|
iannucci
2016/09/12 22:35:16
raise valueerror
dnj
2016/09/12 23:30:11
Done.
| |
| 42 | |
| 43 inst = cls( | |
| 44 bucket=trig.pop('bucket', None), | |
| 45 builder_name=builder_name, | |
| 46 properties=trig.pop('properties', None), | |
| 47 buildbot_changes=changes, | |
| 48 tags=trig.pop('tags', None), | |
| 49 critical=trig.pop('critical', True), | |
| 50 ) | |
| 51 | |
| 52 # Confirm that all dictionary keys have been consumed. | |
| 53 if trig: | |
| 54 unknown_keys = sorted(trig.iterkeys()) | |
| 55 raise KeyError('Unknown trigger spec keys: %s' % ( | |
|
iannucci
2016/09/12 22:35:16
I think this is still ValueError. I think KeyError
dnj
2016/09/12 23:30:11
Done.
| |
| 56 ', '.join(unknown_keys))) | |
| 57 | |
| 58 return inst | |
| 59 | |
| 60 def _render_to_dict(self): | |
| 61 d = dict((k, v) for k, v in self._asdict().iteritems() if v) | |
| 62 if d['critical']: | |
| 63 d.pop('critical') | |
| 64 return d | |
| 65 | |
| 66 | |
| 67 _StepConfig = collections.namedtuple('_StepConfig', | |
| 23 ('name', 'cmd', 'cwd', 'env', 'allow_subannotations', 'trigger_specs', | 68 ('name', 'cmd', 'cwd', 'env', 'allow_subannotations', 'trigger_specs', |
| 24 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin', 'ok_ret', | 69 'timeout', 'infra_step', 'stdout', 'stderr', 'stdin', 'ok_ret', |
| 25 'step_test_data', 'nest_level')) | 70 'step_test_data', 'nest_level')) |
| 26 | 71 |
| 27 class StepConfig(_StepConfig): | 72 class StepConfig(_StepConfig): |
| 28 """ | 73 """ |
| 74 StepConfig is the representation of a raw step as the recipe_engine sees it. | |
| 75 You should use the standard 'step' recipe module, which will construct and | |
| 76 pass this data to the engine for you, instead. The only reason why you would | |
| 77 need to worry about this object is if you're modifying the step module itself. | |
| 78 | |
| 79 The optional "env" parameter provides optional overrides for environment | |
| 80 variables. Each value is % formatted with the entire existing os.environ. A | |
| 81 value of `None` will remove that envvar from the environ. e.g. | |
| 82 | |
| 83 { | |
| 84 "envvar": "%(envvar)s;%(envvar2)s;extra", | |
| 85 "delete_this": None, | |
| 86 "static_value": "something", | |
| 87 } | |
| 88 | |
| 29 StepConfig parameters: | 89 StepConfig parameters: |
| 30 name: name of the step, will appear in buildbots waterfall | 90 name (str): name of the step, will appear in buildbots waterfall |
| 31 cmd: command to run, list of one or more strings | 91 cmd: command to run. Acceptable types: str, Path, Placeholder, or None. |
| 32 cwd: absolute path to working directory for the command | 92 cwd (str or None): absolute path to working directory for the command |
| 33 env: dict with overrides for environment variables | 93 env (dict): overrides for environment variables, described above. |
| 34 allow_subannotations: if True, lets the step emit its own annotations | 94 allow_subannotations (bool): if True, lets the step emit its own annotations |
| 95 NOTE: Enabling this can cause some buggy behavior. Please strongly | |
| 96 consider using step_result.presentation instead. If you have questions, | |
| 97 please contact infra-dev@chromium.org. | |
| 35 trigger_specs: a list of trigger specifications, see also _trigger_builds. | 98 trigger_specs: a list of trigger specifications, see also _trigger_builds. |
| 36 timeout: if not None, a datetime.timedelta for the step timeout. | 99 timeout: if not None, a datetime.timedelta for the step timeout. |
| 37 infra_step: if True, this is an infrastructure step. | 100 infra_step: if True, this is an infrastructure step. Failures will raise |
| 38 stdout: Path to a file to put step stdout into. If used, stdout won't | 101 InfraFailure instead of StepFailure. |
| 39 appear in annotator's stdout (and |allow_subannotations| is | 102 stdout: Placeholder to put step stdout into. If used, stdout won't appear in |
| 40 ignored). | 103 annotator's stdout (and |allow_subannotations| is ignored). |
| 41 stderr: Path to a file to put step stderr into. If used, stderr won't | 104 stderr: Placeholder to put step stderr into. If used, stderr won't appear in |
| 42 appear in annotator's stderr. | 105 annotator's stderr. |
| 43 stdin: Path to a file to read step stdin from. | 106 stdin: Placeholder to read step stdin from. |
| 44 ok_ret: Allowed return code list. | 107 ok_ret (iter): set of return codes allowed. If the step process returns |
|
iannucci
2016/09/12 22:35:16
if you want to say this is an iter, then we should
dnj
2016/09/12 23:30:11
Was converting to a set, but frozenset is even bet
| |
| 45 step_test_data: Possible associated step test data. | 108 something not on this list, it will raise a StepFailure (or InfraFailure |
| 46 nest_level: the step's nesting level. | 109 if infra_step is True). If omitted, {0} will be used. |
| 110 step_test_data (func -> recipe_test_api.StepTestData): A factory which | |
| 111 returns a StepTestData object that will be used as the default test data | |
| 112 for this step. The recipe author can override/augment this object in the | |
| 113 GenTests function. | |
| 114 nest_level (int): the step's nesting level. | |
| 47 """ | 115 """ |
| 48 | 116 |
| 117 _RENDER_WHITELIST=frozenset(( | |
| 118 'cmd', | |
| 119 )) | |
| 120 | |
| 121 _RENDER_BLACKLIST=frozenset(( | |
| 122 'nest_level', | |
| 123 'ok_ret', | |
| 124 'infra_step', | |
| 125 'step_test_data', | |
| 126 )) | |
| 127 | |
| 128 @classmethod | |
| 129 def _make_from_api_dict(cls, step): | |
| 130 step_config = StepConfig( | |
| 131 name=step.pop('name'), | |
| 132 cmd=step.pop('cmd', None), | |
| 133 cwd=step.pop('cwd', None), | |
| 134 env=step.pop('env', None), | |
| 135 allow_subannotations=step.pop('allow_subannotations', False), | |
| 136 trigger_specs=[TriggerSpec._make_from_api_dict(trig) | |
| 137 for trig in step.pop('trigger_specs', ())], | |
| 138 timeout=step.pop('timeout', None), | |
| 139 infra_step=step.pop('infra_step', False), | |
| 140 stdout=step.pop('stdout', None), | |
| 141 stderr=step.pop('stderr', None), | |
| 142 stdin=step.pop('stdin', None), | |
| 143 ok_ret=set(step.pop('ok_ret', {0})), | |
| 144 step_test_data=step.pop('step_test_data', None), | |
| 145 nest_level=int(step.pop('step_nest_level', 0)), | |
| 146 ) | |
| 147 | |
| 148 # Confirm that all dictionary keys have been consumed. | |
| 149 if step: | |
| 150 unknown_keys = sorted(step.iterkeys()) | |
| 151 raise KeyError('Unknown step dictionary keys: %s' % ( | |
| 152 ', '.join(unknown_keys))) | |
| 153 | |
| 154 return step_config | |
| 155 | |
| 156 def render_to_dict(self): | |
| 157 self = self._replace( | |
| 158 trigger_specs=[trig._render_to_dict() | |
| 159 for trig in (self.trigger_specs or ())], | |
| 160 ) | |
| 161 return dict((k, v) for k, v in self._asdict().iteritems() | |
| 162 if (v or k in self._RENDER_WHITELIST) | |
| 163 and k not in self._RENDER_BLACKLIST) | |
| 164 | |
| 49 | 165 |
| 50 def _make_step_config(**step): | 166 def _make_step_config(**step): |
| 51 """Galvanize a step dictionary into a formal StepConfig.""" | 167 """Convert a step API dictionary into a formal StepConfig.""" |
| 52 step_config = StepConfig( | 168 return StepConfig._make_from_api_dict(step) |
| 53 name=step.pop('name'), | |
| 54 cmd=step.pop('cmd', None), | |
| 55 cwd=step.pop('cwd', None), | |
| 56 env=step.pop('env', None), | |
| 57 allow_subannotations=step.pop('allow_subannotations', False), | |
| 58 trigger_specs=step.pop('trigger_specs', ()), | |
| 59 timeout=step.pop('timeout', None), | |
| 60 infra_step=step.pop('infra_step', False), | |
| 61 stdout=step.pop('stdout', None), | |
| 62 stderr=step.pop('stderr', None), | |
| 63 stdin=step.pop('stdin', None), | |
| 64 ok_ret=step.pop('ok_ret', {0}), | |
| 65 step_test_data=step.pop('step_test_data', None), | |
| 66 nest_level=step.pop('step_nest_level', 0), | |
| 67 ) | |
| 68 if step: | |
| 69 unknown_keys = sorted(step.iterkeys()) | |
| 70 raise KeyError('Unknown step dictionary keys: %s' % ( | |
| 71 ', '.join(unknown_keys))) | |
| 72 return step_config | |
| 73 | 169 |
| 74 | 170 |
| 75 class StepFailure(Exception): | 171 class StepFailure(Exception): |
| 76 """ | 172 """ |
| 77 This is the base class for all step failures. | 173 This is the base class for all step failures. |
| 78 | 174 |
| 79 Raising a StepFailure counts as 'running a step' for the purpose of | 175 Raising a StepFailure counts as 'running a step' for the purpose of |
| 80 infer_composite_step's logic. | 176 infer_composite_step's logic. |
| 81 """ | 177 """ |
| 82 def __init__(self, name_or_reason, result=None): | 178 def __init__(self, name_or_reason, result=None): |
| (...skipping 635 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 718 """ | 814 """ |
| 719 Gets the BoundProperty version of this Property. Requires a name. | 815 Gets the BoundProperty version of this Property. Requires a name. |
| 720 """ | 816 """ |
| 721 return BoundProperty( | 817 return BoundProperty( |
| 722 self._default, self.help, self.kind, name, property_type, module, | 818 self._default, self.help, self.kind, name, property_type, module, |
| 723 self.param_name) | 819 self.param_name) |
| 724 | 820 |
| 725 class UndefinedPropertyException(TypeError): | 821 class UndefinedPropertyException(TypeError): |
| 726 pass | 822 pass |
| 727 | 823 |
| OLD | NEW |