Skip to content

Commit e8f6d18

Browse files
committed
Add config to rules
1 parent 576c227 commit e8f6d18

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
@@ -97,13 +98,13 @@ def suggest_for_files(gear, files):
9798
return suggested_files
9899

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

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

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

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

api/jobs/handlers.py

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

182182
validate_data(doc, 'rule-new.json', 'input', 'POST', optional=True)
183183
validate_regexes(doc)
184-
get_gear(doc['gear_id'])
184+
validate_gear_config(get_gear(doc['gear_id']), doc.get('config'))
185185

186186
doc['project_id'] = cid
187187

@@ -231,8 +231,9 @@ def put(self, cid, rid):
231231
updates = self.request.json
232232
validate_data(updates, 'rule-update.json', 'input', 'POST', optional=True)
233233
validate_regexes(updates)
234-
if updates.get('gear_id'):
235-
get_gear(updates['gear_id'])
234+
gear_id = updates.get('gear_id', doc['gear_id'])
235+
config_ = updates.get('config', doc.get('config'))
236+
validate_gear_config(get_gear(gear_id), config_)
236237

237238
doc.update(updates)
238239
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
@@ -158,7 +158,7 @@ def enqueue_job(job_map, origin, perm_check_uid=None):
158158
if gear.get('gear', {}).get('custom', {}).get('flywheel', {}).get('invalid', False):
159159
raise InputValidationException('Gear marked as invalid, will not run!')
160160

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

164164
# Translate maps to FileReferences
@@ -197,7 +197,7 @@ def enqueue_job(job_map, origin, perm_check_uid=None):
197197

198198
# Config options are stored on the job object under the "config" key
199199
config_ = {
200-
'config': config_,
200+
'config': fill_gear_default_values(gear, config_),
201201
'inputs': { },
202202
'destination': {
203203
'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

raml/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" }
@@ -44,6 +45,7 @@
4445
"_id": { "type": "string" },
4546
"gear_id": { "type": "string" },
4647
"name": { "type": "string" },
48+
"config": { "type": "object" },
4749
"any": { "$ref": "#/definitions/rule-items" },
4850
"all": { "$ref": "#/definitions/rule-items" },
4951
"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)