Mercurial > sci
changeset 27:dae2de19ca5d
misc: switch to JSON everywhere
author | David Demelier <markand@malikania.fr> |
---|---|
date | Wed, 03 Aug 2022 15:18:09 +0200 |
parents | 7e10cace67a3 |
children | 4c16bb25e4f1 |
files | Makefile config.mk examples/nsnake.sh lib/apic.c lib/apic.h lib/db.c lib/db.h lib/types.c lib/types.h scictl/scictl.c scid/crud.c scid/crud.h scid/db.c scid/db.h scid/http.c scid/page-api-jobresults.c scid/page-api-jobs.c scid/page-api-projects.c scid/page-api-todo.c scid/page-api-workers.c scid/page-api.h scid/page-index.c sciworkerd/sciworkerd.c sciworkerd/sciworkerd.h sciworkerd/task.c sql/init.sql sql/job-add.sql sql/job-list.sql sql/job-todo.sql sql/jobresult-add.sql sql/jobresult-list-by-job-group.sql sql/jobresult-list-by-job.sql sql/jobresult-list-by-worker.sql sql/project-list.sql sql/worker-list.sql themes/bulma/pages/index.html |
diffstat | 36 files changed, 952 insertions(+), 1640 deletions(-) [+] |
line wrap: on
line diff
--- a/Makefile Tue Aug 02 13:24:13 2022 +0200 +++ b/Makefile Wed Aug 03 15:18:09 2022 +0200 @@ -23,11 +23,9 @@ LIBSCI= lib/libsci.a LIBSCI_SRCS= extern/libsqlite/sqlite3.c \ lib/apic.c \ - lib/db.c \ lib/log.c \ lib/strlcpy.c \ lib/strtonum.c \ - lib/types.c \ lib/util.c LIBSCI_OBJS= ${LIBSCI_SRCS:.c=.o} LIBSCI_DEPS= ${LIBSCI_SRCS:.c=.d} @@ -55,9 +53,10 @@ SCID= scid/scid SCID_SRCS= extern/libmustache4c/mustache.c \ + scid/crud.c \ + scid/db.c \ scid/http.c \ scid/main.c \ - scid/scid.c \ scid/page-api-jobresults.c \ scid/page-api-jobs.c \ scid/page-api-projects.c \ @@ -65,7 +64,8 @@ scid/page-api-workers.c \ scid/page-index.c \ scid/page-static.c \ - scid/page.c + scid/page.c \ + scid/scid.c SCID_OBJS= ${SCID_SRCS:.c=.o} SCID_DEPS= ${SCID_SRCS:.c=.d}
--- a/config.mk Tue Aug 02 13:24:13 2022 +0200 +++ b/config.mk Wed Aug 03 15:18:09 2022 +0200 @@ -1,5 +1,5 @@ CC= cc -CFLAGS= -g -O0 -Wall -Wextra -fsanitize=address +CFLAGS= -g -O0 -Wall -Wextra -fsanitize=address -Wno-format-truncation #CFLAGS= -Wall -Wextra -fsanitize=address,undefined -g -O0 #LDFLAGS= -fsanitize=address,undefined
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/examples/nsnake.sh Wed Aug 03 15:18:09 2022 +0200 @@ -0,0 +1,25 @@ +#!/bin/sh + +set -e + +readonly wrkdir="$(mktemp -d /tmp/nsnake-XXXXXX)" +readonly repo="http://hg.malikania.fr/nsnake" + +trap "cleanup" INT TERM EXIT + +cleanup() +{ + rm -rf $wrkdir +} + +if [ "$#" -ne 1 ]; then + echo "abort: $(basename $0) revision" 1>&2 + exit 1 +fi + +echo "=> Cloning repository $repo (revision $1) into $wrkdir" +hg clone -r "$1" "$repo" "$wrkdir" +cd "$wrkdir" + +echo "=> Building" +make
--- a/lib/apic.c Tue Aug 02 13:24:13 2022 +0200 +++ b/lib/apic.c Wed Aug 03 15:18:09 2022 +0200 @@ -7,7 +7,6 @@ #include <curl/curl.h> #include "apic.h" -#include "types.h" #include "util.h" struct curlpack { @@ -16,13 +15,6 @@ struct curl_slist *headers; }; -struct converter { - void *data; - size_t datasz; - ssize_t (*unpack)(void *, size_t, json_t *); - json_t *(*pack)(const void *, size_t); -}; - struct apiconf apiconf = { .baseurl = "http://127.0.0.1" }; @@ -39,7 +31,7 @@ static inline char * create_url(const char *fmt, va_list args) { - static _Thread_local char ret[256]; + static _Thread_local char ret[1024]; char page[128]; va_list ap; @@ -93,14 +85,14 @@ return pack; } -static int +static json_t * perform(struct apic *req, const char *body, const char *fmt, va_list ap) { FILE *fp; char *response, *url; size_t responsesz; + json_t *doc = NULL; json_error_t error; - int ret = -1; struct curlpack curl; memset(req, 0, sizeof (*req)); @@ -119,10 +111,8 @@ if (req->status != 200) snprintf(req->error, sizeof (req->error), "HTTP returned %ld", req->status); - if (response[0] && !(req->doc = json_loads(response, 0, &error))) + if (response[0] && !(doc = json_loads(response, 0, &error))) snprintf(req->error, sizeof (req->error), "JSON parse error: %s", error.text); - else - ret = 0; } curl_easy_cleanup(curl.curl); @@ -130,112 +120,59 @@ free(response); - return ret; + return doc; } -static ssize_t -get(struct apic *req, const struct converter *cv, const char *fmt, ...) +static json_t * +get(struct apic *req, const char *fmt, ...) { va_list ap; - ssize_t ret; + json_t *ret; va_start(ap, fmt); ret = perform(req, NULL, fmt, ap); va_end(ap); - if (ret < 0) - return -1; - if (!req->doc || (!json_is_object(req->doc) && !json_is_array(req->doc))) - return snprintf(req->error, sizeof (req->error), "invalid JSON document received"), -1; - if ((ret = cv->unpack(cv->data, cv->datasz, req->doc)) < 0) - return snprintf(req->error, sizeof (req->error), "%s", strerror(errno)); + if (!ret || (!json_is_object(ret) && !json_is_array(ret))) + snprintf(req->error, sizeof (req->error), "invalid JSON document received"); return ret; } static int -create(struct apic *req, const struct converter *cv, const char *fmt, ...) +create(struct apic *req, json_t *doc, const char *fmt, ...) { va_list ap; - int ret; - json_t *doc; + json_t *ret; char *body; memset(req, 0, sizeof (*req)); - if (!(doc = cv->pack(cv->data, cv->datasz))) - return snprintf(req->error, sizeof (req->error), "%s", strerror(errno)); if (!(body = json_dumps(doc, JSON_COMPACT))) { json_decref(doc); - return snprintf(req->error, sizeof (req->error), "%s", strerror(errno)); + return snprintf(req->error, sizeof (req->error), "%s", strerror(errno)), -1; } va_start(ap, fmt); ret = perform(req, body, fmt, ap); va_end(ap); - json_decref(doc); + /* TODO: update id. */ + (void)ret; + free(body); - return ret; -} - -static json_t * -wrap_job_to(const void *data, size_t datasz) -{ - return job_to(data, datasz); -} - -static ssize_t -wrap_job_from(void *data, size_t datasz, json_t *doc) -{ - return job_from(data, datasz, doc); -} - -static json_t * -wrap_jobresult_to(const void *data, size_t datasz) -{ - return jobresult_to(data, datasz); + return 0; } -static ssize_t -wrap_project_from(void *data, size_t datasz, json_t *doc) -{ - return project_from(data, datasz, doc); -} - -static json_t * -wrap_project_to(const void *data, size_t datasz) -{ - return project_to(data, datasz); -} - -static ssize_t -wrap_worker_from(void *data, size_t datasz, json_t *doc) -{ - return worker_from(data, datasz, doc); -} - -static json_t * -wrap_worker_to(const void *data, size_t datasz) -{ - return worker_to(data, datasz); -} - -static ssize_t -wrap_jobresult_from(void *data, size_t datasz, json_t *doc) -{ - return jobresult_from(data, datasz, doc); -} - -int +json_t * apic_get(struct apic *req, const char *fmt, ...) { assert(req); assert(fmt); va_list ap; - int ret; + json_t *ret; va_start(ap, fmt); ret = perform(req, NULL, fmt, ap); @@ -244,14 +181,14 @@ return ret; } -int +json_t * apic_post(struct apic *req, const json_t *doc, const char *fmt, ...) { assert(req); assert(fmt); va_list ap; - int ret; + json_t *ret; char *body; if (!(body = json_dumps(doc, JSON_COMPACT))) @@ -267,167 +204,80 @@ } int -apic_job_add(struct apic *req, struct job *job) +apic_job_add(struct apic *req, json_t *job) { assert(req); assert(job); - const struct converter cv = { - .data = job, - .datasz = 1, - .pack = wrap_job_to, - .unpack = wrap_job_from - }; - - return create(req, &cv, "api/v1/jobs"); + return create(req, job, "api/v1/jobs"); } -ssize_t -apic_job_todo(struct apic *req, struct job *jobs, size_t jobsz, const char *worker_name) +json_t * +apic_job_todo(struct apic *req, const char *worker_name) { assert(req); - assert(jobs); + assert(worker_name); - struct converter cv = { - .data = jobs, - .datasz = jobsz, - .unpack = wrap_job_from - }; - - return get(req, &cv, "api/v1/todo/%s", worker_name); + return get(req, "api/v1/todo/%s", worker_name); } int -apic_jobresult_add(struct apic *req, struct jobresult *result) +apic_jobresult_add(struct apic *req, json_t *res) { assert(req); - assert(result); + assert(res); - struct converter cv = { - .data = result, - .datasz = 1, - .pack = wrap_jobresult_to, - .unpack = wrap_jobresult_from - }; - - return create(req, &cv, "api/v1/jobresults"); + return create(req, res, "api/v1/jobresults"); } int -apic_project_save(struct apic *req, struct project *project) +apic_project_save(struct apic *req, json_t *p) +{ + assert(req); + assert(p); + + return create(req, p, "api/v1/projects"); +} + +json_t * +apic_project_list(struct apic *req) { assert(req); - assert(project); + + return get(req, "api/v1/projects"); +} - struct converter cv = { - .data = project, - .datasz = 1, - .pack = wrap_project_to, - .unpack = wrap_project_from - }; +json_t * +apic_project_find(struct apic *req, const char *name) +{ + assert(req); + assert(name); - return create(req, &cv, "api/v1/projects"); + return get(req, "api/v1/projects/%s", name); } int -apic_project_update(struct apic *req, struct project *project) -{ - assert(req); - assert(project); - - struct converter cv = { - .data = project, - .datasz = 1, - .pack = wrap_project_to, - .unpack = wrap_project_from - }; - - return create(req, &cv, "api/v1/projects"); -} - -ssize_t -apic_project_list(struct apic *req, struct project *projects, size_t projectsz) -{ - assert(req); - assert(projects); - - struct converter cv = { - .data = projects, - .datasz = projectsz, - .unpack = wrap_project_from - }; - - return get(req, &cv, "api/v1/projects"); -} - -int -apic_project_find(struct apic *req, struct project *project, const char *name) -{ - assert(req); - assert(project); - - struct converter cv = { - .data = project, - .datasz = 1, - .unpack = wrap_project_from - }; - - return get(req, &cv, "api/v1/projects/%s", name); -} - -int -apic_worker_save(struct apic *req, struct worker *wk) +apic_worker_save(struct apic *req, json_t *wk) { assert(req); assert(wk); - struct converter cv = { - .data = wk, - .datasz = 1, - .pack = wrap_worker_to - }; - - return create(req, &cv, "api/v1/workers"); + return create(req, wk, "api/v1/workers"); } -ssize_t -apic_worker_list(struct apic *req, struct worker *wk, size_t wksz) -{ - assert(req); - assert(wk); - assert(wksz); - - struct converter cv = { - .data = wk, - .datasz = wksz, - .unpack = wrap_worker_from - }; - - return get(req, &cv, "api/v1/workers"); -} - -int -apic_worker_find(struct apic *req, struct worker *wk, const char *name) -{ - assert(req); - assert(wk); - - struct converter cv = { - .data = wk, - .datasz = 1, - .unpack = wrap_worker_from - }; - - return get(req, &cv, "api/v1/workers/%s", name); -} - -void -apic_finish(struct apic *req) +json_t * +apic_worker_list(struct apic *req) { assert(req); - if (req->doc) - json_decref(req->doc); + return get(req, "api/v1/workers"); +} - memset(req, 0, sizeof (*req)); +json_t * +apic_worker_find(struct apic *req, const char *name) +{ + assert(req); + assert(name); + + return get(req, "api/v1/workers/%s", name); }
--- a/lib/apic.h Tue Aug 02 13:24:13 2022 +0200 +++ b/lib/apic.h Wed Aug 03 15:18:09 2022 +0200 @@ -1,75 +1,56 @@ #ifndef SCI_APIC_H #define SCI_APIC_H -#include <sys/types.h> - #include <jansson.h> -#include "config.h" - #define APIC_ERR_MAX 128 -struct job; -struct jobresult; -struct project; -struct worker; - struct apic { - json_t *doc; char error[APIC_ERR_MAX]; long status; }; extern struct apiconf { - char baseurl[SCI_URL_MAX]; + char baseurl[512]; } apiconf; /* Generic HTTP commands using JSON. */ /* Perform GET request. */ -int +json_t * apic_get(struct apic *, const char *, ...); /* Perform POST request with JSON body. */ -int +json_t * apic_post(struct apic *, const json_t *, const char *, ...); -/* - * Commands to fetch, create, delete or update data. - * - * Any of the following commands need to keep apic structure alive as long as - * data objects are being used because they reference JSON values directly from - * the HTTP response. - */ +/* --- */ int -apic_job_add(struct apic *, struct job *); +apic_job_add(struct apic *, json_t *); -ssize_t -apic_job_todo(struct apic *, struct job *, size_t, const char *); +json_t * +apic_job_todo(struct apic *, const char *); int -apic_jobresult_add(struct apic *, struct jobresult *); +apic_jobresult_add(struct apic *, json_t *); int -apic_project_save(struct apic *, struct project *); +apic_project_save(struct apic *, json_t *); -ssize_t -apic_project_list(struct apic *, struct project *, size_t); +json_t * +apic_project_list(struct apic *); -int -apic_project_find(struct apic *, struct project *, const char *); +json_t * +apic_project_find(struct apic *, const char *); int -apic_worker_save(struct apic *, struct worker *); - -ssize_t -apic_worker_list(struct apic *, struct worker *, size_t); +apic_worker_save(struct apic *, json_t *); -int -apic_worker_find(struct apic *, struct worker *, const char *); +json_t * +apic_worker_list(struct apic *); -void -apic_finish(struct apic *); +json_t * +apic_worker_find(struct apic *, const char *); #endif /* !SCI_APIC_H */
--- a/lib/db.c Tue Aug 02 13:24:13 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,378 +0,0 @@ -/* - * db.c -- scid database access - * - * Copyright (c) 2021 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <assert.h> -#include <stdlib.h> -#include <string.h> - -#include <sqlite3.h> - -#include <utlist.h> - -#include "db.h" -#include "log.h" -#include "types.h" -#include "util.h" - -#include "sql/init.h" -#include "sql/job-add.h" -#include "sql/job-list.h" -#include "sql/job-todo.h" -#include "sql/jobresult-add.h" -#include "sql/jobresult-list-by-job.h" -#include "sql/jobresult-list-by-job-group.h" -#include "sql/jobresult-list-by-worker.h" -#include "sql/project-find.h" -#include "sql/project-list.h" -#include "sql/project-save.h" -#include "sql/worker-find.h" -#include "sql/worker-list.h" -#include "sql/worker-save.h" - -#define CHAR(v) (const char *)(v) - -static sqlite3 *db; - -struct list { - void (*unpack)(sqlite3_stmt *, void *); - void *data; - size_t datasz; - size_t elemwidth; -}; - -static void -project_unpacker(sqlite3_stmt *stmt, void *data) -{ - struct project *project = data; - - project->name = util_strdup(CHAR(sqlite3_column_text(stmt, 0))); - project->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 1))); - project->url = util_strdup(CHAR(sqlite3_column_text(stmt, 2))); - project->script = util_strdup(CHAR(sqlite3_column_text(stmt, 3))); -} - -static void -worker_unpacker(sqlite3_stmt *stmt, void *data) -{ - struct worker *w = data; - - w->name = util_strdup(CHAR(sqlite3_column_text(stmt, 0))); - w->desc = util_strdup(CHAR(sqlite3_column_text(stmt, 1))); -} - -static void -job_unpacker(sqlite3_stmt *stmt, void *data) -{ - struct job *job = data; - - job->id = sqlite3_column_int64(stmt, 0); - job->tag = util_strdup(CHAR(sqlite3_column_text(stmt, 1))); - job->project_name = util_strdup(CHAR(sqlite3_column_text(stmt, 2))); -} - -static inline char * -dup(sqlite3_stmt *stmt, int col) -{ - const unsigned char *s; - - if ((s = sqlite3_column_text(stmt, col))) - return util_strdup(CHAR(s)); - - return util_strdup(""); -} - -static void -jobresult_unpacker(sqlite3_stmt *stmt, void *data) -{ - struct jobresult *r = data; - - r->id = sqlite3_column_int64(stmt, 0); - r->job_id = sqlite3_column_int64(stmt, 1); - r->worker_name = util_strdup(CHAR(sqlite3_column_text(stmt, 2))); - r->exitcode = sqlite3_column_int(stmt, 3); - r->log = dup(stmt, 4); -} - -static void -vbind(sqlite3_stmt *stmt, const char *fmt, va_list ap) -{ - for (int index = 1; *fmt; ++fmt) { - switch (*fmt) { - case 'i': - sqlite3_bind_int(stmt, index++, va_arg(ap, int)); - break; - case 'j': - sqlite3_bind_int64(stmt, index++, va_arg(ap, intmax_t)); - break; - case 's': - sqlite3_bind_text(stmt, index++, va_arg(ap, const char *), -1, SQLITE_STATIC); - break; - case 'z': - sqlite3_bind_int64(stmt, index++, va_arg(ap, size_t)); - break; - default: - break; - } - } -} - -static int -insert(const char *sql, const char *fmt, ...) -{ - assert(sql); - assert(fmt); - - sqlite3_stmt *stmt = NULL; - va_list ap; - int ret = -1; - - if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) - return log_warn("db: %s", sqlite3_errmsg(db)), -1; - - va_start(ap, fmt); - vbind(stmt, fmt, ap); - va_end(ap); - - if (sqlite3_step(stmt) != SQLITE_DONE) - log_warn("db: %s", sqlite3_errmsg(db)); - else - ret = sqlite3_last_insert_rowid(db); - - sqlite3_finalize(stmt); - - return ret; -} - -static ssize_t -list(struct list *sel, const char *sql, const char *args, ...) -{ - sqlite3_stmt *stmt = NULL; - va_list ap; - int step; - ssize_t ret = -1; - size_t tot = 0; - - if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) - return log_warn("db: %s", sqlite3_errmsg(db)), -1; - - va_start(ap, args); - vbind(stmt, args, ap); - va_end(ap); - - while (tot < sel->datasz && (step = sqlite3_step(stmt)) == SQLITE_ROW) - sel->unpack(stmt, (unsigned char *)sel->data + (tot++ * sel->elemwidth)); - - if (step == SQLITE_OK || step == SQLITE_DONE || step == SQLITE_ROW) - ret = tot; - else - memset(sel->data, 0, sel->datasz * sel->elemwidth); - - sqlite3_finalize(stmt); - - return ret; -} - -int -db_open(const char *path) -{ - assert(path); - - if (sqlite3_open(path, &db) != SQLITE_OK) - return log_warn("db: open error: %s", sqlite3_errmsg(db)), -1; - - /* Wait for 30 seconds to lock the database. */ - sqlite3_busy_timeout(db, 30000); - - if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK) - return log_warn("db: initialization error: %s", sqlite3_errmsg(db)), -1; - - return 0; -} - -int -db_project_save(struct project *p) -{ - return insert(CHAR(sql_project_save), "ssss", p->name, p->desc, - p->url, p->script) < 0 ? -1 : 0; -} - -ssize_t -db_project_list(struct project *projects, size_t projectsz) -{ - struct list sel = { - .unpack = project_unpacker, - .data = projects, - .datasz = projectsz, - .elemwidth = sizeof (*projects), - }; - - return list(&sel, CHAR(sql_project_list), "z", projectsz); -} - -int -db_project_find(struct project *project, const char *name) -{ - struct list sel = { - .unpack = project_unpacker, - .data = project, - .datasz = 1, - .elemwidth = sizeof (*project), - }; - - return list(&sel, CHAR(sql_project_find), "s", name) == 1 ? 0 : -1; -} - -int -db_worker_save(struct worker *wk) -{ - assert(wk); - - return insert(CHAR(sql_worker_save), "ss", wk->name, wk->desc) < 0 ? -1 : 0; -} - -ssize_t -db_worker_list(struct worker *wk, size_t wksz) -{ - assert(wk); - - struct list sel = { - .unpack = worker_unpacker, - .data = wk, - .datasz = wksz, - .elemwidth = sizeof (*wk), - }; - - return list(&sel, CHAR(sql_worker_list), "z", wksz); -} - -int -db_worker_find(struct worker *wk, const char *name) -{ - struct list sel = { - .unpack = worker_unpacker, - .data = wk, - .datasz = 1, - .elemwidth = sizeof (*wk), - }; - - return list(&sel, CHAR(sql_worker_find), "s", name) == 1 ? 0 : -1; -} - -int -db_job_add(struct job *job) -{ - assert(job); - - return (job->id = insert(CHAR(sql_job_add), - "ss", job->tag, job->project_name)) < 0 ? -1 : 0; -} - -ssize_t -db_job_todo(struct job *jobs, size_t jobsz, const char *worker) -{ - assert(jobs); - - struct list sel = { - .unpack = job_unpacker, - .data = jobs, - .datasz = jobsz, - .elemwidth = sizeof (*jobs) - }; - - return list(&sel, CHAR(sql_job_todo), "ssz", worker, worker, jobsz); -} - -ssize_t -db_job_list(struct job *jobs, size_t jobsz, const char *project) -{ - assert(jobs); - assert(project); - - struct list sel = { - .unpack = job_unpacker, - .data = jobs, - .datasz = jobsz, - .elemwidth = sizeof (*jobs) - }; - - return list(&sel, CHAR(sql_job_list), "sz", project, jobsz); -} - -int -db_jobresult_add(struct jobresult *r) -{ - assert(r); - - return (r->id = insert(CHAR(sql_jobresult_add), "jsis", r->job_id, - r->worker_name, r->exitcode, r->log)) < 0 ? -1 : 0; -} - -ssize_t -db_jobresult_list_by_job(struct jobresult *r, size_t rsz, intmax_t job_id) -{ - assert(r); - - struct list sel = { - .unpack = jobresult_unpacker, - .data = r, - .datasz = rsz, - .elemwidth = sizeof (*r) - }; - - return list(&sel, CHAR(sql_jobresult_list_by_job), "jz", job_id, rsz); -} - -ssize_t -db_jobresult_list_by_job_group(struct jobresult *r, size_t rsz, intmax_t job_id) -{ - assert(r); - - struct list sel = { - .unpack = jobresult_unpacker, - .data = r, - .datasz = rsz, - .elemwidth = sizeof (*r) - }; - - return list(&sel, CHAR(sql_jobresult_list_by_job_group), "jz", job_id, rsz); -} - -ssize_t -db_jobresult_list_by_worker(struct jobresult *r, size_t rsz, const char *worker) -{ - assert(r); - assert(worker); - - struct list sel = { - .unpack = jobresult_unpacker, - .data = r, - .datasz = rsz, - .elemwidth = sizeof (*r) - }; - - return list(&sel, CHAR(sql_jobresult_list_by_worker), "sz", worker, rsz); -} - -void -db_finish(void) -{ - if (db) { - sqlite3_close(db); - db = NULL; - } -}
--- a/lib/db.h Tue Aug 02 13:24:13 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,76 +0,0 @@ -/* - * db.h -- scid database access - * - * Copyright (c) 2021 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SCI_DB_H -#define SCI_DB_H - -#include <sys/types.h> -#include <stddef.h> -#include <stdint.h> - -struct project; -struct worker; -struct job; -struct jobresult; - -int -db_open(const char *); - -int -db_job_add(struct job *); - -ssize_t -db_job_todo(struct job *, size_t, const char *); - -ssize_t -db_job_list(struct job *, size_t, const char *); - -int -db_jobresult_add(struct jobresult *); - -ssize_t -db_jobresult_list_by_job(struct jobresult *, size_t, intmax_t); - -ssize_t -db_jobresult_list_by_job_group(struct jobresult *, size_t, intmax_t); - -ssize_t -db_jobresult_list_by_worker(struct jobresult *, size_t, const char *); - -int -db_project_save(struct project *); - -ssize_t -db_project_list(struct project *, size_t); - -int -db_project_find(struct project *, const char *); - -int -db_worker_save(struct worker *); - -ssize_t -db_worker_list(struct worker *, size_t); - -int -db_worker_find(struct worker *, const char *); - -void -db_finish(void); - -#endif /* !SCI_DB_H */
--- a/lib/types.c Tue Aug 02 13:24:13 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,285 +0,0 @@ -/* - * types.c -- type definitions and conversions - * - * Copyright (c) 2021 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include <assert.h> -#include <errno.h> - -#include "types.h" -#include "util.h" - -typedef json_t * (*packer)(const void *); -typedef int (*unpacker)(void *, json_t *); - -static inline json_t * -job_packer(const struct job *job) -{ - return json_pack("{si ss ss}", - "id", job->id, - "project_name", job->project_name, - "tag", job->tag - ); -} - -static inline int -job_unpacker(struct job *job, json_t *doc) -{ - const int ret = json_unpack(doc, "{si ss ss}", - "id", &job->id, - "project_name", &job->project_name, - "tag", &job->tag - ); - - if (ret == 0) { - job->project_name = util_strdup(job->project_name); - job->tag = util_strdup(job->tag); - } - - return ret; -} - -static inline json_t * -jobresult_packer(const struct jobresult *res) -{ - return json_pack("{si si ss si ss}", - "id", res->id, - "job_id", res->job_id, - "worker_name", res->worker_name, - "exitcode", res->exitcode, - "log", res->log - ); -} - -static inline int -jobresult_unpacker(struct jobresult *res, json_t *doc) -{ - const int ret = json_unpack(doc, "{si si ss si ss}", - "id", &res->id, - "job_id", &res->job_id, - "worker_name", &res->worker_name, - "exitcode", &res->exitcode, - "log", &res->log - ); - - if (ret == 0) { - res->worker_name = util_strdup(res->worker_name); - res->log = util_strdup(res->log); - } - - return ret; -} - -static inline json_t * -worker_packer(const struct worker *w) -{ - return json_pack("{ss ss}", - "name", w->name, - "desc", w->desc - ); -} - -static inline int -worker_unpacker(struct worker *w, json_t *doc) -{ - const int ret = json_unpack(doc, "{ss ss}", - "name", &w->name, - "desc", &w->desc - ); - - if (ret == 0) { - w->name = util_strdup(w->name); - w->desc = util_strdup(w->desc); - } - - return ret; -} - -static inline json_t * -project_packer(struct project *p) -{ - return json_pack("{ss ss ss ss}", - "name", p->name, - "desc", p->desc, - "url", p->url, - "script", p->script - ); -} - -static inline int -project_unpacker(struct project *p, json_t *doc) -{ - const int ret = json_unpack(doc, "{ss ss ss ss}", - "name", &p->name, - "desc", &p->desc, - "url", &p->url, - "script", &p->script - ); - - if (ret == 0) { - p->name = util_strdup(p->name); - p->desc = util_strdup(p->desc); - p->url = util_strdup(p->url); - p->script = util_strdup(p->script); - } - - return ret; -} - -static json_t * -to(const void *array, size_t arraysz, size_t width, packer fn) -{ - json_t *doc; - - if (arraysz == 1) - doc = fn(array); - else { - doc = json_array(); - - for (size_t i = 0; i < arraysz; ++i) - json_array_append(doc, fn((char *)array + (i * width))); - } - - return doc; -} - -static ssize_t -from(void *array, size_t arraysz, size_t width, json_t *doc, unpacker fn) -{ - json_t *val; - size_t i, tot = 0; - - if (json_is_array(doc)) { - json_array_foreach(doc, i, val) { - if (tot >= arraysz) - return errno = ERANGE, -1; - if (fn((char *)array + (tot++ * width), val) < 0) - return errno = EILSEQ, -1; - } - } else if (json_is_object(doc)) { - tot = 1; - - if (fn(array, doc) < 0) - return errno = EILSEQ, -1; - } else - return errno = EINVAL, -1; - - return tot; -} - -json_t * -job_to(const struct job *jobs, size_t jobsz) -{ - assert(jobs); - - return to(jobs, jobsz, sizeof (*jobs), (packer)job_packer); -} - -ssize_t -job_from(struct job *jobs, size_t jobsz, json_t *doc) -{ - assert(jobs); - assert(doc); - - return from(jobs, jobsz, sizeof (*jobs), doc, (unpacker)job_unpacker); -} - -void -job_finish(struct job *job) -{ - assert(job); - - free(job->tag); -} - -json_t * -jobresult_to(const struct jobresult *res, size_t resz) -{ - assert(res); - - return to(res, resz, sizeof (*res), (packer)jobresult_packer); -} - -ssize_t -jobresult_from(struct jobresult *res, size_t resz, json_t *doc) -{ - assert(res); - assert(doc); - - return from(res, resz, sizeof (*res), doc, (unpacker)jobresult_unpacker); -} - -void -jobresult_finish(struct jobresult *res) -{ - assert(res); - - free(res->log); -} - -json_t * -worker_to(const struct worker *w, size_t wsz) -{ - assert(w); - - return to(w, wsz, sizeof (*w), (packer)worker_packer); -} - -ssize_t -worker_from(struct worker *w, size_t wsz, json_t *doc) -{ - assert(w); - assert(doc); - - return from(w, wsz, sizeof (*w), doc, (unpacker)worker_unpacker); -} - -void -worker_finish(struct worker *w) -{ - assert(w); - - free(w->name); - free(w->desc); -} - -json_t * -project_to(const struct project *proj, size_t projsz) -{ - assert(proj); - - return to(proj, projsz, sizeof (*proj), (packer)project_packer); -} - -ssize_t -project_from(struct project *proj, size_t projsz, json_t *doc) -{ - assert(proj); - assert(doc); - - return from(proj, projsz, sizeof (*proj), doc, (unpacker)project_unpacker); -} - -void -project_finish(struct project *proj) -{ - assert(proj); - - free(proj->name); - free(proj->desc); - free(proj->url); - free(proj->script); -}
--- a/lib/types.h Tue Aug 02 13:24:13 2022 +0200 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,98 +0,0 @@ -/* - * types.h -- type definitions and conversions - * - * Copyright (c) 2021 David Demelier <markand@malikania.fr> - * - * Permission to use, copy, modify, and/or distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#ifndef SCI_TYPES_H -#define SCI_TYPES_H - -#include <sys/types.h> -#include <stddef.h> -#include <stdint.h> - -#include <jansson.h> - -struct job { - intmax_t id; - char *project_name; - char *tag; -}; - -struct jobresult { - intmax_t id; - intmax_t job_id; - char *worker_name; - int exitcode; - char *log; -}; - -struct worker { - char *name; - char *desc; -}; - -struct project { - char *name; - char *desc; - char *url; - char *script; -}; - -/* job */ - -json_t * -job_to(const struct job *, size_t); - -ssize_t -job_from(struct job *, size_t, json_t *); - -void -job_finish(struct job *); - -/* jobresult */ - -json_t * -jobresult_to(const struct jobresult *, size_t); - -ssize_t -jobresult_from(struct jobresult *, size_t, json_t *); - -void -jobresult_finish(struct jobresult *); - -/* worker */ - -json_t * -worker_to(const struct worker *, size_t); - -ssize_t -worker_from(struct worker *, size_t, json_t *); - -void -worker_finish(struct worker *); - -/* project */ - -json_t * -project_to(const struct project *, size_t); - -ssize_t -project_from(struct project *, size_t, json_t *); - -void -project_finish(struct project *); - -#endif /* !SCI_TYPES_H */
--- a/scictl/scictl.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scictl/scictl.c Wed Aug 03 15:18:09 2022 +0200 @@ -16,22 +16,24 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#include <errno.h> #include <limits.h> -#include <errno.h> +#include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> +#include <utlist.h> + #include "apic.h" #include "config.h" -#include "types.h" #include "util.h" static void usage(void) { - fprintf(stderr, "usage: %s [-u baseurl] command [args...]\n", getprogname()); + fprintf(stderr, "usage: scictl [-u baseurl] command [args...]\n"); exit(1); } @@ -40,7 +42,6 @@ { fprintf(stderr, "usage: scictl job-add project tag\n"); fprintf(stderr, " scictl job-todo worker\n"); - fprintf(stderr, " scictl jobresult-add id worker exitcode console\n"); fprintf(stderr, " scictl project-add name desc url script\n"); fprintf(stderr, " scictl project-info name\n"); fprintf(stderr, " scictl project-list\n"); @@ -50,13 +51,6 @@ exit(0); } -static inline void -replace(char **str, const char *new) -{ - free(*str); - *str = util_strdup(new); -} - long long toint(const char *s) { @@ -98,97 +92,103 @@ return console; } +static inline intmax_t +get_int(json_t *obj, const char *key) +{ + json_t *val; + + if ((val = json_object_get(obj, key)) && json_is_integer(val)) + return json_integer_value(val); + + return 0; +} + +static inline const char * +get_string(json_t *obj, const char *key) +{ + json_t *val; + + if ((val = json_object_get(obj, key)) && json_is_string(val)) + return json_string_value(val); + + return ""; +} + static void cmd_job_add(int argc, char **argv) { - struct job job = {0}; struct apic req; + json_t *obj; if (argc < 3) usage(); - job.project_name = util_strdup(argv[1]); - job.tag = util_strdup(argv[2]); + obj = json_pack("{ss ss}", + "project_name", argv[1], + "tag", argv[2] + ); - if (apic_job_add(&req, &job) < 0) + if (apic_job_add(&req, obj) < 0) util_die("abort: %s\n", req.error); - apic_finish(&req); - job_finish(&job); + json_decref(obj); } static void cmd_job_todo(int argc, char **argv) { - struct job jobs[SCI_JOB_LIST_MAX] = {0}; - size_t jobsz; struct apic req; + json_t *array, *obj; + size_t i; if (argc < 2) usage(); - if ((jobsz = apic_job_todo(&req, jobs, UTIL_SIZE(jobs), argv[1]))) + if ((array = apic_job_todo(&req, argv[1]))) util_die("abort: %s\n", req.error); - for (size_t i = 0; i < jobsz; ++i) { - printf("%-16s%jd\n", "id:", jobs[i].id); - printf("%-16s%s\n", "tag:", jobs[i].tag); - printf("%-16s%s\n", "project:", jobs[i].project_name); + json_array_foreach(array, i, obj) { + printf("%-16s%jd\n", "id:", get_int(obj, "id")); + printf("%-16s%s\n", "tag:", get_string(obj, "tag")); + printf("%-16s%s\n", "project:", get_string(obj, "project_name")); - if (i + 1 < jobsz) + if (i + 1 < json_array_size(array)) printf("\n"); - - job_finish(&jobs[i]); } - apic_finish(&req); -} - -static void -cmd_jobresult_add(int argc, char **argv) -{ - struct jobresult res = {0}; - struct apic req; - - if (argc < 5) - usage(); - - res.job_id = toint(argv[1]); - res.worker_name = util_strdup(argv[2]); - res.exitcode = toint(argv[3]); - res.log = readfile(argv[4]); - - if (apic_jobresult_add(&req, &res) < 0) - util_die("abort: unable to add job result: %s\n", req.error); - - apic_finish(&req); - jobresult_finish(&res); + json_decref(array); } static void cmd_project_add(int argc, char **argv) { - struct project pc = {0}; struct apic req; + char *script; + json_t *obj; if (argc < 5) usage(); - pc.name = util_strdup(argv[1]); - pc.desc = util_strdup(argv[2]); - pc.url = util_strdup(argv[3]); - pc.script = readfile(argv[4]); + script = readfile(argv[4]); + obj = json_pack("{ss ss ss ss}", + "name", argv[1], + "desc", argv[2], + "url", argv[3], + "script", script + ); - if (apic_project_save(&req, &pc) < 0) + if (apic_project_save(&req, obj) < 0) util_die("abort: unable to create project: %s\n", req.error); - apic_finish(&req); - project_finish(&pc); + json_decref(obj); } static void cmd_project_update(int argc, char **argv) { + (void)argc; + (void)argv; +#if 0 struct project pc; struct apic req; @@ -212,27 +212,27 @@ apic_finish(&req); project_finish(&pc); +#endif } static void cmd_project_info(int argc, char **argv) { - struct project project = {0}; struct apic req; + json_t *obj; if (argc < 2) usage(); - if (apic_project_find(&req, &project, argv[1]) < 0) + if (!(obj = apic_project_find(&req, argv[1]))) util_die("abort: unable to find project: %s\n", req.error); - printf("%-16s%s\n", "name:", project.name); - printf("%-16s%s\n", "desc:", project.desc); - printf("%-16s%s\n", "url:", project.url); + printf("%-16s%s\n", "name:", get_string(obj, "name")); + printf("%-16s%s\n", "desc:", get_string(obj, "desc")); + printf("%-16s%s\n", "url:", get_string(obj, "url")); printf("\n"); - printf("%s", project.script); + printf("%s", get_string(obj, "script")); - apic_finish(&req); - project_finish(&project); + json_decref(obj); } static void @@ -241,44 +241,43 @@ (void)argc; (void)argv; - struct project projects[SCI_PROJECT_MAX] = {0}; struct apic req; - ssize_t projectsz; + json_t *array, *obj; + size_t i; - if ((projectsz = apic_project_list(&req, projects, UTIL_SIZE(projects))) < 0) + if (!(array = apic_project_list(&req))) util_die("abort: unable to list projects: %s\n", req.error); - for (ssize_t i = 0; i < projectsz; ++i) { - printf("%-16s%s\n", "name:", projects[i].name); - printf("%-16s%s\n", "desc:", projects[i].desc); - printf("%-16s%s\n", "url:", projects[i].url); + json_array_foreach(array, i, obj) { + printf("%-16s%s\n", "name:", get_string(obj, "name")); + printf("%-16s%s\n", "desc:", get_string(obj, "desc")); + printf("%-16s%s\n", "url:", get_string(obj, "url")); - if (i + 1 < projectsz) + if (i + 1 < json_array_size(array)) printf("\n"); - - project_finish(&projects[i]); } - apic_finish(&req); + json_decref(array); } static void cmd_worker_add(int argc, char **argv) { - struct worker wk = {0}; struct apic req; + json_t *obj; if (argc < 3) usage(); - wk.name = util_strdup(argv[1]); - wk.desc = util_strdup(argv[2]); + obj = json_pack("{ss ss}", + "name", argv[1], + "desc", argv[2] + ); - if (apic_worker_save(&req, &wk) < 0) + if (apic_worker_save(&req, obj) < 0) util_die("abort: unable to save worker: %s\n", req.error); - worker_finish(&wk); - apic_finish(&req); + json_decref(obj); } static void @@ -287,24 +286,22 @@ (void)argc; (void)argv; - struct worker wk[SCI_WORKER_MAX]; struct apic req; - ssize_t wksz; + json_t *array, *obj; + size_t i; - if ((wksz = apic_worker_list(&req, wk, UTIL_SIZE(wk))) < 0) + if (!(array = apic_worker_list(&req))) util_die("abort: unable to list worker: %s\n", req.error); - for (ssize_t i = 0; i < wksz; ++i) { - printf("%-16s%s\n", "name:", wk[i].name); - printf("%-16s%s\n", "desc:", wk[i].desc); + json_array_foreach(array, i, obj) { + printf("%-16s%s\n", "name:", get_string(obj, "name")); + printf("%-16s%s\n", "desc:", get_string(obj, "desc")); - if (i + 1 < wksz) + if (i + 1 < json_array_size(array)) printf("\n"); - - worker_finish(&wk[i]); } - apic_finish(&req); + json_decref(array); } static struct { @@ -313,7 +310,6 @@ } commands[] = { { "job-add", cmd_job_add }, { "job-todo", cmd_job_todo }, - { "jobresult-add", cmd_jobresult_add }, { "project-add", cmd_project_add }, { "project-info", cmd_project_info }, { "project-list", cmd_project_list },
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/crud.c Wed Aug 03 15:18:09 2022 +0200 @@ -0,0 +1,79 @@ +/* + * crud.c -- convenient helpers for page-api-* + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include "log.h" +#include "page.h" + +static int +save(struct kreq *r, int (*saver)(json_t *), const char *topic) +{ + json_t *doc; + json_error_t err; + int ret = -1; + + if (!(doc = json_loads(r->fields[0].val, 0, &err))) + log_warn("%s: invalid JSON input: %s", topic, err.text); + else { + if (saver(doc) < 0) + log_warn("%s: database insertion failed", topic); + else + ret = 0; + + json_decref(doc); + } + + return ret; +} + +void +crud_insert(struct kreq *r, int (*saver)(json_t *), const char *topic) +{ + if (r->fieldsz < 1) + page(r, KHTTP_400, KMIME_APP_JSON, NULL, NULL); + else if (save(r, saver, topic) < 0) + page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + khttp_free(r); + } +} + +void +crud_list(struct kreq *r, json_t *doc) +{ + char *str; + + if (!doc) + page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); + else { + if (!(str = json_dumps(doc, JSON_COMPACT))) + page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); + else { + khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); + khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); + khttp_body(r); + khttp_printf(r, "%s", str); + khttp_free(r); + json_decref(doc); + } + + free(str); + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/crud.h Wed Aug 03 15:18:09 2022 +0200 @@ -0,0 +1,32 @@ +/* + * crud.h -- convenient helpers for page-api-* + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SCID_CRUD_H +#define SCID_CRUD_H + +#include <jansson.h> + +struct kreq; + +void +crud_insert(struct kreq *, int (*)(json_t *), const char *); + +void +crud_list(struct kreq *, json_t *); + +#endif /* !SCID_CRUD_H */
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/db.c Wed Aug 03 15:18:09 2022 +0200 @@ -0,0 +1,413 @@ +/* + * db.c -- scid database access + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include <assert.h> +#include <stdlib.h> +#include <string.h> + +#include <sqlite3.h> + +#include <utlist.h> + +#include "db.h" +#include "log.h" +#include "util.h" + +#include "sql/init.h" +#include "sql/job-add.h" +#include "sql/job-list.h" +#include "sql/job-todo.h" +#include "sql/jobresult-add.h" +#include "sql/jobresult-list-by-job.h" +#include "sql/jobresult-list-by-job-group.h" +#include "sql/jobresult-list-by-worker.h" +#include "sql/project-find.h" +#include "sql/project-list.h" +#include "sql/project-save.h" +#include "sql/worker-find.h" +#include "sql/worker-list.h" +#include "sql/worker-save.h" + +#define CHAR(v) (const char *)(v) + +static sqlite3 *db; + +static json_t * +project_packer(sqlite3_stmt *stmt) +{ + return json_pack("{ss ss ss ss sI}", + "name", sqlite3_column_text(stmt, 0), + "desc", sqlite3_column_text(stmt, 1), + "url", sqlite3_column_text(stmt, 2), + "script", sqlite3_column_text(stmt, 3), + "date", (json_int_t)sqlite3_column_int64(stmt, 4) + ); +} + +static int +project_binder(json_t *doc, sqlite3_stmt *stmt) +{ + const char *name, *desc, *url, *script; + int ret; + + ret = json_unpack(doc, "{ss ss ss ss}", + "name", &name, + "desc", &desc, + "url", &url, + "script", &script + ); + + if (ret < 0) + return -1; + + sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, desc, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, url, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 4, script, -1, SQLITE_STATIC); + + return 0; +} + +static json_t * +worker_packer(sqlite3_stmt *stmt) +{ + return json_pack("{ss ss}", + "name", sqlite3_column_text(stmt, 0), + "desc", sqlite3_column_text(stmt, 1) + ); +} + +static int +worker_binder(json_t *doc, sqlite3_stmt *stmt) +{ + const char *name, *desc; + int ret; + + ret = json_unpack(doc, "{ss ss}", + "name", &name, + "desc", &desc + ); + + if (ret < 0) + return -1; + + sqlite3_bind_text(stmt, 1, name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, desc, -1, SQLITE_STATIC); + + return 0; +} + +static json_t * +job_packer(sqlite3_stmt *stmt) +{ + return json_pack("{sI ss ss sI}", + "id", (json_int_t)sqlite3_column_int64(stmt, 0), + "tag", sqlite3_column_text(stmt, 1), + "project_name", sqlite3_column_text(stmt, 2), + "date", (json_int_t)sqlite3_column_int64(stmt, 3) + ); +} + +static int +job_binder(json_t *doc, sqlite3_stmt *stmt) +{ + const char *tag, *project_name; + int ret; + + ret = json_unpack(doc, "{ss ss}", + "tag", &tag, + "project_name", &project_name + ); + + if (ret) + return -1; + + sqlite3_bind_text(stmt, 1, tag, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 2, project_name, -1, SQLITE_STATIC); + + return 0; +} + +static json_t * +jobresult_packer(sqlite3_stmt *stmt) +{ + return json_pack("{sI sI ss ss si si sI}", + "id", (json_int_t)sqlite3_column_int64(stmt, 0), + "job_id", (json_int_t)sqlite3_column_int64(stmt, 1), + "worker_name", sqlite3_column_text(stmt, 2), + "console", sqlite3_column_text(stmt, 3), + "exitcode", sqlite3_column_int(stmt, 4), + "sigcode", sqlite3_column_int(stmt, 5), + "date", (json_int_t)sqlite3_column_int64(stmt, 6) + ); +} + +static int +jobresult_binder(json_t *doc, sqlite3_stmt *stmt) +{ + json_int_t job_id; + int exitcode, sigcode, ret; + const char *worker_name, *console; + + ret = json_unpack(doc, "{sI ss ss si si}", + "job_id", &job_id, + "worker_name", &worker_name, + "console", &console, + "exitcode", &exitcode, + "sigcode", &sigcode + ); + + if (ret < 0) + return -1; + + sqlite3_bind_int64(stmt, 1, job_id); + sqlite3_bind_text(stmt, 2, worker_name, -1, SQLITE_STATIC); + sqlite3_bind_text(stmt, 3, console, -1, SQLITE_STATIC); + sqlite3_bind_int(stmt, 4, exitcode); + sqlite3_bind_int(stmt, 5, sigcode); + + return 0; +} + +static void +bindva(sqlite3_stmt *stmt, const char *fmt, va_list ap) +{ + for (int index = 1; *fmt; ++fmt) { + switch (*fmt) { + case 'i': + sqlite3_bind_int(stmt, index++, va_arg(ap, int)); + break; + case 'j': + sqlite3_bind_int64(stmt, index++, va_arg(ap, intmax_t)); + break; + case 's': + sqlite3_bind_text(stmt, index++, va_arg(ap, const char *), -1, SQLITE_STATIC); + break; + case 'z': + sqlite3_bind_int64(stmt, index++, va_arg(ap, size_t)); + break; + default: + break; + } + } +} + +static int +insert(int (*binder)(json_t *, sqlite3_stmt *), json_t *obj, const char *sql) +{ + assert(binder); + assert(obj); + assert(sql); + + sqlite3_stmt *stmt = NULL; + + if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) + return log_warn("db: %s", sqlite3_errmsg(db)), -1; + + if (binder(obj, stmt)) + log_warn("db: unable to bind parameter"); + else { + if (sqlite3_step(stmt) != SQLITE_DONE) + log_warn("db: %s", sqlite3_errmsg(db)); + else + json_object_set(obj, "id", json_integer(sqlite3_last_insert_rowid(db))); + } + + sqlite3_finalize(stmt); + + return 0; +} + +static json_t * +listva(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, va_list ap) +{ + sqlite3_stmt *stmt = NULL; + json_t *array, *obj; + + if (sqlite3_prepare(db, sql, -1, &stmt, NULL) != SQLITE_OK) + return log_warn("db: %s", sqlite3_errmsg(db)), NULL; + + bindva(stmt, args, ap); + array = json_array(); + + while (sqlite3_step(stmt) == SQLITE_ROW) + if ((obj = unpacker(stmt))) + json_array_append(array, obj); + + sqlite3_finalize(stmt); + + return array; +} + +static json_t * +list(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, ...) +{ + va_list ap; + json_t *ret; + + va_start(ap, args); + ret = listva(unpacker, sql, args, ap); + va_end(ap); + + return ret; +} + +/* + * Same as list but the array should have only one element that we extract for + * convenience. + */ +static json_t * +get(json_t * (*unpacker)(sqlite3_stmt *), const char *sql, const char *args, ...) +{ + va_list ap; + json_t *ret, *obj = NULL; + + va_start(ap, args); + ret = listva(unpacker, sql, args, ap); + va_end(ap); + + if (json_array_size(ret) == 1) { + obj = json_array_get(ret, 0); + json_incref(obj); + } + + if (ret) + json_decref(ret); + + return obj; +} + +int +db_open(const char *path) +{ + assert(path); + + if (sqlite3_open(path, &db) != SQLITE_OK) + return log_warn("db: open error: %s", sqlite3_errmsg(db)), -1; + + /* Wait for 30 seconds to lock the database. */ + sqlite3_busy_timeout(db, 30000); + + if (sqlite3_exec(db, CHAR(sql_init), NULL, NULL, NULL) != SQLITE_OK) + return log_warn("db: initialization error: %s", sqlite3_errmsg(db)), -1; + + return 0; +} + +int +db_job_add(json_t *job) +{ + assert(job); + + return insert(job_binder, job, CHAR(sql_job_add)); +} + +json_t * +db_job_todo(const char *worker) +{ + assert(worker); + + return list(job_packer, CHAR(sql_job_todo), "ss", worker, worker); +} + +json_t * +db_job_list(const char *project) +{ + assert(project); + + return list(job_packer, CHAR(sql_job_list), "s", project); +} + +int +db_jobresult_add(json_t *res) +{ + assert(res); + + return insert(jobresult_binder, res, CHAR(sql_jobresult_add)); +} + +json_t * +db_jobresult_list_by_job(intmax_t job_id) +{ + return list(jobresult_packer, CHAR(sql_jobresult_list_by_job), "j", job_id); +} + +json_t * +db_jobresult_list_by_job_group(intmax_t job_id) +{ + return list(jobresult_packer, CHAR(sql_jobresult_list_by_job_group), "j", job_id); +} + +json_t * +db_jobresult_list_by_worker(const char *worker) +{ + assert(worker); + + return list(jobresult_packer, CHAR(sql_jobresult_list_by_worker), "s", worker); +} + +int +db_project_save(json_t *p) +{ + assert(p); + + return insert(project_binder, p, CHAR(sql_project_save)); +} + +json_t * +db_project_list(void) +{ + return list(project_packer, CHAR(sql_project_list), ""); +} + +json_t * +db_project_find(const char *name) +{ + return get(project_packer, CHAR(sql_project_find), "s", name); +} + +int +db_worker_save(json_t *wk) +{ + assert(wk); + + return insert(worker_binder, wk, CHAR(sql_worker_save)); +} + +json_t * +db_worker_list(void) +{ + return list(worker_packer, CHAR(sql_worker_list), ""); +} + +json_t * +db_worker_find(const char *name) +{ + assert(name); + + return get(worker_packer, CHAR(sql_worker_find), "s", name); +} + +void +db_finish(void) +{ + if (db) { + sqlite3_close(db); + db = NULL; + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/db.h Wed Aug 03 15:18:09 2022 +0200 @@ -0,0 +1,71 @@ +/* + * db.h -- scid database access + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#ifndef SCI_DB_H +#define SCI_DB_H + +#include <stdint.h> + +#include <jansson.h> + +int +db_open(const char *); + +int +db_job_add(json_t *); + +json_t * +db_job_todo(const char *); + +json_t * +db_job_list(const char *); + +int +db_jobresult_add(json_t *); + +json_t * +db_jobresult_list_by_job(intmax_t); + +json_t * +db_jobresult_list_by_job_group(intmax_t); + +json_t * +db_jobresult_list_by_worker(const char *); + +int +db_project_save(json_t *); + +json_t * +db_project_list(void); + +json_t * +db_project_find(const char *); + +int +db_worker_save(json_t *); + +json_t * +db_worker_list(void); + +json_t * +db_worker_find(const char *); + +void +db_finish(void); + +#endif /* !SCI_DB_H */
--- a/scid/http.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scid/http.c Wed Aug 03 15:18:09 2022 +0200 @@ -19,8 +19,9 @@ #include <sys/types.h> #include <assert.h> #include <stdarg.h> +#include <stdint.h> +#include <stdio.h> #include <stdlib.h> -#include <stdio.h> #include <string.h> #include <kcgi.h>
--- a/scid/page-api-jobresults.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scid/page-api-jobresults.c Wed Aug 03 15:18:09 2022 +0200 @@ -16,54 +16,16 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <sys/types.h> #include <assert.h> -#include <stdarg.h> -#include <stdint.h> -#include <kcgi.h> - -#include "db.h" -#include "log.h" -#include "page.h" -#include "types.h" - -static int -save(const char *json) -{ - struct jobresult res = {0}; - int ret = -1; - json_t *doc; - json_error_t err; - - if (!(doc = json_loads(json, 0, &err))) - log_warn("api/post: invalid JSON input: %s", err.text); - else if (jobresult_from(&res, 1, doc) < 0) - log_warn("api/post: failed to decode parameters"); - else if (db_jobresult_add(&res) < 0) - log_warn("api/post: database save error"); - else - ret = 0; - - json_decref(doc); - jobresult_finish(&res); - - return ret; -} +#include "crud.h" +#include "db.h" +#include "page.h" static void post(struct kreq *r) { - if (r->fieldsz < 1) - page(r, KHTTP_400, KMIME_APP_JSON, NULL, NULL); - else if (save(r->fields[0].val) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_free(r); - } + crud_insert(r, db_jobresult_add, "page-api-jobresults"); } void
--- a/scid/page-api-jobs.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scid/page-api-jobs.c Wed Aug 03 15:18:09 2022 +0200 @@ -16,54 +16,16 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <sys/types.h> #include <assert.h> -#include <stdarg.h> -#include <stdint.h> -#include <kcgi.h> - -#include "db.h" -#include "log.h" -#include "page.h" -#include "types.h" - -static int -save(const char *json) -{ - struct job job = {0}; - int ret = -1; - json_t *doc; - json_error_t err; - - if (!(doc = json_loads(json, 0, &err))) - log_warn("api/post: invalid JSON input: %s", err.text); - else if (job_from(&job, 1, doc) < 0) - log_warn("api/post: failed to decode parameters"); - else if (db_job_add(&job) < 0) - log_warn("api/post: database save error"); - else - ret = 0; - - json_decref(doc); - job_finish(&job); - - return ret; -} +#include "crud.h" +#include "db.h" +#include "page.h" static void post(struct kreq *r) { - if (r->fieldsz < 1) - page(r, KHTTP_400, KMIME_APP_JSON, NULL, NULL); - else if (save(r->fields[0].val) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_free(r); - } + crud_insert(r, db_job_add, "page-api-job"); } void
--- a/scid/page-api-projects.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scid/page-api-projects.c Wed Aug 03 15:18:09 2022 +0200 @@ -17,103 +17,11 @@ */ #include <assert.h> - -#include "config.h" -#include "db.h" -#include "log.h" -#include "page-api-projects.h" -#include "page.h" -#include "types.h" -#include "util.h" - -static void -list(struct kreq *r, const struct project *projects, size_t projectsz) -{ - struct json_t *doc; - char *dump; - - doc = project_to(projects, projectsz); - dump = json_dumps(doc, JSON_COMPACT); - - khttp_puts(r, dump); - free(dump); - json_decref(doc); -} - -static int -save(const char *json) -{ - struct project res = {0}; - int ret = -1; - - json_t *doc; - json_error_t err; - - if (!(doc = json_loads(json, 0, &err))) - log_warn("api/post: invalid JSON input: %s", err.text); - else if (project_from(&res, 1, doc) < 0) - log_warn("api/post: failed to decode parameters"); - else if (db_project_save(&res) < 0) - log_warn("api/post: database save error"); - else - ret = 0; - - json_decref(doc); - - return ret; -} +#include <stdio.h> -static void -push(struct kreq *r, const struct project *p) -{ - struct json_t *json; - char *dump; - - json = project_to(p, 1); - dump = json_dumps(json, JSON_COMPACT); - - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_puts(r, dump); - khttp_free(r); - - free(dump); - json_decref(json); -} - -static void -get_one(struct kreq *r, const char *name) -{ - struct project project = {0}; - - if (db_project_find(&project, name) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - push(r, &project); - project_finish(&project); - } -} - -static void -get_all(struct kreq *r) -{ - struct project projects[SCI_PROJECT_MAX] = {0}; - ssize_t projectsz; - - if ((projectsz = db_project_list(projects, UTIL_SIZE(projects))) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - list(r, projects, projectsz); - khttp_free(r); - } - - for (ssize_t i = 0; i < projectsz; ++i) - project_finish(&projects[i]); -} +#include "crud.h" +#include "db.h" +#include "page.h" /* * GET /api/v1/projects[/<name>] @@ -128,9 +36,9 @@ char name[128] = {0}; if (sscanf(r->path, "v1/projects/%127s", name) == 1) - get_one(r, name); + crud_list(r, db_project_find(name)); else - get_all(r); + crud_list(r, db_project_list()); } /* @@ -142,16 +50,7 @@ static void post(struct kreq *r) { - if (r->fieldsz < 1) - page(r, KHTTP_400, KMIME_APP_JSON, NULL, NULL); - else if (save(r->fields[0].val) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_free(r); - } + crud_insert(r, db_project_save, "page-api-projects"); } void @@ -170,5 +69,4 @@ page(r, KHTTP_400, KMIME_APP_JSON, NULL, NULL); break; } - }
--- a/scid/page-api-todo.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scid/page-api-todo.c Wed Aug 03 15:18:09 2022 +0200 @@ -16,63 +16,13 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -#include <sys/types.h> #include <assert.h> -#include <stdarg.h> -#include <stdint.h> -#include <string.h> - -#include <kcgi.h> -#include <jansson.h> - -#include "config.h" -#include "db.h" -#include "log.h" -#include "page-api-todo.h" -#include "page.h" -#include "types.h" -#include "util.h" - -static void -list(struct kreq *r, const struct job *jobs, size_t jobsz) -{ - json_t *doc; - char *dump; - - doc = job_to(jobs, jobsz); - dump = json_dumps(doc, JSON_COMPACT); +#include <stdio.h> - khttp_puts(r, dump); - free(dump); - json_decref(doc); -} - -#if 0 - -static int -save(const char *json) -{ - struct jobresult res = {0}; - int ret = -1; - - json_t *doc; - json_error_t err; - - if (!(doc = json_loads(json, 0, &err))) - log_warn("api/post: invalid JSON input: %s", err.text); - else if (jobresult_from(&res, 1, doc) < 0) - log_warn("api/post: failed to decode parameters"); - else if (db_jobresult_add(&res) < 0) - log_warn("api/post: database save error"); - else - ret = 0; - - json_decref(doc); - - return ret; -} - -#endif +#include "crud.h" +#include "db.h" +#include "page.h" +#include "util.h" /* * GET /api/v1/todo/<worker-name> @@ -83,21 +33,7 @@ static void get(struct kreq *r) { - struct job jobs[SCI_JOB_LIST_MAX]; - ssize_t jobsz; - - if ((jobsz = db_job_todo(jobs, UTIL_SIZE(jobs), util_basename(r->path))) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - list(r, jobs, jobsz); - khttp_free(r); - } - - for (ssize_t i = 0; i < jobsz; ++i) - job_finish(&jobs[i]); + crud_list(r, db_job_todo(util_basename(r->path))); } void
--- a/scid/page-api-workers.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scid/page-api-workers.c Wed Aug 03 15:18:09 2022 +0200 @@ -1,101 +1,27 @@ -#include <assert.h> - -#include "config.h" -#include "db.h" -#include "log.h" -#include "page-api-workers.h" -#include "page.h" -#include "types.h" -#include "util.h" - -static void -list(struct kreq *r, const struct worker *workers, size_t workersz) -{ - json_t *doc; - char *dump; - - doc = worker_to(workers, workersz); - dump = json_dumps(doc, JSON_COMPACT); - - khttp_puts(r, dump); - free(dump); - json_decref(doc); -} - -static int -save(const char *json) -{ - struct worker res = {0}; - int ret = -1; - - json_t *doc; - json_error_t err; - - if (!(doc = json_loads(json, 0, &err))) - log_warn("api/post: invalid JSON input: %s", err.text); - else if (worker_from(&res, 1, doc) < 0) - log_warn("api/post: failed to decode parameters"); - else if (db_worker_save(&res) < 0) - log_warn("api/post: database save error"); - else - ret = 0; - - json_decref(doc); - - return ret; -} +/* + * page-api-workers.c -- /api/v?/workers route + * + * Copyright (c) 2021 David Demelier <markand@malikania.fr> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ -static void -push(struct kreq *r, const struct worker *p) -{ - struct json_t *json; - char *dump; - - json = worker_to(p, 1); - dump = json_dumps(json, JSON_COMPACT); - - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_puts(r, dump); - khttp_free(r); - - free(dump); - json_decref(json); -} - -static void -get_one(struct kreq *r, const char *name) -{ - struct worker worker = {0}; +#include <assert.h> +#include <stdio.h> - if (db_worker_find(&worker, name) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - push(r, &worker); - worker_finish(&worker); - } -} - -static void -get_all(struct kreq *r) -{ - struct worker workers[SCI_PROJECT_MAX] = {0}; - ssize_t workersz; - - if ((workersz = db_worker_list(workers, UTIL_SIZE(workers))) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - list(r, workers, workersz); - khttp_free(r); - } - - for (ssize_t i = 0; i < workersz; ++i) - worker_finish(&workers[i]); -} +#include "crud.h" +#include "db.h" +#include "page.h" /* * GET /api/v1/workers[/<name>] @@ -110,9 +36,9 @@ char name[128] = {0}; if (sscanf(r->path, "v1/workers/%127s", name) == 1) - get_one(r, name); + crud_list(r, db_worker_find(name)); else - get_all(r); + crud_list(r, db_worker_list()); } /* @@ -124,16 +50,7 @@ static void post(struct kreq *r) { - if (r->fieldsz < 1) - page(r, KHTTP_400, KMIME_APP_JSON, NULL, NULL); - else if (save(r->fields[0].val) < 0) - page(r, KHTTP_500, KMIME_APP_JSON, NULL, NULL); - else { - khttp_head(r, kresps[KRESP_CONTENT_TYPE], "%s", kmimetypes[KMIME_APP_JSON]); - khttp_head(r, kresps[KRESP_STATUS], "%s", khttps[KHTTP_200]); - khttp_body(r); - khttp_free(r); - } + crud_insert(r, db_worker_save, "page-api-workers"); } void
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/scid/page-api.h Wed Aug 03 15:18:09 2022 +0200 @@ -0,0 +1,6 @@ +#ifndef SCID_PAGE_API_H +#define SCID_PAGE_API_H + + + +#endif /* !SCID_PAGE_API_H */
--- a/scid/page-index.c Tue Aug 02 13:24:13 2022 +0200 +++ b/scid/page-index.c Wed Aug 03 15:18:09 2022 +0200 @@ -23,9 +23,10 @@ #include "config.h" #include "db.h" #include "page.h" -#include "types.h" #include "util.h" +#if 0 + /* * Document we create for templatize. * @@ -102,9 +103,14 @@ ); } +#endif + +static void + static void get(struct kreq *r) { +#if 0 (void)r; struct project projects[SCI_PROJECT_MAX] = {0}; ssize_t projectsz = 0; @@ -122,11 +128,21 @@ page(r, KHTTP_200, KMIME_TEXT_HTML, "pages/index.html", json_pack("{so}", "projects", array )); +#endif + json_t *array; + + if (!(db_project_list())) { + log_warn("page-index: %s", db.error); + page(); + } else + render(array); } void page_index(struct kreq *r) { + (void)r; + switch (r->method) { case KMETHOD_GET: get(r);
--- a/sciworkerd/sciworkerd.c Tue Aug 02 13:24:13 2022 +0200 +++ b/sciworkerd/sciworkerd.c Wed Aug 03 15:18:09 2022 +0200 @@ -1,6 +1,7 @@ #include <errno.h> #include <poll.h> #include <signal.h> +#include <stdint.h> #include <string.h> #include <time.h> @@ -10,21 +11,21 @@ #include "log.h" #include "sciworkerd.h" #include "task.h" -#include "types.h" #include "util.h" #define TAG "sciworkerd: " struct taskentry { + intmax_t job_id; + char *tag; + char *project_name; struct task *task; - struct job job; struct taskentry *next; }; static struct taskentry *taskpending; static struct taskentry *tasks; static struct taskentry *taskfinished; -static struct worker worker; static int run = 1; struct sciworkerd sciworkerd = { @@ -53,41 +54,57 @@ } static inline int -pending(int id) +pending(intmax_t job_id) { const struct taskentry *iter; LL_FOREACH(taskpending, iter) - if (iter->job.id == id) + if (iter->job_id == job_id) return 1; return 0; } static inline void -queue(const struct job *job) +queue(intmax_t id, const char *tag, const char *project_name) { struct taskentry *tk; - log_info(TAG "queued job build (%d) for tag %s", job->id, job->tag); + log_info(TAG "queued job build (%d) for tag %s", id, tag); tk = util_calloc(1, sizeof (*tk)); - tk->task = task_new(job->tag); - memcpy(&tk->job, job, sizeof (*job)); + tk->job_id = id; + tk->tag = util_strdup(tag); + tk->project_name = util_strdup(project_name); + tk->task = task_new(tag); LL_APPEND(taskpending, tk); } static void -merge(struct job *jobs, size_t jobsz) +merge(json_t *jobs) { - size_t total = 0; + json_int_t id; + json_t *val; + const char *tag, *project_name; + size_t total = 0, i; + int parse; - for (size_t i = 0; i < jobsz; ++i) { - if (!pending(jobs[i].id)) { - queue(&jobs[i]); + json_array_foreach(jobs, i, val) { + parse = json_unpack(val, "{sI ss ss}", + "id", &id, + "tag", &tag, + "project_name", &project_name + ); + + if (parse < 0) { + log_warn(TAG "unable to parse job"); + continue; + } + + if (!pending(id)) { + queue(id, tag, project_name); total++; - } else - job_finish(&jobs[i]); + } } log_info(TAG "added %zu new pending tasks", total); @@ -102,38 +119,21 @@ static time_t startup; time_t now; struct apic req; - struct job todo[SCI_JOB_LIST_MAX]; - ssize_t todosz; + json_t *jobs; if (difftime((now = time(NULL)), startup) >= sciworkerd.fetchinterval) { startup = now; - log_info(TAG "fetching jobs"); - if ((todosz = apic_job_todo(&req, todo, UTIL_SIZE(todo), worker.name)) < 0) + if (!(jobs = apic_job_todo(&req, sciworkerd.name))) log_warn(TAG "unable to fetch jobs: %s", req.error); - else - merge(todo, todosz); - - apic_finish(&req); + else { + merge(jobs); + json_decref(jobs); + } } } -/* - * Fetch information about myself. - */ -static void -fetch_worker(void) -{ - struct apic req; - - if (apic_worker_find(&req, &worker, sciworkerd.name) < 0) - log_die(TAG "unable to fetch worker info: %s", req.error); - - log_info("sciworkerd: worker %s (%s)", worker.name, worker.desc); - apic_finish(&req); -} - static inline size_t count(const struct taskentry *head) { @@ -154,14 +154,19 @@ start(struct taskentry *entry) { struct apic req; - struct project project; + json_t *doc; + const char *script; pid_t pid; int ret = -1; - if (apic_project_find(&req, &project, entry->job.project_name) < 0) + if (!(doc = apic_project_find(&req, entry->project_name))) return log_warn(TAG "unable to fetch project, dropping task"), -1; + if (json_unpack(doc, "{ss}", "script", &script) < 0) { + json_decref(doc); + return log_warn(TAG "invalid project JSON object"), -1; + } - if (task_setup(entry->task, project.script) < 0) + if (task_setup(entry->task, script) < 0) log_warn(TAG "unable to setup script code: %s, dropping task", strerror(errno)); else if ((pid = task_start(entry->task)) < 0) log_warn(TAG "unable to spawn task process: %s", strerror(errno)); @@ -170,7 +175,7 @@ ret = 0; } - project_finish(&project); + json_decref(doc); return ret; } @@ -297,24 +302,24 @@ static int publish(struct taskentry *iter) { - // TODO: add sigcode. - struct taskcode code = task_code(iter->task); - struct jobresult res = { - .job_id = iter->job.id, - .exitcode = code.exitcode, - .log = util_strdup(task_console(iter->task)), - .worker_name = worker.name - }; struct apic req; + json_t *obj; int ret = 0; - if (apic_jobresult_add(&req, &res) < 0) { + obj = json_pack("{sI ss ss si si}", + "job_id", iter->job_id, + "worker_name", sciworkerd.name, + "console", task_console(iter->task), + "exitcode", task_code(iter->task).exitcode, + "sigcode", task_code(iter->task).sigcode + ); + + if (apic_jobresult_add(&req, obj) < 0) { log_warn(TAG "unable to publish task: %s", req.error); ret = -1; } - apic_finish(&req); - jobresult_finish(&res); + json_decref(obj); return ret; } @@ -339,6 +344,9 @@ log_open("sigworkerd"); + if (strlen(sciworkerd.name) == 0) + log_die(TAG "no worker name defined"); + sigemptyset(&sa.sa_mask); sa.sa_handler = stop; @@ -346,8 +354,6 @@ if (sigaction(SIGINT, &sa, NULL) < 0 || sigaction(SIGTERM, &sa, NULL) < 0) log_die(TAG "sigaction: %s", strerror(errno)); - - fetch_worker(); } void
--- a/sciworkerd/sciworkerd.h Tue Aug 02 13:24:13 2022 +0200 +++ b/sciworkerd/sciworkerd.h Wed Aug 03 15:18:09 2022 +0200 @@ -1,11 +1,12 @@ #ifndef SCIWORKERD_H #define SCIWORKERD_H -#include "config.h" +#define SCIWORKERD_URL_MAX 512 +#define SCIWORKERD_NAME_MAX 64 extern struct sciworkerd { - char url[SCI_URL_MAX]; - char name[SCI_WORKER_MAX]; + char url[SCIWORKERD_URL_MAX]; + char name[SCIWORKERD_NAME_MAX]; unsigned int fetchinterval; unsigned int maxjobs; unsigned int timeout;
--- a/sciworkerd/task.c Tue Aug 02 13:24:13 2022 +0200 +++ b/sciworkerd/task.c Wed Aug 03 15:18:09 2022 +0200 @@ -13,7 +13,6 @@ #include "log.h" #include "task.h" -#include "types.h" #include "util.h" #define MODE S_IRUSR | S_IWUSR | S_IXUSR @@ -28,7 +27,6 @@ FILE *fp; char *console; size_t consolesz; - int scriptfd; char scriptpath[PATH_MAX]; time_t startup; }; @@ -58,23 +56,26 @@ assert(script); const size_t len = strlen(script); + int fd = -1, ret = -1; snprintf(self->scriptpath, sizeof (self->scriptpath), "/tmp/sciworkerd-XXXXXX"); - if ((self->scriptfd = mkstemp(self->scriptpath)) < 0) + if ((fd = mkstemp(self->scriptpath)) < 0) goto failed; - if (fchmod(self->scriptfd, MODE) < 0) + if (fchmod(fd, MODE) < 0) goto failed; - if (write(self->scriptfd, script, len) != (ssize_t)len) + if (write(fd, script, len) != (ssize_t)len) goto failed; - return 0; + ret = 0; failed: - log_warn("%s", strerror(errno)); - unlink(self->scriptpath); + if (ret == -1) + log_warn("%s", strerror(errno)); - return -1; + close(fd); + + return ret; } pid_t
--- a/sql/init.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/init.sql Wed Aug 03 15:18:09 2022 +0200 @@ -31,7 +31,7 @@ ); CREATE TABLE IF NOT EXISTS job( - `tag` TEXT NOT NULL UNIQUE, + `tag` TEXT NOT NULL, `project_name` INTEGER NOT NULL REFERENCES project (name), `date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) ); @@ -39,8 +39,9 @@ CREATE TABLE IF NOT EXISTS jobresult( `job_id` INTEGER NOT NULL REFERENCES job (rowid), `worker_name` INTEGER NOT NULL REFERENCES worker (name), + `console` TEXT DEFAULT NULL, `exitcode` INTEGER DEFAULT 0, - `console` TEXT DEFAULT NULL, + `sigcode` INTEGER DEFAULT 0, `date` INTEGER NOT NULL DEFAULT (strftime('%s', 'now')) );
--- a/sql/job-add.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/job-add.sql Wed Aug 03 15:18:09 2022 +0200 @@ -17,6 +17,6 @@ -- INSERT INTO job( - `tag`, - `project_name` + `tag`, + `project_name` ) VALUES (?, ?)
--- a/sql/job-list.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/job-list.sql Wed Aug 03 15:18:09 2022 +0200 @@ -3,4 +3,3 @@ FROM `job` WHERE `project_name` = ? ORDER BY `date` ASC - LIMIT ?
--- a/sql/job-todo.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/job-todo.sql Wed Aug 03 15:18:09 2022 +0200 @@ -36,4 +36,3 @@ FROM `worker` WHERE `worker`.`name` = ? ) - LIMIT ?
--- a/sql/jobresult-add.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/jobresult-add.sql Wed Aug 03 15:18:09 2022 +0200 @@ -19,6 +19,7 @@ INSERT INTO jobresult( `job_id`, `worker_name`, + `console`, `exitcode`, - `console` -) VALUES (?, ?, ?, ?) + `sigcode` +) VALUES (?, ?, ?, ?, ?)
--- a/sql/jobresult-list-by-job-group.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/jobresult-list-by-job-group.sql Wed Aug 03 15:18:09 2022 +0200 @@ -4,4 +4,3 @@ FROM `jobresult` WHERE `job_id` = ? GROUP BY `worker_name` - LIMIT ?
--- a/sql/jobresult-list-by-job.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/jobresult-list-by-job.sql Wed Aug 03 15:18:09 2022 +0200 @@ -3,4 +3,3 @@ FROM `jobresult` WHERE `job_id` = ? ORDER BY `job_id` ASC - LIMIT ?
--- a/sql/jobresult-list-by-worker.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/jobresult-list-by-worker.sql Wed Aug 03 15:18:09 2022 +0200 @@ -2,4 +2,3 @@ FROM `jobresult` WHERE `worker_name` = ? ORDER BY `job_id` ASC - LIMIT ?
--- a/sql/project-list.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/project-list.sql Wed Aug 03 15:18:09 2022 +0200 @@ -18,4 +18,3 @@ SELECT * FROM `project` - LIMIT ?
--- a/sql/worker-list.sql Tue Aug 02 13:24:13 2022 +0200 +++ b/sql/worker-list.sql Wed Aug 03 15:18:09 2022 +0200 @@ -18,4 +18,3 @@ SELECT * FROM `worker` - LIMIT ?
--- a/themes/bulma/pages/index.html Tue Aug 02 13:24:13 2022 +0200 +++ b/themes/bulma/pages/index.html Wed Aug 03 15:18:09 2022 +0200 @@ -1,5 +1,5 @@ <div class="columns" style="flex-wrap: wrap"> -{{#projects}} + @@projects@@ <div class="card"> <div class="card-content"> <div class="media"> @@ -30,4 +30,5 @@ <p>No jobs yet.</p> {{/jobs}} </div> -{{/projects}} + </div> +</div>