mirror of https://github.com/openspug/spug
A 任务计划支持Python执行
parent
9ac14e2f9e
commit
cc664887a6
|
@ -38,7 +38,10 @@ def host_executor(host, command):
|
||||||
|
|
||||||
|
|
||||||
def schedule_worker_handler(job):
|
def schedule_worker_handler(job):
|
||||||
history_id, host_id, command = json.loads(job)
|
history_id, host_id, interpreter, command = json.loads(job)
|
||||||
|
if interpreter == 'python':
|
||||||
|
attach = 'INTERPRETER=python\ncommand -v python3 &> /dev/null && INTERPRETER=python3'
|
||||||
|
command = f'{attach}\n$INTERPRETER << EOF\n# -*- coding: UTF-8 -*-\n{command}\nEOF'
|
||||||
if host_id == 'local':
|
if host_id == 'local':
|
||||||
code, duration, out = local_executor(command)
|
code, duration, out = local_executor(command)
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -37,6 +37,7 @@ class Task(models.Model, ModelMixin):
|
||||||
)
|
)
|
||||||
name = models.CharField(max_length=50)
|
name = models.CharField(max_length=50)
|
||||||
type = models.CharField(max_length=50)
|
type = models.CharField(max_length=50)
|
||||||
|
interpreter = models.CharField(max_length=20, default='sh')
|
||||||
command = models.TextField()
|
command = models.TextField()
|
||||||
targets = models.TextField()
|
targets = models.TextField()
|
||||||
trigger = models.CharField(max_length=20, choices=TRIGGERS)
|
trigger = models.CharField(max_length=20, choices=TRIGGERS)
|
||||||
|
|
|
@ -62,7 +62,7 @@ class Scheduler:
|
||||||
self.scheduler.add_job(auto_run_by_day, 'cron', hour=1, minute=20)
|
self.scheduler.add_job(auto_run_by_day, 'cron', hour=1, minute=20)
|
||||||
self.scheduler.add_job(auto_run_by_minute, 'interval', minutes=1)
|
self.scheduler.add_job(auto_run_by_minute, 'interval', minutes=1)
|
||||||
|
|
||||||
def _dispatch(self, task_id, command, targets):
|
def _dispatch(self, task_id, interpreter, command, targets):
|
||||||
output = {x: None for x in targets}
|
output = {x: None for x in targets}
|
||||||
history = History.objects.create(
|
history = History.objects.create(
|
||||||
task_id=task_id,
|
task_id=task_id,
|
||||||
|
@ -73,7 +73,7 @@ class Scheduler:
|
||||||
Task.objects.filter(pk=task_id).update(latest_id=history.id)
|
Task.objects.filter(pk=task_id).update(latest_id=history.id)
|
||||||
rds_cli = get_redis_connection()
|
rds_cli = get_redis_connection()
|
||||||
for t in targets:
|
for t in targets:
|
||||||
rds_cli.rpush(SCHEDULE_WORKER_KEY, json.dumps([history.id, t, command]))
|
rds_cli.rpush(SCHEDULE_WORKER_KEY, json.dumps([history.id, t, interpreter, command]))
|
||||||
connections.close_all()
|
connections.close_all()
|
||||||
|
|
||||||
def _init(self):
|
def _init(self):
|
||||||
|
@ -86,7 +86,7 @@ class Scheduler:
|
||||||
self._dispatch,
|
self._dispatch,
|
||||||
trigger,
|
trigger,
|
||||||
id=str(task.id),
|
id=str(task.id),
|
||||||
args=(task.id, task.command, json.loads(task.targets)),
|
args=(task.id, task.interpreter, task.command, json.loads(task.targets)),
|
||||||
)
|
)
|
||||||
connections.close_all()
|
connections.close_all()
|
||||||
except DatabaseError:
|
except DatabaseError:
|
||||||
|
@ -106,7 +106,7 @@ class Scheduler:
|
||||||
self._dispatch,
|
self._dispatch,
|
||||||
trigger,
|
trigger,
|
||||||
id=str(task.id),
|
id=str(task.id),
|
||||||
args=(task.id, task.command, task.targets),
|
args=(task.id, task.interpreter, task.command, task.targets),
|
||||||
replace_existing=True
|
replace_existing=True
|
||||||
)
|
)
|
||||||
elif task.action == 'remove':
|
elif task.action == 'remove':
|
||||||
|
|
|
@ -27,6 +27,7 @@ class Schedule(View):
|
||||||
Argument('id', type=int, required=False),
|
Argument('id', type=int, required=False),
|
||||||
Argument('type', help='请输入任务类型'),
|
Argument('type', help='请输入任务类型'),
|
||||||
Argument('name', help='请输入任务名称'),
|
Argument('name', help='请输入任务名称'),
|
||||||
|
Argument('interpreter', help='请选择执行解释器'),
|
||||||
Argument('command', help='请输入任务内容'),
|
Argument('command', help='请输入任务内容'),
|
||||||
Argument('rst_notify', type=dict, help='请选择执行失败通知方式'),
|
Argument('rst_notify', type=dict, help='请选择执行失败通知方式'),
|
||||||
Argument('targets', type=list, filter=lambda x: len(x), help='请选择执行对象'),
|
Argument('targets', type=list, filter=lambda x: len(x), help='请选择执行对象'),
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { observer } from 'mobx-react';
|
import { observer } from 'mobx-react';
|
||||||
import { Form, Input, Select, Modal, Button } from 'antd';
|
import { Form, Input, Select, Modal, Button, Radio } from 'antd';
|
||||||
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
import { ExclamationCircleOutlined } from '@ant-design/icons';
|
||||||
import { LinkButton, ACEditor } from 'components';
|
import { LinkButton, ACEditor } from 'components';
|
||||||
import TemplateSelector from '../exec/task/TemplateSelector';
|
import TemplateSelector from '../exec/task/TemplateSelector';
|
||||||
|
@ -11,6 +11,7 @@ export default observer(function () {
|
||||||
const [form] = Form.useForm();
|
const [form] = Form.useForm();
|
||||||
const [showTmp, setShowTmp] = useState(false);
|
const [showTmp, setShowTmp] = useState(false);
|
||||||
const [command, setCommand] = useState(store.record.command || '');
|
const [command, setCommand] = useState(store.record.command || '');
|
||||||
|
const [interpreter, setInterpreter] = useState(store.record.interpreter || 'sh');
|
||||||
|
|
||||||
function handleAddZone() {
|
function handleAddZone() {
|
||||||
let type;
|
let type;
|
||||||
|
@ -79,10 +80,16 @@ export default observer(function () {
|
||||||
<Input placeholder="请输入任务名称"/>
|
<Input placeholder="请输入任务名称"/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item required label="任务内容" extra={<LinkButton onClick={() => setShowTmp(true)}>从模板添加</LinkButton>}>
|
<Form.Item required label="任务内容" extra={<LinkButton onClick={() => setShowTmp(true)}>从模板添加</LinkButton>}>
|
||||||
<ACEditor mode="sh" value={command} width="100%" height="150px" onChange={setCommand}/>
|
<Form.Item noStyle name="interpreter" initialValue="sh">
|
||||||
|
<Radio.Group buttonStyle="solid" style={{marginBottom: 12}} onChange={e => setInterpreter(e.target.value)}>
|
||||||
|
<Radio.Button value="sh" style={{width: 80, textAlign: 'center'}}>Shell</Radio.Button>
|
||||||
|
<Radio.Button value="python" style={{width: 80, textAlign: 'center'}}>Python</Radio.Button>
|
||||||
|
</Radio.Group>
|
||||||
|
</Form.Item>
|
||||||
|
<ACEditor mode={interpreter} value={command} width="100%" height="150px" onChange={setCommand}/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item label="失败通知" extra={(
|
<Form.Item label="失败通知" extra={(
|
||||||
<span>
|
<span>
|
||||||
任务执行失败告警通知,
|
任务执行失败告警通知,
|
||||||
<a target="_blank" rel="noopener noreferrer"
|
<a target="_blank" rel="noopener noreferrer"
|
||||||
href="https://spug.cc/docs/use-problem#use-dd">钉钉收不到通知?</a>
|
href="https://spug.cc/docs/use-problem#use-dd">钉钉收不到通知?</a>
|
||||||
|
|
|
@ -20,7 +20,7 @@ export default observer(function () {
|
||||||
return message.error('任务执行时间不能早于当前时间')
|
return message.error('任务执行时间不能早于当前时间')
|
||||||
}
|
}
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
const formData = lds.pick(store.record, ['id', 'name', 'type', 'command', 'desc', 'rst_notify']);
|
const formData = lds.pick(store.record, ['id', 'name', 'type', 'interpreter', 'command', 'desc', 'rst_notify']);
|
||||||
formData['targets'] = store.targets.filter(x => x);
|
formData['targets'] = store.targets.filter(x => x);
|
||||||
formData['trigger'] = trigger;
|
formData['trigger'] = trigger;
|
||||||
formData['trigger_args'] = _parse_args();
|
formData['trigger_args'] = _parse_args();
|
||||||
|
|
Loading…
Reference in New Issue