guix-devel
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Brainstorming ideas for define-configuration


From: Bruno Victal
Subject: Brainstorming ideas for define-configuration
Date: Thu, 9 Mar 2023 02:28:01 +0000
User-agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Thunderbird/102.8.0

Co-authored-by: Felix Lechner


After spending some time with old and new Guix services, I'd like to
suggest some potential improvements to our define-configuration macro:


User-specified sanitizer support
===============================================================================

The sanitizers should be integrated with the type. Otherwise, they are
tedious to use and appear verbose when repeatedly applied to multiple fields.

--8<---------------cut here---------------start------------->8---
;; Suggestion #1
;; The procedure could return a sanitized value. Upon failure, there are
;; the following options:
;;    - The procedure returns only a special value (akin to %unset-value)
;;      and an error message, as a pair.
;;      Exception raising is done by define-sanitized macro behind the scenes
;;      which makes the procedure easier to write.
;;    - The procedure raises an exception. There would be no consistency
;;      on the message formats, however, except for any agreed convention.
;;      This would involve some code duplication.
;; Cons: too specific, not extensible.

(define-sanitized typename procedure
  (prefix ...))


;; Suggestion #2
;; A user-supplied procedure ('procname' below) would work just like the
;; procedure in option #1.
;; There is some similiarity to the Guix record-type*.
;; This could be extended more easily in the future should it be required.
(define-type typename        ; maybe call this 'define-configuration-type' ?
  (sanitizer procname)
  (maybe-type? #t)
  ;; The properties below are service specific.
  ;; If this is implemented with Guix record-type* then we could have a
  ;; module containing generic types and do something along the lines of:
  ;; (define-type foo-ip-address
  ;;    (inherit generic-ip-address)
  ;;    (serializer ...))
  (serializer procname)      ; define-type/no-serialization = sets this field 
to #f ?
  (prefix ...))
--8<---------------cut here---------------end--------------->8---

The original motivation for this proposal stems from the attempts to
resolve issue #61570. There, one potential fix was to handle the user and
group fields similarly to the way greetd service handles them.
There is some opportunity for generalization here, types that might be
useful elsewhere, such as a port number or a host name, could be placed in
a separate module.

This proposal should also make it easier to handle situations when fields
become obsolete.


Record Validator
===============================================================================

There is also a need to validate records. Matching fields alone do not actually
ensure that the configuration is coherent and usable. For example, some fields
may be mutually incompatible with others.

We could provide procedures that validate each record type within
define-configuration itself instead of validating the value at runtime (i.e.
within the body of the service-type).

--8<---------------cut here---------------start------------->8---
;; the common case
(define-configuration foo-configuration
  (name
   string
   "Lorem ipsum...")

  ;; ...

  (validator procname))

;; [bonus] Simpler configurations that only care for mutually-exclusive fields
(define-configuration foo-configuration
  (name
   string
   "Lorem ipsum...")

  (title
   string
   "Lorem ipsum..."
   (conflicts 'name)))
--8<---------------cut here---------------end--------------->8---


Comments regarding literal placement
-------------------------------------------------------------------------------

Does the placement order matter for the extra fields/literals for 
define-configuration?
Can they be placed arbitrarily or only at the bottom? (where custom-serializer 
is specified)

Another point, extra parameters given to define-configuration should never
clash with field names. For example, it should be possible to name a
field 'prefix', 'location' or similar without causing issues like #59423.


Coalesced documentation
===============================================================================

Currently, we manually edit the texinfo output from configuration->documentation
if we're unsatisfied with the generated result.
For instance, substituting @item with an @itemx marker for fields whose
documentation is similar.

Instead, we could embed hints in define-configuration that affect the texinfo
generation in smarter ways.

In the long term, guix.texi should source some portions of the documentation
directly from the code base. The current workflow involving copy and paste
the output from the evaluation of configuration->documentation carries the
unnecessary risk that future documentation patches are done against guix.texi
rather than the .scm file from where it was generated. (issue #60582)

Snippet based on mympd-ip-acl (gnu/services/audio.scm):
--8<---------------cut here---------------start------------->8---
(define-configuration/no-serialization mympd-ip-acl
  (allow
   (list-of-string '())
   "Allowed/Disallowed IP addresses.")

  (deny
   (list-of-string '())
   (documentation 'allow)))  ; group with field 'allow

;;; --- texi output below ---

@c %start of fragment
@deftp {Data Type} mympd-ip-acl
Available @code{mympd-ip-acl} fields are:

@table @asis
@item @code{allow}
@itemx @code{deny} (default: @code{()}) (type: list-of-string)
Allowed/Disallowed IP addresses.

@end table
@end deftp
@c %end of fragment
--8<---------------cut here---------------end--------------->8---


Serializer access to other fields
===============================================================================

Serialization procedures generally only have access to the values of its own 
field. That
may be insufficient in some cases as whether a field can be serialized
or how that is done, for example, can depend on the value of other fields.

mympd-service-type is one example where each serialized field depends on the 
value of
another field. Our standard serializer procedures were useless for that case.

As a side note, the 'this-record' macro does not work with
define-configuration which made it impossible to use
(custom-serializer) for the same effect.

Instead, serializer procedures could take additional keyword arguments:
--8<---------------cut here---------------start------------->8---
(define* (serialize-string field-name value (#:key config))
   (let ((baz-value (assoc-ref config 'baz)))
      (string-append baz-value "/" value)
      ...))
--8<---------------cut here---------------end--------------->8---


Inheritable record-type definition
===============================================================================

The openvpn-service pairs in (gnu services vpn) define a special
macro define-split-configuration with the purpose to avoid code duplication
since the service pairs have multiple fields in common.

Perhaps in a similar vein to SRFI-136, we could have:

--8<---------------cut here---------------start------------->8---
(define-configuration openvpn-common-configuration
  (openvpn
    (file-like openvpn)
    "The OpenVPN package.")
  ;; ...
)

(define-configuration openvpn-server-configuration openvpn-common-configuration
  (tls-auth
    (tls-auth-server #f)
    "Add an additional layer of HMAC authentication on top of the TLS control
channel to protect against DoS attacks.")
  ;; ...
)

;;; or through a literal/keyword approach

(define-configuration openvpn-server-configuration
  (tls-auth
    (tls-auth-server #f)
    "Add an additional layer of HMAC authentication on top of the TLS control
channel to protect against DoS attacks.")

  (parent openvpn-common-configuration))

--8<---------------cut here---------------end--------------->8---


Generic serialize-configuration
===============================================================================

The procedure serialize-configuration inherently assumes that the serialized
configuration must be a single string. This assumption needn't always hold, 
especially
if the service in question is not a shepherd service.

We could possibly make this procedure a bit more "generic", maybe picking some 
ideas
from SRFI-171 Transducers.

Another improvement to this procedure is to eliminate (or make optional) the 
second
parameter, the configuration fields. It's unclear what value it brings since one
can't use fields from another configuration type here.
An argument can be made for selectively serializing some of the fields but then
it would be more practical to make this an optional parameter.



PS.
===============================================================================

Kind of a late realization, but couldn't many of the items above be satisfied
by improvements to define-record-type* instead?
(define-record-type* paired with a documentation literal is nearly equivalent 
to define-configuration,
sans the serialization scaffolding)



Cheers,
Bruno



reply via email to

[Prev in Thread] Current Thread [Next in Thread]