aboutsummaryrefslogtreecommitdiff
path: root/doc/properties.rst
blob: 265e8f87bde0e79683df87c4c9832732065926af (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
Properties
==========

Defining new properties
-----------------------

Defining new properties is like defining new functions: ``DEFPROP``,
``DEFPROPLIST`` and ``DEFPROPSPEC`` are more like ``DEFUN`` than anything
else.  Here is a guide to these three macros; in particular, why you might
need to move from the basic ``DEFPROPLIST`` to either ``DEFPROPSPEC`` or
``DEFPROP``.

``DEFPROPLIST`` and ``DEFPROPSPEC``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

These macros allow you to define properties by combining existing properties.
Most user properties in consfigs should be defineable using one of these: you
should not need to resort to ``DEFPROP``.  And indeed, most new properties to
be added to Consfigurator itself should not need to use ``DEFPROP`` either.

``DEFPROPLIST``
^^^^^^^^^^^^^^^

``DEFPROPLIST`` allows you to define properties very similarly to how you
define hosts with ``DEFHOST``.  You simply supply an unevaluated property
application specification.  Compare:

::

  (defproplist setup-test-pure-wayland ()
    "Set up system for testing programs' support for running Wayland-native."
    (apt:installed "sway")
    (apt:removed "xwayland")
    (systemd:disabled "lightdm"))

  (defhost laptop.example.com ()
    (os:debian-stable "bullseye" :amd64)
    #| ... |#
    (setup-test-pure-wayland)
    ;; Previously we had these; now factored out:
    ;; (apt:installed "sway")
    ;; (apt:removed "xwayland")
    ;; (systemd:disabled "lightdm")
    #| ... |#)

You can use parameters just like with plain functions:

::

  (defproplist setup-test-pure-wayland (compositor)
    "Set up system for testing programs' support for running Wayland-native."
    (apt:installed compositor)
    (apt:removed "xwayland")
    (systemd:disabled "lightdm"))

  (defhost laptop.example.com ()
    (os:debian-stable "bullseye" :amd64)
    #| ... |#
    (setup-test-pure-wayland "sway")
    #| ... #|)

To compute intermediate values, you can use ``&aux`` parameters.  Be aware
that code for ``&optional`` and ``&key`` default values, and for ``&aux``
parameters, may be executed more than once per application of the property, so
it should usually be side effect-free.

``DEFPROPSPEC``
^^^^^^^^^^^^^^^

Unevaluated property application specifications are not always as expressive
as is required.  For example, what if, in our example, we want to allow the
user to supply a list of packages to install, of arbitrary length?  For this
we need ``DEFPROPSPEC``.  The body of this macro is ordinary Lisp code which
should return a property application specification.  In most cases all you
need is a single backquote expression:

::

  (defpropspec setup-test-pure-wayland :posix (&rest compositor-packages)
    "Set up system for testing programs' support for running Wayland-native."
    `(eseqprops
      (apt:installed ,@compositor-packages)
      (apt:removed "xwayland")
      (systemd:disabled "lightdm")))

  (defhost laptop.example.com ()
    (os:debian-stable "bullseye" :amd64)
    #| ... |#
    (setup-test-pure-wayland "sway" "swayidle" "swaylock")
    #| ... #|)

Use this basic shape, with ``ESEQPROPS``, if you want ``DEFPROPLIST`` with
just a little more expressive power -- ``DEFPROPLIST`` has an implicit
``ESEQPROPS``.

If you want to include elements of the property application specification
conditionally, you will need ``DEFPROPSPEC``.  For example, perhaps disabling
lightdm is not appropriate on all hosts to which we want to apply this
property, because not all of them have it installed.  So we might use::

  (defpropspec setup-test-pure-wayland :posix (lightdmp &rest compositor-packages)
    "Set up system for testing programs' support for running Wayland-native."
    `(eseqprops
      (apt:installed ,@compositor-packages)
      (apt:removed "xwayland")
      ,@(and lightdmp '((systemd:disabled "lightdm")))))

  (defhost laptop.example.com ()
    (os:debian-stable "bullseye" :amd64)
    #| ... |#
    (setup-test-pure-wayland t "sway" "swayidle" "swaylock")
    #| ... #|)

The expression ``,@(and x '((y)))`` (or ``,@(and x `((,y)))``) is a Lisp
idiom for conditional inclusion of sublists of backquoted lists.

One disadvantage of moving to ``DEFPROPSPEC`` (aside from just being less
declarative) is that you can't use unevaluated property application
specification-specific features, such as dotted propapp notation, directly
within backquote expressions.  This won't work:

::

  (defpropspec setup-... :lisp (...)
    `(eseqprops
      #| ... |#
      ;; won't work
      (chroot:os-bootstrapped. nil "/srv/chroot/unstable-amd64"
        (os:debian-unstable :amd64)
	(apt:installed "build-essential"))
      #| ... |#))

However, use of the ``PROPAPP`` macro makes it possible to temporarily switch
back to something more like ``DEFPROPLIST``:

::

  (defpropspec setup-... :lisp (...)
    `(eseqprops
      #| ... |#
      ,(propapp (chroot:os-bootstrapped. nil "/srv/chroot/unstable-amd64"
                  (os:debian-unstable :amd64)
	          (apt:installed "build-essential")))
      #| ... |#))

In all these examples, the body of the ``DEFPROPSPEC`` has been a single form.
Sometimes you will need to wrap binding forms around this, or precede it with
other forms, to compute the propspec expression.  In some cases you might not
use backquote at all; see ``INSTALLER:BOOTLOADER-BINARIES-INSTALLED`` for an
example.

Note that arguments to property applications within backquotes are not
evaluated.  Whereas you might use::

  (file:contains-lines "/etc/foo" '("bar" "baz"))

within ``DEFPROPLIST``, within backquotes within ``DEFPROPSPEC`` you would
need::

  (file:contains-lines "/etc/foo" ("bar" "baz"))

DEFPROP
~~~~~~~

Returning to our ``SETUP-TEST-PURE-WAYLAND`` example, it's not great that the
user has to supply a parameter specifying whether or not lightdm needs to be
disabled.  We should just check whether lightdm is installed, and disable it
only if it's there (some sort of check is necessary because
``SYSTEMD:DISABLED`` will fail if there is no service to disable).

``DEFPROP`` is more fundamental than both ``DEFPROPLIST`` and ``DEFPROPSPEC``:
it allows you to supply arbitrary code for each of the property's subroutines
(see :ref:`property-subroutines`, below).  In our example, the crucial
difference is that ``DEFPROPLIST`` and ``DEFPROPSPEC`` permit you to run code
only before any connection to the host is established, which, of course, is
too early to check whether lightdm is installed.  We need to run the check at
``:APPLY`` time:

::

  (defprop %no-lightdm :posix ()
    (:hostattrs (os:required 'os:debianlike))
    (:apply (if (apt:all-installed-p "lightdm")
                (systemd:disabled "lightdm"))
                :no-change))

  (defpropspec setup-test-pure-wayland :posix (&rest compositor-packages)
    "Set up system for testing programs' support for running Wayland-native."
    `(eseqprops
      (apt:installed ,@compositor-packages)
      (apt:removed "xwayland")
      (%no-lightdm)))

Here, we can call the ordinary function ``APT:ALL-INSTALLED-P`` to examine the
actual state of the host.  We have had to introduce two complexities to
account for several implicit features of ``DEFPROPLIST`` and ``DEFPROPSPEC``.
Firstly, we have to specify that the property is only applicable to
Debian-like hosts (in this case we could get away with not doing that because
we apply ``%NO-LIGHTDM`` only within a ``DEFPROPSPEC`` that has other
properties which are applicable only to Debian-like hosts, but that's highly
contingent).  And secondly, we have to take care to return ``:NO-CHANGE`` in
the case that lightdm is not installed.  Both of these things are taken care
of for us with ``DEFPROPLIST`` and ``DEFPROPSPEC``.  Nevertheless, if you need
to examine the actual state of the host, only ``DEFPROP`` will do.

Finally, note how we keep the rest of ``SETUP-TEST-PURE-WAYLAND`` in a
``DEFPROPSPEC``, only dropping down to ``DEFPROP`` for the part that requires
it.  This is good practice.

Names
-----

The names of properties may not end in the character ``.``, because that has a
special meaning in unevaluated property application specifications.

Properties with ``:APPLY`` subroutines occupy the function cells of symbols,
so except in the case of properties with no ``:APPLY`` subroutine, do not try
to define an ordinary function with the same name as a property.

Working directories
-------------------

Except where specified otherwise in property docstrings, relative paths are
relative to the remote home directory.  ``:LISP`` properties may assume they
will be executed in the remote home directory, and ``:POSIX`` properties may
assume that commands will be executed in the remote home directory, and that
relative paths passed to ``READ-REMOTE-FILE`` and ``WRITE-REMOTE-FILE`` are
relative to the remote home directory.  Use ``WITH-REMOTE-CURRENT-DIRECTORY``
to change the remote working directory in a way which ensures it will get
changed back.

.. _property-subroutines:

Property subroutines
--------------------

A property is composed of up to five subroutines, which all have the same
lambda list (take the same arguments).  At least one of ``:hostattrs``,
``:apply`` or ``:unapply`` must be present.

``:desc`` subroutines
~~~~~~~~~~~~~~~~~~~~~

Pure function of the property's arguments which returns a description of
applying the property, to be used in stdout by deployments to inform the user
what work is being done.

``:preprocess`` subroutines
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Pure function executed to modify the arguments that will be passed to the
other subroutines; should return a fresh list of the new arguments.  This
subroutine is called on each atomic property application within a property
application specification before the effects of property combinators have been
applied.  That is, it is effectively executed on atomic property applications
in isolation from the property application specifications in which they occur.

``:hostattrs`` subroutines
~~~~~~~~~~~~~~~~~~~~~~~~~~

Executed in the root Lisp to (i) add static informational attributes of hosts
to which this property is applied or is to be applied; and (ii) check that
applying this property makes sense -- e.g. that we're not trying to install a
package using apt(1) on a FreeBSD host.

Can retrieve existing static informational attributes using ``GET-HOSTATTRS``,
or things which wrap ``GET-HOSTATTRS``, such as ``GET-HOSTNAME``.  Should
signal the condition ``INCOMPATIBLE-PROPERTY`` if existing static
informational attributes indicate that the property should not be applied to
this host.  Can use ``PUSH-HOSTATTRS`` and ``REQUIRE-DATA`` to add new entries
to the host's static information atributes.

Other than as described in the previous paragraph, should be a pure function.
In particular, should not examine the actual state of the host.  Essentially a
conversion of the arguments to the property to appropriate static
informational attributes.

``:check`` subroutines
~~~~~~~~~~~~~~~~~~~~~~

Determine whether or not the property is already applied to the host and
return a generalised boolean indicating such.  Whether or not the ``:apply``
and ``:unapply`` subroutines get called depends on this return value.  If
absent, it is always assumed the property is unapplied, i.e., an attempt to
apply the property will always be made.

``:apply`` and ``:unapply`` subroutines
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Apply or unapply the property.  Should return ``:no-change`` if the property
was already applied; any other return value is interpreted as meaning that the
property was not (fully) applied before we ran, but now it is.  (If the
``:check`` function indicated that neither ``:apply`` nor ``:unapply`` should
be run, then this is equivalent to those subroutines returning ``:no-change``.)

The point of having both these return value semantics and the ``:check``
subroutine is that a property might only be able to check whether it made a
change after trying to apply itself -- it might check whether running a
command actually made a change to a particular file, for example.

Errors in attempting to apply a property are indicated by signalling a
``FAILED-CHANGE`` error condition.

``:posix`` vs. ``:lisp`` properties
-----------------------------------

``:posix`` properties should not make any assumptions about what localhost is
-- they may be running in the root Lisp, but they might be running in a Lisp
image running on an intermediary host, or even on the host to be configured.
They should perform I/O only by calling ``RUN``, ``RUNLINES``,
``READ-REMOTE-FILE``, ``WRITE-REMOTE-FILE``, requesting prerequisite data, and
applying or unapplying other ``:posix`` properties.  Otherwise, they should be
pure functions.

``:lisp`` properties, by contrast, may (and should) assume that they are
running in a Lisp image on the host to which they are to be applied, so they
can perform arbitrary I/O in that context.  They can also make use of ``RUN``,
``RUNLINES``, ``READ-REMOTE-FILE`` and ``WRITE-REMOTE-FILE`` if desired.

``:posix`` properties are characterised by the limited set of ways in which
they perform I/O, not by the use of only facilities defined in the Single UNIX
Specification.  Nevertheless, if a ``:posix`` property or function intended to
be called by ``:posix`` properties uses non-POSIX facilities, but it is not
obvious given the stated purpose of the property that it will do this, it is
good to mention the use of non-POSIX facilities in the docstring.  For
examples of this, see ``USER:HAS-LOGIN-SHELL`` and ``USER:PASSWD-FIELD``.