aboutsummaryrefslogtreecommitdiff
path: root/src/deployment.lisp
blob: a25e4f21dda16002fb48534120f4ef8c18106635 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
;;; Consfigurator -- Lisp declarative configuration management system

;;; Copyright (C) 2021  Sean Whitton <spwhitton@spwhitton.name>

;;; This file 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, or (at your option)
;;; any later version.

;;; This file 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 <http://www.gnu.org/licenses/>.

(in-package :consfigurator)

;;;; Deployments

(defun deploy* (connections host)
  "Execute the deployment which is defined by the pair (CONNECTIONS . HOST).

This is the entry point to Consfigurator's primary loop.  Typically users use
DEPLOY, DEPLOY-THESE, and the function definitions established by DEFDEPLOY,
DEFDEPLOY-THESE, etc., rather than calling this function.  However, code which
programmatically constructs deployments will need to call this function.

Unlike DEPLOY there is no argument to supply additional properties, and there
is no function DEPLOY-THESE*.  This is because merging/replacing properties
into HOST's propspec cannot be done without either the implicit context
established by a consfig (specifically, by IN-CONSFIG) or with an explicit
specification of the SYSTEMS slot of the resultant property application
specification."
  (labels
      ((connect (connections)
	 (destructuring-bind ((type . args) . remaining) connections
	   ;; implementations of ESTABLISH-CONNECTION which call
	   ;; CONTINUE-DEPLOY* or CONTINUE-DEPLOY*-PROGRAM return nil to us
	   (when-let ((*connection*
		       (apply #'establish-connection type remaining args)))
	     (if remaining
		 (connect remaining)
		 (eval-propspec (host-propspec *host*)))
	     (connection-teardown *connection*)))))
    ;; make a partial own-copy of HOST so that connections can add new pieces
    ;; of required prerequisite data; specifically, so that they can request
    ;; the source code of ASDF systems
    (let ((*host* (make-instance 'host :props (host-propspec host)
				       :attrs (copy-list (hostattrs host)))))
      (connect (normalise-connections connections)))))

(defun normalise-connections (connections)
  (let ((chain (loop for connection in (ensure-cons connections)
		     collect (apply #'preprocess-connection-args
				    (ensure-cons connection)))))
    (if (eq :local (caar chain)) chain (cons '(:local) chain))))

(defmacro defdeploy (name (connection host) &body additional-properties)
  "Define a function which does (DEPLOY CONNECTION HOST ADDITIONAL-PROPERTIES).
You can then eval (NAME) to execute this deployment."
  `(defun ,name ()
     (deploy ,connection ,host ,@additional-properties)))

(defmacro defdeploy-these (name (connection host) &body properties)
  "Define a function which does (DEPLOY-THESE CONNECTION HOST PROPERTIES).
You can then eval (NAME) to execute this deployment."
  `(defun ,name ()
     (deploy-these ,connection ,host ,@properties)))

;; TODO some useful combination of DEFHOST and DEFHOSTDEPLOY so that you don't
;; have to use two forms to specify the default connection type.  Probably
;; want argument order to be similar to DEPLOY, DEPLOY-THESE etc.
(defmacro defhostdeploy (connection host-name)
  "Where HOST-NAME names a host as defined with DEFHOST, define a function
which does (deploy CONNECTION (symbol-value HOST)).
You can then eval (HOST-NAME) to execute this deployment.

For example, if you usually deploy properties to athena by SSH,

    (defhost athena.silentflame.com
      (foo)
      (bar)
      ...)

    (defhostdeploy :ssh athena.silentflame.com)

and then you can eval (athena.silentflame.com) to apply athena's properties."
  `(defdeploy ,host-name (,connection ,host-name)))

(defmacro deploy (connection host &body additional-properties)
  "Establish a connection of type CONNECTION to HOST, and apply each of the
host's usual properties, followed by specified by ADDITIONAL-PROPERTIES, an
unevaluated property application specification.

CONNECTION is either a keyword identifying a connection type, or a list
beginning with such a keyword and followed by keyword arguments required to
establish the connection.

Then HOST has all its usual static informational attributes, plus any set by
ADDITIONAL-PROPERTIES.  Static informational attributes set by
ADDITIONAL-PROPERTIES can override the host's usual static informational
attributes, in the same way that later entries in the list of properties
specified in DEFHOST forms can override earlier entries (see DEFHOST's
docstring)."
  (once-only (host)
    (with-gensyms (propspec new-host)
      `(let* ((,propspec ,(props additional-properties))
	      (,new-host
		(make-instance 'host
			       :attrs (copy-list (slot-value ,host 'hostattrs))
			       :props (append-propspecs
				       (slot-value ,host 'propspec)
				       ,propspec))))
	 (let ((*host* ,new-host))
	   (eval-propspec-hostattrs ,propspec))
	 (deploy* ',connection ,new-host)))))

(defmacro deploy-these (connection host &body properties)
  "Establish a connection of type CONNECTION to HOST, and apply each of
the properties specified by PROPERTIES, an unevaluated property application
specification (and not the host's usual properties, unless they also appear
in PROPERTIES).

CONNECTION is either a keyword identifying a connection type, or a list
beginning with such a keyword and followed by keyword arguments required to
establish the connection.

This function is useful to apply one or two properties to a host right now,
e.g. at the REPL when when testing new property definitions.  If HOST is
usually deployed using a :lisp connection, and the property you are testing
is :posix, you might use a connection type like :ssh so that you can quickly
alternate between redefining your work-in-progress property and attempting to
apply it to HOST.

HOST has all its usual static informational attributes, as set by its usual
properties, plus any set by PROPERTIES.  Static informational attributes set
by PROPERTIES can override the host's usual static informational attributes,
in the same way that later entries in the list of properties specified in
DEFHOST forms can override earlier entries (see DEFHOST's docstring)."
  (with-gensyms (propspec new-host)
    `(let* ((,propspec ,(props properties))
	    (,new-host (make-instance 'host
				      :attrs (copy-list
					      (slot-value ,host 'hostattrs))
				      :props ,propspec)))
       (let ((*host* ,new-host))
	 (eval-propspec-hostattrs ,propspec))
       (deploy* ',connection ,new-host))))

(defun continue-deploy* (remaining-connections)
  "Complete the work of an enclosing call to DEPLOY*.

Used by implementations of ESTABLISH-CONNECTION which need to do something like
fork(2) and then return to Consfigurator's primary loop in the child."
  (deploy* remaining-connections *host*))

;; these might need to be special-cased in parsing propspecs, because we
;; probably want it to be easy for the user to pass unevaluated propspecs to
;; these, but we want the evaluation to happen in the root Lisp.
;;
;; also, :HOSTATTRS subroutines of these will want to call
;; PREPROCESS-CONNECTION-ARGS in order to substitute in any values from
;; prerequisite data as early as possible
;;
;; One possibility is to allow :hostattrs subroutines to modify the arguments
;; which will get passed to the other routines, by giving them a special var
;; bound to the current propapp.  Then they could apply
;; preprocess-connection-args to the arguments.  (This suggests that deploy*
;; becomes simply applying the DEPLOYS property to the root Lisp, hrm.)
;;
;; (defprop deploys :posix (connection host &rest additional-properties)
;;   "Execute a Consfigurator deployment.
;;
;; Useful to have one host act a controller, applying properties to other hosts.
;; Also useful to set up VMs, chroots, disk images etc. on localhost.")
;;
;; (defprop deploys-these :posix (connection host &rest properties)
;;   "Execute a deployment, but replace the properties of host with PROPERTIES.
;; This property is to the DEPLOYS property what the DEPLOY-THESE function is to
;; the DEPLOY function.")