diff --git a/tryme.scm b/examples/install-aws-cli.scm similarity index 55% rename from tryme.scm rename to examples/install-aws-cli.scm index 75d622a..3844110 100644 --- a/tryme.scm +++ b/examples/install-aws-cli.scm @@ -1,8 +1,6 @@ (use-modules (ice-9 filesystem) - (ordo) - (ordo connection) - (ordo logger)) + (ordo)) (define* (install-aws-cli conn #:key (url "https://awscli.amazonaws.com/awscli-exe-linux-x86_64.zip") update? install-dir bin-dir) (let ((tmp-dir (run conn "mktemp" "-d" #:return car #:check? #t))) @@ -20,16 +18,12 @@ (lambda () (run conn "rm" "-rf" tmp-dir #:check? #t))))) -(define test-playbook - (playbook "Test Playbook" - (play "Test play" - #:connection (local-connection) - (task "Install AWS CLI" - (lambda (c) - (install-aws-cli c - #:update? #t - #:install-dir (file-name-join* ($$ #:pwent #:home-dir) ".local" "aws-cli") - #:bin-dir (file-name-join* ($$ #:pwent #:home-dir) ".local" "bin"))))))) - -(setup-logging) -(run-playbook test-playbook) +(playbook "Test Playbook" + (play "Test play" + #:host "localhost" + (task "Install AWS CLI" + (lambda (c) + (install-aws-cli c + #:update? #t + #:install-dir (file-name-join* ($ #:fact.home-dir) ".local" "aws-cli") + #:bin-dir (file-name-join* ($ #:fact.home-dir) ".local" "bin")))))) diff --git a/examples/inventory.scm b/examples/inventory.scm new file mode 100644 index 0000000..00bee3e --- /dev/null +++ b/examples/inventory.scm @@ -0,0 +1,14 @@ +(use-modules (ordo inventory) + (ordo connection)) + +(add-host! "little-rascal" + (local-connection) + #:linux #:guix) + +(add-host! "screw-loose" + (ssh-connection "core" "screw-loose") + #:linux #:coreos) + +(add-host! "limiting-factor" + (ssh-connection "core" "limiting-factor") + #:linux #:coreos) diff --git a/modules/ordo.scm b/modules/ordo.scm index 3a35d84..efd874f 100644 --- a/modules/ordo.scm +++ b/modules/ordo.scm @@ -1,171 +1,22 @@ (define-module (ordo) - #:use-module (ice-9 exceptions) - #:use-module (logging logger) + #:declarative? #f + #:use-module (ice-9 match) + #:use-module (ordo playbook) + #:use-module (ordo play) + #:use-module (ordo task) + #:use-module (ordo handler) #:use-module (ordo connection) - #:use-module (ordo facts) - #:use-module (srfi srfi-1) ; list utils - #:use-module (srfi srfi-9) ; records - #:use-module (srfi srfi-26) ; cut - #:use-module (srfi srfi-69) ; hash-tables - #:export (task - task-name - task-tags - task-action - task-condition - task-register - task-triggers - run-task - play - play-name - play-vars - play-tasks - play-connection - play-handlers - run-play - playbook - playbook-name - playbook-vars - playbook-plays - run-playbook - $ - $$ - register-play-var - register-playbook-var)) + #:use-module (ordo inventory) + #:use-module (ordo vars) + #:use-module (ordo logger) + #:export (main) + #:re-export (add-host! local-connection ssh-connection run playbook play task handler $)) -(define +filter-tags+ '()) - -(define (check-tags tags) - (or (null? +filter-tags+) - (not (null? (lset-intersection eqv? +filter-tags+ tags))))) - -(define +play-vars+ #f) - -(define (register-play-var var-name) - (lambda (v) - (log-msg 'DEBUG "Registering play variable " var-name) - (hash-table-set! +play-vars+ var-name v))) - -(define +playbook-vars+ #f) - -(define (register-playbook-var var-name) - (lambda (v) - (log-msg 'DEBUG "Registering playbook variable " var-name) - (hash-table-set! +playbook-vars+ var-name v))) - -(define ($ var-name) - "Try to resolve var-name as a play variable or a playbook -variable (in that order). Raise an exception if the variable is not found." - (define not-found (cons 'not-found '())) - (define (lookup-var var-name vars) - (cond - ((null? vars) - (raise-exception (make-exception - (make-undefined-variable-error) - (make-exception-with-irritants var-name)))) - ((not (car vars)) (lookup-var var-name (cdr vars))) - (else (let ((v (hash-table-ref/default (car vars) var-name not-found))) - (if (eqv? v not-found) - (lookup-var var-name (cdr vars)) - v))))) - (lookup-var var-name (list +play-vars+ +playbook-vars+))) - -(define ($$ . keys) - "Look up nested keys in gathered facts." - (apply get-fact (hash-table-ref +play-vars+ #:ordo-facts) keys)) - -(define +triggers+ #f) - -(define (add-triggers triggers) - (set! +triggers+ (apply lset-adjoin equal? (or +triggers+ '()) - triggers))) - -(define-record-type - (make-task name tags action condition register triggers) - task? - (name task-name) - (tags task-tags) - (action task-action) - (condition task-condition) - (register task-register) - (triggers task-triggers)) - -(define* (task name action #:key (tags '()) (condition (const #t)) (register (const #f)) (triggers '())) - (make-task name tags action condition register triggers)) - -(define (run-task t c) - (when (check-tags (task-tags t)) - (if (not ((task-condition t) c)) - (log-msg 'NOTICE "Skipping task " (task-name t) " (precondition not met)") - (begin - (log-msg 'NOTICE "Running task " (task-name t)) - (let ((result ((task-action t) c))) - ((task-register t) result) - (add-triggers (task-triggers t))))))) - -(define-record-type - (make-handler name action) - handler? - (name handler-name) - (action handler-action)) - -(define (handler name action) - (make-handler name action)) - -(define-record-type - (make-play name connection vars gather-facts tasks handlers) - play? - (name play-name) - (connection play-connection) - (vars play-vars) - (tasks play-tasks) - (handlers play-handlers) - (gather-facts play-gather-facts)) - -(define* (play name #:key connection (vars '()) (gather-facts #t) . more) - (let ((tasks (filter task? more)) - (handlers (filter handler? more))) - (make-play name connection vars gather-facts tasks handlers))) - -(define (run-play p) - (log-msg 'NOTICE "Running play " (play-name p)) - (dynamic-wind - (lambda () - (set! +play-vars+ (alist->hash-table (play-vars p) equal?)) - (init-connection! (play-connection p))) - (lambda () - (when (play-gather-facts p) - (hash-table-set! +play-vars+ #:ordo-facts (gather-facts (play-connection p)))) - (for-each (cut run-task <> (play-connection p)) (play-tasks p)) - (for-each (lambda (h) - (when (member (handler-name h) +triggers+) - (log-msg 'INFO "Running handler " (handler-name h)) - ((handler-action h) (play-connection p)))) - (play-handlers p))) - (lambda () - (set! +play-vars+ #f) - (set! +triggers+ #f) - (close-connection! (play-connection p))))) - -(define-record-type - (make-playbook name vars plays) - playbook? - (name playbook-name) - (vars playbook-vars) - (plays playbook-plays)) - -(define* (playbook name #:key (vars '()) . plays) - (make-playbook name vars plays)) - -(define* (run-playbook pb #:optional (filter-tags '())) - (log-msg 'NOTICE "Running playbook " (playbook-name pb)) - (dynamic-wind - (lambda () - (set! +filter-tags+ filter-tags) - (set! +playbook-vars+ (alist->hash-table (playbook-vars pb) equal?))) - (lambda () - (for-each run-play (playbook-plays pb))) - (lambda () - (set! +filter-tags+ '()) - (set! +playbook-vars+ #f)))) - -;; TODO: add validate methods for , , and +(define (main args) + (match-let (((_ inventory-path playbook-path) args)) + (setup-logging #:level 'DEBUG) + (init-command-line-vars! '()) + (load inventory-path) + (let ((playbook (load playbook-path))) + (run-playbook playbook))) + (quit)) diff --git a/modules/ordo/connection.scm b/modules/ordo/connection.scm index 8dd40ce..4e57bda 100644 --- a/modules/ordo/connection.scm +++ b/modules/ordo/connection.scm @@ -2,8 +2,6 @@ #:use-module (oop goops) #:use-module (ice-9 exceptions) #:use-module (ice-9 match) - #:use-module (ice-9 popen) - #:use-module (ice-9 rdelim) #:use-module (logging logger) #:use-module (srfi srfi-1) ; list operations #:use-module (srfi srfi-71) ; extended let @@ -14,36 +12,39 @@ #:use-module (ordo util flatten) #:use-module (ordo util shell-quote) #:use-module (ordo util keyword-args) - #:export (connection + #:export (connection? + local-connection + ssh-connection call-with-connection run)) -(define (connection type . kwargs) - (validate-keyword-args kwargs) - (let* ((c (case type - ((#:local) (make )) - ((#:ssh) (apply make - (select-keyword-args kwargs '(#:user #:host #:password #:identity #:authenticate-server?)))))) - (c (if (keyword-arg kwargs #:sudo?) - (apply make #:connection c (select-keyword-args kwargs '(#:become-user #:become-password))) - c))) - (conn:validate c) - c)) +(define (connection? c) + (is-a? c )) -(define (call-with-connection c proc) - (dynamic-wind - (lambda () (conn:setup c)) - (lambda () (proc c)) - (lambda () (conn:teardown c)))) +(define (local-connection) + (make )) -(define (build-command prog args pwd env) +(define* (ssh-connection user host #:key (password #f) (identity #f) (authenticate-server? #t)) + (make #:user user #:host host #:password password + #:identity identity #:authenticate-server? authenticate-server?)) + +(define* (call-with-connection c proc #:key (sudo? #f) (sudo-user #f) (sudo-password #f)) + (let ((c (if sudo? + (make #:connection c #:become-user sudo-user #:become-password sudo-password) + c))) + (dynamic-wind + (lambda () (conn:setup c)) + (lambda () (proc c)) + (lambda () (conn:teardown c))))) + +(define (build-command prog args pwd env redirect-err?) (let ((xs (remove unspecified? (flatten (list "env" (when pwd (list "--chdir" (string-shell-quote pwd))) (when env (map (match-lambda ((k . v) (string-append k "=" (string-shell-quote v)))) env)) prog (map string-shell-quote args) - "2>&1"))))) + (when redirect-err? "2>&1")))))) (string-join xs " "))) (define (run conn prog . args) @@ -54,13 +55,14 @@ (env (keyword-arg kwargs #:env)) (return (keyword-arg kwargs #:return identity)) (check? (keyword-arg kwargs #:check?)) - (command (build-command prog args pwd env)) - (out rc (conn:run conn command))) - (log-msg 'INFO "Command " command " exited " rc) - (if check? - (if (zero? rc) - (return out) - (raise-exception (make-exception - (make-external-error) - (make-exception-with-message (format #f "Non-zero exit (~a) from ~a" rc prog))))) - (values (return out) rc)))) + (command (build-command prog args pwd env #t))) + (log-msg 'INFO "Running command: " command) + (let ((out rc (conn:run conn command))) + (log-msg 'INFO "Command exit code: " rc) + (if check? + (if (zero? rc) + (return out) + (raise-exception (make-exception + (make-external-error) + (make-exception-with-message (format #f "Non-zero exit (~a) from ~a" rc prog))))) + (values (return out) rc))))) diff --git a/modules/ordo/connection/base.scm b/modules/ordo/connection/base.scm index 3e67972..9a3b17c 100644 --- a/modules/ordo/connection/base.scm +++ b/modules/ordo/connection/base.scm @@ -1,7 +1,6 @@ (define-module (ordo connection base) #:use-module (oop goops) #:export ( - conn:validate conn:setup conn:teardown conn:run @@ -10,8 +9,6 @@ (define-class ()) -(define-method (conn:validate (c )) #t) - (define-method (conn:setup (c )) #t) (define-method (conn:teardown (c )) #t) diff --git a/modules/ordo/connection/ssh.scm b/modules/ordo/connection/ssh.scm index 2b0015c..7b6a065 100644 --- a/modules/ordo/connection/ssh.scm +++ b/modules/ordo/connection/ssh.scm @@ -20,18 +20,6 @@ (session #:accessor session) (sftp-session #:accessor sftp-session)) -(define-method (conn:validate (c )) - (unless (slot-bound? c 'user) - (raise-exception - (make-exception - (make-programming-error) - (make-exception-with-message "#:user is required")))) - (unless (slot-bound? c 'host) - (raise-exception - (make-exception - (make-programming-error) - (make-exception-with-message "#:host is required"))))) - (define-method (conn:setup (c )) (unless (slot-bound? c 'session) (set! (session c) (make-session #:user (user c) #:host (host c))) diff --git a/modules/ordo/context.scm b/modules/ordo/context.scm deleted file mode 100644 index 06512b1..0000000 --- a/modules/ordo/context.scm +++ /dev/null @@ -1,59 +0,0 @@ -(define-module (ordo context) - #:use-module (srfi srfi-1) - #:use-module (srfi srfi-9) - #:use-module (srfi srfi-26) - #:use-module (srfi srfi-71) - #:use-module (ordo connection) - #:export (make-context - context? - context-connection - add-context-triggers! - get-context-triggers - context-triggered? - register-context-var! - context-ref - bind-context-vars - run - must)) - -(define-record-type - (make-context connection vars) - context? - (connection context-connection) - (vars context-vars set-context-vars!) - (triggers context-triggers set-context-triggers!)) - -(define (context-ref ctx var-name) - (let ((kv (assoc var-name (context-vars ctx)))) - (if kv - (cdr kv) - (error (format #f "failed to resolve context reference: ~a" var-name))))) - -(define (add-context-triggers! ctx triggers) - (when triggers - (set-context-triggers! ctx - (apply lset-adjoin equal? (or (context-triggers ctx) '()) triggers)))) - -(define (context-triggered? ctx trigger) - (find (lambda (t) (equal? t trigger)) (context-triggers ctx))) - -(define (register-context-var! ctx var-name val) - (set-context-vars! ctx (assoc-set! (context-vars ctx) var-name val))) - -(define-syntax bind-context-vars - (syntax-rules () - ((bind-context-vars (var-name ...) proc) - (lambda (ctx) - (let ((var-name (context-ref ctx (quote var-name))) ...) - (proc ctx)))))) - -(define* (run ctx prog args #:key (env #f) (pwd #f)) - (connection-run (context-connection ctx) pwd env prog args)) - -(define* (must ctx prog args #:key (env #f) (pwd #f) (error-msg #f)) - (let ((out rc (run ctx prog args #:env env #:pwd pwd))) - (if (zero? rc) - out - (error (if error-msg - (format #f "~a: ~a" error-msg out) - (format #f "~a error: ~a" prog out)))))) diff --git a/modules/ordo/facts.scm b/modules/ordo/facts.scm index 6138c59..d3d3e6b 100644 --- a/modules/ordo/facts.scm +++ b/modules/ordo/facts.scm @@ -1,18 +1,18 @@ (define-module (ordo facts) + #:use-module ((srfi srfi-88) #:select (string->keyword)) + #:use-module (ordo vars) #:use-module (ordo facts user) - #:export (gather-facts - get-fact)) + #:export (gather-facts)) -(define (get-fact facts . keys) - (cond - ((null? keys) facts) - ((list? facts) (let ((facts (assoc-ref facts (car keys)))) - (apply get-fact facts (cdr keys)))) - (else #f))) +(define (set-facts! src keys) + (for-each (lambda (k) + (set-play-var! (string->keyword (string-append "fact." k)) + (assoc-ref src (string->keyword k)))) + keys)) (define (gather-facts conn) (let* ((id (fact:id conn)) - (user-name (get-fact id #:user #:name)) + (user-name (assoc-ref id #:user-name)) (pwent (fact:pwent conn user-name))) - `((#:id . ,id) - (#:pwent . ,pwent)))) + (set-facts! id '("user-name" "user-id" "group-name" "group-id" "groups")) + (set-facts! pwent '("gecos" "home-dir" "shell")))) diff --git a/modules/ordo/facts/user.scm b/modules/ordo/facts/user.scm index fb4fa72..80ce865 100644 --- a/modules/ordo/facts/user.scm +++ b/modules/ordo/facts/user.scm @@ -1,7 +1,6 @@ (define-module (ordo facts user) #:use-module (rx irregex) - #:use-module (srfi srfi-1) ; list utils - #:use-module (srfi srfi-2) ; and-let* + #:use-module (srfi srfi-1) #:use-module (ordo connection) #:export (fact:id fact:pwent)) @@ -14,8 +13,10 @@ accum)) '() s)))) - `((#:user . ,(first data)) - (#:group . ,(second data)) + `((#:user-id . ,(assoc-ref (first data) #:id)) + (#:user-name . ,(assoc-ref (first data) #:name)) + (#:group-id . ,(assoc-ref (second data) #:id)) + (#:group-name . ,(assoc-ref (second data) #:name)) (#:groups . ,(drop data 2))))) (define (fact:id conn) diff --git a/modules/ordo/handler.scm b/modules/ordo/handler.scm index ab7ec91..127555e 100644 --- a/modules/ordo/handler.scm +++ b/modules/ordo/handler.scm @@ -1,26 +1,24 @@ (define-module (ordo handler) #:use-module (ice-9 match) - #:use-module (logging logger) - #:use-module (srfi srfi-1) ; list utils #:use-module (srfi srfi-9) ; records - #:use-module (srfi srfi-26) ; cut - #:use-module (ordo context) + #:use-module (logging logger) #:export (handler handler? - handler-description + handler-name handler-action run-handler)) (define-record-type - (make-handler description action) + (make-handler name action) handler? - (description handler-description) + (name handler-name) (action handler-action)) -(define handler make-handler) +(define (handler name action) + (make-handler name action)) -(define (run-handler ctx h) +(define (run-handler c h) (match h - (($ description action) - (log-msg 'NOTICE "Running handler: " description) - (action ctx)))) + (($ name action) + (log-msg 'NOTICE "Running handler: " name) + (action c)))) diff --git a/modules/ordo/interceptor.scm b/modules/ordo/interceptor.scm index 0e60674..772aaf5 100644 --- a/modules/ordo/interceptor.scm +++ b/modules/ordo/interceptor.scm @@ -14,9 +14,7 @@ terminate-when execute bind - unbind - run - must)) + unbind)) (define-record-type (make-interceptor name enter leave error) @@ -187,21 +185,3 @@ ((< (length args) 2) #f) ((equal? (first args) kw) (second args)) (else (keyword-arg kw (cddr args))))) - -(define (run ctx prog . args) - (let* ((args kwargs (break keyword? args)) - (pwd (keyword-arg #:pwd kwargs)) - (env (keyword-arg #:env kwargs))) - (connection-run (context-connection ctx) pwd env prog args))) - -(define (must ctx prog . args) - (let* ((args kwargs (break keyword? args)) - (pwd (keyword-arg #:pwd kwargs)) - (env (keyword-arg #:env kwargs)) - (error-msg (keyword-arg #:error-msg kwargs)) - (out rc (connection-run (context-connection ctx) pwd env prog args))) - (if (zero? rc) - out - (error (if error-msg - (format #f "~a: ~a" error-msg out) - (format #f "~a error: ~a" prog out)))))) diff --git a/modules/ordo/inventory.scm b/modules/ordo/inventory.scm new file mode 100644 index 0000000..a433fa2 --- /dev/null +++ b/modules/ordo/inventory.scm @@ -0,0 +1,54 @@ +(define-module (ordo inventory) + #:use-module (ice-9 match) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-69) + #:use-module (logging logger) + #:use-module (ordo connection) + #:export (make-host + host? + host-name + host-connection + host-tags + add-host! + resolve-hosts)) + +(define *hosts* (make-hash-table equal?)) + +(define-record-type + (make-host name connection tags) + host? + (name host-name) + (connection host-connection) + (tags host-tags)) + +(define (add-host! name connection . tags) + (log-msg 'DEBUG "Adding host to inventory: " name) + (hash-table-set! *hosts* name (make-host name connection tags))) + +(define (tagged-all? wanted-tags) + (lambda (h) + (lset= equal? wanted-tags (lset-intersection equal? (host-tags h) wanted-tags)))) + +(define (tagged-any? wanted-tags) + (lambda (h) + (not (null? (lset-intersection equal? (host-tags h) wanted-tags))))) + +(define resolve-hosts + (match-lambda + ("localhost" (list (or (hash-table-ref/default *hosts* "localhost" #f) + (make-host "localhost" (local-connection) '())))) + ((? string? name) (list (hash-table-ref *hosts* name))) + ('all (hash-table-values *hosts*)) + (('every-tag tag . tags) (filter (tagged-all? (cons tag tags)) (hash-table-values *hosts*))) + (('any-tag tag . tags) (filter (tagged-any? (cons tag tags)) (hash-table-values *hosts*))))) + +#! +(define (setup-test-data) + (add-host! "little-rascal" (ssh-connection "ray" "little-rascal") #:linux #:guix) + (add-host! "linux-1" (ssh-connection "root" "linux-1") #:linux) + (add-host! "linux-2" (ssh-connection "root" "linux-2") #:linux) + (add-host! "debian-1" (ssh-connection "root" "debian-1") #:linux #:debian) + (add-host! "debian-2" (ssh-connection "root" "debian-2") #:linux #:debian) + (add-host! "debian-3" (ssh-connection "root" "debian-3") #:linux #:debian #:eu-west-1)) +!# diff --git a/modules/ordo/play.scm b/modules/ordo/play.scm index 87f1ae9..adf857c 100644 --- a/modules/ordo/play.scm +++ b/modules/ordo/play.scm @@ -1,76 +1,67 @@ (define-module (ordo play) - #:use-module (oop goops) - #:use-module (ice-9 match) + #:use-module (srfi srfi-9) + #:use-module (srfi srfi-26) #:use-module (logging logger) - #:use-module (srfi srfi-1) ; list utils - #:use-module (srfi srfi-9) ; records - #:use-module (srfi srfi-26) ; cut #:use-module (ordo connection) - #:use-module (ordo context) #:use-module (ordo task) #:use-module (ordo handler) - #:use-module (ordo logger) - #:export (play run-play)) + #:use-module (ordo vars) + #:use-module (ordo inventory) + #:use-module (ordo facts) + #:export (play + play? + play-host + play-sudo? + play-sudo-user + play-sudo-password + play-vars + play-tasks + play-handlers + play-gather-facts + run-play)) (define-record-type - (make-play description connection vars tasks handlers) + (make-play name host sudo? sudo-user sudo-password vars gather-facts tasks handlers) play? - (connection play-connection) + (name play-name) + (host play-host) + (sudo? play-sudo?) + (sudo-user play-sudo-user) + (sudo-password play-sudo-password) (vars play-vars) - (description play-description) (tasks play-tasks) - (handlers play-handlers)) + (handlers play-handlers) + (gather-facts play-gather-facts)) -(define (validate-connection connection) - (unless (and connection (is-a? connection )) - (error (format #f "invalid connection: ~a" connection)))) +(define* (play name #:key host (sudo? #f) (sudo-user #f) (sudo-password #f) (vars '()) (gather-facts #t) . more) + (let ((tasks (filter task? more)) + (handlers (filter handler? more))) + (make-play name host sudo? sudo-user sudo-password vars gather-facts tasks handlers))) -(define (validate-tasks tasks) - (unless (and tasks (not (null? tasks)) (every task? tasks)) - (error (format #f "invalid tasks: ~a" tasks)))) +(define (run-play p) + (log-msg 'NOTICE "Running play: " (play-name p)) + (for-each (lambda (h) (run-host-play p h)) + (resolve-hosts (play-host p)))) -(define (validate-handlers handlers) - (unless (every (lambda (h) (and (pair? h) (handler? (cdr h)))) handlers) - (error (format #f "invalid handlers: ~a" handlers)))) - -(define (validate-vars vars) - (unless (every pair? vars) - (error (format #f "invalid vars: ~a" vars)))) - -(define (validate-triggers tasks handlers) - (for-each (lambda (task) - (for-each (lambda (trigger) - (unless (assoc-ref handlers trigger) - (error (format #f "task \"~a\" references an undefined trigger: ~a" - (task-description task) - trigger)))) - (task-triggers task))) - tasks)) - -(define* (play description #:key connection tasks (vars '()) (handlers '())) - (validate-connection connection) - (validate-tasks tasks) - (validate-handlers handlers) - (validate-triggers tasks handlers) - (validate-vars vars) - ;; Reconstruct the vars here because, when a quoted list is passed in the - ;; play, it can result in an error (expected mutable pair) from assoc-set! - ;; from register-context-var!. - (make-play description connection (fold (match-lambda* (((k . v) accum) (alist-cons k v accum))) '() vars) tasks handlers)) - -(define (run-play play) - ;; TODO move logging setup and shutdown to a higher level when we implement playbook etc. - (setup-logging) - (log-msg 'NOTICE "Running play: " (play-description play)) - (call-with-connection - (play-connection play) - (lambda (c) - (let* ((ctx (make-context c (play-vars play)))) - (for-each (cut run-task ctx <>) (play-tasks play)) - (for-each (match-lambda - ((name . handler) - (when (context-triggered? ctx name) - (run-handler ctx handler)))) - (play-handlers play))))) - (log-msg 'NOTICE "Completed play: " (play-description play)) - (shutdown-logging)) +(define (run-host-play p h) + (log-msg 'NOTICE "Running play: " (play-name p) " on host: " (host-name h)) + (dynamic-wind + (lambda () + (init-play-vars! (play-vars p))) + (lambda () + (call-with-connection + (host-connection h) + (lambda (c) + (when (play-gather-facts p) + (gather-facts c)) + (for-each (cut run-task <> c) + (play-tasks p)) + (for-each (cut run-handler <> c) + (filter (compose play-triggered? handler-name) + (play-handlers p)))) + #:sudo? (play-sudo? p) + #:sudo-user (play-sudo-user p) + #:sudo-password (play-sudo-password p))) + (lambda () + (reset-play-vars!) + (reset-play-triggers!)))) diff --git a/modules/ordo/playbook.scm b/modules/ordo/playbook.scm new file mode 100644 index 0000000..376510e --- /dev/null +++ b/modules/ordo/playbook.scm @@ -0,0 +1,31 @@ +(define-module (ordo playbook) + #:use-module (srfi srfi-9) + #:use-module (logging logger) + #:use-module (ordo play) + #:use-module (ordo vars) + #:export (playbook + playbook? + playbook-name + playbook-vars + playbook-plays + run-playbook)) + +(define-record-type + (make-playbook name vars plays) + playbook? + (name playbook-name) + (vars playbook-vars) + (plays playbook-plays)) + +(define* (playbook name #:key (vars '()) . plays) + (make-playbook name vars plays)) + +(define (run-playbook pb) + (log-msg 'NOTICE "Running playbook: " (playbook-name pb)) + (dynamic-wind + (lambda () + (init-playbook-vars! (playbook-vars pb))) + (lambda () + (for-each run-play (playbook-plays pb))) + (lambda () + (reset-playbook-vars!)))) diff --git a/modules/ordo/task.scm b/modules/ordo/task.scm index e1db8e0..feee5ab 100644 --- a/modules/ordo/task.scm +++ b/modules/ordo/task.scm @@ -1,42 +1,41 @@ (define-module (ordo task) - #:use-module (ice-9 match) + #:use-module (srfi srfi-9) #:use-module (logging logger) - #:use-module (srfi srfi-1) ; list utils - #:use-module (srfi srfi-9) ; records - #:use-module (srfi srfi-26) ; cut - #:use-module (ordo context) + #:use-module (ordo vars) #:export (task task? - task-description - task-condition + task-name + task-tags task-action - task-register + task-condition + task-register-play-var + task-register-playbook-var task-triggers run-task)) (define-record-type - (make-task description condition action register triggers) + (make-task name tags action condition register-play-var register-playbook-var triggers) task? - (description task-description) - (condition task-condition) + (name task-name) + (tags task-tags) (action task-action) - (register task-register) + (condition task-condition) + (register-play-var task-register-play-var) + (register-playbook-var task-register-playbook-var) (triggers task-triggers)) -(define* (task description action #:key (condition (const #t)) (register #f) (triggers '())) - (make-task description condition action register triggers)) +(define* (task name action #:key (tags '()) (condition (const #t)) (register-play-var #f) (register-playbook-var #f) (triggers '())) + (make-task name tags action condition register-play-var register-playbook-var triggers)) -(define (run-task ctx t) - (match t - (($ description condition action register triggers) - (if (not (condition ctx)) - (log-msg 'NOTICE "Skipping task: " description " (precondition not met)") - (begin - (log-msg 'NOTICE "Running task: " description) - (let ((result (action ctx))) - (when register - (log-msg 'INFO "Registering result " register) - (register-context-var! ctx register result)) - (when (and triggers (not (null? triggers))) - (log-msg 'INFO "Scheduling triggers " triggers) - (add-context-triggers! ctx triggers)))))))) +(define (run-task t c) + (when (check-filter-tags (task-tags t)) + (if (not ((task-condition t) c)) + (log-msg 'NOTICE "Skipping task: " (task-name t) " (precondition not met)") + (begin + (log-msg 'NOTICE "Running task: " (task-name t)) + (let ((result ((task-action t) c))) + (when (task-register-play-var t) + (set-play-var! (task-register-play-var t) result)) + (when (task-register-playbook-var t) + (set-playbook-var! (task-register-playbook-var t) result)) + (add-play-triggers! (task-triggers t))))))) diff --git a/modules/ordo/vars.scm b/modules/ordo/vars.scm new file mode 100644 index 0000000..bef2af4 --- /dev/null +++ b/modules/ordo/vars.scm @@ -0,0 +1,104 @@ +(define-module (ordo vars) + #:use-module (ice-9 exceptions) + #:use-module (srfi srfi-1) + #:use-module (srfi srfi-69) + #:export (init-playbook-vars! + get-playbook-var + set-playbook-var! + reset-playbook-vars! + init-play-vars! + get-play-var + set-play-var! + reset-play-vars! + init-command-line-vars! + get-command-line-var + set-command-line-var! + $ + reset-play-triggers! + add-play-triggers! + play-triggered? + set-filter-tag! + reset-filter-tags! + check-filter-tags)) + +(define not-found (cons 'not-found '())) + +(define (not-found? x) (eq? x not-found)) + +(define *playbook-vars* #f) + +(define (init-playbook-vars! alist) + (set! *playbook-vars* (alist->hash-table alist equal?))) + +(define (get-playbook-var var-name) + (hash-table-ref/default *playbook-vars* var-name not-found)) + +(define (set-playbook-var! var-name val) + (hash-table-set! *playbook-vars* var-name val)) + +(define (reset-playbook-vars!) + (set! *playbook-vars* #f)) + +(define *play-vars* #f) + +(define (init-play-vars! alist) + (set! *play-vars* (alist->hash-table alist equal?))) + +(define (get-play-var var-name) + (hash-table-ref/default *play-vars* var-name not-found)) + +(define (set-play-var! var-name val) + (hash-table-set! *play-vars* var-name val)) + +(define (reset-play-vars!) + (set! *play-vars* #f)) + +(define *command-line-vars* #f) + +(define (init-command-line-vars! alist) + (set! *command-line-vars* (alist->hash-table alist equal?))) + +(define (get-command-line-var var-name) + (hash-table-ref/default *command-line-vars* var-name not-found)) + +(define (set-command-line-var var-name val) + (hash-table-set! *command-line-vars* var-name val)) + +(define ($ var-name) + "Try to resolve var-name as a command-line variable, a play variable or a +playbook variable (in that order). Raise an exception if the variable is not +found." + (define (lookup-var procs) + (if (null? procs) + (raise-exception (make-exception + (make-undefined-variable-error) + (make-exception-with-irritants var-name))) + (let ((v ((car procs) var-name))) + (if (not-found? v) + (lookup-var (cdr procs)) + v)))) + (lookup-var (list get-command-line-var get-play-var get-playbook-var))) + +(define *play-triggers* '()) + +(define (reset-play-triggers!) + (set! *play-triggers* '())) + +(define (add-play-triggers! triggers) + (set! *play-triggers* (apply lset-adjoin equal? (or *play-triggers* '()) + triggers))) + +(define (play-triggered? trigger) + (member trigger *play-triggers*)) + +(define *filter-tags* '()) + +(define (set-filter-tag! tag) + (set! *filter-tags* (lset-adjoin equal? *filter-tags* tag))) + +(define (reset-filter-tags!) + (set! *filter-tags* '())) + +(define (check-filter-tags tags) + (or (null? *filter-tags*) + (not (null? (lset-intersection eqv? *filter-tags* tags))))) diff --git a/tryme-interceptors.scm b/tryme-interceptors.scm deleted file mode 100644 index 00a8ded..0000000 --- a/tryme-interceptors.scm +++ /dev/null @@ -1,55 +0,0 @@ -(use-modules - (ice-9 filesystem) - (logging logger) - (srfi srfi-9) - (ordo connection) - (ordo interceptor) - (ordo logger)) - -(define-record-type - (make-play name connection vars interceptors) - play? - (connection play-connection) - (vars play-vars) - (name play-name) - (interceptors play-interceptors)) - -(define* (play #:key name connection (interceptors '()) (vars '())) - (make-play name connection vars interceptors)) - -(define (run-play play) - (dynamic-wind - (lambda () - (log-msg 'NOTICE "Running play: " (play-name play)) - (init-connection! (play-connection play))) - (lambda () - (let ((ctx (init-context (play-connection play) #:vars (play-vars play)))) - (execute ctx (play-interceptors play)) - (if (context-error ctx) - (log-msg 'ERROR "Play " (play-name play) " terminated with error: " (context-error ctx)) - (log-msg 'NOTICE "Completed play: " (play-name play))))) - (lambda () - (close-connection! (play-connection play))))) - -(define test-play - (play - #:name "Test play" - #:connection (local-connection) - #:vars '((base-dir . "/home/ray/ordo-test")) - #:interceptors (list - (interceptor - "Handle errors" - #:error (lambda (ctx err) - (log-msg 'WARN "Handling error " err) - (set-context-error! ctx #f))) - (interceptor - "Create base directory" - #:enter (lambda (ctx) - (must ctx "mkdir" "-p" (unbind ctx base-dir)))) - (interceptor - "Create test file" - #:enter (lambda (ctx) - (must ctx "touch" (file-name-join* (unbind ctx base-dir) "test-file")))) - (interceptor - "Throw an error" - #:enter (lambda (ctx) (error "badness"))))))