diff --git a/.fixtures.yml b/.fixtures.yml
index 93ec5df..8623ad7 100644
--- a/.fixtures.yml
+++ b/.fixtures.yml
@@ -3,6 +3,7 @@
---
fixtures:
forge_modules:
+ systemd: "puppet/systemd"
stdlib: "puppetlabs/stdlib"
concat: "puppetlabs/concat"
selinux_core: "puppetlabs/selinux_core"
diff --git a/README.md b/README.md
index d778ea8..4f53e0c 100644
--- a/README.md
+++ b/README.md
@@ -189,6 +189,36 @@ Several additional examples are in a separate [github project](https://github.co
a Traefik container configuration that enables SSL termination and proxy access to other containers running on the host, with a dynamic
configuration directory enabling updates to proxy rules as new containers are added and/or removed.
+## Creating Containers,Volumes, Pods, ... with Quadlet Unit Files
+
+Container unit files can be created and managed, for example:
+
+```puppet
+podman::quadlet{'centos.container':
+ ensure => present,
+ unit_entry => {
+ 'Description' => 'Trivial Container that will be very lazy',
+ },
+ service_entry => {
+ 'TimeoutStartSec' => '900',
+ },
+ container_entry => {
+ 'Image' => 'quay.io/centos/centos:latest',
+ 'Exec' => 'sh -c "sleep inf'
+ },
+ install_entry => {
+ 'WantedBy' => 'default.target'
+ },
+ active => true,
+}
+
+```
+
+Will create a service `centos.service` that is then started and enabled for boot.
+
+Similarly quadlets for volumes, pods, ... can be created in a similar manner
+
+
## Limitations
The module was written and tested with RedHat/CentOS, but should work with any distribution that uses systemd and includes
diff --git a/REFERENCE.md b/REFERENCE.md
index c0acda8..4051358 100644
--- a/REFERENCE.md
+++ b/REFERENCE.md
@@ -18,15 +18,23 @@
### Defined types
-* [`podman::container`](#podmancontainer): manage podman container and register as a systemd service
-* [`podman::image`](#podmanimage): pull or remove container images
-* [`podman::network`](#podmannetwork): Create a podman network with defined flags
-* [`podman::pod`](#podmanpod): Create a podman pod with defined flags
-* [`podman::rootless`](#podmanrootless): Enable a given user to run rootless podman containers as a systemd user service.
-* [`podman::secret`](#podmansecret): Manage a podman secret. Create and remove secrets, it cannot replace.
-* [`podman::subgid`](#podmansubgid): Define an entry in the `/etc/subgid` file.
-* [`podman::subuid`](#podmansubuid): Manage entries in `/etc/subuid`
-* [`podman::volume`](#podmanvolume): Create a podman volume with defined flags
+* [`podman::container`](#podman--container): manage podman container and register as a systemd service
+* [`podman::image`](#podman--image): pull or remove container images
+* [`podman::network`](#podman--network): Create a podman network with defined flags
+* [`podman::pod`](#podman--pod): Create a podman pod with defined flags
+* [`podman::quadlet`](#podman--quadlet): Generate and manage podman quadlet definitions (podman > 4.4.0)
+* [`podman::rootless`](#podman--rootless): Enable a given user to run rootless podman containers as a systemd user service.
+* [`podman::secret`](#podman--secret): Manage a podman secret. Create and remove secrets, it cannot replace.
+* [`podman::subgid`](#podman--subgid): Define an entry in the `/etc/subgid` file.
+* [`podman::subuid`](#podman--subuid): Manage entries in `/etc/subuid`
+* [`podman::volume`](#podman--volume): Create a podman volume with defined flags
+
+### Data types
+
+* [`Podman::Quadlet_name`](#Podman--Quadlet_name): custom datatype that validates different filenames for quadlet units
+* [`Podman::Unit::Container`](#Podman--Unit--Container): custom datatype for container entries of podman container quadlet
+* [`Podman::Unit::Pod`](#Podman--Unit--Pod): custom datatype for Volume entries of podman container quadlet
+* [`Podman::Unit::Volume`](#Podman--Unit--Volume): custom datatype for Volume entries of podman container quadlet
## Classes
@@ -73,31 +81,31 @@ podman::containers:
The following parameters are available in the `podman` class:
-* [`podman_pkg`](#podman_pkg)
-* [`skopeo_pkg`](#skopeo_pkg)
-* [`buildah_pkg`](#buildah_pkg)
-* [`podman_docker_pkg`](#podman_docker_pkg)
-* [`compose_pkg`](#compose_pkg)
-* [`machinectl_pkg`](#machinectl_pkg)
-* [`buildah_pkg_ensure`](#buildah_pkg_ensure)
-* [`podman_docker_pkg_ensure`](#podman_docker_pkg_ensure)
-* [`compose_pkg_ensure`](#compose_pkg_ensure)
-* [`machinectl_pkg_ensure`](#machinectl_pkg_ensure)
-* [`nodocker`](#nodocker)
-* [`storage_options`](#storage_options)
-* [`rootless_users`](#rootless_users)
-* [`enable_api_socket`](#enable_api_socket)
-* [`manage_subuid`](#manage_subuid)
-* [`file_header`](#file_header)
-* [`match_subuid_subgid`](#match_subuid_subgid)
-* [`subid`](#subid)
-* [`pods`](#pods)
-* [`volumes`](#volumes)
-* [`images`](#images)
-* [`containers`](#containers)
-* [`networks`](#networks)
-
-##### `podman_pkg`
+* [`podman_pkg`](#-podman--podman_pkg)
+* [`skopeo_pkg`](#-podman--skopeo_pkg)
+* [`buildah_pkg`](#-podman--buildah_pkg)
+* [`podman_docker_pkg`](#-podman--podman_docker_pkg)
+* [`compose_pkg`](#-podman--compose_pkg)
+* [`machinectl_pkg`](#-podman--machinectl_pkg)
+* [`buildah_pkg_ensure`](#-podman--buildah_pkg_ensure)
+* [`podman_docker_pkg_ensure`](#-podman--podman_docker_pkg_ensure)
+* [`compose_pkg_ensure`](#-podman--compose_pkg_ensure)
+* [`machinectl_pkg_ensure`](#-podman--machinectl_pkg_ensure)
+* [`nodocker`](#-podman--nodocker)
+* [`storage_options`](#-podman--storage_options)
+* [`rootless_users`](#-podman--rootless_users)
+* [`enable_api_socket`](#-podman--enable_api_socket)
+* [`manage_subuid`](#-podman--manage_subuid)
+* [`file_header`](#-podman--file_header)
+* [`match_subuid_subgid`](#-podman--match_subuid_subgid)
+* [`subid`](#-podman--subid)
+* [`pods`](#-podman--pods)
+* [`volumes`](#-podman--volumes)
+* [`images`](#-podman--images)
+* [`containers`](#-podman--containers)
+* [`networks`](#-podman--networks)
+
+##### `podman_pkg`
Data type: `String`
@@ -105,7 +113,7 @@ The name of the podman package (default 'podman')
Default value: `'podman'`
-##### `skopeo_pkg`
+##### `skopeo_pkg`
Data type: `String`
@@ -113,7 +121,7 @@ The name of the skopeo package (default 'skopeo')
Default value: `'skopeo'`
-##### `buildah_pkg`
+##### `buildah_pkg`
Data type: `String`
@@ -121,7 +129,7 @@ The name of the buildah package (default 'buildah')
Default value: `'buildah'`
-##### `podman_docker_pkg`
+##### `podman_docker_pkg`
Data type: `String`
@@ -129,7 +137,7 @@ The name of the podman-docker package (default 'podman-docker').
Default value: `'podman-docker'`
-##### `compose_pkg`
+##### `compose_pkg`
Data type: `String`
@@ -137,7 +145,7 @@ The name of the podman-compose package (default 'podman-compose').
Default value: `'podman-compose'`
-##### `machinectl_pkg`
+##### `machinectl_pkg`
Data type: `String`
@@ -145,7 +153,7 @@ The name of the machinectl package (default 'systemd-container').
Default value: `'systemd-container'`
-##### `buildah_pkg_ensure`
+##### `buildah_pkg_ensure`
Data type: `Enum['absent', 'installed']`
@@ -153,7 +161,7 @@ The ensure value for the buildah package (default 'absent')
Default value: `'absent'`
-##### `podman_docker_pkg_ensure`
+##### `podman_docker_pkg_ensure`
Data type: `Enum['absent', 'installed']`
@@ -161,7 +169,7 @@ The ensure value for the podman docker package (default 'installed')
Default value: `'installed'`
-##### `compose_pkg_ensure`
+##### `compose_pkg_ensure`
Data type: `Enum['absent', 'installed']`
@@ -169,7 +177,7 @@ The ensure value for the podman-compose package (default 'absent')
Default value: `'absent'`
-##### `machinectl_pkg_ensure`
+##### `machinectl_pkg_ensure`
Data type: `Enum['absent', 'installed']`
@@ -177,7 +185,7 @@ The ensure value for the machinectl package (default 'installed')
Default value: `'installed'`
-##### `nodocker`
+##### `nodocker`
Data type: `Enum['absent', 'file']`
@@ -186,7 +194,7 @@ Values should be either 'file' or 'absent'. (default is 'absent')
Default value: `'absent'`
-##### `storage_options`
+##### `storage_options`
Data type: `Hash`
@@ -194,7 +202,7 @@ A hash containing any storage options you wish to set in /etc/containers/storage
Default value: `{}`
-##### `rootless_users`
+##### `rootless_users`
Data type: `Array`
@@ -202,15 +210,15 @@ An array of users to manage using [`podman::rootless`](#podmanrootless)
Default value: `[]`
-##### `enable_api_socket`
+##### `enable_api_socket`
Data type: `Boolean`
The enable value of the API socket (default `false`)
-Default value: ``false``
+Default value: `false`
-##### `manage_subuid`
+##### `manage_subuid`
Data type: `Boolean`
@@ -219,9 +227,9 @@ The implementation uses [concat](https://forge.puppet.com/puppetlabs/concat) fra
out the subuid/subgid entries. If you have a large number of entries you may want to manage them
with another method. You cannot use the `subuid` and `subgid` defined types unless this is `true`.
-Default value: ``false``
+Default value: `false`
-##### `file_header`
+##### `file_header`
Data type: `String`
@@ -230,7 +238,7 @@ Default file_header is `# FILE MANAGED BY PUPPET`
Default value: `'# FILE MANAGED BY PUPPET'`
-##### `match_subuid_subgid`
+##### `match_subuid_subgid`
Data type: `Boolean`
@@ -238,9 +246,9 @@ Enable the `subid` parameter to manage both subuid and subgid entries with the s
This setting requires `manage_subuid` to be `true` or it will have no effect.
(default is true)
-Default value: ``true``
+Default value: `true`
-##### `subid`
+##### `subid`
Data type: `Hash`
@@ -250,7 +258,7 @@ Hash key `subuid` is the subordinate UID, and `count` is the number of subordina
Default value: `{}`
-##### `pods`
+##### `pods`
Data type: `Hash`
@@ -258,7 +266,7 @@ A hash of pods to manage using [`podman::pod`](#podmanpod)
Default value: `{}`
-##### `volumes`
+##### `volumes`
Data type: `Hash`
@@ -266,7 +274,7 @@ A hash of volumes to manage using [`podman::volume`](#podmanvolume)
Default value: `{}`
-##### `images`
+##### `images`
Data type: `Hash`
@@ -274,7 +282,7 @@ A hash of images to manage using [`podman::image`](#podmanimage)
Default value: `{}`
-##### `containers`
+##### `containers`
Data type: `Hash`
@@ -282,7 +290,7 @@ A hash of containers to manage using [`podman::container`](#podmancontainer)
Default value: `{}`
-##### `networks`
+##### `networks`
Data type: `Hash`
@@ -292,7 +300,7 @@ Default value: `{}`
## Defined types
-### `podman::container`
+### `podman::container`
manage podman container and register as a systemd service
@@ -319,36 +327,36 @@ podman::container { 'jenkins':
The following parameters are available in the `podman::container` defined type:
-* [`image`](#image)
-* [`user`](#user)
-* [`flags`](#flags)
-* [`service_flags`](#service_flags)
-* [`command`](#command)
-* [`ensure`](#ensure)
-* [`enable`](#enable)
-* [`update`](#update)
-* [`ruby`](#ruby)
+* [`image`](#-podman--container--image)
+* [`user`](#-podman--container--user)
+* [`flags`](#-podman--container--flags)
+* [`service_flags`](#-podman--container--service_flags)
+* [`command`](#-podman--container--command)
+* [`ensure`](#-podman--container--ensure)
+* [`enable`](#-podman--container--enable)
+* [`update`](#-podman--container--update)
+* [`ruby`](#-podman--container--ruby)
-##### `image`
+##### `image`
-Data type: `String`
+Data type: `Optional[String]`
Container registry source of the image being deployed. Required when
`ensure` is `present` but optional when `ensure` is set to `absent`.
-Default value: `''`
+Default value: `undef`
-##### `user`
+##### `user`
-Data type: `String`
+Data type: `Optional[String]`
Optional user for running rootless containers. For rootless containers,
the user must also be defined as a puppet resource that includes at least
'uid', 'gid', and 'home' attributes.
-Default value: `''`
+Default value: `undef`
-##### `flags`
+##### `flags`
Data type: `Hash`
@@ -366,43 +374,43 @@ YAML representation you can use `~` or `null` as the value.
Default value: `{}`
-##### `service_flags`
+##### `service_flags`
Data type: `Hash`
When a container is created, a systemd unit file for the container service
is generated using the 'podman generate systemd' command. All flags for the
-command are supported using the 'service_flags" hash parameter, again using
+command are supported using the 'service_flags' hash parameter, again using
only the long form of the flag names.
Default value: `{}`
-##### `command`
+##### `command`
-Data type: `String`
+Data type: `Optional[String]`
Optional command to be used as the container entry point.
-Default value: `''`
+Default value: `undef`
-##### `ensure`
+##### `ensure`
-Data type: `String`
+Data type: `Enum['present', 'absent']`
Valid values are 'present' or 'absent'
Default value: `'present'`
-##### `enable`
+##### `enable`
Data type: `Boolean`
Status of the automatically generated systemd service for the container.
Valid values are 'running' or 'stopped'.
-Default value: ``true``
+Default value: `true`
-##### `update`
+##### `update`
Data type: `Boolean`
@@ -412,19 +420,19 @@ value of the running container image with the digest of the registry image.
When `false`, the container will only be redeployed when the declared state
of the puppet resource is changed.
-Default value: ``true``
+Default value: `true`
-##### `ruby`
+##### `ruby`
-Data type: `Stdlib::Unixpath`
+Data type: `Optional[Stdlib::Unixpath]`
The absolute path to the ruby binary to use in scripts. The default path is
'/opt/puppetlabs/puppet/bin/ruby' for Puppetlabs packaged puppet, and
'/usr/bin/ruby' for all others.
-Default value: `$facts['ruby']['sitedir']`
+Default value: `undef`
-### `podman::image`
+### `podman::image`
pull or remove container images
@@ -445,28 +453,28 @@ podman::image { 'my_container':
The following parameters are available in the `podman::image` defined type:
-* [`image`](#image)
-* [`ensure`](#ensure)
-* [`flags`](#flags)
-* [`user`](#user)
-* [`exec_env`](#exec_env)
+* [`image`](#-podman--image--image)
+* [`ensure`](#-podman--image--ensure)
+* [`flags`](#-podman--image--flags)
+* [`user`](#-podman--image--user)
+* [`exec_env`](#-podman--image--exec_env)
-##### `image`
+##### `image`
Data type: `String`
The name of the container image to pull, which should be present in a
configured container registry.
-##### `ensure`
+##### `ensure`
-Data type: `String`
+Data type: `Enum['present', 'absent']`
State of the resource must be either `present` or `absent`.
Default value: `'present'`
-##### `flags`
+##### `flags`
Data type: `Hash`
@@ -475,17 +483,17 @@ long form of the flag name.
Default value: `{}`
-##### `user`
+##### `user`
-Data type: `String`
+Data type: `Optional[String]`
Optional user for running rootless containers. When using this parameter,
the user must also be defined as a Puppet resource and must include the
'uid', 'gid', and 'home'
-Default value: `''`
+Default value: `undef`
-##### `exec_env`
+##### `exec_env`
Data type: `Array`
@@ -495,7 +503,7 @@ pulled. Useful for defining a proxy for downloads. For example:
Default value: `[]`
-### `podman::network`
+### `podman::network`
Create a podman network with defined flags
@@ -514,19 +522,19 @@ podman::network { 'mnetwork':
The following parameters are available in the `podman::network` defined type:
-* [`ensure`](#ensure)
-* [`disable_dns`](#disable_dns)
-* [`driver`](#driver)
-* [`opts`](#opts)
-* [`gateway`](#gateway)
-* [`internal`](#internal)
-* [`ip_range`](#ip_range)
-* [`labels`](#labels)
-* [`subnet`](#subnet)
-* [`ipv6`](#ipv6)
-* [`user`](#user)
+* [`ensure`](#-podman--network--ensure)
+* [`disable_dns`](#-podman--network--disable_dns)
+* [`driver`](#-podman--network--driver)
+* [`opts`](#-podman--network--opts)
+* [`gateway`](#-podman--network--gateway)
+* [`internal`](#-podman--network--internal)
+* [`ip_range`](#-podman--network--ip_range)
+* [`labels`](#-podman--network--labels)
+* [`subnet`](#-podman--network--subnet)
+* [`ipv6`](#-podman--network--ipv6)
+* [`user`](#-podman--network--user)
-##### `ensure`
+##### `ensure`
Data type: `Enum['present', 'absent']`
@@ -534,16 +542,16 @@ State of the resource must be either 'present' or 'absent'.
Default value: `'present'`
-##### `disable_dns`
+##### `disable_dns`
Data type: `Boolean`
Disables the DNS plugin for this network which if enabled, can perform container
to container name resolution.
-Default value: ``false``
+Default value: `false`
-##### `driver`
+##### `driver`
Data type: `Enum['bridge', 'macvlan']`
@@ -551,7 +559,7 @@ Driver to manage the network.
Default value: `'bridge'`
-##### `opts`
+##### `opts`
Data type: `Array[String]`
@@ -559,32 +567,32 @@ A list of driver specific options.
Default value: `[]`
-##### `gateway`
+##### `gateway`
Data type: `Optional[String]`
Define the gateway for the network. Must also provide the subnet.
-Default value: ``undef``
+Default value: `undef`
-##### `internal`
+##### `internal`
Data type: `Boolean`
Restrict external access of this network.
-Default value: ``false``
+Default value: `false`
-##### `ip_range`
+##### `ip_range`
Data type: `Optional[String]`
Allocate container IP from a range. The range must be a complete subnet and in
CIDR notation.
-Default value: ``undef``
+Default value: `undef`
-##### `labels`
+##### `labels`
Data type: `Hash[String,String]`
@@ -592,33 +600,33 @@ A hash of metadata labels to set on the network.
Default value: `{}`
-##### `subnet`
+##### `subnet`
Data type: `Optional[String]`
The subnet in CIDR notation
-Default value: ``undef``
+Default value: `undef`
-##### `ipv6`
+##### `ipv6`
Data type: `Boolean`
Enable IPv6 (dual-stack) networking.
-Default value: ``false``
+Default value: `false`
-##### `user`
+##### `user`
-Data type: `String`
+Data type: `Optional[String]`
Optional user for creating rootless container networks. For rootless containers,
the user must also be defined as a puppet resource that includes at least
'uid', 'gid', and 'home' attributes.
-Default value: `''`
+Default value: `undef`
-### `podman::pod`
+### `podman::pod`
Create a podman pod with defined flags
@@ -638,19 +646,19 @@ podman::pod { 'mypod':
The following parameters are available in the `podman::pod` defined type:
-* [`ensure`](#ensure)
-* [`flags`](#flags)
-* [`user`](#user)
+* [`ensure`](#-podman--pod--ensure)
+* [`flags`](#-podman--pod--flags)
+* [`user`](#-podman--pod--user)
-##### `ensure`
+##### `ensure`
-Data type: `String`
+Data type: `Enum['present', 'absent']`
State of the resource, which must be either 'present' or 'absent'.
Default value: `'present'`
-##### `flags`
+##### `flags`
Data type: `Hash`
@@ -660,21 +668,157 @@ pod name unless the 'name' flag is included in the hash of flags.
Default value: `{}`
-##### `user`
+##### `user`
-Data type: `String`
+Data type: `Optional[String]`
Optional user for running rootless containers. When using this parameter,
the user must also be defined as a Puppet resource and must include the
'uid', 'gid', and 'home'
-Default value: `''`
+Default value: `undef`
+
+### `podman::quadlet`
+
+Generate and manage podman quadlet definitions (podman > 4.4.0)
+
+* **See also**
+ * podman-systemd.unit.5
+ * https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+
+#### Examples
+
+##### Run a CentOS Container
+
+```puppet
+quadlet::quadlet{'centos.container':
+ ensure => present,
+ unit_entry => {
+ 'Description' => 'Trivial Container that will be very lazy',
+ },
+ service_entry => {
+ 'TimeoutStartSec' => '900',
+ },
+ container_entry => {
+ 'Image' => 'quay.io/centos/centos:latest',
+ 'Exec' => 'sh -c "sleep inf"'
+ },
+ install_entry => {
+ 'WantedBy' => 'default.target'
+ },
+ active => true,
+}
+```
+
+#### Parameters
+
+The following parameters are available in the `podman::quadlet` defined type:
+
+* [`quadlet`](#-podman--quadlet--quadlet)
+* [`ensure`](#-podman--quadlet--ensure)
+* [`user`](#-podman--quadlet--user)
+* [`mode`](#-podman--quadlet--mode)
+* [`active`](#-podman--quadlet--active)
+* [`unit_entry`](#-podman--quadlet--unit_entry)
+* [`install_entry`](#-podman--quadlet--install_entry)
+* [`service_entry`](#-podman--quadlet--service_entry)
+* [`container_entry`](#-podman--quadlet--container_entry)
+* [`pod_entry`](#-podman--quadlet--pod_entry)
+* [`volume_entry`](#-podman--quadlet--volume_entry)
+
+##### `quadlet`
+
+Data type: `Podman::Quadlet_name`
+
+of the quadlet file this is the namevar.
+
+Default value: `$title`
+
+##### `ensure`
+
+Data type: `Enum['present', 'absent']`
+
+State of the container definition.
+
+Default value: `'present'`
+
+##### `user`
+
+Data type: `String[1]`
+
+User running the container
+
+Default value: `'root'`
+
+##### `mode`
+
+Data type: `Stdlib::Filemode`
+
+Filemode of container file.
+
+Default value: `'0444'`
+
+##### `active`
+
+Data type: `Optional[Boolean]`
+
+Make sure the container is running.
+
+Default value: `undef`
-### `podman::rootless`
+##### `unit_entry`
+
+Data type: `Optional[Systemd::Unit::Unit]`
+
+The `[Unit]` section definition.
+
+Default value: `undef`
+
+##### `install_entry`
+
+Data type: `Optional[Systemd::Unit::Install]`
+
+The `[Install]` section definition.
+
+Default value: `undef`
+
+##### `service_entry`
+
+Data type: `Optional[Systemd::Unit::Service]`
+
+The `[Service]` section definition.
+
+Default value: `undef`
+
+##### `container_entry`
+
+Data type: `Optional[Podman::Unit::Container]`
+
+The `[Container]` section defintion.
+
+Default value: `undef`
+
+##### `pod_entry`
+
+Data type: `Optional[Podman::Unit::Pod]`
+
+The `[Pod]` section defintion.
+
+Default value: `undef`
+
+##### `volume_entry`
+
+Data type: `Optional[Podman::Unit::Volume]`
+
+The `[Volume]` section defintion.
+
+Default value: `undef`
+
+### `podman::rootless`
Enable a given user to run rootless podman containers as a systemd user service.
-### `podman::secret`
+### `podman::secret`
Manage a podman secret. Create and remove secrets, it cannot replace.
@@ -717,13 +861,13 @@ podman::secret{'ora_password':
The following parameters are available in the `podman::secret` defined type:
-* [`ensure`](#ensure)
-* [`path`](#path)
-* [`secret`](#secret)
-* [`flags`](#flags)
-* [`user`](#user)
+* [`ensure`](#-podman--secret--ensure)
+* [`path`](#-podman--secret--path)
+* [`secret`](#-podman--secret--secret)
+* [`flags`](#-podman--secret--flags)
+* [`user`](#-podman--secret--user)
-##### `ensure`
+##### `ensure`
Data type: `Enum['present','absent']`
@@ -731,16 +875,16 @@ State of the resource must be either 'present' or 'absent'.
Default value: `'present'`
-##### `path`
+##### `path`
Data type: `Optional[Stdlib::Unixpath]`
Load secret from an existing file path
The secret and path parameters are mutually exclusive.
-Default value: ``undef``
+Default value: `undef`
-##### `secret`
+##### `secret`
Data type: `Optional[Sensitive[String]]`
@@ -749,9 +893,9 @@ changed the secret will **NOT** be modified. Best to set a secret version
as a label.
The secret and path parameters are mutually exclusive.
-Default value: ``undef``
+Default value: `undef`
-##### `flags`
+##### `flags`
Data type: `Hash`
@@ -762,7 +906,7 @@ If the flags for a secret are modified the secret will be recreated.
Default value: `{}`
-##### `user`
+##### `user`
Data type: `Optional[String[1]]`
@@ -770,9 +914,9 @@ Optional user for running rootless containers. When using this parameter,
the user must also be defined as a Puppet resource and must include the
'uid', 'gid', and 'home'
-Default value: ``undef``
+Default value: `undef`
-### `podman::subgid`
+### `podman::subgid`
Define an entry in the `/etc/subgid` file.
@@ -791,23 +935,23 @@ podman::subgid { 'myuser':
The following parameters are available in the `podman::subgid` defined type:
-* [`subgid`](#subgid)
-* [`count`](#count)
-* [`order`](#order)
+* [`subgid`](#-podman--subgid--subgid)
+* [`count`](#-podman--subgid--count)
+* [`order`](#-podman--subgid--order)
-##### `subgid`
+##### `subgid`
Data type: `Integer`
Numerical subordinate group ID
-##### `count`
+##### `count`
Data type: `Integer`
Numerical subordinate group ID count
-##### `order`
+##### `order`
Data type: `Integer`
@@ -815,7 +959,7 @@ Sequence number for concat fragments#
Default value: `10`
-### `podman::subuid`
+### `podman::subuid`
Manage entries in `/etc/subuid`
@@ -834,23 +978,23 @@ podman::subuid { 'namevar':
The following parameters are available in the `podman::subuid` defined type:
-* [`subuid`](#subuid)
-* [`count`](#count)
-* [`order`](#order)
+* [`subuid`](#-podman--subuid--subuid)
+* [`count`](#-podman--subuid--count)
+* [`order`](#-podman--subuid--order)
-##### `subuid`
+##### `subuid`
Data type: `Integer`
Numerical subordinate user ID
-##### `count`
+##### `count`
Data type: `Integer`
Numerical subordinate user ID count
-##### `order`
+##### `order`
Data type: `Integer`
@@ -858,7 +1002,7 @@ Sequence number for concat fragments
Default value: `10`
-### `podman::volume`
+### `podman::volume`
Create a podman volume with defined flags
@@ -878,19 +1022,19 @@ podman::volume { 'myvolume':
The following parameters are available in the `podman::volume` defined type:
-* [`ensure`](#ensure)
-* [`flags`](#flags)
-* [`user`](#user)
+* [`ensure`](#-podman--volume--ensure)
+* [`flags`](#-podman--volume--flags)
+* [`user`](#-podman--volume--user)
-##### `ensure`
+##### `ensure`
-Data type: `String`
+Data type: `Enum['present', 'absent']`
State of the resource must be either 'present' or 'absent'.
Default value: `'present'`
-##### `flags`
+##### `flags`
Data type: `Hash`
@@ -901,13 +1045,140 @@ Volume names are created based on the resoure title (namevar)
Default value: `{}`
-##### `user`
+##### `user`
-Data type: `String`
+Data type: `Optional[String]`
Optional user for running rootless containers. When using this parameter,
the user must also be defined as a Puppet resource and must include the
'uid', 'gid', and 'home'
-Default value: `''`
+Default value: `undef`
+
+## Data types
+
+### `Podman::Quadlet_name`
+
+custom datatype that validates different filenames for quadlet units
+
+* **See also**
+ * https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+
+Alias of `Pattern[/^[a-zA-Z0-9:\-_.\\@%]+\.(container|volume|pod|network)$/]`
+
+### `Podman::Unit::Container`
+
+custom datatype for container entries of podman container quadlet
+
+* **See also**
+ * https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+
+Alias of
+
+```puppet
+Struct[Optional['AddCapability'] => Array[String[1],1],
+ Optional['Annotation'] => Array[String[1],1],
+ Optional['AutoUpdate'] => Enum['registry','local'],
+ Optional['ContainerName'] => String[1],
+ Optional['DNS'] => Array[Stdlib::IP::Address,0],
+ Optional['DNSOption'] => Array[String[1],0],
+ Optional['DNSSearch'] => Array[Stdlib::Fqdn,0],
+ Optional['DropCapability'] => Array[String[1],0],
+ Optional['Environment'] => Array[String[1],0],
+ Optional['EnvironmentFile'] => Array[String[1],0],
+ Optional['Exec'] => String[1],
+ Optional['ExposeHostPort'] => Array[Stdlib::Port,0],
+ Optional['GIDMap'] => Array[String[1],0],
+ Optional['GlobalArgs'] => Array[String[1],0],
+ Optional['Group'] => Integer[0],
+ Optional['HealthCmd'] => String[1],
+ Optional['HealthOnFailure'] => Enum['none','kill','restart','stop'],
+ Optional['HealthStartPeriod'] => String[1],
+ Optional['HealthStartupCmd'] => String[1],
+ Optional['HealthStartupInterval'] => Variant[Enum['disable'],Integer[0]],
+ Optional['HealthStartupTimeout'] => String[1],
+ Optional['HealthTimeout'] => String[1],
+ Optional['Image'] => String[1],
+ Optional['IP'] => Stdlib::IP::Address::V4,
+ Optional['IP6'] => Stdlib::IP::Address::V6,
+ Optional['Label'] => Variant[String[1],Array[String[1],1]],
+ Optional['LogDriver'] => Enum['k8s-file','journald','none','passthrough'],
+ Optional['Mask'] => String[1],
+ Optional['Mount'] => Array[String[1],0],
+ Optional['Network'] => String[1],
+ Optional['NoNewPrivileges'] => Boolean,
+ Optional['Notify'] => Boolean,
+ Optional['PidsLimits'] => Integer[-1],
+ Optional['Pod'] => Pattern[/^[a-zA-Z0-0_-]+\.pod$/],
+ Optional['PodmanArgs'] => Array[String[1],0],
+ Optional['PublishPort'] => Array[Stdlib::Port,1],
+ Optional['Pull'] => Enum['always','missing','never','newer'],
+ Optional['ReadOnly'] => Boolean,
+ Optional['ReadOnlyTmpfs'] => Boolean,
+ Optional['Rootfs'] => String[1],
+ Optional['RunInit'] => Boolean,
+ Optional['SeccompProfile'] => String[1],
+ Optional['Secret'] => Array[String[1],0],
+ Optional['SecurityLabelDisable'] => Boolean,
+ Optional['SecurityLabelFileType'] => String[1],
+ Optional['SecurityLabelNested'] => Boolean,
+ Optional['ShmSize'] => String[1],
+ Optional['StopTimeout'] => Integer[1],
+ Optional['SubGIDMap'] => String[1],
+ Optional['SubUIDMap'] => String[1],
+ Optional['Sysctl'] => Array[String[1],0],
+ Optional['Timezone'] => String[1],
+ Optional['Tmpfs'] => Array[String[1],0],
+ Optional['UIDMap'] => Array[String[1],0],
+ Optional['Ulimit'] => String[1],
+ Optional['Unmask'] => String[1],
+ Optional['User'] => String[1],
+ Optional['UserNS'] => String[1],
+ Optional['Volume'] => Array[String[1],0],
+ Optional['WorkingDir'] => Stdlib::Unixpath]
+```
+
+### `Podman::Unit::Pod`
+
+custom datatype for Volume entries of podman container quadlet
+
+* **See also**
+ * https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+
+Alias of
+
+```puppet
+Struct[Optional['ContainersConfModule'] => Variant[Stdlib::Unixpath,Array[Stdlib::Unixpath,1]],
+ Optional['GlobalArgs'] => Variant[String[1],Array[String[1],1]],
+ Optional['Network'] => String[1],
+ Optional['PodmanArgs'] => Variant[String[1],Array[String[1]]],
+ Optional['PodName'] => String[1],
+ Optional['PublishPort'] => Array[Stdlib::Port,1],
+ Optional['Volume'] => Variant[String[1],Array[String[1],]]]
+```
+
+### `Podman::Unit::Volume`
+
+custom datatype for Volume entries of podman container quadlet
+
+* **See also**
+ * https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+
+Alias of
+
+```puppet
+Struct[Optional['ContainersConfModule'] => Variant[Stdlib::Unixpath,Array[Stdlib::Unixpath,1]],
+ Optional['Copy'] => Boolean,
+ Optional['Device'] => String[1],
+ Optional['Driver'] => String[1],
+ Optional['GlobalArgs'] => Variant[String[1],Array[String[1],1]],
+ Optional['Group'] => String[1],
+ Optional['Image'] => String[1],
+ Optional['Label'] => Variant[String[1],Array[String[1],1]],
+ Optional['Options'] => String[1],
+ Optional['PodmanArgs'] => Variant[String[1],Array[String[1]]],
+ Optional['Type'] => String[1],
+ Optional['User'] => String[1],
+ Optional['VolumeName'] => String[1]]
+```
diff --git a/manifests/quadlet.pp b/manifests/quadlet.pp
new file mode 100644
index 0000000..bbc55d0
--- /dev/null
+++ b/manifests/quadlet.pp
@@ -0,0 +1,150 @@
+# @summary Generate and manage podman quadlet definitions (podman > 4.4.0)
+#
+# @see podman-systemd.unit.5 https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+#
+# @param quadlet of the quadlet file this is the namevar.
+# @param ensure State of the container definition.
+# @param user User running the container
+# @param mode Filemode of container file.
+# @param active Make sure the container is running.
+# @param unit_entry The `[Unit]` section definition.
+# @param install_entry The `[Install]` section definition.
+# @param service_entry The `[Service]` section definition.
+# @param container_entry The `[Container]` section defintion.
+# @param pod_entry The `[Pod]` section defintion.
+# @param volume_entry The `[Volume]` section defintion.
+#
+# @example Run a CentOS Container
+# quadlet::quadlet{'centos.container':
+# ensure => present,
+# unit_entry => {
+# 'Description' => 'Trivial Container that will be very lazy',
+# },
+# service_entry => {
+# 'TimeoutStartSec' => '900',
+# },
+# container_entry => {
+# 'Image' => 'quay.io/centos/centos:latest',
+# 'Exec' => 'sh -c "sleep inf"'
+# },
+# install_entry => {
+# 'WantedBy' => 'default.target'
+# },
+# active => true,
+# }
+#
+define podman::quadlet (
+ Enum['present', 'absent'] $ensure = 'present',
+ Podman::Quadlet_name $quadlet = $title,
+ String[1] $user = 'root',
+ Stdlib::Filemode $mode = '0444',
+ Optional[Boolean] $active = undef,
+ Optional[Systemd::Unit::Install] $install_entry = undef,
+ Optional[Systemd::Unit::Unit] $unit_entry = undef,
+ Optional[Systemd::Unit::Service] $service_entry = undef,
+ Optional[Podman::Unit::Container] $container_entry = undef,
+ Optional[Podman::Unit::Volume] $volume_entry = undef,
+ Optional[Podman::Unit::Pod] $pod_entry = undef,
+) {
+ $_split = $quadlet.split('[.]')
+ $_name = $_split[0]
+ $_type = $_split[1]
+ # Validate the input and find the service name.
+ case $_type {
+ 'container': {
+ if $volume_entry or $pod_entry {
+ fail('A volume_entry or pod_entry makes no sense on a container quadlet')
+ }
+ $_service = "${_name}.service"
+ }
+ 'volume': {
+ if $container_entry or $pod_entry {
+ fail('A container_entry or pod_entry makes no sense on a volume quadlet')
+ }
+ $_service = "${_name}-volume.service"
+ }
+ 'pod': {
+ if $container_entry or $volume_entry {
+ fail('A container_entry or volume_entry makes no sense on a pod quadlet')
+ }
+ $_service = "${_name}-pod.service"
+ }
+ default: {
+ fail('Should never be here due to typing on quadlet')
+ }
+ }
+
+ include podman
+
+ # Determine path
+ if $user == 'root' {
+ $_path = '/etc/containers/systemd'
+ $_group = root
+ } else {
+ $_path = "${User[$user]['home']}/.config/containers/systemd"
+ $_group = User[$user]['gid']
+ ensure_resource('podman::rootless', $user, {})
+ }
+
+ file { "${_path}/${quadlet}":
+ ensure => $ensure,
+ owner => $user,
+ group => $_group,
+ mode => $mode,
+ content => epp('podman/quadlet_file.epp', {
+ 'unit_entry' => $unit_entry,
+ 'service_entry' => $service_entry,
+ 'install_entry' => $install_entry,
+ 'container_entry' => $container_entry,
+ 'volume_entry' => $volume_entry,
+ 'pod_entry' => $pod_entry,
+ }),
+ }
+
+ if $user == 'root' {
+ ensure_resource('systemd::daemon_reload', $quadlet)
+ File["${_path}/${quadlet}"] ~> Systemd::Daemon_reload[$quadlet]
+ } else {
+ File["${_path}/${quadlet}"] ~> Exec["daemon-reload-${user}"]
+ File["${_path}/${quadlet}"] -> Exec["start_${user}.slice"]
+ }
+
+ if $active != undef {
+ if $user == 'root' {
+ service { $_service:
+ ensure => $active,
+ }
+
+ if $ensure == 'absent' {
+ Service[$_service] -> File["${_path}/${quadlet}"]
+ File["${_path}/${quadlet}"] ~> Systemd::Daemon_reload[$quadlet]
+ } else {
+ File["${_path}/${quadlet}"] ~> Service[$_service]
+ Systemd::Daemon_reload[$quadlet] ~> Service[$_service]
+ }
+ } else {
+ $_systemctl_user = ['systemd-run','--pipe', '--wait', '--user','--machine',"${user}@.host", 'systemctl','--user']
+ if $active == true {
+ exec { "start-${quadlet}-${user}":
+ command => $_systemctl_user + ['start', $_service],
+ unless => [$_systemctl_user + ['is-active', $_service]],
+ path => $facts['path'],
+ }
+ exec { "reload-${quadlet}-${user}":
+ command => $_systemctl_user + ['try-reload-or-restart', $_service],
+ refreshonly => true,
+ path => $facts['path'],
+ before => Exec["start-${quadlet}-${user}"],
+ }
+ File["${_path}/${quadlet}"] ~> Exec["reload-${quadlet}-${user}"]
+ Exec["daemon-reload-${user}"] ~> Exec["reload-${quadlet}-${user}"]
+ } else {
+ exec { "stop-${quadlet}-${user}":
+ command => $_systemctl_user + ['stop', $_service],
+ onlyif => [$_systemctl_user + ['is-active', $_service]],
+ path => $facts['path'],
+ }
+ }
+ }
+ }
+}
diff --git a/manifests/rootless.pp b/manifests/rootless.pp
index af01d62..1a0203f 100644
--- a/manifests/rootless.pp
+++ b/manifests/rootless.pp
@@ -15,7 +15,9 @@
ensure_resource('File', [
"${User[$name]['home']}/.config",
"${User[$name]['home']}/.config/systemd",
- "${User[$name]['home']}/.config/systemd/user"
+ "${User[$name]['home']}/.config/systemd/user",
+ "${User[$name]['home']}/.config/containers",
+ "${User[$name]['home']}/.config/containers/systemd",
], {
ensure => directory,
owner => $name,
@@ -36,6 +38,13 @@
],
}
+ # Use https://github.com/voxpupuli/puppet-systemd/pull/443 once available
+ exec { "daemon-reload-${name}":
+ command => ['systemd-run', '--pipe' , '--wait', '--user', '--machine', "${name}@.host", '/usr/bin/systemctl', '--user', 'daemon-reload'],
+ refreshonly => true,
+ path => $facts['path'],
+ }
+
if $podman::enable_api_socket {
exec { "podman rootless api socket ${name}":
command => 'systemctl --user enable --now podman.socket',
diff --git a/metadata.json b/metadata.json
index fe21a0f..f2d5ac7 100644
--- a/metadata.json
+++ b/metadata.json
@@ -12,6 +12,10 @@
"name": "puppetlabs/stdlib",
"version_requirement": ">= 4.1.0 <= 10.0.0"
},
+ {
+ "name": "puppet/systemd",
+ "version_requirement": ">= 5.0.0 <= 7.0.0"
+ },
{
"name": "puppetlabs/concat",
"version_requirement": ">= 2.1.0 <= 10.0.0"
diff --git a/spec/defines/quadlet_spec.rb b/spec/defines/quadlet_spec.rb
new file mode 100644
index 0000000..840b909
--- /dev/null
+++ b/spec/defines/quadlet_spec.rb
@@ -0,0 +1,188 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'podman::quadlet' do
+ on_supported_os.each do |os, os_facts|
+ context "on #{os}" do
+ let(:facts) { os_facts }
+
+ context 'with a simple centos container image' do
+ let(:title) { 'centos.container' }
+ let(:params) do
+ {
+ ensure: 'present',
+ unit_entry: {
+ 'Description' => 'Simple centos container',
+ },
+ service_entry: {
+ 'TimeoutStartSec' => '900',
+ },
+ container_entry: {
+ 'Image' => 'quay.io/centos/centos:latest',
+ 'Exec' => 'sh -c "sleep inf"',
+ },
+ }
+ end
+
+ it { is_expected.to compile.with_all_deps }
+
+ it {
+ is_expected.to contain_file('/etc/containers/systemd/centos.container')
+ .with_ensure('present')
+ .with_owner('root')
+ .with_group('root')
+ .with_mode('0444')
+ .with_content(%r{^\[Unit\]$})
+ .with_content(%r{^Description=Simple centos container$})
+ .with_content(%r{^\[Service\]$})
+ .with_content(%r{^TimeoutStartSec=900})
+ .with_content(%r{^\[Container\]$})
+ .with_content(%r{^Image=quay.io/centos/centos:latest$})
+ .with_content(%r{^Exec=sh -c "sleep inf"$})
+ }
+
+ it { is_expected.to contain_systemd__daemon_reload('centos.container') }
+ it { is_expected.not_to contain_service('centos.container') }
+ it { is_expected.not_to contain_service('centos.service') }
+
+ context 'with the container absent' do
+ let(:params) do
+ super().merge({ ensure: 'absent' })
+ end
+
+ it { is_expected.to contain_file('/etc/containers/systemd/centos.container').with_ensure('absent') }
+ it { is_expected.to contain_systemd__daemon_reload('centos.container') }
+ end
+
+ context 'with the service active' do
+ let(:params) do
+ super().merge({ active: true })
+ end
+
+ it { is_expected.to contain_service('centos.service').with_ensure(true) }
+ end
+
+ context 'with a user set' do
+ let(:pre_condition) do
+ <<~PUPPET
+ user{'steve':
+ ensure => present,
+ uid => 1000,
+ gid => 1000,
+ home => '/var/lib/steve',
+ }
+ file{'/var/lib/steve':
+ ensure => directory,
+ owner => steve,
+ group => 1000,
+ mode => '0644',
+ }
+ PUPPET
+ end
+ let(:params) do
+ super().merge({
+ user: 'steve',
+ })
+ end
+
+ it { is_expected.to compile.with_all_deps }
+ it { is_expected.to contain_podman__rootless('steve') }
+ context 'with active true' do
+ let(:params) do
+ super().merge({
+ active: true,
+ })
+ end
+
+ it { is_expected.to contain_exec('start-centos.container-steve') }
+ it {
+ is_expected.to contain_exec('start-centos.container-steve').with(
+ {
+ command: ['systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', 'systemctl', '--user', 'start', 'centos.service'],
+ unless: [['systemd-run', '--pipe', '--wait', '--user', '--machine', 'steve@.host', 'systemctl', '--user', 'is-active', 'centos.service']],
+ },
+ )
+ }
+ end
+ end
+ end
+ context 'with a simple pod quadlet' do
+ let(:title) { 'mypod.pod' }
+ let(:params) do
+ {
+ ensure: 'present',
+ unit_entry: {
+ 'Description' => 'Simple Pod',
+ },
+ service_entry: {
+ 'TimeoutStartSec' => '900',
+ },
+ pod_entry: {
+ 'Network' => 'host',
+ },
+ }
+ end
+
+ it { is_expected.to compile.with_all_deps }
+
+ it {
+ is_expected.to contain_file('/etc/containers/systemd/mypod.pod')
+ .with_ensure('present')
+ .with_owner('root')
+ .with_group('root')
+ .with_mode('0444')
+ .with_content(%r{^\[Unit\]$})
+ .with_content(%r{^Description=Simple Pod$})
+ .with_content(%r{^\[Pod\]$})
+ .with_content(%r{^Network=host$})
+ }
+ context 'with the pod active' do
+ let(:params) do
+ super().merge({ active: true })
+ end
+
+ it { is_expected.to contain_service('mypod-pod.service') }
+ end
+ end
+ context 'with a simple volume quadlet' do
+ let(:title) { 'myvolume.volume' }
+ let(:params) do
+ {
+ ensure: 'present',
+ unit_entry: {
+ 'Description' => 'Simple Volume',
+ },
+ service_entry: {
+ 'TimeoutStartSec' => '900',
+ },
+ volume_entry: {
+ 'Driver' => 'image',
+ },
+ }
+ end
+
+ it { is_expected.to compile.with_all_deps }
+
+ it {
+ is_expected.to contain_file('/etc/containers/systemd/myvolume.volume')
+ .with_ensure('present')
+ .with_owner('root')
+ .with_group('root')
+ .with_mode('0444')
+ .with_content(%r{^\[Unit\]$})
+ .with_content(%r{^Description=Simple Volume$})
+ .with_content(%r{^\[Volume\]$})
+ .with_content(%r{^Driver=image$})
+ }
+ context 'with the service active' do
+ let(:params) do
+ super().merge({ active: true })
+ end
+
+ it { is_expected.to contain_service('myvolume-volume.service') }
+ end
+ end
+ end
+ end
+end
diff --git a/spec/defines/rootless_spec.rb b/spec/defines/rootless_spec.rb
index fe8725c..bf4da62 100644
--- a/spec/defines/rootless_spec.rb
+++ b/spec/defines/rootless_spec.rb
@@ -79,6 +79,40 @@
)
end
+ it do
+ is_expected.to contain_file('/home/testing-title/.config/containers').only_with(
+ {
+ 'ensure' => 'directory',
+ 'owner' => 'testing-title',
+ 'group' => '1111',
+ 'mode' => '0700',
+ 'require' => 'File[/home/testing-title]',
+ },
+ )
+ end
+
+ it do
+ is_expected.to contain_file('/home/testing-title/.config/containers/systemd').only_with(
+ {
+ 'ensure' => 'directory',
+ 'owner' => 'testing-title',
+ 'group' => '1111',
+ 'mode' => '0700',
+ 'require' => 'File[/home/testing-title]',
+ },
+ )
+ end
+
+ it do
+ is_expected.to contain_exec('daemon-reload-testing-title').only_with(
+ {
+ 'command' => ['systemd-run', '--pipe', '--wait', '--user', '--machine', 'testing-title@.host', '/usr/bin/systemctl', '--user', 'daemon-reload'],
+ 'refreshonly' => true,
+ 'path' => os_facts[:path],
+ },
+ )
+ end
+
it do
is_expected.to contain_exec('start_testing-title.slice').only_with(
{
diff --git a/spec/type_aliases/quadlet_name_spec.rb b/spec/type_aliases/quadlet_name_spec.rb
new file mode 100644
index 0000000..c63afec
--- /dev/null
+++ b/spec/type_aliases/quadlet_name_spec.rb
@@ -0,0 +1,27 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Podman::Quadlet_name' do
+ context 'with a permitted unit name' do
+ [
+ 'centos.container',
+ 'fast.network',
+ 'empty.volume',
+ 'masive.pod',
+ ].each do |unit|
+ it { is_expected.to allow_value(unit) }
+ end
+ end
+
+ context 'with a illegal unit name' do
+ [
+ 'a space.service',
+ 'noending',
+ 'wrong.ending',
+ 'forward/slash.unit',
+ ].each do |unit|
+ it { is_expected.not_to allow_value(unit) }
+ end
+ end
+end
diff --git a/spec/type_aliases/unit_container_spec.rb b/spec/type_aliases/unit_container_spec.rb
new file mode 100644
index 0000000..d7ecd76
--- /dev/null
+++ b/spec/type_aliases/unit_container_spec.rb
@@ -0,0 +1,9 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Podman::Unit::Container' do
+ it { is_expected.to allow_value({ 'Image' => 'busybox' }) }
+ it { is_expected.to allow_value({ 'Exec' => '/bin/bash' }) }
+ it { is_expected.to allow_value({ 'Exec' => './entrypoint.sh' }) }
+end
diff --git a/spec/type_aliases/unit_pod_spec.rb b/spec/type_aliases/unit_pod_spec.rb
new file mode 100644
index 0000000..18e6bd3
--- /dev/null
+++ b/spec/type_aliases/unit_pod_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Podman::Unit::Pod' do
+ it { is_expected.to allow_value({ 'PodName' => 'special' }) }
+end
diff --git a/spec/type_aliases/unit_volume_spec.rb b/spec/type_aliases/unit_volume_spec.rb
new file mode 100644
index 0000000..cbbd0de
--- /dev/null
+++ b/spec/type_aliases/unit_volume_spec.rb
@@ -0,0 +1,7 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe 'Podman::Unit::Volume' do
+ it { is_expected.to allow_value({ 'Driver' => 'image' }) }
+end
diff --git a/templates/quadlet_file.epp b/templates/quadlet_file.epp
new file mode 100644
index 0000000..785606a
--- /dev/null
+++ b/templates/quadlet_file.epp
@@ -0,0 +1,42 @@
+<%- |
+ Optional[Hash] $unit_entry,
+ Optional[Hash] $service_entry,
+ Optional[Hash] $install_entry,
+ Optional[Hash] $container_entry,
+ Optional[Hash] $volume_entry,
+ Optional[Hash] $pod_entry,
+| -%>
+<%-
+
+ # List of possible of unit sections, should match the list of _entry variables
+ # above.
+
+ $_unit_sections = [
+ 'Unit',
+ 'Service',
+ 'Container',
+ 'Pod',
+ 'Volume',
+ 'Install',
+]
+
+-%>
+# Deployed with puppet
+#
+<%-
+$_unit_sections.each | $_section | {
+ $_values = getvar("${downcase($_section)}_entry")
+ if $_values {
+-%>
+
+[<%= $_section %>]
+<%-
+ $_values.each | $_key, $_value | {
+ Array($_value, true).each | $_subvalue | { -%>
+<%= $_key %>=<%= $_subvalue %>
+<%-
+ }
+ }
+ }
+}
+-%>
diff --git a/types/quadlet_name.pp b/types/quadlet_name.pp
new file mode 100644
index 0000000..8f3b996
--- /dev/null
+++ b/types/quadlet_name.pp
@@ -0,0 +1,3 @@
+# @summary custom datatype that validates different filenames for quadlet units
+# @see https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+type Podman::Quadlet_name = Pattern[/^[a-zA-Z0-9:\-_.\\@%]+\.(container|volume|pod|network)$/]
diff --git a/types/unit/container.pp b/types/unit/container.pp
new file mode 100644
index 0000000..dd2f380
--- /dev/null
+++ b/types/unit/container.pp
@@ -0,0 +1,64 @@
+# @summary custom datatype for container entries of podman container quadlet
+# @see https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+type Podman::Unit::Container = Struct[
+ Optional['AddCapability'] => Array[String[1],1],
+ Optional['Annotation'] => Array[String[1],1],
+ Optional['AutoUpdate'] => Enum['registry','local'],
+ Optional['ContainerName'] => String[1],
+ Optional['DNS'] => Array[Stdlib::IP::Address,0],
+ Optional['DNSOption'] => Array[String[1],0],
+ Optional['DNSSearch'] => Array[Stdlib::Fqdn,0],
+ Optional['DropCapability'] => Array[String[1],0],
+ Optional['Environment'] => Array[String[1],0],
+ Optional['EnvironmentFile'] => Array[String[1],0],
+ Optional['Exec'] => String[1],
+ Optional['ExposeHostPort'] => Array[Stdlib::Port,0],
+ Optional['GIDMap'] => Array[String[1],0],
+ Optional['GlobalArgs'] => Array[String[1],0],
+ Optional['Group'] => Integer[0],
+ Optional['HealthCmd'] => String[1],
+ Optional['HealthOnFailure'] => Enum['none','kill','restart','stop'],
+ Optional['HealthStartPeriod'] => String[1],
+ Optional['HealthStartupCmd'] => String[1],
+ Optional['HealthStartupInterval'] => Variant[Enum['disable'],Integer[0]],
+ Optional['HealthStartupTimeout'] => String[1],
+ Optional['HealthTimeout'] => String[1],
+ Optional['Image'] => String[1],
+ Optional['IP'] => Stdlib::IP::Address::V4,
+ Optional['IP6'] => Stdlib::IP::Address::V6,
+ Optional['Label'] => Variant[String[1],Array[String[1],1]],
+ Optional['LogDriver'] => Enum['k8s-file','journald','none','passthrough'],
+ Optional['Mask'] => String[1],
+ Optional['Mount'] => Array[String[1],0],
+ Optional['Network'] => String[1],
+ Optional['NoNewPrivileges'] => Boolean,
+ Optional['Notify'] => Boolean,
+ Optional['PidsLimits'] => Integer[-1],
+ Optional['Pod'] => Pattern[/^[a-zA-Z0-0_-]+\.pod$/],
+ Optional['PodmanArgs'] => Array[String[1],0],
+ Optional['PublishPort'] => Array[Stdlib::Port,1],
+ Optional['Pull'] => Enum['always','missing','never','newer'],
+ Optional['ReadOnly'] => Boolean,
+ Optional['ReadOnlyTmpfs'] => Boolean,
+ Optional['Rootfs'] => String[1],
+ Optional['RunInit'] => Boolean,
+ Optional['SeccompProfile'] => String[1],
+ Optional['Secret'] => Array[String[1],0],
+ Optional['SecurityLabelDisable'] => Boolean,
+ Optional['SecurityLabelFileType'] => String[1],
+ Optional['SecurityLabelNested'] => Boolean,
+ Optional['ShmSize'] => String[1],
+ Optional['StopTimeout'] => Integer[1],
+ Optional['SubGIDMap'] => String[1],
+ Optional['SubUIDMap'] => String[1],
+ Optional['Sysctl'] => Array[String[1],0],
+ Optional['Timezone'] => String[1],
+ Optional['Tmpfs'] => Array[String[1],0],
+ Optional['UIDMap'] => Array[String[1],0],
+ Optional['Ulimit'] => String[1],
+ Optional['Unmask'] => String[1],
+ Optional['User'] => String[1],
+ Optional['UserNS'] => String[1],
+ Optional['Volume'] => Array[String[1],0],
+ Optional['WorkingDir'] => Stdlib::Unixpath,
+]
diff --git a/types/unit/pod.pp b/types/unit/pod.pp
new file mode 100644
index 0000000..84fdc0d
--- /dev/null
+++ b/types/unit/pod.pp
@@ -0,0 +1,11 @@
+# @summary custom datatype for Volume entries of podman container quadlet
+# @see https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+type Podman::Unit::Pod = Struct[
+ Optional['ContainersConfModule'] => Variant[Stdlib::Unixpath,Array[Stdlib::Unixpath,1]],
+ Optional['GlobalArgs'] => Variant[String[1],Array[String[1],1]],
+ Optional['Network'] => String[1],
+ Optional['PodmanArgs'] => Variant[String[1],Array[String[1]]],
+ Optional['PodName'] => String[1],
+ Optional['PublishPort'] => Array[Stdlib::Port,1],
+ Optional['Volume'] => Variant[String[1],Array[String[1],]],
+]
diff --git a/types/unit/volume.pp b/types/unit/volume.pp
new file mode 100644
index 0000000..4b130e6
--- /dev/null
+++ b/types/unit/volume.pp
@@ -0,0 +1,17 @@
+# @summary custom datatype for Volume entries of podman container quadlet
+# @see https://docs.podman.io/en/latest/markdown/podman-systemd.unit.5.html
+type Podman::Unit::Volume = Struct[
+ Optional['ContainersConfModule'] => Variant[Stdlib::Unixpath,Array[Stdlib::Unixpath,1]],
+ Optional['Copy'] => Boolean,
+ Optional['Device'] => String[1],
+ Optional['Driver'] => String[1],
+ Optional['GlobalArgs'] => Variant[String[1],Array[String[1],1]],
+ Optional['Group'] => String[1],
+ Optional['Image'] => String[1],
+ Optional['Label'] => Variant[String[1],Array[String[1],1]],
+ Optional['Options'] => String[1],
+ Optional['PodmanArgs'] => Variant[String[1],Array[String[1]]],
+ Optional['Type'] => String[1],
+ Optional['User'] => String[1],
+ Optional['VolumeName'] => String[1],
+]