Merge lp:~zyga/checkbox/fix-1297746 into lp:checkbox
- fix-1297746
- Merge into trunk
Status: | Merged |
---|---|
Approved by: | Daniel Manrique |
Approved revision: | 2852 |
Merged at revision: | 2854 |
Proposed branch: | lp:~zyga/checkbox/fix-1297746 |
Merge into: | lp:checkbox |
Diff against target: |
505 lines (+153/-53) 6 files modified
plainbox/plainbox/impl/job.py (+35/-20) plainbox/plainbox/impl/secure/providers/test_v1.py (+10/-7) plainbox/plainbox/impl/secure/providers/v1.py (+55/-10) plainbox/plainbox/impl/test_job.py (+16/-8) plainbox/plainbox/provider_manager.py (+13/-1) plainbox/plainbox/test_provider_manager.py (+24/-7) |
To merge this branch: | bzr merge lp:~zyga/checkbox/fix-1297746 |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Daniel Manrique (community) | Approve | ||
Review via email: mp+213341@code.launchpad.net |
Commit message
Description of the change
5a7415f plainbox:job: don't require the id field
3a845c8 plainbox:job: validate the presence of job.partial_id
eddc7e0 plainbox:job: drop validator_cls argument
17f4314 plainbox:job: add Problem.deprecated
ad51337 plainbox:job: add strictness control to job validator
3f05db4 plainbox:
c1974b4 plainbox:
b2e6e20 plainbox:
dd9036e plainbox:
- 2848. By Zygmunt Krynicki
-
plainbox:job: add strictness control to job validator
This patch adds two optional arguments to the validator, strict, which
effectively runs the validator at maximum strictness and deprecated
which makes it find deprecated fields or values.The default settings will ignore harmless errors (thus making the
validator do no evil) while still reporting (and preventing) serious
problems that would definitely fail at job runtime.Signed-off-by: Zygmunt Krynicki <email address hidden>
- 2849. By Zygmunt Krynicki
-
plainbox:
secure: providers: drop useless import Signed-off-by: Zygmunt Krynicki <email address hidden>
- 2850. By Zygmunt Krynicki
-
plainbox:
secure: providers: expose control for job validation This patch allows two settings (validate and validation_kwargs) to propagate
from Provider1 to JobDefinitionPlugIn. By default validation is enabled
but no validation keywords are supplied so only serious problems are
"reported" (as in preventing a job definition from being loaded).
Those can be changed by passing the two new keyword-only arguments.They are passed from Provider1.
from_definition (),
through Provider1.__init_ _() and to JobDefinitionPlugIn where they control
the validation block. The second argument (validation_kwargs) is passed
directly to the validator to control its behavior.Some tests were altered not to overly validate everything where it is
obvious we don't care about valid jobs, just about getting some dummy
data across.Signed-off-by: Zygmunt Krynicki <email address hidden>
- 2851. By Zygmunt Krynicki
-
plainbox:
provider_ manager: enforce strong validation in `manage.py validate` This patch ironically allows the validate command to load broken job
definitions and inspect them manually inside the command core.Signed-off-by: Zygmunt Krynicki <email address hidden>
- 2852. By Zygmunt Krynicki
-
plainbox:
provider_ manager: handle deprecated fields This patch makes the `manage.py validate` command support the
Problem.deprecated problem which is used to report 'name' being used
inside job definitions.Signed-off-by: Zygmunt Krynicki <email address hidden>
Preview Diff
1 | === modified file 'plainbox/plainbox/impl/job.py' | |||
2 | --- plainbox/plainbox/impl/job.py 2014-03-27 13:57:14 +0000 | |||
3 | +++ plainbox/plainbox/impl/job.py 2014-03-28 22:08:58 +0000 | |||
4 | @@ -50,6 +50,7 @@ | |||
5 | 50 | missing = 'missing' | 50 | missing = 'missing' |
6 | 51 | wrong = 'wrong' | 51 | wrong = 'wrong' |
7 | 52 | useless = 'useless' | 52 | useless = 'useless' |
8 | 53 | deprecated = 'deprecated' | ||
9 | 53 | 54 | ||
10 | 54 | 55 | ||
11 | 55 | class ValidationError(ValueError): | 56 | class ValidationError(ValueError): |
12 | @@ -75,15 +76,27 @@ | |||
13 | 75 | """ | 76 | """ |
14 | 76 | 77 | ||
15 | 77 | @staticmethod | 78 | @staticmethod |
17 | 78 | def validate(job): | 79 | def validate(job, strict=False, deprecated=False): |
18 | 79 | """ | 80 | """ |
19 | 80 | Validate the specified job | 81 | Validate the specified job |
20 | 82 | |||
21 | 83 | :param strict: | ||
22 | 84 | Enforce strict validation. Non-conforming jobs will be rejected. | ||
23 | 85 | This is off by default to ensure that non-critical errors don't | ||
24 | 86 | prevent jobs from running. | ||
25 | 87 | :param deprecated: | ||
26 | 88 | Enforce deprecation validation. Jobs having deprecated fields will | ||
27 | 89 | be rejected. This is off by default to allow backwards compatible | ||
28 | 90 | jobs to be used without any changes. | ||
29 | 81 | """ | 91 | """ |
32 | 82 | # Check if id is empty | 92 | # Check if name is still being used, if running in strict mode |
33 | 83 | if job.id is None: | 93 | if deprecated and job.name is not None: |
34 | 94 | raise ValidationError(job.fields.name, Problem.deprecated) | ||
35 | 95 | # Check if the partial_id field is empty | ||
36 | 96 | if job.partial_id is None: | ||
37 | 84 | raise ValidationError(job.fields.id, Problem.missing) | 97 | raise ValidationError(job.fields.id, Problem.missing) |
40 | 85 | # Check if summary is empty | 98 | # Check if summary is empty, if running in strict mode |
41 | 86 | if job.summary is None: | 99 | if strict and job.summary is None: |
42 | 87 | raise ValidationError(job.fields.summary, Problem.missing) | 100 | raise ValidationError(job.fields.summary, Problem.missing) |
43 | 88 | # Check if plugin is empty | 101 | # Check if plugin is empty |
44 | 89 | if job.plugin is None: | 102 | if job.plugin is None: |
45 | @@ -91,11 +104,13 @@ | |||
46 | 91 | # Check if plugin has a good value | 104 | # Check if plugin has a good value |
47 | 92 | if job.plugin not in JobDefinition.plugin.get_all_symbols(): | 105 | if job.plugin not in JobDefinition.plugin.get_all_symbols(): |
48 | 93 | raise ValidationError(job.fields.plugin, Problem.wrong) | 106 | raise ValidationError(job.fields.plugin, Problem.wrong) |
51 | 94 | # Check if user is given without a command to run | 107 | # Check if user is given without a command to run, if running in strict |
52 | 95 | if job.user is not None and job.command is None: | 108 | # mode |
53 | 109 | if strict and job.user is not None and job.command is None: | ||
54 | 96 | raise ValidationError(job.fields.user, Problem.useless) | 110 | raise ValidationError(job.fields.user, Problem.useless) |
57 | 97 | # Check if environ is given without a command to run | 111 | # Check if environ is given without a command to run, if running in |
58 | 98 | if job.environ is not None and job.command is None: | 112 | # strict mode |
59 | 113 | if strict and job.environ is not None and job.command is None: | ||
60 | 99 | raise ValidationError(job.fields.environ, Problem.useless) | 114 | raise ValidationError(job.fields.environ, Problem.useless) |
61 | 100 | # Verify that command is present on a job within the subset that should | 115 | # Verify that command is present on a job within the subset that should |
62 | 101 | # really have them (shell, local, resource, attachment, user-verify and | 116 | # really have them (shell, local, resource, attachment, user-verify and |
63 | @@ -121,8 +136,9 @@ | |||
64 | 121 | if job.description is None: | 136 | if job.description is None: |
65 | 122 | raise ValidationError( | 137 | raise ValidationError( |
66 | 123 | job.fields.description, Problem.missing) | 138 | job.fields.description, Problem.missing) |
69 | 124 | # Ensure that manual jobs don't have command | 139 | # Ensure that manual jobs don't have command, if running in strict |
70 | 125 | if job.command is not None: | 140 | # mode |
71 | 141 | if strict and job.command is not None: | ||
72 | 126 | raise ValidationError(job.fields.command, Problem.useless) | 142 | raise ValidationError(job.fields.command, Problem.useless) |
73 | 127 | 143 | ||
74 | 128 | 144 | ||
75 | @@ -481,16 +497,12 @@ | |||
76 | 481 | @classmethod | 497 | @classmethod |
77 | 482 | def from_rfc822_record(cls, record): | 498 | def from_rfc822_record(cls, record): |
78 | 483 | """ | 499 | """ |
80 | 484 | Create a JobDefinition instance from rfc822 record | 500 | Create a JobDefinition instance from rfc822 record. The resulting |
81 | 501 | instance may not be valid but will always be created. Only valid jobs | ||
82 | 502 | should be executed. | ||
83 | 485 | 503 | ||
84 | 486 | The record must be a RFC822Record instance. | 504 | The record must be a RFC822Record instance. |
85 | 487 | |||
86 | 488 | Only the 'id' and 'plugin' keys are required. | ||
87 | 489 | All other data is stored as is and is entirely optional. | ||
88 | 490 | """ | 505 | """ |
89 | 491 | if 'id' not in record.data and 'name' not in record.data: | ||
90 | 492 | # TRANSLATORS: don't translate id or translate it as 'id field' | ||
91 | 493 | raise ValueError(_("Cannot create job without an id")) | ||
92 | 494 | # Strip the trailing newlines form all the raw values coming from the | 506 | # Strip the trailing newlines form all the raw values coming from the |
93 | 495 | # RFC822 parser. We don't need them and they don't match gettext keys | 507 | # RFC822 parser. We don't need them and they don't match gettext keys |
94 | 496 | # (xgettext strips out those newlines) | 508 | # (xgettext strips out those newlines) |
95 | @@ -498,14 +510,17 @@ | |||
96 | 498 | key: value.rstrip('\n') | 510 | key: value.rstrip('\n') |
97 | 499 | for key, value in record.raw_data.items()}) | 511 | for key, value in record.raw_data.items()}) |
98 | 500 | 512 | ||
100 | 501 | def validate(self, validator_cls=CheckBoxJobValidator): | 513 | def validate(self, **validation_kwargs): |
101 | 502 | """ | 514 | """ |
102 | 503 | Validate this job definition with the specified validator | 515 | Validate this job definition with the specified validator |
103 | 504 | 516 | ||
104 | 517 | :param validation_kwargs: | ||
105 | 518 | Keyword arguments to pass to the | ||
106 | 519 | :meth:`CheckBoxJobValidator.validate()` | ||
107 | 505 | :raises ValidationError: | 520 | :raises ValidationError: |
108 | 506 | If the job has any problems that make it unsuitable for execution. | 521 | If the job has any problems that make it unsuitable for execution. |
109 | 507 | """ | 522 | """ |
111 | 508 | validator_cls.validate(self) | 523 | CheckBoxJobValidator.validate(self, **validation_kwargs) |
112 | 509 | 524 | ||
113 | 510 | def create_child_job_from_record(self, record): | 525 | def create_child_job_from_record(self, record): |
114 | 511 | """ | 526 | """ |
115 | 512 | 527 | ||
116 | === modified file 'plainbox/plainbox/impl/secure/providers/test_v1.py' | |||
117 | --- plainbox/plainbox/impl/secure/providers/test_v1.py 2014-03-28 13:02:24 +0000 | |||
118 | +++ plainbox/plainbox/impl/secure/providers/test_v1.py 2014-03-28 22:08:58 +0000 | |||
119 | @@ -25,7 +25,6 @@ | |||
120 | 25 | """ | 25 | """ |
121 | 26 | 26 | ||
122 | 27 | from unittest import TestCase | 27 | from unittest import TestCase |
123 | 28 | import os | ||
124 | 29 | 28 | ||
125 | 30 | from plainbox.impl.job import JobDefinition | 29 | from plainbox.impl.job import JobDefinition |
126 | 31 | from plainbox.impl.secure.config import Unset | 30 | from plainbox.impl.secure.config import Unset |
127 | @@ -649,7 +648,10 @@ | |||
128 | 649 | self.provider = Provider1( | 648 | self.provider = Provider1( |
129 | 650 | self.NAME, self.VERSION, self.DESCRIPTION, self.SECURE, | 649 | self.NAME, self.VERSION, self.DESCRIPTION, self.SECURE, |
130 | 651 | self.GETTEXT_DOMAIN, self.JOBS_DIR, self.WHITELISTS_DIR, | 650 | self.GETTEXT_DOMAIN, self.JOBS_DIR, self.WHITELISTS_DIR, |
132 | 652 | self.DATA_DIR, self.BIN_DIR, self.LOCALE_DIR, self.BASE_DIR) | 651 | self.DATA_DIR, self.BIN_DIR, self.LOCALE_DIR, self.BASE_DIR, |
133 | 652 | # We are using dummy job definitions so let's not shout about those | ||
134 | 653 | # being invalid in each test | ||
135 | 654 | validate=False) | ||
136 | 653 | 655 | ||
137 | 654 | def test_repr(self): | 656 | def test_repr(self): |
138 | 655 | self.assertEqual( | 657 | self.assertEqual( |
139 | @@ -792,11 +794,11 @@ | |||
140 | 792 | JobDefinitionPlugIn("/path/to/jobs1.txt", ( | 794 | JobDefinitionPlugIn("/path/to/jobs1.txt", ( |
141 | 793 | "id: a2\n" | 795 | "id: a2\n" |
142 | 794 | "\n" | 796 | "\n" |
144 | 795 | "id: a1\n"), self.provider), | 797 | "id: a1\n"), self.provider, validate=False), |
145 | 796 | JobDefinitionPlugIn("/path/to/jobs2.txt", ( | 798 | JobDefinitionPlugIn("/path/to/jobs2.txt", ( |
146 | 797 | "id: a3\n" | 799 | "id: a3\n" |
147 | 798 | "\n" | 800 | "\n" |
149 | 799 | "id: a4\n"), self.provider) | 801 | "id: a4\n"), self.provider, validate=False) |
150 | 800 | ] | 802 | ] |
151 | 801 | with self.provider._job_collection.fake_plugins(fake_plugins): | 803 | with self.provider._job_collection.fake_plugins(fake_plugins): |
152 | 802 | job_list = self.provider.get_builtin_jobs() | 804 | job_list = self.provider.get_builtin_jobs() |
153 | @@ -840,11 +842,11 @@ | |||
154 | 840 | JobDefinitionPlugIn("/path/to/jobs1.txt", ( | 842 | JobDefinitionPlugIn("/path/to/jobs1.txt", ( |
155 | 841 | "id: a2\n" | 843 | "id: a2\n" |
156 | 842 | "\n" | 844 | "\n" |
158 | 843 | "id: a1\n"), self.provider), | 845 | "id: a1\n"), self.provider, validate=False), |
159 | 844 | JobDefinitionPlugIn("/path/to/jobs2.txt", ( | 846 | JobDefinitionPlugIn("/path/to/jobs2.txt", ( |
160 | 845 | "id: a3\n" | 847 | "id: a3\n" |
161 | 846 | "\n" | 848 | "\n" |
163 | 847 | "id: a4\n"), self.provider) | 849 | "id: a4\n"), self.provider, validate=False) |
164 | 848 | ] | 850 | ] |
165 | 849 | with self.provider._job_collection.fake_plugins(fake_plugins): | 851 | with self.provider._job_collection.fake_plugins(fake_plugins): |
166 | 850 | job_list, problem_list = self.provider.load_all_jobs() | 852 | job_list, problem_list = self.provider.load_all_jobs() |
167 | @@ -862,7 +864,8 @@ | |||
168 | 862 | """ | 864 | """ |
169 | 863 | fake_plugins = [ | 865 | fake_plugins = [ |
170 | 864 | JobDefinitionPlugIn( | 866 | JobDefinitionPlugIn( |
172 | 865 | "/path/to/jobs1.txt", "id: working\n", self.provider) | 867 | "/path/to/jobs1.txt", "id: working\n", self.provider, |
173 | 868 | validate=False) | ||
174 | 866 | ] | 869 | ] |
175 | 867 | fake_problems = [ | 870 | fake_problems = [ |
176 | 868 | PlugInError("some problem"), | 871 | PlugInError("some problem"), |
177 | 869 | 872 | ||
178 | === modified file 'plainbox/plainbox/impl/secure/providers/v1.py' | |||
179 | --- plainbox/plainbox/impl/secure/providers/v1.py 2014-03-28 13:02:24 +0000 | |||
180 | +++ plainbox/plainbox/impl/secure/providers/v1.py 2014-03-28 22:08:58 +0000 | |||
181 | @@ -31,6 +31,7 @@ | |||
182 | 31 | from plainbox.abc import IProvider1, IProviderBackend1 | 31 | from plainbox.abc import IProvider1, IProviderBackend1 |
183 | 32 | from plainbox.i18n import gettext as _ | 32 | from plainbox.i18n import gettext as _ |
184 | 33 | from plainbox.impl.job import JobDefinition | 33 | from plainbox.impl.job import JobDefinition |
185 | 34 | from plainbox.impl.job import ValidationError | ||
186 | 34 | from plainbox.impl.secure.config import Config, Variable | 35 | from plainbox.impl.secure.config import Config, Variable |
187 | 35 | from plainbox.impl.secure.config import IValidator | 36 | from plainbox.impl.secure.config import IValidator |
188 | 36 | from plainbox.impl.secure.config import NotEmptyValidator | 37 | from plainbox.impl.secure.config import NotEmptyValidator |
189 | @@ -87,12 +88,30 @@ | |||
190 | 87 | list of :class:`plainbox.impl.job.JobDefinition` instances from a file. | 88 | list of :class:`plainbox.impl.job.JobDefinition` instances from a file. |
191 | 88 | """ | 89 | """ |
192 | 89 | 90 | ||
194 | 90 | def __init__(self, filename, text, provider): | 91 | def __init__(self, filename, text, provider, *, |
195 | 92 | validate=True, validation_kwargs=None): | ||
196 | 91 | """ | 93 | """ |
197 | 92 | Initialize the plug-in with the specified name text | 94 | Initialize the plug-in with the specified name text |
198 | 95 | |||
199 | 96 | :param filename: | ||
200 | 97 | Name of the file with job definitions | ||
201 | 98 | :param text: | ||
202 | 99 | Full text of the file with job definitions | ||
203 | 100 | :param provider: | ||
204 | 101 | A provider object to which those jobs belong to | ||
205 | 102 | :param validate: | ||
206 | 103 | Enable job validation. Incorrect job definitions will not be loaded | ||
207 | 104 | and will abort the process of loading of the remainder of the jobs. | ||
208 | 105 | This is ON by default to prevent broken job definitions from being | ||
209 | 106 | used. This is a keyword-only argument. | ||
210 | 107 | :param validation_kwargs: | ||
211 | 108 | Keyword arguments to pass to the JobDefinition.validate(). Note, | ||
212 | 109 | this is a single argument. This is a keyword-only argument. | ||
213 | 93 | """ | 110 | """ |
214 | 94 | self._filename = filename | 111 | self._filename = filename |
215 | 95 | self._job_list = [] | 112 | self._job_list = [] |
216 | 113 | if validation_kwargs is None: | ||
217 | 114 | validation_kwargs = {} | ||
218 | 96 | logger.debug(_("Loading jobs definitions from %r..."), filename) | 115 | logger.debug(_("Loading jobs definitions from %r..."), filename) |
219 | 97 | try: | 116 | try: |
220 | 98 | records = load_rfc822_records( | 117 | records = load_rfc822_records( |
221 | @@ -108,10 +127,16 @@ | |||
222 | 108 | raise PlugInError( | 127 | raise PlugInError( |
223 | 109 | _("Cannot define job from record {!r}: {}").format( | 128 | _("Cannot define job from record {!r}: {}").format( |
224 | 110 | record, exc)) | 129 | record, exc)) |
229 | 111 | else: | 130 | job._provider = provider |
230 | 112 | job._provider = provider | 131 | if validate: |
231 | 113 | self._job_list.append(job) | 132 | try: |
232 | 114 | logger.debug(_("Loaded %r"), job) | 133 | job.validate(**validation_kwargs) |
233 | 134 | except ValidationError as exc: | ||
234 | 135 | raise PlugInError( | ||
235 | 136 | _("Problem in job definition, field {}: {}").format( | ||
236 | 137 | exc.field, exc.problem)) | ||
237 | 138 | self._job_list.append(job) | ||
238 | 139 | logger.debug(_("Loaded %r"), job) | ||
239 | 115 | 140 | ||
240 | 116 | @property | 141 | @property |
241 | 117 | def plugin_name(self): | 142 | def plugin_name(self): |
242 | @@ -142,7 +167,7 @@ | |||
243 | 142 | 167 | ||
244 | 143 | def __init__(self, name, version, description, secure, gettext_domain, | 168 | def __init__(self, name, version, description, secure, gettext_domain, |
245 | 144 | jobs_dir, whitelists_dir, data_dir, bin_dir, locale_dir, | 169 | jobs_dir, whitelists_dir, data_dir, bin_dir, locale_dir, |
247 | 145 | base_dir): | 170 | base_dir, *, validate=True, validation_kwargs=None): |
248 | 146 | """ | 171 | """ |
249 | 147 | Initialize a provider with a set of meta-data and directories. | 172 | Initialize a provider with a set of meta-data and directories. |
250 | 148 | 173 | ||
251 | @@ -187,6 +212,16 @@ | |||
252 | 187 | path of the directory with (perhaps) all of jobs_dir, | 212 | path of the directory with (perhaps) all of jobs_dir, |
253 | 188 | whitelist_dir, data_dir, bin_dir, locale_dir. This may be None. | 213 | whitelist_dir, data_dir, bin_dir, locale_dir. This may be None. |
254 | 189 | This is also the effective value of $CHECKBOX_SHARE | 214 | This is also the effective value of $CHECKBOX_SHARE |
255 | 215 | |||
256 | 216 | :param validate: | ||
257 | 217 | Enable job validation. Incorrect job definitions will not be loaded | ||
258 | 218 | and will abort the process of loading of the remainder of the jobs. | ||
259 | 219 | This is ON by default to prevent broken job definitions from being | ||
260 | 220 | used. This is a keyword-only argument. | ||
261 | 221 | |||
262 | 222 | :param validation_kwargs: | ||
263 | 223 | Keyword arguments to pass to the JobDefinition.validate(). Note, | ||
264 | 224 | this is a single argument. This is a keyword-only argument. | ||
265 | 190 | """ | 225 | """ |
266 | 191 | # Meta-data | 226 | # Meta-data |
267 | 192 | self._name = name | 227 | self._name = name |
268 | @@ -215,22 +250,31 @@ | |||
269 | 215 | jobs_dir_list = [] | 250 | jobs_dir_list = [] |
270 | 216 | self._job_collection = FsPlugInCollection( | 251 | self._job_collection = FsPlugInCollection( |
271 | 217 | jobs_dir_list, ext=(".txt", ".txt.in"), | 252 | jobs_dir_list, ext=(".txt", ".txt.in"), |
273 | 218 | wrapper=JobDefinitionPlugIn, provider=self) | 253 | wrapper=JobDefinitionPlugIn, provider=self, |
274 | 254 | validate=validate, validation_kwargs=validation_kwargs) | ||
275 | 219 | # Setup translations | 255 | # Setup translations |
276 | 220 | if gettext_domain and locale_dir: | 256 | if gettext_domain and locale_dir: |
277 | 221 | gettext.bindtextdomain(self._gettext_domain, self._locale_dir) | 257 | gettext.bindtextdomain(self._gettext_domain, self._locale_dir) |
278 | 222 | 258 | ||
279 | 223 | @classmethod | 259 | @classmethod |
281 | 224 | def from_definition(cls, definition, secure): | 260 | def from_definition(cls, definition, secure, *, validate=True, |
282 | 261 | validation_kwargs=None): | ||
283 | 225 | """ | 262 | """ |
284 | 226 | Initialize a provider from Provider1Definition object | 263 | Initialize a provider from Provider1Definition object |
285 | 227 | 264 | ||
286 | 228 | :param definition: | 265 | :param definition: |
287 | 229 | A Provider1Definition object to use as reference | 266 | A Provider1Definition object to use as reference |
288 | 230 | |||
289 | 231 | :param secure: | 267 | :param secure: |
290 | 232 | Value of the secure flag. This cannot be expressed by a definition | 268 | Value of the secure flag. This cannot be expressed by a definition |
291 | 233 | object. | 269 | object. |
292 | 270 | :param validate: | ||
293 | 271 | Enable job validation. Incorrect job definitions will not be loaded | ||
294 | 272 | and will abort the process of loading of the remainder of the jobs. | ||
295 | 273 | This is ON by default to prevent broken job definitions from being | ||
296 | 274 | used. This is a keyword-only argument. | ||
297 | 275 | :param validation_kwargs: | ||
298 | 276 | Keyword arguments to pass to the JobDefinition.validate(). Note, | ||
299 | 277 | this is a single argument. This is a keyword-only argument. | ||
300 | 234 | 278 | ||
301 | 235 | This method simplifies initialization of a Provider1 object where the | 279 | This method simplifies initialization of a Provider1 object where the |
302 | 236 | caller already has a Provider1Definition object. Depending on the value | 280 | caller already has a Provider1Definition object. Depending on the value |
303 | @@ -248,7 +292,8 @@ | |||
304 | 248 | secure, definition.effective_gettext_domain, | 292 | secure, definition.effective_gettext_domain, |
305 | 249 | definition.effective_jobs_dir, definition.effective_whitelists_dir, | 293 | definition.effective_jobs_dir, definition.effective_whitelists_dir, |
306 | 250 | definition.effective_data_dir, definition.effective_bin_dir, | 294 | definition.effective_data_dir, definition.effective_bin_dir, |
308 | 251 | definition.effective_locale_dir, definition.location or None) | 295 | definition.effective_locale_dir, definition.location or None, |
309 | 296 | validate=validate, validation_kwargs=validation_kwargs) | ||
310 | 252 | 297 | ||
311 | 253 | def __repr__(self): | 298 | def __repr__(self): |
312 | 254 | return "<{} name:{!r}>".format(self.__class__.__name__, self.name) | 299 | return "<{} name:{!r}>".format(self.__class__.__name__, self.name) |
313 | 255 | 300 | ||
314 | === modified file 'plainbox/plainbox/impl/test_job.py' | |||
315 | --- plainbox/plainbox/impl/test_job.py 2014-03-27 13:57:24 +0000 | |||
316 | +++ plainbox/plainbox/impl/test_job.py 2014-03-28 22:08:58 +0000 | |||
317 | @@ -42,6 +42,19 @@ | |||
318 | 42 | 42 | ||
319 | 43 | class CheckBoxJobValidatorTests(TestCase): | 43 | class CheckBoxJobValidatorTests(TestCase): |
320 | 44 | 44 | ||
321 | 45 | def test_validate_checks_for_deprecated_name(self): | ||
322 | 46 | """ | ||
323 | 47 | verify that validate() checks if jobs have a value for the 'id' | ||
324 | 48 | field. | ||
325 | 49 | """ | ||
326 | 50 | job = JobDefinition({ | ||
327 | 51 | 'name': 'name' | ||
328 | 52 | }) | ||
329 | 53 | with self.assertRaises(ValidationError) as boom: | ||
330 | 54 | CheckBoxJobValidator.validate(job, deprecated=True) | ||
331 | 55 | self.assertEqual(boom.exception.field, JobDefinition.fields.name) | ||
332 | 56 | self.assertEqual(boom.exception.problem, Problem.deprecated) | ||
333 | 57 | |||
334 | 45 | def test_validate_checks_for_missing_id(self): | 58 | def test_validate_checks_for_missing_id(self): |
335 | 46 | """ | 59 | """ |
336 | 47 | verify that validate() checks if jobs have a value for the 'id' | 60 | verify that validate() checks if jobs have a value for the 'id' |
337 | @@ -91,7 +104,7 @@ | |||
338 | 91 | 'user': 'root' | 104 | 'user': 'root' |
339 | 92 | }) | 105 | }) |
340 | 93 | with self.assertRaises(ValidationError) as boom: | 106 | with self.assertRaises(ValidationError) as boom: |
342 | 94 | CheckBoxJobValidator.validate(job) | 107 | CheckBoxJobValidator.validate(job, strict=True) |
343 | 95 | self.assertEqual(boom.exception.field, JobDefinition.fields.user) | 108 | self.assertEqual(boom.exception.field, JobDefinition.fields.user) |
344 | 96 | self.assertEqual(boom.exception.problem, Problem.useless) | 109 | self.assertEqual(boom.exception.problem, Problem.useless) |
345 | 97 | 110 | ||
346 | @@ -106,7 +119,7 @@ | |||
347 | 106 | 'environ': 'VAR_NAME' | 119 | 'environ': 'VAR_NAME' |
348 | 107 | }) | 120 | }) |
349 | 108 | with self.assertRaises(ValidationError) as boom: | 121 | with self.assertRaises(ValidationError) as boom: |
351 | 109 | CheckBoxJobValidator.validate(job) | 122 | CheckBoxJobValidator.validate(job, strict=True) |
352 | 110 | self.assertEqual(boom.exception.field, JobDefinition.fields.environ) | 123 | self.assertEqual(boom.exception.field, JobDefinition.fields.environ) |
353 | 111 | self.assertEqual(boom.exception.problem, Problem.useless) | 124 | self.assertEqual(boom.exception.problem, Problem.useless) |
354 | 112 | 125 | ||
355 | @@ -137,7 +150,7 @@ | |||
356 | 137 | 'command': 'run_some_test' | 150 | 'command': 'run_some_test' |
357 | 138 | }) | 151 | }) |
358 | 139 | with self.assertRaises(ValidationError) as boom: | 152 | with self.assertRaises(ValidationError) as boom: |
360 | 140 | CheckBoxJobValidator.validate(job) | 153 | CheckBoxJobValidator.validate(job, strict=True) |
361 | 141 | self.assertEqual(boom.exception.field, JobDefinition.fields.command) | 154 | self.assertEqual(boom.exception.field, JobDefinition.fields.command) |
362 | 142 | self.assertEqual(boom.exception.problem, Problem.useless) | 155 | self.assertEqual(boom.exception.problem, Problem.useless) |
363 | 143 | 156 | ||
364 | @@ -254,11 +267,6 @@ | |||
365 | 254 | self.assertEqual(job.command, None) | 267 | self.assertEqual(job.command, None) |
366 | 255 | self.assertEqual(job.description, None) | 268 | self.assertEqual(job.description, None) |
367 | 256 | 269 | ||
368 | 257 | def test_from_rfc822_record_missing_id(self): | ||
369 | 258 | record = RFC822Record({'plugin': 'plugin'}) | ||
370 | 259 | with self.assertRaises(ValueError): | ||
371 | 260 | JobDefinition.from_rfc822_record(record) | ||
372 | 261 | |||
373 | 262 | def test_str(self): | 270 | def test_str(self): |
374 | 263 | job = JobDefinition(self._min_record.data) | 271 | job = JobDefinition(self._min_record.data) |
375 | 264 | self.assertEqual(str(job), "id") | 272 | self.assertEqual(str(job), "id") |
376 | 265 | 273 | ||
377 | === modified file 'plainbox/plainbox/provider_manager.py' | |||
378 | --- plainbox/plainbox/provider_manager.py 2014-03-28 13:22:04 +0000 | |||
379 | +++ plainbox/plainbox/provider_manager.py 2014-03-28 22:08:58 +0000 | |||
380 | @@ -623,7 +623,7 @@ | |||
381 | 623 | problem_list = [] | 623 | problem_list = [] |
382 | 624 | for job in job_list: | 624 | for job in job_list: |
383 | 625 | try: | 625 | try: |
385 | 626 | job.validate() | 626 | job.validate(strict=True, deprecated=True) |
386 | 627 | except JobValidationError as exc: | 627 | except JobValidationError as exc: |
387 | 628 | problem_list.append((job, exc)) | 628 | problem_list.append((job, exc)) |
388 | 629 | return problem_list | 629 | return problem_list |
389 | @@ -636,6 +636,7 @@ | |||
390 | 636 | Problem.missing: _("missing definition of required field"), | 636 | Problem.missing: _("missing definition of required field"), |
391 | 637 | Problem.wrong: _("incorrect value supplied"), | 637 | Problem.wrong: _("incorrect value supplied"), |
392 | 638 | Problem.useless: _("useless field in this context"), | 638 | Problem.useless: _("useless field in this context"), |
393 | 639 | Problem.deprecated: _("usage of deprecated field"), | ||
394 | 639 | } | 640 | } |
395 | 640 | for job, error in problem_list: | 641 | for job, error in problem_list: |
396 | 641 | if isinstance(error, JobValidationError): | 642 | if isinstance(error, JobValidationError): |
397 | @@ -664,6 +665,17 @@ | |||
398 | 664 | else: | 665 | else: |
399 | 665 | print(_("All jobs seem to be valid")) | 666 | print(_("All jobs seem to be valid")) |
400 | 666 | 667 | ||
401 | 668 | def get_provider(self): | ||
402 | 669 | """ | ||
403 | 670 | Get a Provider1 that describes the current provider | ||
404 | 671 | |||
405 | 672 | This version disables all validation so that we can see totally broken | ||
406 | 673 | providers and let us validate them and handle the errors explicitly. | ||
407 | 674 | """ | ||
408 | 675 | return Provider1.from_definition( | ||
409 | 676 | # NOTE: don't validate, we want to validate manually | ||
410 | 677 | self.definition, secure=False, validate=False) | ||
411 | 678 | |||
412 | 667 | 679 | ||
413 | 668 | @docstring( | 680 | @docstring( |
414 | 669 | # TRANSLATORS: please leave various options (both long and short forms), | 681 | # TRANSLATORS: please leave various options (both long and short forms), |
415 | 670 | 682 | ||
416 | === modified file 'plainbox/plainbox/test_provider_manager.py' | |||
417 | --- plainbox/plainbox/test_provider_manager.py 2014-03-28 13:22:04 +0000 | |||
418 | +++ plainbox/plainbox/test_provider_manager.py 2014-03-28 22:08:58 +0000 | |||
419 | @@ -120,7 +120,7 @@ | |||
420 | 120 | self.tmpdir + os.path.join("/foo", "lib", "plainbox-providers-1", | 120 | self.tmpdir + os.path.join("/foo", "lib", "plainbox-providers-1", |
421 | 121 | "2014.com.example:test", "jobs", | 121 | "2014.com.example:test", "jobs", |
422 | 122 | "jobs.txt"), | 122 | "jobs.txt"), |
424 | 123 | "name: dummy\nplugin: shell\ncommand: true\n") | 123 | "id: dummy\nplugin: shell\ncommand: true\n") |
425 | 124 | 124 | ||
426 | 125 | def test_install__flat_partial(self): | 125 | def test_install__flat_partial(self): |
427 | 126 | """ | 126 | """ |
428 | @@ -155,7 +155,7 @@ | |||
429 | 155 | self.assertFileContent( | 155 | self.assertFileContent( |
430 | 156 | self.tmpdir + os.path.join( | 156 | self.tmpdir + os.path.join( |
431 | 157 | prefix, "share", "2014.com.example:test", "jobs", "jobs.txt"), | 157 | prefix, "share", "2014.com.example:test", "jobs", "jobs.txt"), |
433 | 158 | "name: dummy\nplugin: shell\ncommand: true\n") | 158 | "id: dummy\nplugin: shell\ncommand: true\n") |
434 | 159 | self.assertFileContent( | 159 | self.assertFileContent( |
435 | 160 | self.tmpdir + os.path.join( | 160 | self.tmpdir + os.path.join( |
436 | 161 | prefix, "share", "2014.com.example:test", "whitelists", | 161 | prefix, "share", "2014.com.example:test", "whitelists", |
437 | @@ -198,7 +198,7 @@ | |||
438 | 198 | self.tmpdir, "dist", "2014.com.example.test-1.0.tar.gz") | 198 | self.tmpdir, "dist", "2014.com.example.test-1.0.tar.gz") |
439 | 199 | self.assertTarballContent( | 199 | self.assertTarballContent( |
440 | 200 | tarball, "2014.com.example.test-1.0/jobs/jobs.txt", | 200 | tarball, "2014.com.example.test-1.0/jobs/jobs.txt", |
442 | 201 | "name: dummy\nplugin: shell\ncommand: true\n") | 201 | "id: dummy\nplugin: shell\ncommand: true\n") |
443 | 202 | self.assert_common_sdist(tarball) | 202 | self.assert_common_sdist(tarball) |
444 | 203 | 203 | ||
445 | 204 | def test_sdist__partial(self): | 204 | def test_sdist__partial(self): |
446 | @@ -287,7 +287,7 @@ | |||
447 | 287 | """ | 287 | """ |
448 | 288 | filename = os.path.join(self.tmpdir, "jobs", "broken.txt") | 288 | filename = os.path.join(self.tmpdir, "jobs", "broken.txt") |
449 | 289 | with open(filename, "wt", encoding='UTF-8') as stream: | 289 | with open(filename, "wt", encoding='UTF-8') as stream: |
451 | 290 | print("name: broken", file=stream) | 290 | print("id: broken", file=stream) |
452 | 291 | print("plugin: shell", file=stream) | 291 | print("plugin: shell", file=stream) |
453 | 292 | with TestIO() as test_io: | 292 | with TestIO() as test_io: |
454 | 293 | self.tool.main(["validate"]) | 293 | self.tool.main(["validate"]) |
455 | @@ -303,7 +303,7 @@ | |||
456 | 303 | """ | 303 | """ |
457 | 304 | filename = os.path.join(self.tmpdir, "jobs", "broken.txt") | 304 | filename = os.path.join(self.tmpdir, "jobs", "broken.txt") |
458 | 305 | with open(filename, "wt", encoding='UTF-8') as stream: | 305 | with open(filename, "wt", encoding='UTF-8') as stream: |
460 | 306 | print("name: broken", file=stream) | 306 | print("id: broken", file=stream) |
461 | 307 | print("plugin: magic", file=stream) | 307 | print("plugin: magic", file=stream) |
462 | 308 | with TestIO() as test_io: | 308 | with TestIO() as test_io: |
463 | 309 | self.tool.main(["validate"]) | 309 | self.tool.main(["validate"]) |
464 | @@ -321,7 +321,7 @@ | |||
465 | 321 | """ | 321 | """ |
466 | 322 | filename = os.path.join(self.tmpdir, "jobs", "broken.txt") | 322 | filename = os.path.join(self.tmpdir, "jobs", "broken.txt") |
467 | 323 | with open(filename, "wt", encoding='UTF-8') as stream: | 323 | with open(filename, "wt", encoding='UTF-8') as stream: |
469 | 324 | print("name: broken", file=stream) | 324 | print("id: broken", file=stream) |
470 | 325 | print("plugin: manual", file=stream) | 325 | print("plugin: manual", file=stream) |
471 | 326 | print("description: broken job definition", file=stream) | 326 | print("description: broken job definition", file=stream) |
472 | 327 | print("command: true", file=stream) | 327 | print("command: true", file=stream) |
473 | @@ -332,6 +332,23 @@ | |||
474 | 332 | "jobs/broken.txt:1-4: job '2014.com.example::broken', field 'command': " | 332 | "jobs/broken.txt:1-4: job '2014.com.example::broken', field 'command': " |
475 | 333 | "useless field in this context\n")) | 333 | "useless field in this context\n")) |
476 | 334 | 334 | ||
477 | 335 | def test_validate__broken_deprecated_field(self): | ||
478 | 336 | """ | ||
479 | 337 | verify that ./manage.py validate shows information about deprecated fields | ||
480 | 338 | """ | ||
481 | 339 | filename = os.path.join(self.tmpdir, "jobs", "broken.txt") | ||
482 | 340 | with open(filename, "wt", encoding='UTF-8') as stream: | ||
483 | 341 | print("name: broken", file=stream) | ||
484 | 342 | print("plugin: manual", file=stream) | ||
485 | 343 | print("description: broken job definition", file=stream) | ||
486 | 344 | print("command: true", file=stream) | ||
487 | 345 | with TestIO() as test_io: | ||
488 | 346 | self.tool.main(["validate"]) | ||
489 | 347 | self.assertEqual( | ||
490 | 348 | test_io.stdout, ( | ||
491 | 349 | "jobs/broken.txt:1-4: job '2014.com.example::broken', field 'name': " | ||
492 | 350 | "usage of deprecated field\n")) | ||
493 | 351 | |||
494 | 335 | def test_info(self): | 352 | def test_info(self): |
495 | 336 | """ | 353 | """ |
496 | 337 | verify that ./manage.py info shows basic provider information | 354 | verify that ./manage.py info shows basic provider information |
497 | @@ -359,7 +376,7 @@ | |||
498 | 359 | os.mkdir(os.path.join(tmpdir, "jobs")) | 376 | os.mkdir(os.path.join(tmpdir, "jobs")) |
499 | 360 | filename = os.path.join(tmpdir, "jobs", "jobs.txt") | 377 | filename = os.path.join(tmpdir, "jobs", "jobs.txt") |
500 | 361 | with open(filename, "wt", encoding='UTF-8') as stream: | 378 | with open(filename, "wt", encoding='UTF-8') as stream: |
502 | 362 | print("name: dummy", file=stream) | 379 | print("id: dummy", file=stream) |
503 | 363 | print("plugin: shell", file=stream) | 380 | print("plugin: shell", file=stream) |
504 | 364 | print("command: true", file=stream) | 381 | print("command: true", file=stream) |
505 | 365 | os.mkdir(os.path.join(tmpdir, "whitelists")) | 382 | os.mkdir(os.path.join(tmpdir, "whitelists")) |
Awesome, maybe next we can plug this validation into the tarmac tests to protect against borked jobs?