From c1cb9aa3db137e3115871d3a5c423b30c2509a95 Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Mon, 23 Jun 2025 22:21:37 +0100 Subject: [PATCH] Start to experiment with blueprints --- examples/forgejo.scm | 104 +++++++++++++++++++++---------------------- ordo/blueprint.scm | 89 ++++++++++++++++++++++++++++++++++++ ordo/connection.scm | 34 +++++--------- ordo/core.scm | 59 +++++++----------------- ordo/handler.scm | 39 ---------------- ordo/inventory.scm | 20 ++++----- ordo/play.scm | 92 -------------------------------------- ordo/playbook.scm | 61 ------------------------- ordo/task.scm | 43 ------------------ 9 files changed, 174 insertions(+), 367 deletions(-) create mode 100644 ordo/blueprint.scm delete mode 100644 ordo/handler.scm delete mode 100644 ordo/play.scm delete mode 100644 ordo/playbook.scm delete mode 100644 ordo/task.scm diff --git a/examples/forgejo.scm b/examples/forgejo.scm index bada9dd..ad3d44a 100644 --- a/examples/forgejo.scm +++ b/examples/forgejo.scm @@ -1,60 +1,58 @@ (use-modules + (ordo blueprint) + (ordo core) ((ordo action filesystem) #:prefix fs:) ((ordo action quadlet) #:prefix quadlet:) ((ordo action systemctl) #:prefix systemctl:)) (define* (install-forgejo #:key (version "11")) - (list - (task "Install configuration directory" - #:action fs:install-dir - #:args '(#:path "/etc/forgejo") - #:trigger '("Restart pod")) - (task "Install timezone configuration" - #:action fs:install-file - #:args '(#:path "/etc/forgejo/timezone" #:local-src "files/timezone") - #:trigger '("Restart pod")) - (task "Install localtime configuration" - #:action fs:install-file - #:args '(#:path "/etc/forgejo/localtime" #:local-src "files/localtime") - #:trigger '("Restart pod")) - (task "Create data volume quadlet" - #:action quadlet:create-volume - #:args '(#:name "forgejo" #:description "Forgejo data volume") - #:trigger '("Reload systemd" "Restart pod")) - (task "Create pod quadlet" - #:action quadlet:create-pod - #:args '(#:name "forgejo" - #:pod ((PodName . "forge") - (Volume . "forgejo.volume:U,Z") - (PodmanArgs . "--userns auto"))) - #:trigger '("Reload systemd" "Restart pod")) - (task "Create image quadlet" - #:action quadlet:create-image - #:args `(#:name "forgejo" - #:image (Image . ,(format #f "codeberg.org/forgejo/forgejo:~a" version))) - #:trigger '("Reload systemd" "Restart pod")) - (task "Create container quadlet" - #:action quadlet:create-container - #:args '(#:name "forgejo" - #:container ((Pod . "forgejo.pod") - (Image . "forgejo.image") - (Network . "services.network") - (Volume . "/etc/forgejo/timezone:/etc/timezone:ro,U,Z") - (Volume . "/etc/forgejo/localtime:/etc/localtime:ro,U,Z") - (Environment . "USER_UID=1000") - (Environment . "USER_GID=1000") - (Environment . "FORGEJO__service__DISABLE_REGISTRATION=true") - (Environment . "FORGEJO__webhook__ALLOWED_HOST_LIST=private"))) - #:trigger '("Reload systemd" "Restart pod")) - (handler "Reload systemd" - #:action systemctl:daemon-reload) - (handler "Restart pod" - #:action systemctl:restart-unit - #:args '((#:unit . "forgejo-pod.service"))))) + (blueprint (format #f "Install forgejo version ~a" version) + (task "Install configuration directory" + #:action fs:install-dir + #:args '(#:path "/etc/forgejo") + #:trigger '("Restart pod")) + (task "Install timezone configuration" + #:action fs:install-file + #:args '(#:path "/etc/forgejo/timezone" #:local-src "files/timezone") + #:trigger '("Restart pod")) + (task "Install localtime configuration" + #:action fs:install-file + #:args '(#:path "/etc/forgejo/localtime" #:local-src "files/localtime") + #:trigger '("Restart pod")) + (task "Create data volume quadlet" + #:action quadlet:create-volume + #:args '(#:name "forgejo" #:description "Forgejo data volume") + #:trigger '("Reload systemd" "Restart pod")) + (task "Create pod quadlet" + #:action quadlet:create-pod + #:args '(#:name "forgejo" + #:pod ((PodName . "forge") + (Volume . "forgejo.volume:U,Z") + (PodmanArgs . "--userns auto"))) + #:trigger '("Reload systemd" "Restart pod")) + (task "Create image quadlet" + #:action quadlet:create-image + #:args `(#:name "forgejo" + #:image (Image . ,(format #f "codeberg.org/forgejo/forgejo:~a" version))) + #:trigger '("Reload systemd" "Restart pod")) + (task "Create container quadlet" + #:action quadlet:create-container + #:args '(#:name "forgejo" + #:container ((Pod . "forgejo.pod") + (Image . "forgejo.image") + (Network . "services.network") + (Volume . "/etc/forgejo/timezone:/etc/timezone:ro,U,Z") + (Volume . "/etc/forgejo/localtime:/etc/localtime:ro,U,Z") + (Environment . "USER_UID=1000") + (Environment . "USER_GID=1000") + (Environment . "FORGEJO__service__DISABLE_REGISTRATION=true") + (Environment . "FORGEJO__webhook__ALLOWED_HOST_LIST=private"))) + #:trigger '("Reload systemd" "Restart pod")) + (handler "Reload systemd" + #:action systemctl:daemon-reload) + (handler "Restart pod" + #:action systemctl:restart + #:args '((#:unit . "forgejo-pod.service"))))) -(playbook "Install Forgejo on limiting-factor" - ;; #:vars '((forgejo-version . "11.0.2")) - (play - #:host "limiting-factor" - #:become? #t - (install-forgejo #:version "11"))) +(workflow + (execute (install-forgejo #:version "11") "root@limiting-factor")) diff --git a/ordo/blueprint.scm b/ordo/blueprint.scm new file mode 100644 index 0000000..4b803d1 --- /dev/null +++ b/ordo/blueprint.scm @@ -0,0 +1,89 @@ +#| +This file is part of Ordo. + +Copyright (C) 2025 Ray Miller + +This program is free software: you can redistribute it and/or modify it under +the terms of the GNU General Public License as published by the Free Software +Foundation, version 3. + +This program is distributed in the hope that it will be useful, but WITHOUT ANY +WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A +PARTICULAR PURPOSE. See the GNU General Public License for more details. + +You should have received a copy of the GNU General Public License along with +this program. If not, see . +|# + +(define-module (ordo blueprint) + #:use-module (ice-9 exceptions) + #:use-module (ice-9 format) + #:use-module (oop goops) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-235) + #:export ( + task + task? + task-name + task-pre-condititon + task-action + task-args + task-trigger + + + handler + handler? + handler-name + handler-action + handler-args + + + blueprint + blueprint? + blueprint-name + blueprint-steps + blueprint-handlers)) + +(define-class () + (name #:init-keyword #:name #:getter task-name) + (pre-condition #:init-keyword #:pre-condition #:getter task-pre-condition) + (action #:init-keyword #:action #:getter task-action) + (args #:init-keyword #:args #:getter task-args) + (trigger #:init-keyword #:trigger #:getter task-trigger)) + +(define (task . args) (apply make args)) +(define (task? x) (is-a? x )) + +(define-class () + (name #:init-keyword #:name #:getter handler-name) + (action #:init-keyword #:action #:getter handler-action) + (args #:init-keyword #:args #:getter handler-args)) + +(define (handler . args) (apply make args)) +(define (handler? x) (is-a? x )) + +(define-class () + (name #:init-keyword #:name #:getter blueprint-name) + (steps #:init-keyword #:steps #:getter blueprint-steps) + (handlers #:init-keyword #:handlers #:getter blueprint-handlers)) + +(define (blueprint? x) (is-a? x )) + +(define (validate-triggers blueprint-name tasks handlers) + (let ((handler-names (map handler-name handlers))) + (for-each (lambda (task) + (for-each (lambda (trigger) + (unless (member trigger handler-names) + (raise-exception + (make-exception + (make-programming-error) + (make-exception-with-message (format #f "Task ~a in blueprint ~a references unknown trigger: ~a" + blueprint-name (task-name task) trigger)))))) + (task-trigger task))) + tasks))) + +(define (blueprint name . args) + (let ((steps (filter (disjoin task? blueprint?) args)) + (handlers (filter handler? args))) + (validate-triggers name (filter task? steps) handlers) + (make #:name name #:steps steps #:handlers handlers))) diff --git a/ordo/connection.scm b/ordo/connection.scm index 4c31470..302bffd 100644 --- a/ordo/connection.scm +++ b/ordo/connection.scm @@ -21,7 +21,6 @@ this program. If not, see . #:use-module (ordo connection base) #:use-module (ordo connection local) #:use-module (ordo connection ssh) - #:use-module (ordo connection sudo) #:use-module (ordo logger) #:use-module (ordo util flatten) #:use-module (ordo util keyword-args) @@ -32,35 +31,22 @@ this program. If not, see . ssh-connection call-with-connection remote-cmd) - #:re-export (remote-exec with-remote-input-file with-remote-output-file)) + #:re-export ( remote-exec with-remote-input-file with-remote-output-file)) (define (connection? c) (is-a? c )) -(define (local-connection) - (make )) +(define (local-connection . args) + (apply make args)) -(define* (ssh-connection host #:key (user (getlogin)) (password #f) (identity #f) (authenticate-server? #t) - (sudo? #f) (sudo-user #f) (sudo-password #f)) - (make #:user user #:host host #:password password - #:identity identity #:authenticate-server? authenticate-server? - #:sudo? sudo? #:sudo-user sudo-user #:sudo-password sudo-password)) +(define (ssh-connection . args) + (apply make args)) -(define* (call-with-connection conn proc #:key sudo? sudo-user sudo-password) - (let ((conn (deep-clone conn))) - (when sudo? - (unless (is-a? conn ) - (raise-exception - (make-exception - (make-programming-error) - (make-exception-with-message (format #f "connection ~a does not support sudo" conn))))) - (set! (become? conn) sudo?) - (set! (become-user conn) sudo-user) - (set! (become-password conn) sudo-password)) - (dynamic-wind - (lambda () (setup conn)) - (lambda () (proc conn)) - (lambda () (teardown conn))))) +(define* (call-with-connection conn proc) + (dynamic-wind + (lambda () (setup conn)) + (lambda () (proc conn)) + (lambda () (teardown conn)))) (define (remote-cmd conn prog . args) (let* ((args options (break keyword? args)) diff --git a/ordo/core.scm b/ordo/core.scm index d12c7c1..eb2230a 100644 --- a/ordo/core.scm +++ b/ordo/core.scm @@ -15,55 +15,26 @@ You should have received a copy of the GNU General Public License along with this program. If not, see . |# (define-module (ordo core) + #:use-module (oop goops) + #:use-module (ordo blueprint) #:use-module (ordo connection) #:use-module (ordo context) - #:use-module (ordo handler) #:use-module (ordo inventory) #:use-module (ordo logger) - #:use-module (ordo playbook) - #:use-module (ordo play) - #:use-module (ordo task) - #:use-module ((srfi srfi-26) #:select (cut))) + #:use-module (srfi srfi-26) + #:export (execute)) -(define (run-playbook ctx pb) - (log-msg 'NOTICE "Running playbook: " (playbook-name pb)) - (set-ctx-playbook! ctx pb) - (for-each (cut run-play ctx <>) (playbook-plays pb))) +(define-method (execute (blueprint ) (conn )) + #f + ) -(define (run-play ctx p) - (log-msg 'NOTICE "Running play: " (play-name p)) - (set-ctx-play! ctx p) - (let ((hosts (resolve-hosts (ctx-inventory ctx) (play-host p)))) - (if (null? hosts) - (log-msg 'WARN "No hosts matched: " (play-host p)) - (for-each (cut run-host-play ctx p <>) hosts)))) +(define-method (execute (blueprint ) (host )) + (log-msg 'NOTICE "Executing blueprint " (blueprint-name blueprint) " on host " (host-name host)) + (call-with-connection (host-connection host) (cut execute blueprint <>))) -(define (run-host-play ctx p h) - (log-msg 'NOTICE "Running play on host: " (host-name h)) - (set-ctx-host! ctx h) - (call-with-connection - (host-connection h) - (lambda (conn) - (dynamic-wind - (lambda () - (set-ctx-connection! ctx conn)) - (lambda () - (for-each (cut run-task ctx <>) (play-tasks p)) - (for-each (cut run-handler ctx <>) (play-handlers p))) - (lambda () - (set-ctx-connection! ctx #f)))) - #:sudo? (play-sudo? p) - #:sudo-user (play-sudo-user p) - #:sudo-password (play-sudo-password p))) +(define-method (execute (task ) (conn )) + #f) -(define (run-task ctx t) - (if ((task-pre-condition t) ctx) - (begin - (log-msg 'NOTICE "Running task " (task-name t)) - ((task-action t) ctx)) - (log-msg 'NOTICE "Skipping task " (task-name t) ": pre-condition not met"))) - -(define (run-handler ctx h) - (when (member (ctx-triggers ctx) (handler-name h)) - (log-msg 'NOTICE "Running handler: " (handler-name h)) - ((handler-action h) ctx))) +(define-method (execute (task ) (host )) + (log-msg 'NOTICE "Executing task " (task-name task) " on host " (host-name host)) + (call-with-connection (host-connection host) (cut execute task <>))) diff --git a/ordo/handler.scm b/ordo/handler.scm deleted file mode 100644 index 883f734..0000000 --- a/ordo/handler.scm +++ /dev/null @@ -1,39 +0,0 @@ -#| -This file is part of Ordo. - -Copyright (C) 2025 Ray Miller - -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, version 3. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see . -|# - -(define-module (ordo handler) - #:use-module (srfi srfi-9) - #:use-module (ordo logger) - #:export (make-handler - handler? - handler-name - handler-action - handler - run-handler)) - -(define-record-type - (make-handler name action) - handler? - (name handler-name) - (action handler-action)) - -(define* (handler #:key name action) - (make-handler name action)) - -(define (run-handler h conn) - (log-msg 'NOTICE "Running handler: " (handler-name h)) - ((handler-action h) conn)) diff --git a/ordo/inventory.scm b/ordo/inventory.scm index 354e8e4..41b901b 100644 --- a/ordo/inventory.scm +++ b/ordo/inventory.scm @@ -25,25 +25,23 @@ this program. If not, see . #:use-module (srfi srfi-1) #:use-module (srfi srfi-9) #:use-module (srfi srfi-69) - #:export (host + #:export ( + host host? host-name host-connection host-tags - host-vars resolve-hosts load-inventory)) -(define-record-type - (make-host name connection tags vars) - host? - (name host-name) - (connection host-connection) - (tags host-tags) - (vars host-vars)) +(define-class () + (name #:init-keyword #:name #:getter host-name) + (connection #:init-keyword #:connection #:getter host-connection) + (tags #:init-keyword #:tags #:getter host-tags)) -(define* (host #:key name connection (tags '()) (vars '())) - (make-host name connection tags (alist->hash-table vars))) +(define (host? h) (is-a? h )) + +(define (host . args) (apply make args)) (define (tagged-every? wanted-tags) (lambda (h) diff --git a/ordo/play.scm b/ordo/play.scm deleted file mode 100644 index 326d5c6..0000000 --- a/ordo/play.scm +++ /dev/null @@ -1,92 +0,0 @@ -#| -This file is part of Ordo. - -Copyright (C) 2025 Ray Miller - -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, version 3. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see . -|# - -(define-module (ordo play) - #:use-module (ordo connection) - #:use-module (ordo context) - #:use-module (ordo handler) - #:use-module (ordo inventory) - #:use-module (ordo logger) - #:use-module (ordo task) - #:use-module (ordo util flatten) - #:use-module (ordo util keyword-args) - #:use-module (srfi srfi-1) ; lists - #:use-module (srfi srfi-9) ; records - #:use-module (srfi srfi-26) ; cut/cute - #:use-module (srfi srfi-69) ; hash tables - #:use-module (srfi srfi-71) ; extended let - #:export (play - play? - play-host - play-sudo? - play-sudo-user - play-sudo-password - play-vars - play-tasks - play-handlers - run-play - trigger-handler!)) - -(define-record-type - (make-play name host sudo? sudo-user sudo-password vars tasks handlers) - play? - (name play-name) - (host play-host) - (sudo? play-sudo?) - (sudo-user play-sudo-user) - (sudo-password play-sudo-password) - (vars play-vars) - (tasks play-tasks) - (handlers play-handlers)) - -(define (play name . args) - (let* ((tasks args (partition task? args)) - (handlers kwargs (partition handler? args))) - (make-play name - (keyword-arg #:host kwargs) - (keyword-arg #:sudo? kwargs) - (keyword-arg #:sudo-user kwargs) - (keyword-arg #:sudo-password kwargs) - (and=> (keyword-arg #:vars kwargs) alist->hash-table) - tasks - handlers))) - -(define (run-play p) - (log-msg 'NOTICE "Running play: " (play-name p)) - (parameterize ((*play* p)) - (let ((hosts (resolve-hosts (*inventory*) (play-host p)))) - (if (null? hosts) - (log-msg 'WARN "No hosts matched: " (play-host p)) - (for-each (cut run-host-play p <>) hosts))))) - -(define (run-host-play p h) - (log-msg 'NOTICE "Running play on host: " (host-name h)) - (parameterize ((*host* h) - (*triggered-handlers* (make-hash-table))) - (call-with-connection - (host-connection h) - (lambda (conn) - (for-each (cut run-task <> conn) (play-tasks p)) - (for-each (cut run-handler <> conn) - (filter (compose (cut hash-table-ref/default *triggered-handlers* <> #f) handler-name) - (play-handlers p)))) - #:sudo? (play-sudo? p) - #:sudo-user (play-sudo-user p) - #:sudo-password (play-sudo-password p)))) - -(define (trigger-handler! handler-name) - (hash-table-set! *triggered-handlers* handler-name #t)) diff --git a/ordo/playbook.scm b/ordo/playbook.scm deleted file mode 100644 index b22fc3c..0000000 --- a/ordo/playbook.scm +++ /dev/null @@ -1,61 +0,0 @@ -#| -This file is part of Ordo. - -Copyright (C) 2025 Ray Miller - -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, version 3. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see . -|# - -(define-module (ordo playbook) - #:use-module (ice-9 eval-string) - #:use-module (ice-9 textual-ports) - #:use-module (ordo context) - #:use-module (ordo handler) - #:use-module (ordo logger) - #:use-module (ordo play) - #:use-module (ordo task) - #:use-module (ordo util keyword-args) - #:use-module (srfi srfi-1) ; lists - #:use-module (srfi srfi-9) ; records - #:use-module (srfi srfi-26) ; cut/cute - #:use-module (srfi srfi-69) ; hash tables - #:use-module (srfi srfi-71) ; extended let - #:export ( - playbook - playbook? - playbook-name - playbook-vars - playbook-plays - load-playbook - run-playbook)) - -(define-record-type - (make-playbook name vars plays) - playbook? - (name playbook-name) - (vars playbook-vars) - (plays playbook-plays)) - -(define (playbook name . args) - (let ((plays kwargs (partition play? args))) - (make-playbook name (alist->hash-table (keyword-arg #:vars kwargs '())) plays))) - -(define (load-playbook filename) - (log-msg 'INFO "Loading playbook " filename) - (eval-string (call-with-input-file filename get-string-all) - #:file filename)) - -(define (run-playbook pb inventory) - (log-msg 'NOTICE "Running playbook: " (playbook-name pb)) - (parameterize ((*inventory* inventory) - (*playbook* pb)) - (for-each run-play (playbook-plays pb)))) diff --git a/ordo/task.scm b/ordo/task.scm deleted file mode 100644 index 9399317..0000000 --- a/ordo/task.scm +++ /dev/null @@ -1,43 +0,0 @@ -#| -This file is part of Ordo. - -Copyright (C) 2025 Ray Miller - -This program is free software: you can redistribute it and/or modify it under -the terms of the GNU General Public License as published by the Free Software -Foundation, version 3. - -This program is distributed in the hope that it will be useful, but WITHOUT ANY -WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A -PARTICULAR PURPOSE. See the GNU General Public License for more details. - -You should have received a copy of the GNU General Public License along with -this program. If not, see . -|# - -(define-module (ordo task) - #:use-module (ordo logger) - #:use-module (srfi srfi-9) - #:export (task - task? - task-name - task-pre-condition - task-action - run-task)) - -(define-record-type - (make-task name action pre-condition) - task? - (name task-name) - (pre-condition task-pre-condition) - (action task-action)) - -(define* (task #:key name action (pre-condition (const #t))) - (make-task name action pre-condition)) - -(define (run-task t conn) - (if ((task-pre-condition t) conn) - (begin - (log-msg 'NOTICE "Running task " (task-name t)) - ((task-action t) conn)) - (log-msg 'NOTICE "Skipping task " (task-name t) ": pre-condition not met")))