148 lines
5.1 KiB
Text
148 lines
5.1 KiB
Text
# Generic scheduler helper — produces a scheduling artefact for any of the
|
|
# four runtime targets (K8s CronJob, systemd timer, cron.d entry, daemon
|
|
# task registration). Not coupled to backup nor to Kubernetes; any task in
|
|
# the repo that needs to be scheduled can build on top of this.
|
|
#
|
|
# Example:
|
|
# let s = (import "scheduler.ncl").make_schedule {
|
|
# name = "etcd-snapshot",
|
|
# schedule_kind = 'cron, cron_expr = "0 */6 * * *",
|
|
# target = { kind = 'systemd_timer, host_selector = "control_planes",
|
|
# user = "root", unit_name = "prvng-etcd-snapshot" },
|
|
# command = "/usr/local/bin/prvng-backup one-shot backup etcd-snapshot",
|
|
# env = { …secret refs… },
|
|
# } in s.systemd_units
|
|
|
|
{
|
|
# === Target descriptors ===================================================
|
|
|
|
K8sCronJobTarget = {
|
|
kind | [| 'k8s_cronjob |],
|
|
namespace | String,
|
|
image | String,
|
|
image_pull_policy | [| 'IfNotPresent, 'Always, 'Never |] | default = 'IfNotPresent,
|
|
service_account | String | optional,
|
|
node_selector | { _ | String } | default = {},
|
|
restart_policy | [| 'OnFailure, 'Never |] | default = 'OnFailure,
|
|
successful_jobs_history_limit | Number | default = 3,
|
|
failed_jobs_history_limit | Number | default = 5,
|
|
},
|
|
|
|
SystemdTimerTarget = {
|
|
kind | [| 'systemd_timer |],
|
|
unit_name | String,
|
|
host_selector | String | doc "Hostname pattern or role (e.g. 'control_planes')",
|
|
user | String | default = "root",
|
|
after | Array String | default = ["network-online.target"],
|
|
persistent | Bool | default = true,
|
|
},
|
|
|
|
CronDTarget = {
|
|
kind | [| 'cron_d |],
|
|
file_name | String | doc "Filename under /etc/cron.d/",
|
|
host_selector | String,
|
|
user | String | default = "root",
|
|
},
|
|
|
|
DaemonTaskTarget = {
|
|
kind | [| 'daemon_task |],
|
|
task_id | String,
|
|
daemon_endpoint | String | default = "unix:///run/prvng-backup.sock",
|
|
},
|
|
|
|
# === Top-level builder ====================================================
|
|
|
|
# make_schedule returns a record with one populated branch out of:
|
|
# { manifests, systemd_units, cron_files, daemon_registrations }.
|
|
# Callers serialise the appropriate branch.
|
|
make_schedule = fun spec =>
|
|
let target_kind = spec.target.kind in
|
|
let cron_expr = spec.cron_expr in
|
|
let name = spec.name in
|
|
let command = spec.command in
|
|
let env = spec.env in
|
|
|
|
{
|
|
manifests =
|
|
if target_kind == 'k8s_cronjob then
|
|
[{
|
|
apiVersion = "batch/v1",
|
|
kind = "CronJob",
|
|
metadata = {
|
|
name = name,
|
|
namespace = spec.target.namespace,
|
|
},
|
|
spec = {
|
|
schedule = cron_expr,
|
|
successfulJobsHistoryLimit = spec.target.successful_jobs_history_limit,
|
|
failedJobsHistoryLimit = spec.target.failed_jobs_history_limit,
|
|
jobTemplate.spec.template.spec = {
|
|
restartPolicy = std.string.from_enum spec.target.restart_policy,
|
|
serviceAccountName = spec.target.service_account,
|
|
nodeSelector = spec.target.node_selector,
|
|
containers = [{
|
|
name = name,
|
|
image = spec.target.image,
|
|
imagePullPolicy = std.string.from_enum spec.target.image_pull_policy,
|
|
command = ["/bin/sh", "-c", command],
|
|
env = std.record.to_array env
|
|
|> std.array.map (fun e => { name = e.field, value = e.value }),
|
|
}],
|
|
},
|
|
},
|
|
}]
|
|
else [],
|
|
|
|
systemd_units =
|
|
if target_kind == 'systemd_timer then
|
|
[{
|
|
host_selector = spec.target.host_selector,
|
|
unit_name = spec.target.unit_name,
|
|
service_unit = m%"
|
|
[Unit]
|
|
Description=%{name}
|
|
After=%{std.string.join " " spec.target.after}
|
|
|
|
[Service]
|
|
Type=oneshot
|
|
User=%{spec.target.user}
|
|
ExecStart=%{command}
|
|
EnvironmentFile=-/etc/prvng-backup/%{name}.env
|
|
"%,
|
|
timer_unit = m%"
|
|
[Unit]
|
|
Description=Timer for %{name}
|
|
|
|
[Timer]
|
|
OnCalendar=%{cron_expr}
|
|
Persistent=%{if spec.target.persistent then "true" else "false"}
|
|
|
|
[Install]
|
|
WantedBy=timers.target
|
|
"%,
|
|
}]
|
|
else [],
|
|
|
|
cron_files =
|
|
if target_kind == 'cron_d then
|
|
[{
|
|
host_selector = spec.target.host_selector,
|
|
path = "/etc/cron.d/%{spec.target.file_name}",
|
|
content = m%"
|
|
%{cron_expr} %{spec.target.user} %{command}
|
|
"%,
|
|
}]
|
|
else [],
|
|
|
|
daemon_registrations =
|
|
if target_kind == 'daemon_task then
|
|
[{
|
|
task_id = spec.target.task_id,
|
|
daemon_endpoint = spec.target.daemon_endpoint,
|
|
schedule = cron_expr,
|
|
command = command,
|
|
env = env,
|
|
}]
|
|
else [],
|
|
},
|
|
}
|