Schedule script run
This commit is contained in:
parent
fc992b8bdc
commit
6d05152ab2
6 changed files with 162 additions and 7 deletions
109
script.go
109
script.go
|
|
@ -6,6 +6,9 @@ import (
|
|||
"github.com/jmoiron/sqlx"
|
||||
|
||||
// Standard
|
||||
"crypto/md5"
|
||||
"database/sql"
|
||||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -26,6 +29,17 @@ type Hook struct {
|
|||
SSH string
|
||||
}
|
||||
|
||||
func GetScript(scriptID int) (script Script, err error) { // {{{
|
||||
row := db.QueryRowx(`SELECT * FROM script WHERE id=$1`, scriptID)
|
||||
|
||||
err = row.StructScan(&script)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
} // }}}
|
||||
func GetScripts() (scripts []Script, err error) { // {{{
|
||||
scripts = []Script{}
|
||||
|
||||
|
|
@ -146,11 +160,40 @@ func SearchScripts(search string) (scripts []Script, err error) { // {{{
|
|||
|
||||
return
|
||||
} // }}}
|
||||
func HookScript(nodeID, scriptID int) (err error) {// {{{
|
||||
func HookScript(nodeID, scriptID int) (err error) { // {{{
|
||||
_, err = db.Exec(`INSERT INTO hook(node_id, script_id, ssh) VALUES($1, $2, '<host>')`, nodeID, scriptID)
|
||||
return
|
||||
}// }}}
|
||||
} // }}}
|
||||
|
||||
func GetHook(hookID int) (hook Hook, err error) { // {{{
|
||||
row := db.QueryRow(`
|
||||
SELECT
|
||||
to_json(res)
|
||||
FROM (
|
||||
SELECT
|
||||
h.id,
|
||||
h.ssh,
|
||||
(SELECT to_json(node) FROM node WHERE id = h.node_id) AS node,
|
||||
(SELECT to_json(script) FROM script WHERE id = h.script_id) AS script
|
||||
FROM hook h
|
||||
WHERE
|
||||
h.id = $1
|
||||
) res
|
||||
`,
|
||||
hookID,
|
||||
)
|
||||
var data []byte
|
||||
if err = row.Scan(&data); err != nil {
|
||||
err = werr.Wrap(err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &hook)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
}
|
||||
|
||||
return
|
||||
} // }}}
|
||||
func UpdateHook(hook Hook) (err error) { // {{{
|
||||
_, err = db.Exec(`UPDATE hook SET ssh=$2 WHERE id=$1`, hook.ID, strings.TrimSpace(hook.SSH))
|
||||
if err != nil {
|
||||
|
|
@ -167,3 +210,65 @@ func DeleteHook(hookID int) (err error) { // {{{
|
|||
}
|
||||
return
|
||||
} // }}}
|
||||
func ScheduleHook(hookID int) (err error) { // {{{
|
||||
/* Script source is needed to preserve the full execution
|
||||
against changing of the script at a later date.
|
||||
To not waste db disk, the md5sum of the script is
|
||||
calculated and the changed version of the script is just
|
||||
stored once.
|
||||
*/
|
||||
hook, err := GetHook(hookID)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
scriptLogID, err := ScriptPreservedID(hook.Script.Source)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
// The node tree data is retrieved and sent as input to the script.
|
||||
var topNode *Node
|
||||
topNode, err = GetNodeTree(hook.Node.ID, 0, true)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
nodeData, err := json.Marshal(topNode)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
_, err = db.Exec(`INSERT INTO execution(script_log_id, data, ssh) VALUES($1, $2, $3)`, scriptLogID, nodeData, hook.SSH)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
return
|
||||
} // }}}
|
||||
|
||||
func ScriptPreservedID(source string) (id int, err error) { // {{{
|
||||
sum := md5.Sum([]byte(source))
|
||||
md5sum := hex.EncodeToString(sum[:])
|
||||
|
||||
row := db.QueryRow(`SELECT id FROM script_log WHERE md5sum=$1`, md5sum)
|
||||
if err = row.Scan(&id); err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err != sql.ErrNoRows {
|
||||
err = werr.Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
row = db.QueryRow(`INSERT INTO script_log(md5sum, source) VALUES($1, $2) RETURNING id`, md5sum, source)
|
||||
err = row.Scan(&id)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
}
|
||||
return
|
||||
} // }}}
|
||||
|
|
|
|||
20
sql/0013.sql
Normal file
20
sql/0013.sql
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
CREATE TABLE public.script_log (
|
||||
id serial NOT NULL,
|
||||
md5sum char(32) NOT NULL,
|
||||
source text NOT NULL,
|
||||
CONSTRAINT script_log_pk PRIMARY KEY (id)
|
||||
);
|
||||
|
||||
CREATE TABLE public.execution (
|
||||
id serial NOT NULL,
|
||||
time_start timestamptz NULL,
|
||||
script_log_id int4 NOT NULL,
|
||||
"data" jsonb NOT NULL,
|
||||
ssh varchar NOT NULL,
|
||||
time_end timestamptz NULL,
|
||||
output_stdout text NULL,
|
||||
output_stderr text NULL,
|
||||
exitcode int NULL,
|
||||
CONSTRAINT execution_pk PRIMARY KEY (id),
|
||||
CONSTRAINT execution_script_log_fk FOREIGN KEY (script_log_id) REFERENCES public.script_log(id) ON DELETE RESTRICT ON UPDATE RESTRICT
|
||||
);
|
||||
|
|
@ -260,7 +260,7 @@ select:focus {
|
|||
}
|
||||
#script-hooks .scripts-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content);
|
||||
grid-template-columns: repeat(4, min-content);
|
||||
align-items: center;
|
||||
grid-gap: 2px 0px;
|
||||
}
|
||||
|
|
@ -276,9 +276,11 @@ select:focus {
|
|||
font-weight: bold;
|
||||
margin-top: 8px;
|
||||
}
|
||||
#script-hooks .scripts-grid .script-unhook img {
|
||||
#script-hooks .scripts-grid .script-unhook img,
|
||||
#script-hooks .scripts-grid .script-run img {
|
||||
display: block;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
#script-hooks .scripts-grid .script-name,
|
||||
#script-hooks .scripts-grid .script-ssh {
|
||||
|
|
|
|||
|
|
@ -1067,7 +1067,7 @@ class ScriptHooks extends Component {
|
|||
NodeID: _app.currentNode.ID,
|
||||
ScriptID: script.ID,
|
||||
})
|
||||
.then(()=>mbus.dispatch('NODE_HOOKED', _app.currentNode.ID))
|
||||
.then(() => mbus.dispatch('NODE_HOOKED', _app.currentNode.ID))
|
||||
.catch(err => showError(err))
|
||||
}// }}}
|
||||
}
|
||||
|
|
@ -1085,12 +1085,14 @@ class ScriptHook extends Component {
|
|||
<div class="script-name">${this.hook.Script.Name}</div>
|
||||
<div class="script-ssh"></div>
|
||||
<div class="script-unhook"><img src="/images/${_VERSION}/node_modules/@mdi/svg/svg/trash-can.svg" /></div>
|
||||
<div class="script-run"><img src="/images/${_VERSION}/node_modules/@mdi/svg/svg/play-box.svg" /></div>
|
||||
`
|
||||
this.element_ssh = tmpl.content.querySelector('.script-ssh')
|
||||
this.element_ssh.innerText = this.hook.SSH
|
||||
|
||||
tmpl.content.querySelector('.script-ssh').addEventListener('click', () => this.update())
|
||||
tmpl.content.querySelector('.script-unhook').addEventListener('click', () => this.delete())
|
||||
tmpl.content.querySelector('.script-run').addEventListener('click', () => this.run())
|
||||
|
||||
return tmpl.content
|
||||
}// }}}
|
||||
|
|
@ -1138,6 +1140,10 @@ class ScriptHook extends Component {
|
|||
})
|
||||
.catch(err => showError(err))
|
||||
}// }}}
|
||||
run() {// {{{
|
||||
_app.query(`/hooks/schedule/${this.hook.ID}`)
|
||||
.catch(err => showError(err))
|
||||
}// }}}
|
||||
}
|
||||
|
||||
class ScriptsList extends Component {
|
||||
|
|
|
|||
|
|
@ -352,7 +352,7 @@ select:focus {
|
|||
}
|
||||
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, min-content);
|
||||
grid-template-columns: repeat(4, min-content);
|
||||
align-items: center;
|
||||
grid-gap: 2px 0px;
|
||||
|
||||
|
|
@ -366,10 +366,11 @@ select:focus {
|
|||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.script-unhook {
|
||||
.script-unhook, .script-run {
|
||||
img {
|
||||
display: block;
|
||||
height: 24px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
21
webserver.go
21
webserver.go
|
|
@ -54,6 +54,7 @@ func initWebserver() (err error) {
|
|||
http.HandleFunc("/hooks/search", actionScriptsSearch)
|
||||
http.HandleFunc("/hooks/update", actionHookUpdate)
|
||||
http.HandleFunc("/hooks/delete/{hookID}", actionHookDelete)
|
||||
http.HandleFunc("/hooks/schedule/{hookID}", actionHookSchedule)
|
||||
|
||||
err = http.ListenAndServe(address, nil)
|
||||
return
|
||||
|
|
@ -730,5 +731,25 @@ func actionHookDelete(w http.ResponseWriter, r *http.Request) { // {{{
|
|||
j, _ := json.Marshal(out)
|
||||
w.Write(j)
|
||||
} // }}}
|
||||
func actionHookSchedule(w http.ResponseWriter, r *http.Request) { // {{{
|
||||
hookID := 0
|
||||
hookIDStr := r.PathValue("hookID")
|
||||
hookID, _ = strconv.Atoi(hookIDStr)
|
||||
|
||||
err := ScheduleHook(hookID)
|
||||
if err != nil {
|
||||
err = werr.Wrap(err)
|
||||
httpError(w, err)
|
||||
return
|
||||
}
|
||||
|
||||
out := struct {
|
||||
OK bool
|
||||
}{
|
||||
true,
|
||||
}
|
||||
j, _ := json.Marshal(out)
|
||||
w.Write(j)
|
||||
} // }}}
|
||||
|
||||
// vim: foldmethod=marker
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue