From c7f75954bf741b82df888bbcfb83f61c345826ba Mon Sep 17 00:00:00 2001
From: Thomas Bellman <bellman@lysator.liu.se>
Date: Wed, 14 Aug 2024 20:41:15 +0200
Subject: [PATCH] Support per-path parameters in systemd::tmpfiles.

This adds the ability to give per-path parameters when configuring
systemd-tmpfiles(8).  The $paths parameter to systemd::tmpfiles can
now be a hash of hashes, mapping from paths to hashes of parameters
overriding the parameters given directly to systemd::tmpfiles, e.g.

    systemd::tmpfiles {
        'smurf':
            type => 'd', mode => '0750', owner => 'gargamel',
            paths => {
                '/run/alldefaults' => {},
                '/run/file' => { 'type' => 'f', 'group' => 'smurfs' },
            };
    }

which will generate

    d  /run/alldefaults  0750  gargamel  -       -
    f  /run/file         0750  gargamel  smurfs  -

in /etc/tmpfiles.d/smurf.conf.

The original API, where you specify the $paths parameter as a simple
list of paths, or just a single string, is still available, as that is
easier to use for many simple cases.
---
 manifests/tmpfiles.pp       | 78 ++++++++++++++++++++++++++++++-------
 templates/tmpfiles.conf.erb | 30 ++++++++++----
 2 files changed, 86 insertions(+), 22 deletions(-)

diff --git a/manifests/tmpfiles.pp b/manifests/tmpfiles.pp
index e86700b..18eb954 100644
--- a/manifests/tmpfiles.pp
+++ b/manifests/tmpfiles.pp
@@ -12,27 +12,76 @@
  * config file, so e.g. any directory the config file says should be created,
  * will be.
  *
+ * Example:
+ *
+ *	systemd::tmpfiles {
+ *	    'smurf':
+ *		type => 'd', mode => '0750', owner => 'gargamel',
+ *		paths => {
+ *		    '/run/alldefaults' => {},
+ *		    '/run/file' => { 'type' => 'f', 'group' => 'smurfs' },
+ *		};
+ *	}
+ *
+ * This will generate the entries
+ *
+ *	d  /run/alldefaults     0750  gargamel  -       -
+ *	f  /run/file            0750  gargamel  smurfs  -
+ *
+ * in /etc/tmpfiles.d/smurf.conf.  Since the maxage parameter is not
+ * specified either in the "defaults", or in the hashes for either of the
+ * paths, it will default to "-".
+ *
  * Note that there is basically no verification that the parameter values
  * are syntactically correct, so it is possible to create config files
  * that are not valid.
  */
 define systemd::tmpfiles(
-	# Path(s) to manage.  Can be a single path, or a (possibly nested)
-	# list of paths.  If more than one path is given, the same type,
-	# permissions, and maxage is applied to them all.
+
+	# Path(s) to manage, and their parameters.
+	#
+	# Simple case, this is a path or a (nested) list of paths to put
+	# into the tmpfiles.d config file.  The 'type', 'mode', 'owner',
+	# 'group', 'maxage' and 'argument' parameters will apply equally to
+	# all paths.
 	#
-	# If specified, then at least $type must also be specified.
+	# Alternatively, this can be a hash of hashes, mapping from paths to
+	# a hash of parameter values for each path, e.g.
 	#
-	$paths = [],
+	#     paths => {
+	#         '/run/foo' => { 'type' => 'd', 'mode' => '0700' },
+	#         '/run/bar' => { 'type' => 'f', 'owner' => 'zork' },
+	#     }
+	#
+	# In this case, the resource parameters will act as defaults for
+	# parameters missing in a specific path's parameter hash.
+	#
+	# The 'type' parameter must be specified for every path, either in
+	# its parameter hash, or as the $type parameter on the resource.
+	# All other parameters defaults to "-" or nothing.
+	#
+	# One limitation is that you can only have one entry per path.  If
+	# you e.g. need one "d" entry and one "a" (ACL) entry for the same
+	# path, you must either use the $content parameter, forgoing some of
+	# the abstraction here, or you have to use more than one resource,
+	# putting the entries in different tmpfiles.d config files.
+	#
+	$paths = {},
 
-	# The entry type for the paths in $paths.  All path entries will
-	# share the same type.
+	# The entry type for the paths in $paths, unless overridden in the
+	# path-specific parameter hash.
+	#
+	# The entry type must be specified for all paths, either in the
+	# path's parameter hash in $paths, or using this parameter.
 	#
 	$type = undef,
 
-	# The mode, uid, gid, age, and argument fields, respectively, of the
-	# tmpfiles.d config entries for $paths.  All entries will share the
-	# same settings.
+	# The values for the mode, uid, gid, age, and argument fields,
+	# respectively, of the tmpfiles.d config entries for $paths, unless
+	# overridden in the path-specific parameter hash in.
+	#
+	# If a parameter is not specified, either here or in the parameter
+	# hash, it defaults to "-" (or to "" for the $argument parameter).
 	#
 	$mode = undef,
 	$owner = undef,
@@ -62,13 +111,12 @@ define systemd::tmpfiles(
     case $ensure
     {
 	'present': {
-	    if (($paths != []) and ((! $type) or ($type == ""))) {
-		fail("${resource_ref}: paths parameter set, but not type")
-	    }
-	    if (($type) and ($paths == [])) {
+	    if (($type) and ($paths == [] or $paths == {})) {
 		fail("${resource_ref}: type parameter set, but no paths")
 	    }
-	    if (($paths == []) and ($content == '' or $content == [])) {
+	    if (($paths == [] or $paths == {}) and
+		($content == '' or $content == []))
+	    {
 		fail("${resource_ref}: both paths and content are empty")
 	    }
 	    contain systemd::tmpfiles::trigger
diff --git a/templates/tmpfiles.conf.erb b/templates/tmpfiles.conf.erb
index 9d2a895..687aa4e 100644
--- a/templates/tmpfiles.conf.erb
+++ b/templates/tmpfiles.conf.erb
@@ -8,15 +8,31 @@
 <%  if not comment_lines.empty? -%>
 <%	%>
 <%  end -%>
-<%  [@paths].flatten.each do |path|
+<%  if @paths.respond_to?('each_pair')	# Is a hash (of hashes)
+	path_params = @paths
+    else
+	path_params = [@paths].flatten.collect { |path| [path, {}] }
+    end
+
+    path_params.sort.each do |path,params|
+        if ! params.is_a?(Hash)
+	    raise(Puppet::ParseError,
+		  "#{resource_ref}: value not a hash for path #{path}")
+	end
+	type =  (params['type'] || @type)
+	if ! type
+	    raise(Puppet::ParseError,
+		  "#{resource_ref}: No type for path #{path}")
+	end
+
 	fields = [ path ]
-	fields << (@mode || '-')
-	fields << (@owner || '-')
-	fields << (@group || '-')
-	fields << (@maxage || '-')
-	fields << (@argument || '')
+	fields << (params['mode']     || @mode     || '-')
+	fields << (params['owner']    || @owner    || '-')
+	fields << (params['group']    || @group    || '-')
+	fields << (params['maxage']   || @maxage   || '-')
+	fields << (params['argument'] || @argument || '')
 -%>
-<%=	sprintf('%-2s %s', @type, fields.join("\t")).rstrip() %>
+<%=	sprintf('%-2s %s', type, fields.join("\t")).rstrip() %>
 <%  end -%>
 <%  [@content].flatten.each do |chunk| -%>
 <%=	chunk %>
-- 
GitLab