Get rudimentary connections working again
This commit is contained in:
parent
38115b8a57
commit
f6ef09f91d
9 changed files with 337 additions and 0 deletions
59
ordo/connection.scm
Normal file
59
ordo/connection.scm
Normal file
|
@ -0,0 +1,59 @@
|
|||
(define-module (ordo connection)
|
||||
#:use-module (ice-9 exceptions)
|
||||
#:use-module (oop goops)
|
||||
#: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)
|
||||
#:use-module (srfi srfi-1)
|
||||
#:use-module (srfi srfi-71)
|
||||
#:export (connection?
|
||||
local-connection
|
||||
ssh-connection
|
||||
call-with-connection
|
||||
run)
|
||||
#:re-export (remote-exec with-remote-input-file with-remote-output-file))
|
||||
|
||||
(define (connection? c)
|
||||
(is-a? c <connection>))
|
||||
|
||||
(define (local-connection)
|
||||
(make <local-connection>))
|
||||
|
||||
(define* (ssh-connection user host #:key (password #f) (identity #f) (authenticate-server? #t))
|
||||
(make <ssh-connection> #: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 <sudo-connection>)))
|
||||
(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))
|
||||
(args (remove unspecified? (flatten args)))
|
||||
(return (keyword-arg options #:return identity))
|
||||
(check? (keyword-arg options #:check?))
|
||||
(command (build-command conn prog args options)))
|
||||
(log-msg 'INFO "Running command: " command)
|
||||
(let ((out rc (remote-exec 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)))))
|
40
ordo/connection/base.scm
Normal file
40
ordo/connection/base.scm
Normal file
|
@ -0,0 +1,40 @@
|
|||
(define-module (ordo connection base)
|
||||
#:use-module (ice-9 match)
|
||||
#:use-module (oop goops)
|
||||
#:use-module (ordo util flatten)
|
||||
#:use-module (ordo util keyword-args)
|
||||
#:use-module (ordo util shell-quote)
|
||||
#:use-module ((srfi srfi-1) #:select (remove))
|
||||
#:export (<connection>
|
||||
setup
|
||||
teardown
|
||||
build-command
|
||||
remote-exec
|
||||
with-remote-input-file
|
||||
with-remote-output-file))
|
||||
|
||||
(define-generic setup)
|
||||
(define-generic teardown)
|
||||
(define-generic build-command)
|
||||
(define-generic remote-exec)
|
||||
(define-generic with-remote-input-file)
|
||||
(define-generic with-remote-output-file)
|
||||
|
||||
(define-class <connection> ())
|
||||
|
||||
(define-method (setup (c <connection>)) #t)
|
||||
|
||||
(define-method (teardown (c <connection>)) #t)
|
||||
|
||||
(define-method (build-command (c <connection>) (prog-name <string>) (prog-args <list>) (options <list>))
|
||||
(let* ((pwd (keyword-arg options #:pwd))
|
||||
(env (keyword-arg options #:env))
|
||||
(redirect-err? (keyword-arg options #:redirect-err?))
|
||||
(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-name
|
||||
(map string-shell-quote prog-args)
|
||||
(when redirect-err? "2>&1"))))))
|
||||
(string-join xs " ")))
|
21
ordo/connection/local.scm
Normal file
21
ordo/connection/local.scm
Normal file
|
@ -0,0 +1,21 @@
|
|||
(define-module (ordo connection local)
|
||||
#:use-module (ice-9 popen)
|
||||
#:use-module (oop goops)
|
||||
#:use-module (ordo connection base)
|
||||
#:use-module (ordo connection sudo)
|
||||
#:use-module (ordo util read-lines)
|
||||
#:export (<local-connection>))
|
||||
|
||||
(define-class <local-connection> (<sudo-connection>))
|
||||
|
||||
(define-method (remote-exec (c <local-connection>) (command <string>))
|
||||
(let* ((port (open-input-pipe command))
|
||||
(output (read-lines port))
|
||||
(exit-status (status:exit-val (close-pipe port))))
|
||||
(values output exit-status)))
|
||||
|
||||
(define-method (with-remote-input-file (c <local-connection>) (filename <string>) (proc <procedure>))
|
||||
(call-with-input-file filename proc))
|
||||
|
||||
(define-method (with-remote-output-file (c <local-connection>) (filename <string>) (proc <procedure>))
|
||||
(call-with-output-file filename proc))
|
63
ordo/connection/ssh.scm
Normal file
63
ordo/connection/ssh.scm
Normal file
|
@ -0,0 +1,63 @@
|
|||
(define-module (ordo connection ssh)
|
||||
#:use-module (oop goops)
|
||||
#:use-module (ice-9 exceptions)
|
||||
#:use-module (ice-9 popen)
|
||||
#:use-module (ssh session)
|
||||
#:use-module (ssh channel)
|
||||
#:use-module (ssh auth)
|
||||
#:use-module (ssh popen)
|
||||
#:use-module (ssh sftp)
|
||||
#:use-module (ordo connection base)
|
||||
#:use-module (ordo connection sudo)
|
||||
#:use-module (ordo util read-lines)
|
||||
#:export (<ssh-connection>))
|
||||
|
||||
(define-class <ssh-connection> (<sudo-connection>)
|
||||
(user #:getter ssh-connection-user #:init-keyword #:user)
|
||||
(host #:getter ssh-connection-host #:init-keyword #:host)
|
||||
(password #:getter ssh-connection-password #:init-keyword #:password #:init-val #f)
|
||||
(identity #:getter ssh-connection-identity #:init-keyword #:identity #:init-val #f)
|
||||
(authenticate-server? #:getter ssh-connection-authenticate-server? #:init-keyword #:authenticate-server? #:init-val #t)
|
||||
(session)
|
||||
(sftp-session))
|
||||
|
||||
(define-method (setup (c <ssh-connection>))
|
||||
(unless (slot-bound? c 'session)
|
||||
(slot-set! c 'session (make-session #:user (ssh-connection-user c) #:host (ssh-connection-host c)))
|
||||
(when (ssh-connection-identity c) (session-set! (slot-ref c 'session) 'identity (ssh-connection-identity c))))
|
||||
(let ((s (slot-ref c 'session)))
|
||||
(unless (connected? s)
|
||||
(connect! s)
|
||||
(when (ssh-connection-authenticate-server? s)
|
||||
(let ((server-auth (authenticate-server s)))
|
||||
(unless (equal? 'ok server-auth)
|
||||
(error (format #f "authenticate-server: ~a" server-auth)))))
|
||||
(let ((user-auth (if (ssh-connection-password c)
|
||||
(userauth-password! s (ssh-connection-password c))
|
||||
(userauth-public-key/auto! s))))
|
||||
(unless (equal? 'success user-auth)
|
||||
(error (format #f "userauth: ~a" user-auth)))))))
|
||||
|
||||
(define-method (remote-exec (c <ssh-connection>) (command <string>))
|
||||
(let* ((channel (open-remote-input-pipe (slot-ref c 'session) command))
|
||||
(output (read-lines channel))
|
||||
(exit-status (channel-get-exit-status channel)))
|
||||
(close channel)
|
||||
(values output exit-status)))
|
||||
|
||||
(define-method (sftp-session (c <ssh-connection>))
|
||||
(unless (slot-bound? c 'sftp-session)
|
||||
(slot-set! c 'sftp-session (make-sftp-session (session c))))
|
||||
(slot-ref c 'sftp-session))
|
||||
|
||||
(define-method (with-remote-input-file (c <ssh-connection>) (filename <string>) (proc <procedure>))
|
||||
(call-with-remote-input-file (sftp-session c) filename proc))
|
||||
|
||||
(define-method (with-remote-output-file (c <ssh-connection>) (filename <string>) (proc <procedure>))
|
||||
(call-with-remote-output-file (sftp-session c) filename proc))
|
||||
|
||||
(define-method (teardown (c <ssh-connection>))
|
||||
(when (slot-bound? c 'session)
|
||||
(let ((s (slot-ref c session)))
|
||||
(when (connected? s)
|
||||
(disconnect! s)))))
|
49
ordo/connection/sudo.scm
Normal file
49
ordo/connection/sudo.scm
Normal file
|
@ -0,0 +1,49 @@
|
|||
(define-module (ordo connection sudo)
|
||||
#:use-module (oop goops)
|
||||
#:use-module (ice-9 exceptions)
|
||||
#:use-module (ice-9 rdelim)
|
||||
#:use-module (srfi srfi-26)
|
||||
#:use-module (srfi srfi-71)
|
||||
#:use-module (ordo connection base)
|
||||
#:use-module (ordo util shell-quote)
|
||||
#:export (<sudo-connection>
|
||||
become?
|
||||
become-user
|
||||
become-password))
|
||||
|
||||
(define-class <sudo-connection> (<connection>)
|
||||
(become? #:accessor become? #:init-keyword become? #:init-form #f)
|
||||
(become-user #:accessor become-user #:init-keyword #:become-user #:init-form #f)
|
||||
(become-password #:accessor become-password #:init-keyword #:become-password #:init-form #f)
|
||||
(password-tmp-file #:accessor password-tmp-file))
|
||||
|
||||
(define-method (setup (conn <sudo-connection>))
|
||||
(when (become-password conn)
|
||||
(let ((out rc (exec conn "mktemp")))
|
||||
(unless (zero? rc)
|
||||
(raise-exception (make-exception
|
||||
(make-external-error)
|
||||
(make-exception-with-message (format #f "Failed to create temporary directory: ~a" (car out))))))
|
||||
(let ((tmp-file (car out)))
|
||||
(call-with-output-file conn tmp-file (cut write-line (become-password conn) <>))
|
||||
(set! (password-tmp-file conn) tmp-file)))))
|
||||
|
||||
(define-method (build-command (conn <sudo-connection>) (prog-name <string>) (prog-args <list>) (options <list>))
|
||||
(cond
|
||||
((not (become? conn))
|
||||
(next-method))
|
||||
|
||||
((and (become-user conn) (become-password conn))
|
||||
(format #f "cat ~a - | sudo -k -S -H -u ~a -- ~a" (string-shell-quote (password-tmp-file conn)) (string-shell-quote (become-user conn)) (next-method)))
|
||||
|
||||
((become-password conn)
|
||||
(format #f "cat ~a - | sudo -k -S -H -- ~a" (string-shell-quote (password-tmp-file conn)) (next-method)))
|
||||
|
||||
((become-user conn)
|
||||
(format #f "sudo -k -n -H -u ~a -- ~a" (string-shell-quote (become-user conn)) (next-method)))
|
||||
|
||||
(else (format #f "sudo -k -n -H -- ~a" (next-method)))))
|
||||
|
||||
(define-method (teardown (conn <sudo-connection>))
|
||||
(when (slot-bound? conn 'password-tmp-file)
|
||||
(exec conn (format #f "rm -f ~a" (string-shell-quote (password-tmp-file conn))))))
|
10
ordo/util/flatten.scm
Normal file
10
ordo/util/flatten.scm
Normal file
|
@ -0,0 +1,10 @@
|
|||
(define-module (ordo util flatten)
|
||||
#:export (flatten))
|
||||
|
||||
(define (flatten lst)
|
||||
(cond
|
||||
((null? lst) '())
|
||||
((list? (car lst))
|
||||
(append (flatten (car lst)) (flatten (cdr lst))))
|
||||
(else
|
||||
(cons (car lst) (flatten (cdr lst))))))
|
27
ordo/util/keyword-args.scm
Normal file
27
ordo/util/keyword-args.scm
Normal file
|
@ -0,0 +1,27 @@
|
|||
(define-module (ordo util keyword-args)
|
||||
#:use-module (ice-9 exceptions)
|
||||
#:export (keyword-arg
|
||||
select-keyword-args
|
||||
validate-keyword-args))
|
||||
|
||||
(define* (keyword-arg args kw #:optional (default #f))
|
||||
(cond
|
||||
((< (length args) 2) default)
|
||||
((equal? (car args) kw) (cadr args))
|
||||
(else (keyword-arg (cddr args) kw default))))
|
||||
|
||||
(define (select-keyword-args kwargs wanted)
|
||||
(let loop ((kwargs kwargs) (accum '()))
|
||||
(cond
|
||||
((null? kwargs)
|
||||
(reverse accum))
|
||||
((member (car kwargs) wanted)
|
||||
(loop (cddr kwargs) (cons* (car kwargs) (cadr kwargs) accum)))
|
||||
(else (loop (cddr kwargs) accum)))))
|
||||
|
||||
(define (validate-keyword-args kwargs)
|
||||
(unless (even? (length kwargs))
|
||||
(raise-exception
|
||||
(make-exception
|
||||
(make-programming-error)
|
||||
(make-exception-with-message "keyword args should have an even number of elements")))))
|
11
ordo/util/read-lines.scm
Normal file
11
ordo/util/read-lines.scm
Normal file
|
@ -0,0 +1,11 @@
|
|||
(define-module (ordo util read-lines)
|
||||
#:use-module (ice-9 rdelim)
|
||||
#:export (read-lines))
|
||||
|
||||
(define (read-lines port)
|
||||
"Read lines from port until eof is encountered. Return list of all lines read."
|
||||
(define (loop line result)
|
||||
(if (eof-object? line)
|
||||
(reverse result)
|
||||
(loop (read-line port) (cons line result))))
|
||||
(loop (read-line port) '()))
|
57
ordo/util/shell-quote.scm
Normal file
57
ordo/util/shell-quote.scm
Normal file
|
@ -0,0 +1,57 @@
|
|||
;; This file is part of Ordo.
|
||||
;;
|
||||
;; Shell quoting implementation is based on Perl's String::ShellQuote
|
||||
;; Copyright (c) 1997 Roderick Schertler.
|
||||
;;
|
||||
;; Guile implementation Copyright (c) 2025 Ray Miller.
|
||||
;;
|
||||
;; Ordo 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, either version 3 of the License, or (at your option)
|
||||
;; any later version.
|
||||
;;
|
||||
;; Ordo 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
|
||||
;; Ordo. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
(define-module (ordo util shell-quote)
|
||||
#:use-module (rx irregex)
|
||||
#:use-module ((srfi srfi-197) #:select (chain))
|
||||
#:export (string-shell-quote))
|
||||
|
||||
(define unsafe-characters (irregex '(~ (or alphanumeric ("!%+,\\-./:=@^")))))
|
||||
|
||||
(define (needs-escape? s)
|
||||
(irregex-search unsafe-characters s))
|
||||
|
||||
(define (squash-quotes m)
|
||||
(let ((n (/ (- (irregex-match-end-index m)
|
||||
(irregex-match-start-index m))
|
||||
4)))
|
||||
(list->string (append
|
||||
'(#\' #\")
|
||||
(make-list n #\')
|
||||
'(#\" #\')))))
|
||||
|
||||
(define (escape s)
|
||||
(chain s
|
||||
;; ' -> '\''
|
||||
(irregex-replace/all (irregex "'") _ "'\\''")
|
||||
;; make multiple ' in a row look simpler
|
||||
;; '\'''\'''\'' -> '"'''"'
|
||||
(irregex-replace/all (irregex '(>= 2 "'\\''")) _ squash-quotes)
|
||||
;; wrap in single quotes
|
||||
(string-append "'" _ "'")
|
||||
;; kill leading/trailing pair of single quotes
|
||||
(irregex-replace (irregex '(seq bos "''")) _ "")
|
||||
(irregex-replace (irregex '(seq "''" eos)) _ "")))
|
||||
|
||||
(define (string-shell-quote s)
|
||||
"Quote strings for passing through the shell"
|
||||
(cond
|
||||
((zero? (string-length s)) "''")
|
||||
((needs-escape? s) (escape s))
|
||||
(else s)))
|
Loading…
Add table
Add a link
Reference in a new issue