From 38f08e8ce4508b80fbbecc522716e7fe40c1aa9d Mon Sep 17 00:00:00 2001 From: Ray Miller Date: Sun, 1 Jun 2025 15:50:12 +0100 Subject: [PATCH] Basic playbook functionality --- examples/inventory.scm | 19 ++++++++++++++ examples/playbook.scm | 8 ++++++ ordo/cli/run.scm | 17 +++++++------ ordo/connection.scm | 28 +++++++++++---------- ordo/context.scm | 49 ++++++++++++++++++++++++++++++++++++ ordo/inventory.scm | 47 ++++++++++++++++++++++++---------- ordo/play.scm | 57 ++++++++++++++++++++++++++++++++++++++++++ ordo/playbook.scm | 45 +++++++++++++++++++++++++++++++++ 8 files changed, 237 insertions(+), 33 deletions(-) create mode 100644 examples/inventory.scm create mode 100644 examples/playbook.scm create mode 100644 ordo/context.scm create mode 100644 ordo/play.scm create mode 100644 ordo/playbook.scm diff --git a/examples/inventory.scm b/examples/inventory.scm new file mode 100644 index 0000000..7d5b9a9 --- /dev/null +++ b/examples/inventory.scm @@ -0,0 +1,19 @@ +(use-modules (ordo connection) + (ordo inventory)) + +(list + (host #:name "localhost" + #:connection (local-connection) + #:tags '(#:linux #:guix)) + + (host #:name "limiting-factor" + #:connection (ssh-connection "limiting-factor" #:user "core") + #:tags '(#:linux #:coreos)) + + (host #:name "screw-loose" + #:connection (ssh-connection "screw-loose" #:user "core") + #:tags '(#:linux #:coreos)) + + (host #:name "control-surface" + #:connection (ssh-connection "control-surface") + #:tags '(#:linux #:debian))) diff --git a/examples/playbook.scm b/examples/playbook.scm new file mode 100644 index 0000000..64836f5 --- /dev/null +++ b/examples/playbook.scm @@ -0,0 +1,8 @@ +(use-modules (ordo playbook)) + +(playbook + #:name "Example playbook" + #:vars '((foo . 1) (bar . "baz")) + #:plays (list + (play #:name "Example play" + #:host "localhost"))) diff --git a/ordo/cli/run.scm b/ordo/cli/run.scm index efbc8cc..98f31bf 100644 --- a/ordo/cli/run.scm +++ b/ordo/cli/run.scm @@ -1,8 +1,12 @@ (define-module (ordo cli run) #:use-module (config) #:use-module (config api) + #:use-module (ice-9 filesystem) + #:use-module (ordo inventory) #:use-module (ordo logger) + #:use-module (ordo playbook) #:use-module (srfi srfi-1) + #:use-module (srfi srfi-26) #:export (config handler)) (define (valid-tags? x) @@ -21,9 +25,9 @@ (list (setting (name 'inventory) - (default "inventory.scm") + (default "/dev/null") (example "examples/inventory.scm") - (handler identity) + (handler (cut expand-file-name <> #f #t)) (test file-exists?) (synopsis "Inventory file")) (switch @@ -37,12 +41,11 @@ (list (argument (name 'playbook) - (handler identity) + (handler (cut expand-file-name <> #f #t)) (test file-exists?)))) (synopsis "Run a playbook"))) (define (handler options) - (let ((inventory (option-ref options 'inventory)) - (playbook (option-ref options '(playbook)))) - (log-msg 'INFO "Running playbook " playbook " with inventory " inventory) - )) + (let ((inventory (load-inventory (option-ref options 'inventory))) + (playbook (load-playbook (option-ref options '(playbook))))) + (run-playbook playbook inventory))) diff --git a/ordo/connection.scm b/ordo/connection.scm index c871fe9..2cda66f 100644 --- a/ordo/connection.scm +++ b/ordo/connection.scm @@ -23,23 +23,25 @@ (define (local-connection) (make )) -(define* (ssh-connection user host #:key (password #f) (identity #f) (authenticate-server? #t)) +(define* (ssh-connection host #:key (user (getlogin)) (password #f) (identity #f) (authenticate-server? #t)) (make #:user user #:host host #:password password #:identity identity #:authenticate-server? authenticate-server?)) (define* (call-with-connection conn proc #:key sudo? sudo-user sudo-password) - (when (and sudo? (not (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)))) + (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 (run conn prog . args) (let* ((args options (break keyword? args)) diff --git a/ordo/context.scm b/ordo/context.scm new file mode 100644 index 0000000..a10ff0a --- /dev/null +++ b/ordo/context.scm @@ -0,0 +1,49 @@ +(define-module (ordo context) + #:use-module (srfi srfi-69)) + +;; +;; Inventory +;; +(define-public *inventory* (make-parameter #f)) + +;; +;; Playbook vars +;; +(define-public *playbook-vars* (make-parameter #f)) + +(define-public (playbook-var-ref key) + (hash-table-ref (*playbook-vars*) key)) + +(define-public (playbook-var-ref/default key default) + (hash-table-ref/default (*playbook-vars*) key default)) + +(define-public (playbook-var-set! key value) + (hash-table-set! (*playbook-vars*) key value)) + +;; +;; Play vars +;; +(define-public *play-vars* (make-parameter #f)) + +(define-public (play-var-ref key) + (hash-table-ref (*play-vars*) key)) + +(define-public (play-var-ref/default key default) + (hash-table-ref/default (*play-vars*) key default)) + +(define-public (play-var-set! key value) + (hash-table-set! (*play-vars*) key value)) + +;; +;; Host vars +;; +(define-public *host-vars* (make-parameter #f)) + +(define-public (host-var-ref key) + (hash-table-ref (*host-vars*) key)) + +(define-public (host-var-ref/default key default) + (hash-table-ref/default (*host-vars*) key default)) + +(define-public (host-var-set! key value) + (hash-table-set! (*host-vars*) key value)) diff --git a/ordo/inventory.scm b/ordo/inventory.scm index 11123e4..946f8be 100644 --- a/ordo/inventory.scm +++ b/ordo/inventory.scm @@ -1,25 +1,37 @@ (define-module (ordo inventory) + #:use-module (ice-9 eval-string) #:use-module (ice-9 match) - #:use-module (srfi srfi-1) - #:use-module (srfi srfi-9) + #:use-module (ice-9 textual-ports) + #:use-module (oop goops) #:use-module ((ordo connection) #:select (local-connection)) + #:use-module (ordo logger) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-69) #:export (host host? host-name host-connection host-tags - add-host! - resolve-hosts)) + host-vars + resolve-hosts + load-inventory)) -(define-record-type - (make-host name connection tags) - host? - (name host-name) - (connection host-connection) - (tags host-tags)) +(define-class () + (name #:init-keyword #:name #:getter host-name) + (connection #:init-keyword #:connection #:getter host-connection) + (tags #:init-keyword #:tags #:getter host-tags #:init-form (list)) + (vars #:init-keyword #:vars #:getter host-vars #:init-form (list))) -(define (host name connection . tags) - (make-host name connection tags)) +(define-method (initialize (object ) initargs) + (next-method) + (slot-set! object 'vars (alist->hash-table (slot-ref object 'vars))) + object) + +(define (host . args) + (apply make args)) + +(define (host? x) + (is-a? x )) (define (tagged-every? wanted-tags) (lambda (h) @@ -36,9 +48,18 @@ (define (resolve-hosts inventory expr) (match expr ("localhost" (list (or (find (named? "localhost") inventory) - (make-host "localhost" (local-connection) '())))) + (make #:name "localhost" #:connection (local-connection))))) ((? string? hostname) (filter (named? hostname) inventory)) ('all inventory) (('tagged tag) (filter (tagged-every? (list tag)) inventory)) (('tagged/every tag . tags) (filter (tagged-every? (cons tag tags)) inventory)) (('tagged/any tag . tags) (filter (tagged-any? (cons tag tags)) inventory)))) + +(define (load-inventory filename) + (log-msg 'INFO "Loading inventory " filename) + (let* ((inventory (eval-string (call-with-input-file filename get-string-all) + #:file filename)) + (inventory (if (list? inventory) inventory '()))) + (when (null? inventory) + (log-msg 'NOTICE "Inventory is empty, only localhost will be available")) + inventory)) diff --git a/ordo/play.scm b/ordo/play.scm new file mode 100644 index 0000000..8d4ce80 --- /dev/null +++ b/ordo/play.scm @@ -0,0 +1,57 @@ +(define-module (ordo play) + #:use-module (oop goops) + #:use-module (ordo connection) + #:use-module (ordo context) + #:use-module (ordo inventory) + #:use-module (ordo logger) + #:use-module (ordo util flatten) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-69) + #:export (play + play? + play-host + play-sudo? + play-sudo-user + play-sudo-password + play-vars + play-actions + play-handlers + run-play)) + +(define-class () + (name #:init-keyword #:name #:getter play-name) + (host #:init-keyword #:host #:getter play-host) + (sudo? #:init-keyword #:sudo? #:getter play-sudo? #:init-value #f) + (sudo-user #:init-keyword #:sudo-user #:getter play-sudo-user #:init-value #f) + (sudo-password #:init-keyword #:sudo-password #:getter play-sudo-password #:init-value #f) + (vars #:init-keyword #:vars #:getter play-vars #:init-form (list)) + (actions #:init-keyword #:actions #:getter play-actions #:init-form (list)) + (handlers #:init-keyword #:handlers #:getter play-handlers #:init-form (list))) + +(define-method (initialize (object ) initargs) + (next-method) + (slot-set! object 'vars (alist->hash-table (slot-ref object 'vars))) + object) + +(define (play . args) + (apply make args)) + +(define (run-play p) + (log-msg 'NOTICE "Running play: " (play-name p)) + (parameterize ((*play-vars* (play-vars p))) + (let ((hosts (resolve-hosts (*inventory*) (play-host p)))) + (if (null? hosts) + (log-msg 'WARN "No hosts matched: " (play-host p)) + (for-each (lambda (h) (run-host-play p h)) hosts))))) + +(define (run-host-play p h) + (log-msg 'NOTICE "Running play: " (play-name p) " on host: " (host-name h)) + (parameterize ((*host-vars* (host-vars h))) + (call-with-connection + (host-connection h) + (lambda (conn) + #f + ) + #:sudo? (play-sudo? p) + #:sudo-user (play-sudo-user p) + #:sudo-password (play-sudo-password p)))) diff --git a/ordo/playbook.scm b/ordo/playbook.scm new file mode 100644 index 0000000..e980585 --- /dev/null +++ b/ordo/playbook.scm @@ -0,0 +1,45 @@ +(define-module (ordo playbook) + #:use-module (ice-9 eval-string) + #:use-module (ice-9 textual-ports) + #:use-module (oop goops) + #:use-module (ordo context) + #:use-module (ordo logger) + #:use-module (ordo play) + #:use-module (srfi srfi-26) + #:use-module (srfi srfi-69) + #:export ( + playbook + playbook? + playbook-name + playbook-vars + playbook-plays + load-playbook + run-playbook) + #:re-export (play)) + +(define-class () + (name #:init-keyword #:name #:getter playbook-name) + (vars #:init-keyword #:vars #:getter playbook-vars) + (plays #:init-keyword #:plays #:getter playbook-plays)) + +(define-method (initialize (object ) initargs) + (next-method) + (slot-set! object 'vars (alist->hash-table (slot-ref object 'vars))) + object) + +(define (playbook . args) + (apply make args)) + +(define (playbook? p) + (is-a? p )) + +(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-vars* (playbook-vars pb))) + (for-each run-play (playbook-plays pb))))