Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(413)

Side by Side Diff: recipe_modules/context/api.py

Issue 2985323002: Add support for LUCI_CONTEXT.
Patch Set: Created 3 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
1 # Copyright 2017 The LUCI Authors. All rights reserved. 1 # Copyright 2017 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 """The context module provides APIs for manipulating a few pieces of 'ambient' 5 """The context module provides APIs for manipulating a few pieces of 'ambient'
6 data that affect how steps are run. 6 data that affect how steps are run.
7 7
8 The pieces of information which can be modified are: 8 The pieces of information which can be modified are:
9 * cwd - The current working directory. 9 * cwd - The current working directory.
10 * env - The environment variables. 10 * env - The environment variables.
(...skipping 10 matching lines...) Expand all
21 program. 21 program.
22 22
23 Example: 23 Example:
24 ```python 24 ```python
25 with api.context(cwd=api.path['start_dir'].join('subdir')): 25 with api.context(cwd=api.path['start_dir'].join('subdir')):
26 # this step is run inside of the subdir directory. 26 # this step is run inside of the subdir directory.
27 api.step("cat subdir/foo", ['cat', './foo']) 27 api.step("cat subdir/foo", ['cat', './foo'])
28 ``` 28 ```
29 """ 29 """
30 30
31
32 import collections 31 import collections
33 import os 32 import copy
34 import types
35 33
36 from contextlib import contextmanager 34 from contextlib import contextmanager
37 35
38 from recipe_engine import recipe_api
39 from recipe_engine.config_types import Path 36 from recipe_engine.config_types import Path
40 from recipe_engine.recipe_api import RecipeApi 37 from recipe_engine.recipe_api import RecipeApi
41 38
39 from libs import luci_context as luci_ctx
40
42 41
43 def check_type(name, var, expect): 42 def check_type(name, var, expect):
44 if not isinstance(var, expect): # pragma: no cover 43 if not isinstance(var, expect): # pragma: no cover
45 raise TypeError('%s is not %s: %r (%s)' % ( 44 raise TypeError('%s is not %s: %r (%s)' % (
46 name, expect.__name__, var, type(var).__name__)) 45 name, expect.__name__, var, type(var).__name__))
47 46
48 47
49 class ContextApi(RecipeApi): 48 class ContextApi(RecipeApi):
50 49
51 # TODO(iannucci): move implementation of these data directly into this class. 50 # TODO(iannucci): move implementation of these data directly into this class.
52 def __init__(self, **kwargs): 51 def __init__(self, **kwargs):
53 super(RecipeApi, self).__init__(**kwargs) 52 super(RecipeApi, self).__init__(**kwargs)
54 53
55 self._cwd = [None] 54 self._cwd = [None]
56 self._env_prefixes = [{}] 55 self._env_prefixes = [{}]
57 self._env = [{}] 56 self._env = [{}]
57 if self._test_data.enabled:
58 self._luci_context = [self._test_data.get('luci_context', {})]
59 else: # pragma: no cover
60 self._luci_context = [luci_ctx.read_full()]
58 self._infra_step = [False] 61 self._infra_step = [False]
59 self._name_prefix = [''] 62 self._name_prefix = ['']
60 # this could be a number, but it makes the logic easier to use a stack. 63 # this could be a number, but it makes the logic easier to use a stack.
61 self._nest_level = [0] 64 self._nest_level = [0]
62 65
63 @contextmanager 66 @contextmanager
64 def __call__(self, cwd=None, env_prefixes=None, env=None, 67 def __call__(self, cwd=None, env_prefixes=None, env=None, luci_context=None,
65 increment_nest_level=None, infra_steps=None, name_prefix=None): 68 increment_nest_level=None, infra_steps=None, name_prefix=None):
66 """Allows adjustment of multiple context values in a single call. 69 """Allows adjustment of multiple context values in a single call.
67 70
68 Args: 71 Args:
69 * cwd (Path) - the current working directory to use for all steps. 72 * cwd (Path) - the current working directory to use for all steps.
70 To 'reset' to the original cwd at the time recipes started, pass 73 To 'reset' to the original cwd at the time recipes started, pass
71 `api.path['start_dir']`. 74 `api.path['start_dir']`.
72 * env_prefixes (dict) - Environmental variable prefix augmentations. See 75 * env_prefixes (dict) - Environmental variable prefix augmentations. See
73 below for more info. 76 below for more info.
74 * env (dict) - Environmental variable overrides. See below for more info. 77 * env (dict) - Environmental variable overrides. See below for more info.
78 * luci_context (dict) - LUCI_CONTEXT overrides.
iannucci 2017/08/04 18:55:50 "see below for more info"
75 * increment_nest_level (True) - increment the nest level by 1 in this 79 * increment_nest_level (True) - increment the nest level by 1 in this
76 context. Typically you won't directly interact with this, but should 80 context. Typically you won't directly interact with this, but should
77 use api.step.nest instead. 81 use api.step.nest instead.
78 * infra_steps (bool) - if steps in this context should be considered 82 * infra_steps (bool) - if steps in this context should be considered
79 infrastructure steps. On failure, these will raise InfraFailure 83 infrastructure steps. On failure, these will raise InfraFailure
80 exceptions instead of StepFailure exceptions. 84 exceptions instead of StepFailure exceptions.
81 * name_prefix (str) - A string to prepend to the names of all steps in 85 * name_prefix (str) - A string to prepend to the names of all steps in
82 this context. These compose with '.' characters if multiple name prefix 86 this context. These compose with '.' characters if multiple name prefix
83 contexts occur. See below for more info. 87 contexts occur. See below for more info.
84 88
(...skipping 25 matching lines...) Expand all
110 Which, at the time the step executes, will inject the current value of 114 Which, at the time the step executes, will inject the current value of
111 $PATH. 115 $PATH.
112 116
113 "env_prefix" is a list of Path or strings that get prefixed to their 117 "env_prefix" is a list of Path or strings that get prefixed to their
114 respective environment variables, delimited with the system's path 118 respective environment variables, delimited with the system's path
115 separator. This can be used to add entries to environment variables such 119 separator. This can be used to add entries to environment variables such
116 as "PATH" and "PYTHONPATH". If prefixes are specified and a value is also 120 as "PATH" and "PYTHONPATH". If prefixes are specified and a value is also
117 defined in "env", it will be installed as the last path component if it is 121 defined in "env", it will be installed as the last path component if it is
118 not empty. 122 not empty.
119 123
124 LUCI_CONTEXT overrides:
125
126 This is advanced stuff. LUCI_CONTEXT is used to pass ambient information
127 between layers of LUCI stack. 'luci_context' can be used to replace, add or
128 pop items in the current LUCI_CONTEXT. Unlike 'env', LUCI_CONTEXT contains
129 structured values, so values of 'luci_context' dict can be anything (not
130 only strings), and we are not attempting to merge them in any way, e.g
131 there's no equivalent of %(PATH)s trick.
iannucci 2017/08/04 18:55:50 we're going to delete the %(PATH) trick anyway, so
132
133 See https://github.com/luci/client-py/blob/master/LUCI_CONTEXT.md.
134
120 **TODO(iannucci): combine nest_level and name_prefix** 135 **TODO(iannucci): combine nest_level and name_prefix**
121 136
122 Look at the examples in "examples/" for examples of context module usage. 137 Look at the examples in "examples/" for examples of context module usage.
123 """ 138 """
124 to_pop = [] 139 to_pop = []
125 def _push(st, val): 140 def _push(st, val):
126 st.append(val) 141 st.append(val)
127 to_pop.append(st) 142 to_pop.append(st)
128 143
129 if cwd is not None: 144 if cwd is not None:
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after
175 # If the string contains any accidental sequential lookups, this 190 # If the string contains any accidental sequential lookups, this
176 # will raise an exception. If not, then this is a pluasible format 191 # will raise an exception. If not, then this is a pluasible format
177 # string. 192 # string.
178 ('%(foo)s'+v) % collections.defaultdict(str) 193 ('%(foo)s'+v) % collections.defaultdict(str)
179 except Exception: 194 except Exception:
180 raise ValueError(('Invalid %%-formatting parameter in envvar, ' 195 raise ValueError(('Invalid %%-formatting parameter in envvar, '
181 'only %%(ENVVAR)s allowed: %r') % (v,)) 196 'only %%(ENVVAR)s allowed: %r') % (v,))
182 new[k] = v 197 new[k] = v
183 _push(self._env, new) 198 _push(self._env, new)
184 199
200 if luci_context is not None and len(luci_context) > 0:
201 check_type('luci_context', luci_context, dict)
202 new = dict(self._luci_context[-1])
203 for k, v in luci_context.iteritems():
204 k = str(k)
205 if v is None:
206 new.pop(k, None)
207 elif isinstance(v, dict):
208 new[k] = copy.deepcopy(v)
iannucci 2017/08/04 18:55:51 can we assert that v is serializable to json? Othe
209 else:
210 raise TypeError(
211 'Bad type for LUCI_CONTEXT[%r]: %s', k, type(v).__name__)
212 _push(self._luci_context, new)
213
185 try: 214 try:
186 yield 215 yield
187 finally: 216 finally:
188 for p in to_pop: 217 for p in to_pop:
189 p.pop() 218 p.pop()
190 219
191 @property 220 @property
192 def cwd(self): 221 def cwd(self):
193 """Returns the current working directory that steps will run in. 222 """Returns the current working directory that steps will run in.
194 223
(...skipping 26 matching lines...) Expand all
221 prefixes registered with the environment. 250 prefixes registered with the environment.
222 251
223 **Returns (dict)** - The env-key -> value(Path) mapping of current 252 **Returns (dict)** - The env-key -> value(Path) mapping of current
224 environment prefix modifications. 253 environment prefix modifications.
225 """ 254 """
226 # TODO(iannucci): store env in an immutable way to avoid excessive copies. 255 # TODO(iannucci): store env in an immutable way to avoid excessive copies.
227 # TODO(iannucci): handle case-insensitive keys on windows 256 # TODO(iannucci): handle case-insensitive keys on windows
228 return dict(self._env_prefixes[-1]) 257 return dict(self._env_prefixes[-1])
229 258
230 @property 259 @property
260 def luci_context(self):
261 """Returns a dict with current state of LUCI_CONTEXT.
262
263 This is advanced stuff. LUCI_CONTEXT is used to pass ambient information
264 between layers of LUCI stack. Unlike 'env', it MAY be populated with
265 information about LUCI execution environment when recipe engine starts.
266
267 Do not dump the entirety of LUCI_CONTEXT into logs, it may contain secrets.
iannucci 2017/08/04 18:55:50 s/may contain/contains make them scared :)
268
269 See https://github.com/luci/client-py/blob/master/LUCI_CONTEXT.md.
270 """
271 return dict(self._luci_context[-1])
272
273 @property
231 def infra_step(self): 274 def infra_step(self):
232 """Returns the current value of the infra_step setting. 275 """Returns the current value of the infra_step setting.
233 276
234 **Returns (bool)** - True iff steps are currently considered infra steps. 277 **Returns (bool)** - True iff steps are currently considered infra steps.
235 """ 278 """
236 return self._infra_step[-1] 279 return self._infra_step[-1]
237 280
238 @property 281 @property
239 def name_prefix(self): 282 def name_prefix(self):
240 """Gets the current step name prefix. 283 """Gets the current step name prefix.
241 284
242 **Returns (str)** - The string prefix that every step will have prepended to 285 **Returns (str)** - The string prefix that every step will have prepended to
243 it. 286 it.
244 """ 287 """
245 return self._name_prefix[-1] 288 return self._name_prefix[-1]
246 289
247 @property 290 @property
248 def nest_level(self): 291 def nest_level(self):
249 """Returns the current 'nesting' level. 292 """Returns the current 'nesting' level.
250 293
251 Note: This api is low-level, and you should always prefer to use 294 Note: This api is low-level, and you should always prefer to use
252 `api.step.nest`. This api is included for completeness and documentation 295 `api.step.nest`. This api is included for completeness and documentation
253 purposes. 296 purposes.
254 297
255 **Returns (int)** - The current nesting level. 298 **Returns (int)** - The current nesting level.
256 """ 299 """
257 return self._nest_level[-1] 300 return self._nest_level[-1]
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698