From 6808af3becc9986d5dafdcc4412c13a3961a7e64 Mon Sep 17 00:00:00 2001 From: Sean Whitton Date: Wed, 1 Mar 2023 13:59:12 -0700 Subject: doc/: start generating API documentation from docstrings Signed-off-by: Sean Whitton --- .gitignore | 12 ++ consfigurator.mk | 4 + doc/GNUmakefile | 28 ++++ doc/Makefile | 9 -- doc/combinator.rst.in | 2 + doc/conf.py | 2 +- doc/connection.rst.in | 170 ++++++++++++++++++++ doc/connections.rst | 170 -------------------- doc/data.rst | 101 ------------ doc/data.rst.in | 101 ++++++++++++ doc/deployment.rst.in | 2 + doc/host.rst.in | 53 +++++++ doc/hosts.rst | 53 ------- doc/image.rst.in | 2 + doc/index.rst | 43 +++++- doc/properties.rst | 326 --------------------------------------- doc/property.rst.in | 326 +++++++++++++++++++++++++++++++++++++++ doc/propspec.rst.in | 175 +++++++++++++++++++++ doc/propspecs.rst | 175 --------------------- doc/tutorial/disk_image.rst | 4 +- doc/tutorial/os_installation.rst | 4 +- doc/util.rst.in | 2 + emacs/Makefile | 8 +- src/package.lisp | 2 + src/property.lisp | 152 +++++++++++++++++- src/property/chroot.lisp | 7 +- src/property/cron.lisp | 4 +- src/property/disk.lisp | 42 ++--- src/reader.lisp | 4 + 29 files changed, 1102 insertions(+), 881 deletions(-) create mode 100644 consfigurator.mk create mode 100644 doc/GNUmakefile delete mode 100644 doc/Makefile create mode 100644 doc/combinator.rst.in create mode 100644 doc/connection.rst.in delete mode 100644 doc/connections.rst delete mode 100644 doc/data.rst create mode 100644 doc/data.rst.in create mode 100644 doc/deployment.rst.in create mode 100644 doc/host.rst.in delete mode 100644 doc/hosts.rst create mode 100644 doc/image.rst.in delete mode 100644 doc/properties.rst create mode 100644 doc/property.rst.in create mode 100644 doc/propspec.rst.in delete mode 100644 doc/propspecs.rst create mode 100644 doc/util.rst.in diff --git a/.gitignore b/.gitignore index 0972ba5..da0b518 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,15 @@ /doc/_build/ /emacs/put-forms.el /emacs/consfigurator.el + +/doc/connection.rst +/doc/property.rst +/doc/propspec.rst +/doc/host.rst +/doc/combinator.rst +/doc/deployment.rst +/doc/data.rst +/doc/image.rst +/doc/property/*.rst +/doc/util.rst +/doc/util/*.rst diff --git a/consfigurator.mk b/consfigurator.mk new file mode 100644 index 0000000..ad72d65 --- /dev/null +++ b/consfigurator.mk @@ -0,0 +1,4 @@ +SBCL = sbcl --disable-debugger --eval '(require "asdf")' --eval \ + '(let ((asdf:*user-cache* "/tmp") \ + (asdf:*central-registry* (list (truename "..")))) \ + (asdf:load-system "consfigurator"))' diff --git a/doc/GNUmakefile b/doc/GNUmakefile new file mode 100644 index 0000000..53d9b35 --- /dev/null +++ b/doc/GNUmakefile @@ -0,0 +1,28 @@ +include ../consfigurator.mk + +LISP = $(wildcard ../src/property/*.lisp ../src/util/*.lisp) \ + ../src/connection.lisp ../src/property.lisp ../src/propspec.lisp \ + ../src/host.lisp ../src/combinator.lisp ../src/deployment.lisp \ + ../src/data.lisp ../src/image.lisp ../src/util.lisp +PAGES = $(patsubst ../src/%,%,$(LISP:lisp=rst)) + +.PHONY: all +all: html info + +.PHONY: html info +html info: $(PAGES) conf.py $(wildcard *.rst */*.rst) + sphinx-build -M $@ . _build + +$(PAGES) &: $(wildcard *.rst.in */*.rst.in) $(LISP) + $(SBCL) --eval "(mapc #'consfigurator::build-manual-rst \ + uiop:*command-line-arguments*)" --quit $(PAGES) + +.PHONY: clean +clean: + rm -rf _build + rm -f $(PAGES) + +# property.lisp contains the definition of BUILD-MANUAL-RST. +.SECONDEXPANSION: +%.rst: $$(wildcard $$*.rst.in) ../src/$$(*D)/$$(*F).lisp ../src/property.lisp + $(SBCL) --eval '(consfigurator::build-manual-rst "$@")' --quit diff --git a/doc/Makefile b/doc/Makefile deleted file mode 100644 index 50d9481..0000000 --- a/doc/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -SPHINXOPTS ?= -SPHINXBUILD ?= sphinx-build -SOURCEDIR = . -BUILDDIR = _build - -# Catch-all target: route all unknown targets to Sphinx using the new -# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). -%: Makefile - @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/doc/combinator.rst.in b/doc/combinator.rst.in new file mode 100644 index 0000000..d54fcdf --- /dev/null +++ b/doc/combinator.rst.in @@ -0,0 +1,2 @@ +Property combinators +==================== diff --git a/doc/conf.py b/doc/conf.py index 984073d..99da27e 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -18,7 +18,7 @@ # -- Project information ----------------------------------------------------- project = 'Consfigurator' -copyright = '2020-2022, Sean Whitton' +copyright = '2015-2018, 2020-2023, Sean Whitton, 2021-2022 David Bremner' author = 'Sean Whitton' # The full version, including alpha/beta/rc tags diff --git a/doc/connection.rst.in b/doc/connection.rst.in new file mode 100644 index 0000000..b958139 --- /dev/null +++ b/doc/connection.rst.in @@ -0,0 +1,170 @@ +Connections +=========== + +Connection chain specifications +------------------------------- + +The normalised form is a list of lists, where the car of each inner list is a +keyword symbol identifying a connection type, and the cdr of each inner list +is arguments to that connection, e.g.:: + + ((:ssh :foo foo :bar bar) (:sudo :baz baz :quux quux)) + +There are two notational simplifications permitted when passing connection +chain specifications to properties, functions and macros. Firstly, for each +inner list which contains only a single keyword identifying a connection type +and no arguments, this list may be replaced with only the keyword identifying +the connection type, e.g.:: + + (:ssh (:sudo :baz baz :quux quux)) + +Secondly, when there is exactly one connection and it takes no arguments, you +may specify just the keyword identifying the connection type, e.g. ``:ssh``. + +Note that if there is a single connection but it takes arguments, you will +need two sets of parentheses, i.e.:: + + ((:ssh :foo foo :bar bar)) + +rather than:: + + (:ssh :foo foo :bar bar) + +which is invalid. + +Defining connection types +------------------------- + +The code which establishes connections (i.e., implementations of the +``ESTABLISH-CONNECTION`` generic) is like code in ``:posix`` properties -- it +should restrict its I/O to ``RUN``, ``RUNLINES``, ``READ-REMOTE-FILE`` and +``WRITE-REMOTE-FILE``, functions which access the currently active connection. +This is in order to permit the arbitrary nesting of connections. If +establishing a connection really does require more I/O, such as in the case of +``:CHROOT.FORK`` connections, code can call ``LISP-CONNECTION-P``, and either +signal an error, or fall back to another connection type. + +Connection attributes ("connattrs") +----------------------------------- + +Information about hosts which cannot be known without looking at the host, or +for other reasons should not be recorded in consfigs, can be stored as +connection attributes, associated with the current connection. Typically +property combinators set and unset connattrs, and property ``:APPLY`` and +``:UNAPPLY`` subroutines read them. They can be used to create context for +the application of properties. Connection attributes are stored in a plist. +Property combinators use the ``WITH-CONNATTRS`` macro to set them, and +properties use ``GET-CONNATTR`` to read them. + +Like hostattrs, connection attributes are identified by keywords for connattrs +which are expected to be used in many contexts, and by other symbols for +connattrs which will be used only among a co-operating group of properties and +property combinators. However, unlike hostattrs, each connattr need not be a +list to which new items are pushed. + +By default the list of connattrs is reset when establishing a new connection +within the context of an existing connection. However, for some connattrs it +makes sense to propagate them along to the new connection. For example, a +list of connected hardware of a particular type might still be useful in the +context of a connection which chroots, as /dev might still give access to this +hardware. Implementations of the ``PROPAGATE-CONNATTR`` generic function can +be used to enable propagation where it makes sense. Methods can copy and +modify connattrs as appropriate; in the chroot example, paths might be updated +so that they are relative to the new filesystem root. + +The propagation of connattrs is currently limited to the establishing of +connections within the same Lisp image; i.e., connection types which start up +new Lisp images never propagate any existing connattrs. + +Reserved names for connection attributes +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The semantics of connattrs identified by keywords are documented here. + +[list incomplete] + +Notes on particular connection types +------------------------------------ + +``:SUDO`` +~~~~~~~~~ + +Passing the ``:AS`` option to this connection means that Consfigurator will +assume a password is required for all commands, and not passing ``:AS`` means +that Consfigurator will assume a password is not required for any commands. +Consfigurator sends your sudo password on stdin, so if the assumption that a +password is required is violated, your sudo password will end up in the stdin +to whatever command is being run using sudo. There is no facility for +directly passing in a passphrase; you must use ``:AS`` to obtain passwords +from sources of prerequisite data. The passphrase will be written to a +private temporary file which is deleted when the ``:SUDO`` connection is torn +down. + +If any connection types which start up remote Lisp images occur before a +``:SUDO`` entry in your connection chain, ``ESTABLISH-CONNECTION`` will need +to inform the newly-started remote Lisp image of any sudo passwords needed for +establishing the remaining hops. Depending on how the connection type feeds +instructions to the remote Lisp image, this may involve writing your sudo +password to a file under ``~/.cache`` on the machine which runs the remote +Lisp image. At least ``:SBCL`` avoids this by sending your password in on +stdin. Even with ``:SBCL``, if the Lisp image dumps a copy of itself to disk, +e.g. for the purposes of cronjobs, then your sudo password will be contained +in that saved image. Typically a ``:SUDO`` connection hop is used before hops +which start up remote Lisp images, so these issues will not arise for most +users. + +``:SETUID`` +~~~~~~~~~~~ + +As this connection type subclasses FORK-CONNECTION, it shouldn't leak +root-accessible secrets to a process running under the unprivileged UID. +However, when using the :AS connection type, the unprivileged process will +have access to all the hostattrs of the host. Potentially, something like +ptrace(2) could be used to extract those. But hostattrs should not normally +contain any secrets, and at least on Linux, the unprivileged process will not +be ptraceable because it was once privileged. + +Connections which fork: ``:CHROOT.FORK``, ``:SETUID`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +These connection types cannot be used as the first hop, i.e., directly out of +the root Lisp. This is because they must call fork(2), and Consfigurator only +makes this system call in contexts in which there shouldn't ever be more than +one thread (excluding Lisp implementation finaliser threads and the like). +The root Lisp is not such a context, because it is often multithreaded due to +the use of SLIME. This is, however, not much of a restriction, because +typically the root Lisp is running under a UID which cannot use system calls +like chroot(2) and setuid(2) anyway. Thus, typical usage on localhost would +be something like:: + + (deploy (:sudo :sbcl (:chroot.fork :into "...")) ...) + +Connections which use setns(2) to enter containers +-------------------------------------------------- + +When the current connection is a Lisp-type connection, connection types which +enter Linux containers, such as ``:LXC`` and ``:SYSTEMD-MACHINED``, invoke the +setns(2) system call directly. The implementation of this is the connection +type ``CONSFIGURATOR.CONNECTION.LINUX-NAMESPACE::SETNS``. The implementation +of the ``POST-FORK`` generic for that connection type is structured similarly +to the nsenter(1) command from util-linux. This has the advantage that +``CONSFIGURATOR.CONNECTION.LINUX-NAMESPACE::SETNS`` should be reusable for +implementing connection types which enter other kinds of Linux container; the +container runtime-specific code is limited to determining the PID of the +container's leading process. However, there are some security implications to +this approach. + +Firstly, the current implementation does not join the control group of the +container's leading process, and thus the Consfigurator process running inside +the container is not subject to resource limits applied to the container. It +might be possible for a process in the container to exploit this to escape its +resource limits. + +Secondly, we do not attempt to enter the LSM security context of the +container, such as the container's SELinux execution context or AppArmor +profile. This is because LSM usage is container runtime-specific. In the +case of unprivileged containers which make use of user namespaces, however, +failing to enter the LSM security context typically does not breach container +security. For such containers, employment of an LSM serves as an extra layer +of protection against kernel exploits, not as part of the enforcement of the +container's basic security model. diff --git a/doc/connections.rst b/doc/connections.rst deleted file mode 100644 index b958139..0000000 --- a/doc/connections.rst +++ /dev/null @@ -1,170 +0,0 @@ -Connections -=========== - -Connection chain specifications -------------------------------- - -The normalised form is a list of lists, where the car of each inner list is a -keyword symbol identifying a connection type, and the cdr of each inner list -is arguments to that connection, e.g.:: - - ((:ssh :foo foo :bar bar) (:sudo :baz baz :quux quux)) - -There are two notational simplifications permitted when passing connection -chain specifications to properties, functions and macros. Firstly, for each -inner list which contains only a single keyword identifying a connection type -and no arguments, this list may be replaced with only the keyword identifying -the connection type, e.g.:: - - (:ssh (:sudo :baz baz :quux quux)) - -Secondly, when there is exactly one connection and it takes no arguments, you -may specify just the keyword identifying the connection type, e.g. ``:ssh``. - -Note that if there is a single connection but it takes arguments, you will -need two sets of parentheses, i.e.:: - - ((:ssh :foo foo :bar bar)) - -rather than:: - - (:ssh :foo foo :bar bar) - -which is invalid. - -Defining connection types -------------------------- - -The code which establishes connections (i.e., implementations of the -``ESTABLISH-CONNECTION`` generic) is like code in ``:posix`` properties -- it -should restrict its I/O to ``RUN``, ``RUNLINES``, ``READ-REMOTE-FILE`` and -``WRITE-REMOTE-FILE``, functions which access the currently active connection. -This is in order to permit the arbitrary nesting of connections. If -establishing a connection really does require more I/O, such as in the case of -``:CHROOT.FORK`` connections, code can call ``LISP-CONNECTION-P``, and either -signal an error, or fall back to another connection type. - -Connection attributes ("connattrs") ------------------------------------ - -Information about hosts which cannot be known without looking at the host, or -for other reasons should not be recorded in consfigs, can be stored as -connection attributes, associated with the current connection. Typically -property combinators set and unset connattrs, and property ``:APPLY`` and -``:UNAPPLY`` subroutines read them. They can be used to create context for -the application of properties. Connection attributes are stored in a plist. -Property combinators use the ``WITH-CONNATTRS`` macro to set them, and -properties use ``GET-CONNATTR`` to read them. - -Like hostattrs, connection attributes are identified by keywords for connattrs -which are expected to be used in many contexts, and by other symbols for -connattrs which will be used only among a co-operating group of properties and -property combinators. However, unlike hostattrs, each connattr need not be a -list to which new items are pushed. - -By default the list of connattrs is reset when establishing a new connection -within the context of an existing connection. However, for some connattrs it -makes sense to propagate them along to the new connection. For example, a -list of connected hardware of a particular type might still be useful in the -context of a connection which chroots, as /dev might still give access to this -hardware. Implementations of the ``PROPAGATE-CONNATTR`` generic function can -be used to enable propagation where it makes sense. Methods can copy and -modify connattrs as appropriate; in the chroot example, paths might be updated -so that they are relative to the new filesystem root. - -The propagation of connattrs is currently limited to the establishing of -connections within the same Lisp image; i.e., connection types which start up -new Lisp images never propagate any existing connattrs. - -Reserved names for connection attributes -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -The semantics of connattrs identified by keywords are documented here. - -[list incomplete] - -Notes on particular connection types ------------------------------------- - -``:SUDO`` -~~~~~~~~~ - -Passing the ``:AS`` option to this connection means that Consfigurator will -assume a password is required for all commands, and not passing ``:AS`` means -that Consfigurator will assume a password is not required for any commands. -Consfigurator sends your sudo password on stdin, so if the assumption that a -password is required is violated, your sudo password will end up in the stdin -to whatever command is being run using sudo. There is no facility for -directly passing in a passphrase; you must use ``:AS`` to obtain passwords -from sources of prerequisite data. The passphrase will be written to a -private temporary file which is deleted when the ``:SUDO`` connection is torn -down. - -If any connection types which start up remote Lisp images occur before a -``:SUDO`` entry in your connection chain, ``ESTABLISH-CONNECTION`` will need -to inform the newly-started remote Lisp image of any sudo passwords needed for -establishing the remaining hops. Depending on how the connection type feeds -instructions to the remote Lisp image, this may involve writing your sudo -password to a file under ``~/.cache`` on the machine which runs the remote -Lisp image. At least ``:SBCL`` avoids this by sending your password in on -stdin. Even with ``:SBCL``, if the Lisp image dumps a copy of itself to disk, -e.g. for the purposes of cronjobs, then your sudo password will be contained -in that saved image. Typically a ``:SUDO`` connection hop is used before hops -which start up remote Lisp images, so these issues will not arise for most -users. - -``:SETUID`` -~~~~~~~~~~~ - -As this connection type subclasses FORK-CONNECTION, it shouldn't leak -root-accessible secrets to a process running under the unprivileged UID. -However, when using the :AS connection type, the unprivileged process will -have access to all the hostattrs of the host. Potentially, something like -ptrace(2) could be used to extract those. But hostattrs should not normally -contain any secrets, and at least on Linux, the unprivileged process will not -be ptraceable because it was once privileged. - -Connections which fork: ``:CHROOT.FORK``, ``:SETUID`` -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -These connection types cannot be used as the first hop, i.e., directly out of -the root Lisp. This is because they must call fork(2), and Consfigurator only -makes this system call in contexts in which there shouldn't ever be more than -one thread (excluding Lisp implementation finaliser threads and the like). -The root Lisp is not such a context, because it is often multithreaded due to -the use of SLIME. This is, however, not much of a restriction, because -typically the root Lisp is running under a UID which cannot use system calls -like chroot(2) and setuid(2) anyway. Thus, typical usage on localhost would -be something like:: - - (deploy (:sudo :sbcl (:chroot.fork :into "...")) ...) - -Connections which use setns(2) to enter containers --------------------------------------------------- - -When the current connection is a Lisp-type connection, connection types which -enter Linux containers, such as ``:LXC`` and ``:SYSTEMD-MACHINED``, invoke the -setns(2) system call directly. The implementation of this is the connection -type ``CONSFIGURATOR.CONNECTION.LINUX-NAMESPACE::SETNS``. The implementation -of the ``POST-FORK`` generic for that connection type is structured similarly -to the nsenter(1) command from util-linux. This has the advantage that -``CONSFIGURATOR.CONNECTION.LINUX-NAMESPACE::SETNS`` should be reusable for -implementing connection types which enter other kinds of Linux container; the -container runtime-specific code is limited to determining the PID of the -container's leading process. However, there are some security implications to -this approach. - -Firstly, the current implementation does not join the control group of the -container's leading process, and thus the Consfigurator process running inside -the container is not subject to resource limits applied to the container. It -might be possible for a process in the container to exploit this to escape its -resource limits. - -Secondly, we do not attempt to enter the LSM security context of the -container, such as the container's SELinux execution context or AppArmor -profile. This is because LSM usage is container runtime-specific. In the -case of unprivileged containers which make use of user namespaces, however, -failing to enter the LSM security context typically does not breach container -security. For such containers, employment of an LSM serves as an extra layer -of protection against kernel exploits, not as part of the enforcement of the -container's basic security model. diff --git a/doc/data.rst b/doc/data.rst deleted file mode 100644 index ae657c6..0000000 --- a/doc/data.rst +++ /dev/null @@ -1,101 +0,0 @@ -Prerequisite data -================= - -Naming ------- - -An item of prerequisite data is identified by two strings, called ``IDEN1`` -and ``IDEN2``; together these are the *prerequisite data identifiers* for an -item of prerequisite data. Typically ``IDEN1`` specifies the context in which -the data is relevant, and ``IDEN2`` identifies the data within its context. -``IDEN2`` is very often the filename in which the prerequisite data will -eventually be stored. It might also be a human-readable string describing the -purpose of the data. The following are the valid forms of ``IDEN1``, and -their meanings. - -- ``(HOSTNAME . PATH)`` means the data that should be uploaded to ``PATH`` on - ``HOSTNAME`` (and usually nowhere else, except in the case of, e.g., a - public key). ``PATH`` must be absolute, not relative. - -- ``("--lisp-system" . SYSTEM)`` means the data is Lisp code which, when - loaded, defines the packages and symbols contained in the ASDF system - ``SYSTEM`` - -- ``("--user-passwd--HOSTNAME" . USER)`` means the data is the password for - user ``USER`` on ``HOSTNAME`` - -- ``("--git-snapshot" . NAME)`` means the data is a snapshot of a git repo - identified by ``NAME``; see ``DATA.GIT-SNAPSHOT`` - -- ``("--pgp-pubkey" . FINGERPRINT)`` means the/a OpenPGP public key with - fingerprint FINGERPRINT, ASCII-armoured - -- ``("--pgp-seckey" . FINGERPRINT)`` means the/a OpenPGP secret key with - fingerprint FINGERPRINT, ASCII-armoured - -- ``("--luks-passphrase" . VOLUME-LABEL)`` means a LUKS passphrase for volume - with label ``VOLUME-LABEL``. - -- Any other ``IDEN1`` beginning with exactly two hyphens is reserved for - future use. - -- ``(_CONTEXT . ITEM)`` is an arbitrary prerequisite data context named - ``CONTEXT``; typically ``CONTEXT`` will be a network or grouping name, - rather than referring to a single host. ``ITEM`` might be a path or some - other identifier. Reserved for consfigs; will not be used by property - definitions included with Consfigurator, and should not be used by third - party extensions. - -- ``(---CONTEXT . ITEM)`` is, similarly, an arbitrary prerequisite data - context named ``CONTEXT``. This form is intended for contexts similar to - the reserved names beginning with two hyphens: types of information rather - than site-local network or grouping names. This form will not be used by - property definitions included with Consfigurator, but may be used by both - consfigs and third party extensions. - -Any other forms are invalid. In particular, an ``IDEN1`` that is not a valid -hostname and does not begin with a hyphen or an underscore must not be used. - -Mechanics ---------- - -Properties declare that they need certain pieces of prerequisite data by -adding static informational attributes, and a deployment of those properties -will make an attempt to provide the data. Properties then either call the -``GET-DATA-STREAM`` function or the ``GET-DATA-STRING`` function, or depend on -the ``DATA-UPLOADED`` property, to get access to the requested data. - -A Lisp connection gathers all needed prerequisite data once at the beginning, -and copies it to an on-disk cache inside the home directory of the remote UID -which will run the Lisp image. A POSIX connection only attempts to obtain -prerequisite data when a property's check function indicates the property is -not already applied. - -Sources of prerequisite data ----------------------------- - -Sources of prerequisite data register two functions. The second returns -either a string of the prerequisite data itself, or a path to a file -containing the data. The first returns the latest version number of the data -that source is able to provide -- i.e., the version number of the data that -the second function would return if called. - -Consfigurator will call the first function to find out if it needs to call the -first rather than just using its caches. The first function should return nil -if it can't obtain the prerequisite data on this host, perhaps because it -can't decrypt the store. If a prerequisite data source wants to effectively -bypass caching and provide fresh data every time Consfigurator deploys the -host, it can use ``GET-UNIVERSAL-TIME`` as its first function. - -Versions are compared using ``UIOP:VERSION<`` and ``UIOP:VERSION<=``. - -Security issues ---------------- - -Nothing is done to prevent prerequisite data being swapped out, so ensure your -swap is encrypted. - -Certain connection types require storing unencrypted copies of prerequisite -data under ``~/.cache/consfigurator/data``. Consfigurator only stores data -there when it has to, only the subset of the data that has to be uploaded for -the requested deployment to be successful, and never in the root Lisp. diff --git a/doc/data.rst.in b/doc/data.rst.in new file mode 100644 index 0000000..ae657c6 --- /dev/null +++ b/doc/data.rst.in @@ -0,0 +1,101 @@ +Prerequisite data +================= + +Naming +------ + +An item of prerequisite data is identified by two strings, called ``IDEN1`` +and ``IDEN2``; together these are the *prerequisite data identifiers* for an +item of prerequisite data. Typically ``IDEN1`` specifies the context in which +the data is relevant, and ``IDEN2`` identifies the data within its context. +``IDEN2`` is very often the filename in which the prerequisite data will +eventually be stored. It might also be a human-readable string describing the +purpose of the data. The following are the valid forms of ``IDEN1``, and +their meanings. + +- ``(HOSTNAME . PATH)`` means the data that should be uploaded to ``PATH`` on + ``HOSTNAME`` (and usually nowhere else, except in the case of, e.g., a + public key). ``PATH`` must be absolute, not relative. + +- ``("--lisp-system" . SYSTEM)`` means the data is Lisp code which, when + loaded, defines the packages and symbols contained in the ASDF system + ``SYSTEM`` + +- ``("--user-passwd--HOSTNAME" . USER)`` means the data is the password for + user ``USER`` on ``HOSTNAME`` + +- ``("--git-snapshot" . NAME)`` means the data is a snapshot of a git repo + identified by ``NAME``; see ``DATA.GIT-SNAPSHOT`` + +- ``("--pgp-pubkey" . FINGERPRINT)`` means the/a OpenPGP public key with + fingerprint FINGERPRINT, ASCII-armoured + +- ``("--pgp-seckey" . FINGERPRINT)`` means the/a OpenPGP secret key with + fingerprint FINGERPRINT, ASCII-armoured + +- ``("--luks-passphrase" . VOLUME-LABEL)`` means a LUKS passphrase for volume + with label ``VOLUME-LABEL``. + +- Any other ``IDEN1`` beginning with exactly two hyphens is reserved for + future use. + +- ``(_CONTEXT . ITEM)`` is an arbitrary prerequisite data context named + ``CONTEXT``; typically ``CONTEXT`` will be a network or grouping name, + rather than referring to a single host. ``ITEM`` might be a path or some + other identifier. Reserved for consfigs; will not be used by property + definitions included with Consfigurator, and should not be used by third + party extensions. + +- ``(---CONTEXT . ITEM)`` is, similarly, an arbitrary prerequisite data + context named ``CONTEXT``. This form is intended for contexts similar to + the reserved names beginning with two hyphens: types of information rather + than site-local network or grouping names. This form will not be used by + property definitions included with Consfigurator, but may be used by both + consfigs and third party extensions. + +Any other forms are invalid. In particular, an ``IDEN1`` that is not a valid +hostname and does not begin with a hyphen or an underscore must not be used. + +Mechanics +--------- + +Properties declare that they need certain pieces of prerequisite data by +adding static informational attributes, and a deployment of those properties +will make an attempt to provide the data. Properties then either call the +``GET-DATA-STREAM`` function or the ``GET-DATA-STRING`` function, or depend on +the ``DATA-UPLOADED`` property, to get access to the requested data. + +A Lisp connection gathers all needed prerequisite data once at the beginning, +and copies it to an on-disk cache inside the home directory of the remote UID +which will run the Lisp image. A POSIX connection only attempts to obtain +prerequisite data when a property's check function indicates the property is +not already applied. + +Sources of prerequisite data +---------------------------- + +Sources of prerequisite data register two functions. The second returns +either a string of the prerequisite data itself, or a path to a file +containing the data. The first returns the latest version number of the data +that source is able to provide -- i.e., the version number of the data that +the second function would return if called. + +Consfigurator will call the first function to find out if it needs to call the +first rather than just using its caches. The first function should return nil +if it can't obtain the prerequisite data on this host, perhaps because it +can't decrypt the store. If a prerequisite data source wants to effectively +bypass caching and provide fresh data every time Consfigurator deploys the +host, it can use ``GET-UNIVERSAL-TIME`` as its first function. + +Versions are compared using ``UIOP:VERSION<`` and ``UIOP:VERSION<=``. + +Security issues +--------------- + +Nothing is done to prevent prerequisite data being swapped out, so ensure your +swap is encrypted. + +Certain connection types require storing unencrypted copies of prerequisite +data under ``~/.cache/consfigurator/data``. Consfigurator only stores data +there when it has to, only the subset of the data that has to be uploaded for +the requested deployment to be successful, and never in the root Lisp. diff --git a/doc/deployment.rst.in b/doc/deployment.rst.in new file mode 100644 index 0000000..f61b83b --- /dev/null +++ b/doc/deployment.rst.in @@ -0,0 +1,2 @@ +Deployments +=========== diff --git a/doc/host.rst.in b/doc/host.rst.in new file mode 100644 index 0000000..af18d0d --- /dev/null +++ b/doc/host.rst.in @@ -0,0 +1,53 @@ +Hosts +===== + +The HOSTATTRS list +------------------ + +This is a plist of lists, such that for each keyword symbol identifying a type +of static informational attribute ("hostattr"), there is a list of entries. +Property ``:HOSTATTRS`` subroutines may only push new entries to the front of +each such sublist, using the function ``PUSH-HOSTATTR``. Use +``GET-HOSTATTRS`` and ``GET-HOSTATTRS-CAR`` to access the lists. + +The relationship between older and newer entries in the sublist for each type +of static informational attribute is attribute-dependent. For example, for +the ``:DATA`` attribute, the order of entries does not matter and each item is +equally a piece of prerequisite data required by the host's properties. For +other kinds of attribute, it might be that later entries supercede earlier +ones, or that the entries should be combined in some way. Property ``:APPLY`` +subroutines decide how to interpret each type of static informational +attribute. + +Reserved names for static informational attributes +-------------------------------------------------- + +For attributes that will only be used among a co-ordinating group of +properties, use a non-keyword symbol, whose package is one in which some or +all of those properties are defined. This minimises the risk of any clashes. +Many attributes, however, will be shared across properties, and should use +keyword symbols. The semantics of these attributes are documented here: + +- ``:HOSTNAME``: the host's hostname -- if the host has a domain name, then + the FQDN, not just the part before the first dot + +- ``:ALIASES``: see ``NETWORK:ALIASES`` + +- ``:IPV4``: the host's public IPv4 addresses + +- ``:IPV6``: the host's public IPv6 addresses + +- ``:DATA``: items of prerequisite data required by the host + +- ``:OS``: the operating system of the host + +- ``:APT.MIRRORS``: for hosts running Debian or a Debian derivative, the + host's preferred apt mirrors + +Host designators +---------------- + +A string designates a host with that hostname and no properties. Using +strings to designate hosts is not valid in all contexts -- some macros and +properties where it might be useful to pass a string instead of a ``HOST`` +object call ``ENSURE-HOST`` to convert, but this is not done everywhere. diff --git a/doc/hosts.rst b/doc/hosts.rst deleted file mode 100644 index af18d0d..0000000 --- a/doc/hosts.rst +++ /dev/null @@ -1,53 +0,0 @@ -Hosts -===== - -The HOSTATTRS list ------------------- - -This is a plist of lists, such that for each keyword symbol identifying a type -of static informational attribute ("hostattr"), there is a list of entries. -Property ``:HOSTATTRS`` subroutines may only push new entries to the front of -each such sublist, using the function ``PUSH-HOSTATTR``. Use -``GET-HOSTATTRS`` and ``GET-HOSTATTRS-CAR`` to access the lists. - -The relationship between older and newer entries in the sublist for each type -of static informational attribute is attribute-dependent. For example, for -the ``:DATA`` attribute, the order of entries does not matter and each item is -equally a piece of prerequisite data required by the host's properties. For -other kinds of attribute, it might be that later entries supercede earlier -ones, or that the entries should be combined in some way. Property ``:APPLY`` -subroutines decide how to interpret each type of static informational -attribute. - -Reserved names for static informational attributes --------------------------------------------------- - -For attributes that will only be used among a co-ordinating group of -properties, use a non-keyword symbol, whose package is one in which some or -all of those properties are defined. This minimises the risk of any clashes. -Many attributes, however, will be shared across properties, and should use -keyword symbols. The semantics of these attributes are documented here: - -- ``:HOSTNAME``: the host's hostname -- if the host has a domain name, then - the FQDN, not just the part before the first dot - -- ``:ALIASES``: see ``NETWORK:ALIASES`` - -- ``:IPV4``: the host's public IPv4 addresses - -- ``:IPV6``: the host's public IPv6 addresses - -- ``:DATA``: items of prerequisite data required by the host - -- ``:OS``: the operating system of the host - -- ``:APT.MIRRORS``: for hosts running Debian or a Debian derivative, the - host's preferred apt mirrors - -Host designators ----------------- - -A string designates a host with that hostname and no properties. Using -strings to designate hosts is not valid in all contexts -- some macros and -properties where it might be useful to pass a string instead of a ``HOST`` -object call ``ENSURE-HOST`` to convert, but this is not done everywhere. diff --git a/doc/image.rst.in b/doc/image.rst.in new file mode 100644 index 0000000..bbe9f3a --- /dev/null +++ b/doc/image.rst.in @@ -0,0 +1,2 @@ +Remote Lisp images +================== diff --git a/doc/index.rst b/doc/index.rst index 96430cf..5c2f0c8 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -3,21 +3,48 @@ Consfigurator user's manual .. toctree:: :maxdepth: 1 - :caption: Contents: introduction installation - tutorial/disk_image - tutorial/os_installation - connections - properties - hosts - propspecs - data pitfalls news ideas +.. toctree:: + :maxdepth: 1 + :caption: Tutorials + + tutorial/disk_image + tutorial/os_installation + +.. toctree:: + :maxdepth: 1 + :caption: Core + + connection + property + propspec + host + combinator + deployment + data + image + +.. toctree:: + :maxdepth: 1 + :caption: Properties packages + :glob: + + property/* + +.. toctree:: + :maxdepth: 1 + :caption: Utilities packages + :glob: + + util + util/* + Indices and search ================== diff --git a/doc/properties.rst b/doc/properties.rst deleted file mode 100644 index 265e8f8..0000000 --- a/doc/properties.rst +++ /dev/null @@ -1,326 +0,0 @@ -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``. diff --git a/doc/property.rst.in b/doc/property.rst.in new file mode 100644 index 0000000..265e8f8 --- /dev/null +++ b/doc/property.rst.in @@ -0,0 +1,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``. diff --git a/doc/propspec.rst.in b/doc/propspec.rst.in new file mode 100644 index 0000000..bec4e6e --- /dev/null +++ b/doc/propspec.rst.in @@ -0,0 +1,175 @@ +Property application specifications ("propspecs") +================================================= + +If you understand the difference between propspecs and unevaluated propspecs +well enough to be able to read and understand most of the uses of +``DEFPROPLIST`` in Consfigurator's own source, then you can probably get away +without working through this section of the manual. You can build your own +propspecs by taking uses of ``DEFPROPLIST`` in Consfigurator's own source as +models. What follows is mainly for those who want to implement new property +combinators, though normal usage of Consfigurator should not require doing +that. + +Purposes +-------- + +Property application specifications are a domain-specific language which +enables + +1. combining properties to make new properties, e.g. with ``DEFPROPLIST``; and + +2. representing the properties which are to be applied to a host in a form + which can be serialised using the Lisp printer. + +Definitions +----------- + +An *atomic property application* ("propapp") is a list satisfying the lambda +list ``(PROPERTY &rest ARGS)`` where ``PROPERTY`` is a symbol naming a +property and ``ARGS`` satisfies that property's lambda list, or the empty +list, which means a no-op property. A *property combinator* is a function +which takes at least one propapp as one of its arguments and returns a single +propapp, or a macro which takes at least propapp as one of its arguments and +returns an expression which yields a single propapp (in the return values of +property combinators, often ``PROPERTY`` will be a gensym). A *property +application specification* is + +i. a readably printable Lisp expression, **P**, known as a *property + application specification expression*, which contains + + a. propapps combined using property combinators; but + + b. no free variables except as bound by macro property combinators, i.e., + such that the evaluation of **P** would not be affected by any dynamic + or lexical context; where + + c. evaluating **P** means an operation equivalent to quoting each propapp + occurring in **P**, and then passing the whole thing as the only + argument to ``EVAL``; and + +ii. a list of ASDF systems **S**; such that + +iii. evaluating **P** yields a propapp ``(PROPERTY &rest ARGS)``; such that + +iv. when each of **S** are loaded, calling ``PROPERTY`` with ``ARGS`` will + apply each of the propapps occurring in **P** in accordance with the + combinators occuring in **P**. + +A property application specification should not contain any binding forms +except as will be eliminated after macroexpansion. + +We do not always cleanly distinguish between property application +specifications and property application specification expressions, and the +abbreviation "propspec" refers to both. It is usually clear from the context +which is meant. Within Lisp they are easy to distinguish because property +application specifications are CLOS objects, whereas property application +specification expressions are just conses. + +An *unevaluated property application specification expression* ("unevaluated +propspec") is a list of Lisp forms which can be converted into a property +application specification expression by + +i. replacing each argument in each propapp with the result of evaluating that + argument in the dynamic and lexical contexts in which the unevaluated + propspec occurs; and + +ii. wrapping a single property combinator which takes a variable number of + arguments around the resulting list of Lisp forms, usually ``SEQPROPS`` or + ``ESEQPROPS``. + +That is, the arguments to propapps in unevaluated propspecs are forms which +will produce the arguments to the properties, rather than those arguments +themselves. There is one special case: if the symbol naming the property ends +with the character ``.``, then the propapp is replaced with a new one +according to the following rules (the "dotted propapp rules"): + +i. the property to be applied is the property named by the symbol in the same + package and with the same name as the first element of the propapp, but + with the trailing period removed from the name; + +ii. the first argument is not evaluated if it is a list whose first element is + a keyword, or a if it is a list of lists where the first element of the + first list is a keyword; and + +iii. the last required or optional argument to the property to be applied is + converted to a ``&rest`` parameter and its elements are treated as an + embedded unevaluated propspec, which is recursively converted into a + propspec according to the usual evaluation rules, where the surrounding + combinator is ``ESEQPROPS``. + +The dotted propapp rules are implemented by ``DEFPROP`` and ``DEFPROPLIST``, +which define a macro with the dotted name which performs the replacement. + +Available combinators +--------------------- + +``SEQPROPS`` +~~~~~~~~~~~~~ + +Function. Applies each of the propapps passed as arguments without stopping +if any of them signal a failed change. Semantically, the propapps are ordered +with respect to their ``:HOSTATTRS`` subroutines, but not with respect to +their ``:APPLY`` subroutines. + +``ESEQPROPS`` +~~~~~~~~~~~~~ + +Function. Applies each of the propapps passed as arguments, stopping and +signalling a failed change if any of the propapps signal a failed change. +Semantically, each propapp implicitly depends upon the preceding propapps. + +``UNAPPLIED`` +~~~~~~~~~~~~~ + +Function. Unapplies a single propapp. + +``ON-CHANGE`` +~~~~~~~~~~~~~ + +Macro. Applies properties when attempting to apply the first did not return +``:NO-CHANGE``. + +Remarks +------- + +The conversion of an unevaluated propspec into a propspec must resolve any +free variable references, except where those will be resolved by macro +property combinators. + +The ``PROPS`` macro converts an unevaluated propspec into a propspec. + +The evaluation of arguments to propapps in unevaluated propspecs appearing in +calls to ``DEFHOST``, ``DEPLOYS.`` and ``DEPLOYS-THESE.`` cannot retrieve +hostattrs, because these propspecs will be evaluated as part of the initial +definitions of hosts, before they have any hostattrs. By contrast, the +unevaluated propspecs in calls to ``DEFPROPLIST``, ``DEPLOY``, and +``DEPLOY-THESE``, and the code which produces propspecs in ``DEFPROPSPEC``, +may retrieve hostattrs set by other properties, because that code is run in +the context of a host which has already been defined. You cannot retrieve +hostattrs set by properties in the propspec resulting from evaluating the +evaluated propspec, however, since that propspec has not yet been applied to +the host. New hostattrs should not be pushed outside of the definitions of +``:HOSTATTRS`` subroutines. + +The elements of unevaluated propspecs are typically arguments to macros, such +that the context of evaluation for forms which produce the arguments to the +propapps is the context in which the call to the containing macro appears. +The single property combinator which will be wrapped around the list of forms +depends on the macro. ``DEFHOST`` uses ``SEQPROPS``, while ``DEFPROPLIST`` +uses ``ESEQPROPS``. + +The dotted propapp rules are intended to make applications of properties like +``DEPLOYS``, ``DEPLOYS-THESE`` and ``CHROOT:DEBOOTSTRAPPED``, which take +property application specifications as arguments, easier to read and write in +the most common cases. For example, you can write:: + + (deploys. (:ssh (:sudo :as "spwhitton@athena.example.com")) athena.example.com + (a-further-property val1) + (additional-property val2)) + +instead of:: + + (deploys '(:ssh (:sudo :as "spwhitton@athena.example.com")) athena.example.com + (make-propspec :props `((a-further-property ,val1) + (additional-property ,val2)))) + diff --git a/doc/propspecs.rst b/doc/propspecs.rst deleted file mode 100644 index bec4e6e..0000000 --- a/doc/propspecs.rst +++ /dev/null @@ -1,175 +0,0 @@ -Property application specifications ("propspecs") -================================================= - -If you understand the difference between propspecs and unevaluated propspecs -well enough to be able to read and understand most of the uses of -``DEFPROPLIST`` in Consfigurator's own source, then you can probably get away -without working through this section of the manual. You can build your own -propspecs by taking uses of ``DEFPROPLIST`` in Consfigurator's own source as -models. What follows is mainly for those who want to implement new property -combinators, though normal usage of Consfigurator should not require doing -that. - -Purposes --------- - -Property application specifications are a domain-specific language which -enables - -1. combining properties to make new properties, e.g. with ``DEFPROPLIST``; and - -2. representing the properties which are to be applied to a host in a form - which can be serialised using the Lisp printer. - -Definitions ------------ - -An *atomic property application* ("propapp") is a list satisfying the lambda -list ``(PROPERTY &rest ARGS)`` where ``PROPERTY`` is a symbol naming a -property and ``ARGS`` satisfies that property's lambda list, or the empty -list, which means a no-op property. A *property combinator* is a function -which takes at least one propapp as one of its arguments and returns a single -propapp, or a macro which takes at least propapp as one of its arguments and -returns an expression which yields a single propapp (in the return values of -property combinators, often ``PROPERTY`` will be a gensym). A *property -application specification* is - -i. a readably printable Lisp expression, **P**, known as a *property - application specification expression*, which contains - - a. propapps combined using property combinators; but - - b. no free variables except as bound by macro property combinators, i.e., - such that the evaluation of **P** would not be affected by any dynamic - or lexical context; where - - c. evaluating **P** means an operation equivalent to quoting each propapp - occurring in **P**, and then passing the whole thing as the only - argument to ``EVAL``; and - -ii. a list of ASDF systems **S**; such that - -iii. evaluating **P** yields a propapp ``(PROPERTY &rest ARGS)``; such that - -iv. when each of **S** are loaded, calling ``PROPERTY`` with ``ARGS`` will - apply each of the propapps occurring in **P** in accordance with the - combinators occuring in **P**. - -A property application specification should not contain any binding forms -except as will be eliminated after macroexpansion. - -We do not always cleanly distinguish between property application -specifications and property application specification expressions, and the -abbreviation "propspec" refers to both. It is usually clear from the context -which is meant. Within Lisp they are easy to distinguish because property -application specifications are CLOS objects, whereas property application -specification expressions are just conses. - -An *unevaluated property application specification expression* ("unevaluated -propspec") is a list of Lisp forms which can be converted into a property -application specification expression by - -i. replacing each argument in each propapp with the result of evaluating that - argument in the dynamic and lexical contexts in which the unevaluated - propspec occurs; and - -ii. wrapping a single property combinator which takes a variable number of - arguments around the resulting list of Lisp forms, usually ``SEQPROPS`` or - ``ESEQPROPS``. - -That is, the arguments to propapps in unevaluated propspecs are forms which -will produce the arguments to the properties, rather than those arguments -themselves. There is one special case: if the symbol naming the property ends -with the character ``.``, then the propapp is replaced with a new one -according to the following rules (the "dotted propapp rules"): - -i. the property to be applied is the property named by the symbol in the same - package and with the same name as the first element of the propapp, but - with the trailing period removed from the name; - -ii. the first argument is not evaluated if it is a list whose first element is - a keyword, or a if it is a list of lists where the first element of the - first list is a keyword; and - -iii. the last required or optional argument to the property to be applied is - converted to a ``&rest`` parameter and its elements are treated as an - embedded unevaluated propspec, which is recursively converted into a - propspec according to the usual evaluation rules, where the surrounding - combinator is ``ESEQPROPS``. - -The dotted propapp rules are implemented by ``DEFPROP`` and ``DEFPROPLIST``, -which define a macro with the dotted name which performs the replacement. - -Available combinators ---------------------- - -``SEQPROPS`` -~~~~~~~~~~~~~ - -Function. Applies each of the propapps passed as arguments without stopping -if any of them signal a failed change. Semantically, the propapps are ordered -with respect to their ``:HOSTATTRS`` subroutines, but not with respect to -their ``:APPLY`` subroutines. - -``ESEQPROPS`` -~~~~~~~~~~~~~ - -Function. Applies each of the propapps passed as arguments, stopping and -signalling a failed change if any of the propapps signal a failed change. -Semantically, each propapp implicitly depends upon the preceding propapps. - -``UNAPPLIED`` -~~~~~~~~~~~~~ - -Function. Unapplies a single propapp. - -``ON-CHANGE`` -~~~~~~~~~~~~~ - -Macro. Applies properties when attempting to apply the first did not return -``:NO-CHANGE``. - -Remarks -------- - -The conversion of an unevaluated propspec into a propspec must resolve any -free variable references, except where those will be resolved by macro -property combinators. - -The ``PROPS`` macro converts an unevaluated propspec into a propspec. - -The evaluation of arguments to propapps in unevaluated propspecs appearing in -calls to ``DEFHOST``, ``DEPLOYS.`` and ``DEPLOYS-THESE.`` cannot retrieve -hostattrs, because these propspecs will be evaluated as part of the initial -definitions of hosts, before they have any hostattrs. By contrast, the -unevaluated propspecs in calls to ``DEFPROPLIST``, ``DEPLOY``, and -``DEPLOY-THESE``, and the code which produces propspecs in ``DEFPROPSPEC``, -may retrieve hostattrs set by other properties, because that code is run in -the context of a host which has already been defined. You cannot retrieve -hostattrs set by properties in the propspec resulting from evaluating the -evaluated propspec, however, since that propspec has not yet been applied to -the host. New hostattrs should not be pushed outside of the definitions of -``:HOSTATTRS`` subroutines. - -The elements of unevaluated propspecs are typically arguments to macros, such -that the context of evaluation for forms which produce the arguments to the -propapps is the context in which the call to the containing macro appears. -The single property combinator which will be wrapped around the list of forms -depends on the macro. ``DEFHOST`` uses ``SEQPROPS``, while ``DEFPROPLIST`` -uses ``ESEQPROPS``. - -The dotted propapp rules are intended to make applications of properties like -``DEPLOYS``, ``DEPLOYS-THESE`` and ``CHROOT:DEBOOTSTRAPPED``, which take -property application specifications as arguments, easier to read and write in -the most common cases. For example, you can write:: - - (deploys. (:ssh (:sudo :as "spwhitton@athena.example.com")) athena.example.com - (a-further-property val1) - (additional-property val2)) - -instead of:: - - (deploys '(:ssh (:sudo :as "spwhitton@athena.example.com")) athena.example.com - (make-propspec :props `((a-further-property ,val1) - (additional-property ,val2)))) - diff --git a/doc/tutorial/disk_image.rst b/doc/tutorial/disk_image.rst index d4b7a20..7001474 100644 --- a/doc/tutorial/disk_image.rst +++ b/doc/tutorial/disk_image.rst @@ -1,5 +1,5 @@ -Tutorial: building disk images -============================== +Building disk images +==================== In this tutorial we will show you what properties you need to use to build bootable disk images. diff --git a/doc/tutorial/os_installation.rst b/doc/tutorial/os_installation.rst index d89ccbf..9e6b077 100644 --- a/doc/tutorial/os_installation.rst +++ b/doc/tutorial/os_installation.rst @@ -1,5 +1,5 @@ -Tutorial: OS installation -========================= +OS installation +=============== Consfigurator implements a number of methods for installing operating systems. diff --git a/doc/util.rst.in b/doc/util.rst.in new file mode 100644 index 0000000..6d39466 --- /dev/null +++ b/doc/util.rst.in @@ -0,0 +1,2 @@ +``CONSFIGURATOR`` exported utilities +==================================== diff --git a/emacs/Makefile b/emacs/Makefile index 2226d2c..d15384c 100644 --- a/emacs/Makefile +++ b/emacs/Makefile @@ -1,7 +1,5 @@ -LOAD = '(let ((asdf:*user-cache* "/tmp") \ - (asdf:*central-registry* (list (truename "..")))) \ - (asdf:load-system "consfigurator"))' +include ../consfigurator.mk consfigurator.el: consfigurator.el.in - sbcl --disable-debugger --eval '(require "asdf")' --eval $(LOAD) \ - --eval '(consfigurator::dump-properties-for-emacs "$<" "$@")' --quit + $(SBCL) --eval '(consfigurator::dump-properties-for-emacs "$<" "$@")'\ + --quit diff --git a/src/package.lisp b/src/package.lisp index 6b72a1b..157bcfb 100644 --- a/src/package.lisp +++ b/src/package.lisp @@ -16,6 +16,7 @@ #:slurp-stream-string #:subprocess-error #:stripln + #:println #:unix-namestring #:parse-unix-namestring #:pathname-directory-pathname @@ -58,6 +59,7 @@ #:slurp-stream-string #:subprocess-error #:stripln + #:println #:unix-namestring #:parse-unix-namestring #:pathname-directory-pathname diff --git a/src/property.lisp b/src/property.lisp index 3aebe75..968b205 100644 --- a/src/property.lisp +++ b/src/property.lisp @@ -203,6 +203,152 @@ (when indent (setf (get sym 'indent) indent))))) +(defun docstring-to-rst (docstring) + ;; Unsurprisingly this gets a lot of cases wrong, so turned off for now. + ;; The block capitals already make them easy to pick out. + ;; (setq docstring + ;; (re:regex-replace-all #?/(? #'cl-heredoc:read-heredoc)) + +(named-readtables:defreadtable :consfigurator.without-read-eval + (:merge :consfigurator) + (:dispatch-macro-char #\# #\. (constantly nil))) -- cgit v1.2.3