Skip to content

Commit 56fa85e

Browse files
committed
Add config to rules
1 parent 508e626 commit 56fa85e

File tree

6 files changed

+72
-23
lines changed

6 files changed

+72
-23
lines changed

api/jobs/gears.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from __future__ import absolute_import
66

77
import bson.objectid
8+
import copy
89
import datetime
910
from jsonschema import Draft4Validator, ValidationError
1011
import gears as gear_tools
@@ -96,13 +97,13 @@ def suggest_for_files(gear, files):
9697
return suggested_files
9798

9899
def validate_gear_config(gear, config_):
99-
if len(gear.get('manifest', {}).get('config', {})) > 0:
100-
invocation = gear_tools.derive_invocation_schema(gear['manifest'])
100+
if len(gear.get('gear', {}).get('config', {})) > 0:
101+
invocation = gear_tools.derive_invocation_schema(gear['gear'])
101102
ci = gear_tools.isolate_config_invocation(invocation)
102103
validator = Draft4Validator(ci)
103104

104105
try:
105-
validator.validate(config_)
106+
validator.validate(fill_gear_default_values(gear, config_))
106107
except ValidationError as err:
107108
key = None
108109
if len(err.relative_path) > 0:
@@ -120,8 +121,7 @@ def fill_gear_default_values(gear, config_):
120121
Given a gear and a config map, fill any missing keys using defaults from the gear's config
121122
"""
122123

123-
if config_ is None:
124-
config_ = {}
124+
config_ = copy.deepcopy(config_) or {}
125125

126126
for k,v in gear['gear'].get('config', {}).iteritems():
127127
if 'default' in v:

api/jobs/handlers.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def post(self, cid):
147147

148148
validate_data(payload, 'rule-new.json', 'input', 'POST', optional=True)
149149
validate_regexes(payload)
150-
get_gear(payload['gear_id'])
150+
validate_gear_config(get_gear(payload['gear_id']), payload.get('config'))
151151

152152
payload['project_id'] = cid
153153

@@ -197,8 +197,9 @@ def put(self, cid, rid):
197197
updates = self.request.json
198198
validate_data(updates, 'rule-update.json', 'input', 'POST', optional=True)
199199
validate_regexes(updates)
200-
if updates.get('gear_id'):
201-
get_gear(updates['gear_id'])
200+
gear_id = updates.get('gear_id', doc['gear_id'])
201+
config_ = updates.get('config', doc.get('config'))
202+
validate_gear_config(get_gear(gear_id), config_)
202203

203204
doc.update(updates)
204205
config.db.project_rules.replace_one({'_id': bson.ObjectId(rid)}, doc)

api/jobs/queue.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,7 @@ def enqueue_job(job_map, origin, perm_check_uid=None):
157157
if gear.get('gear', {}).get('custom', {}).get('flywheel', {}).get('invalid', False):
158158
raise InputValidationException('Gear marked as invalid, will not run!')
159159

160-
config_ = fill_gear_default_values(gear, job_map.get('config', {}))
160+
config_ = job_map.get('config', {})
161161
validate_gear_config(gear, config_)
162162

163163
# Translate maps to FileReferences
@@ -194,7 +194,7 @@ def enqueue_job(job_map, origin, perm_check_uid=None):
194194

195195
# Config options are stored on the job object under the "config" key
196196
config_ = {
197-
'config': config_,
197+
'config': fill_gear_default_values(gear, config_),
198198
'inputs': { },
199199
'destination': {
200200
'type': destination.type,

api/jobs/rules.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,9 @@ def create_potential_jobs(db, container, container_type, file_):
203203

204204
job = Job(str(gear['_id']), inputs, tags=['auto', gear_tag])
205205

206+
if 'config' in rule:
207+
job.config = rule['config']
208+
206209
potential_jobs.append({
207210
'job': job,
208211
'rule': rule

swagger/schemas/definitions/rule.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
"project_id": { "type": "string" },
3232
"gear_id": { "type": "string" },
3333
"name": { "type": "string" },
34+
"config": { "type": "object" },
3435
"any": { "$ref": "#/definitions/rule-items" },
3536
"all": { "$ref": "#/definitions/rule-items" },
3637
"disabled": { "type": "boolean" }
@@ -45,6 +46,7 @@
4546
"_id": { "type": "string" },
4647
"gear_id": { "type": "string" },
4748
"name": { "type": "string" },
49+
"config": { "type": "object" },
4850
"any": { "$ref": "#/definitions/rule-items" },
4951
"all": { "$ref": "#/definitions/rule-items" },
5052
"disabled": { "type": "boolean" }

tests/integration_tests/python/test_rules.py

Lines changed: 56 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -222,9 +222,10 @@ def test_site_rules_copied_to_new_projects(randstr, data_builder, file_form, as_
222222
data_builder.delete_group(group, recursive=True)
223223

224224

225-
def test_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, api_db):
225+
def test_project_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, api_db):
226226
# create versioned gear to cover code selecting latest gear
227-
gear = data_builder.create_gear(gear={'version': '0.0.1'})
227+
gear_config = {'param': {'type': 'string', 'pattern': '^default|custom$', 'default': 'default'}}
228+
gear = data_builder.create_gear(gear={'version': '0.0.1', 'config': gear_config})
228229
project = data_builder.create_project()
229230

230231
bad_payload = {'test': 'rules'}
@@ -267,19 +268,42 @@ def test_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, a
267268
'gear_id': '000000000000000000000000',
268269
'name': 'csv-job-trigger-rule',
269270
'any': [],
270-
'all': [
271-
{'type': 'file.type', 'value': 'tabular data'},
272-
]
271+
'all': [],
273272
}
274273

274+
# try to add project rule w/ invalid rule-item (invalid type)
275+
# NOTE this is a legacy rule
276+
rule_json['all'] = [{'type': 'invalid', 'value': 'test'}]
277+
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
278+
assert r.status_code == 400
279+
assert "'invalid' is not one of" in r.json()['message']
280+
281+
# try to add project rule w/ invalid rule-item (missing value)
282+
# NOTE this is a legacy rule
283+
rule_json['all'] = [{'type': 'file.name'}]
284+
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
285+
assert r.status_code == 400
286+
assert "'value' is a required property" in r.json()['message']
287+
288+
# set valid rule-item
289+
rule_json['all'] = [{'type': 'file.type', 'value': 'tabular data'}]
290+
275291
# try to add project rule w/ non-existent gear
276292
# NOTE this is a legacy rule
277293
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
278294
assert r.status_code == 404
279295

280-
# add project rule w/ proper gear id
296+
# try to add project rule w/ invalid config
281297
# NOTE this is a legacy rule
282298
rule_json['gear_id'] = gear
299+
rule_json['config'] = {'param': 'invalid'}
300+
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
301+
assert r.status_code == 422
302+
assert r.json()['reason'] == 'config did not match manifest'
303+
del rule_json['config']
304+
305+
# add project rule w/ proper gear id
306+
# NOTE this is a legacy rule
283307
r = as_admin.post('/projects/' + project + '/rules', json=rule_json)
284308
assert r.ok
285309
rule = r.json()['_id']
@@ -305,10 +329,15 @@ def test_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, a
305329
r = with_user.session.put('/projects/' + project + '/rules/' + rule, json={'gear_id': gear})
306330
assert r.status_code == 403
307331

308-
# try to update rule to with invalid gear id
332+
# try to update rule with invalid gear id
309333
r = as_admin.put('/projects/' + project + '/rules/' + rule, json={'gear_id': '000000000000000000000000'})
310334
assert r.status_code == 404
311335

336+
# try to update rule with invalid gear config
337+
r = as_admin.put('/projects/' + project + '/rules/' + rule, json={'config': {'param': 'invalid'}})
338+
assert r.status_code == 422
339+
assert r.json()['reason'] == 'config did not match manifest'
340+
312341
# update name of rule
313342
rule_name = 'improved-csv-trigger-rule'
314343
r = as_admin.put('/projects/' + project + '/rules/' + rule, json={'name': rule_name})
@@ -323,11 +352,25 @@ def test_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, a
323352
r = as_admin.post('/projects/' + project + '/files', files=file_form('test2.csv'))
324353
assert r.ok
325354

326-
# test that job was created via rule
355+
# test that job was created via rule and uses gear default config
327356
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear})]
328357
assert len(gear_jobs) == 1
329358
assert len(gear_jobs[0]['inputs']) == 1
330359
assert gear_jobs[0]['inputs'][0]['name'] == 'test2.csv'
360+
assert gear_jobs[0]['config']['config'] == {'param': 'default'}
361+
362+
# update rule to have a custom config
363+
r = as_admin.put('/projects/' + project + '/rules/' + rule, json={'config': {'param': 'custom'}})
364+
assert r.ok
365+
366+
# upload another file that matches rule
367+
r = as_admin.post('/projects/' + project + '/files', files=file_form('test3.csv'))
368+
assert r.ok
369+
370+
# test that job was created via rule and custom config
371+
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear})]
372+
assert len(gear_jobs) == 2
373+
assert gear_jobs[1]['config']['config'] == {'param': 'custom'}
331374

332375
# try to delete rule of non-existent project
333376
r = as_admin.delete('/projects/000000000000000000000000/rules/000000000000000000000000')
@@ -368,7 +411,7 @@ def test_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, a
368411

369412
# test that job was not created via rule
370413
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear})]
371-
assert len(gear_jobs) == 1 # still 1 from before
414+
assert len(gear_jobs) == 2 # still 2 from before
372415

373416
# update test2.csv's metadata to include a valid measurement to spawn job
374417
metadata = {
@@ -396,9 +439,9 @@ def test_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, a
396439

397440
# test that only one job was created via rule
398441
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear})]
399-
assert len(gear_jobs) == 2
400-
assert len(gear_jobs[1]['inputs']) == 1
401-
assert gear_jobs[1]['inputs'][0]['name'] == 'test3.txt'
442+
assert len(gear_jobs) == 3
443+
assert len(gear_jobs[2]['inputs']) == 1
444+
assert gear_jobs[2]['inputs'][0]['name'] == 'test3.txt'
402445

403446
# delete rule
404447
r = as_admin.delete('/projects/' + project + '/rules/' + rule2)
@@ -423,7 +466,7 @@ def test_rules(randstr, data_builder, file_form, as_root, as_admin, with_user, a
423466

424467
# test that job was created via regex rule
425468
gear_jobs = [job for job in api_db.jobs.find({'gear_id': gear})]
426-
assert len(gear_jobs) == 3
469+
assert len(gear_jobs) == 4
427470

428471
# delete rule
429472
r = as_admin.delete('/projects/' + project + '/rules/' + rule3)

0 commit comments

Comments
 (0)