1
0
Fork 0
mirror of https://github.com/containers/ansible-podman-collections.git synced 2026-02-03 23:01:48 +00:00
ansible-podman-collections/plugins/modules/podman_generate_systemd.py
Sergey f2e813671a
Run black -l 120 on all files, again (#943)
Signed-off-by: Sagi Shnaidman <sshnaidm@redhat.com>
2025-06-26 13:24:44 +03:00

602 lines
19 KiB
Python

#!/usr/bin/python
# coding: utf-8 -*-
# 2022, Sébastien Gendre <seb@k-7.ch>
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function
__metaclass__ = type
DOCUMENTATION = """
module: podman_generate_systemd
author:
- Sébastien Gendre (@CyberFox001)
short_description: Generate systemd unit from a pod or a container
description:
- Generate systemd .service unit file(s) from a pod or a container
- Support Ansible check mode
options:
name:
description:
- Name of the pod or container to export
type: str
required: true
dest:
description:
- Destination of the generated systemd unit file(s).
- Use C(/etc/systemd/system) for the system-wide systemd instance.
- Use C(/etc/systemd/user) or C(~/.config/systemd/user) for use with per-user instances of systemd.
type: path
force:
description:
- Replace the systemd unit file(s) even if it already exists.
- This works with dest option.
type: bool
default: false
new:
description:
- Generate unit files that create containers and pods, not only start them.
- Refer to podman-generate-systemd(1) man page for more information.
type: bool
default: false
restart_policy:
description:
- Restart policy of the service
type: str
choices:
- no-restart
- on-success
- on-failure
- on-abnormal
- on-watchdog
- on-abort
- always
restart_sec:
description:
- Configures the time to sleep before restarting a service (as configured with restart-policy).
- Takes a value in seconds.
- Only with Podman 4.0.0 and above
type: int
start_timeout:
description:
- Override the default start timeout for the container with the given value in seconds.
- Only with Podman 4.0.0 and above
type: int
stop_timeout:
description:
- Override the default stop timeout for the container with the given value in seconds.
type: int
env:
description:
- Set environment variables to the systemd unit files.
- Keys are the environment variable names, and values are the environment variable values
- Only with Podman 4.3.0 and above
type: dict
use_names:
description:
- Use name of the containers for the start, stop, and description in the unit file.
type: bool
default: true
container_prefix:
description:
- Set the systemd unit name prefix for containers.
- If not set, use the default defined by podman, C(container).
- Refer to podman-generate-systemd(1) man page for more information.
type: str
pod_prefix:
description:
- Set the systemd unit name prefix for pods.
- If not set, use the default defined by podman, C(pod).
- Refer to podman-generate-systemd(1) man page for more information.
type: str
separator:
description:
- Systemd unit name separator between the name/id of a container/pod and the prefix.
- If not set, use the default defined by podman, C(-).
- Refer to podman-generate-systemd(1) man page for more information.
type: str
no_header:
description:
- Do not generate the header including meta data such as the Podman version and the timestamp.
type: bool
default: false
after:
description:
- Add the systemd unit after (C(After=)) option, that ordering dependencies between the list of dependencies and this service.
- This option may be specified more than once.
- User-defined dependencies will be appended to the generated unit file
- But any existing options such as needed or defined by default (e.g. C(online.target)) will not be removed or overridden.
- Only with Podman 4.0.0 and above
type: list
elements: str
wants:
description:
- Add the systemd unit wants (C(Wants=)) option, that this service is (weak) dependent on.
- This option may be specified more than once.
- This option does not influence the order in which services are started or stopped.
- User-defined dependencies will be appended to the generated unit file
- But any existing options such as needed or defined by default (e.g. C(online.target)) will not be removed or overridden.
- Only with Podman 4.0.0 and above
type: list
elements: str
requires:
description:
- Set the systemd unit requires (Requires=) option.
- Similar to wants, but declares a stronger requirement dependency.
- Only with Podman 4.0.0 and above
type: list
elements: str
executable:
description:
- C(Podman) executable name or full path
type: str
default: podman
requirements:
- Podman installed on target host
notes:
- If you indicate a pod, the systemd units for it and all its containers will be generated
- Create all your pods, containers and their dependencies before generating the systemd files
- If a container or pod is already started before you do a C(systemctl daemon-reload),
systemd will not see the container or pod as started
- Stop your container or pod before you do a C(systemctl daemon-reload),
then you can start them with C(systemctl start my_container.service)
"""
EXAMPLES = """
# Example of creating a container and systemd unit file.
# When using podman_generate_systemd with new:true then
# the container needs rm:true for idempotence.
- name: Create postgres container
containers.podman.podman_container:
name: postgres
image: docker.io/library/postgres:latest
rm: true
state: created
- name: Generate systemd unit file for postgres container
containers.podman.podman_generate_systemd:
name: postgres
new: true
no_header: true
dest: /etc/systemd/system
- name: Ensure postgres container is started and enabled
ansible.builtin.systemd:
name: container-postgres
daemon_reload: true
state: started
enabled: true
# Example of creating a container and integrate it into systemd
- name: A postgres container must exist, stopped
containers.podman.podman_container:
name: postgres_local
image: docker.io/library/postgres:latest
state: stopped
- name: Systemd unit files for postgres container must exist
containers.podman.podman_generate_systemd:
name: postgres_local
dest: ~/.config/systemd/user/
- name: Postgres container must be started and enabled on systemd
ansible.builtin.systemd:
name: container-postgres_local
scope: user
daemon_reload: true
state: started
enabled: true
# Generate the unit files, but store them on an Ansible variable
# instead of writing them on target host
- name: Systemd unit files for postgres container must be generated
containers.podman.podman_generate_systemd:
name: postgres_local
register: postgres_local_systemd_unit
# Generate the unit files with environment variables sets
- name: Systemd unit files for postgres container must be generated
containers.podman.podman_generate_systemd:
name: postgres_local
env:
POSTGRES_USER: my_app
POSTGRES_PASSWORD: example
register: postgres_local_systemd_unit
"""
RETURN = """
systemd_units:
description: A copy of the generated systemd .service unit(s)
returned: always
type: dict
sample: {
"container-postgres_local": " #Content of the systemd .servec unit for postgres_local container",
"pod-my_webapp": " #Content of the systemd .servec unit for my_webapp pod",
}
podman_command:
description: A copy of the podman command used to generate the systemd unit(s)
returned: always
type: str
sample: "podman generate systemd my_webapp"
"""
import os
from ansible.module_utils.basic import AnsibleModule
import json
from ansible_collections.containers.podman.plugins.module_utils.podman.common import (
compare_systemd_file_content,
)
RESTART_POLICY_CHOICES = [
"no-restart",
"on-success",
"on-failure",
"on-abnormal",
"on-watchdog",
"on-abort",
"always",
]
def generate_systemd(module):
"""Generate systemd .service unit file from a pod or container.
Parameter:
- module (AnsibleModule): An AnsibleModule object
Returns (tuple[bool, list[str], str]):
- A boolean which indicate whether the targeted systemd state is modified
- A copy of the generated systemd .service units content
- A copy of the command, as a string
"""
# Flag which indicate whether the targeted system state is modified
changed = False
# Build the podman command, based on the module parameters
command_options = []
# New option
if module.params["new"]:
command_options.append("--new")
# Restart policy option
restart_policy = module.params["restart_policy"]
if restart_policy:
# add the restart policy to options
if restart_policy == "no-restart":
restart_policy = "no"
command_options.append(
"--restart-policy={restart_policy}".format(
restart_policy=restart_policy,
),
)
# Restart-sec option (only for Podman 4.0.0 and above)
restart_sec = module.params["restart_sec"]
if restart_sec:
command_options.append(
"--restart-sec={restart_sec}".format(
restart_sec=restart_sec,
),
)
# Start-timeout option (only for Podman 4.0.0 and above)
start_timeout = module.params["start_timeout"]
if start_timeout:
command_options.append(
"--start-timeout={start_timeout}".format(
start_timeout=start_timeout,
),
)
# Stop-timeout option
stop_timeout = module.params["stop_timeout"]
if stop_timeout:
command_options.append(
"--stop-timeout={stop_timeout}".format(
stop_timeout=stop_timeout,
),
)
# Use container name(s) option
if module.params["use_names"]:
command_options.append("--name")
# Container-prefix option
container_prefix = module.params["container_prefix"]
if container_prefix is not None:
command_options.append(
"--container-prefix={container_prefix}".format(
container_prefix=container_prefix,
),
)
# Pod-prefix option
pod_prefix = module.params["pod_prefix"]
if pod_prefix is not None:
command_options.append(
"--pod-prefix={pod_prefix}".format(
pod_prefix=pod_prefix,
),
)
# Separator option
separator = module.params["separator"]
if separator is not None:
command_options.append(
"--separator={separator}".format(
separator=separator,
),
)
# No-header option
if module.params["no_header"]:
command_options.append("--no-header")
# After option (only for Podman 4.0.0 and above)
after = module.params["after"]
if after:
for item in after:
command_options.append(
"--after={item}".format(
item=item,
),
)
# Wants option (only for Podman 4.0.0 and above)
wants = module.params["wants"]
if wants:
for item in wants:
command_options.append(
"--wants={item}".format(
item=item,
)
)
# Requires option (only for Podman 4.0.0 and above)
requires = module.params["requires"]
if requires:
for item in requires:
command_options.append(
"--requires={item}".format(
item=item,
),
)
# Environment variables (only for Podman 4.3.0 and above)
environment_variables = module.params["env"]
if environment_variables:
for env_var_name, env_var_value in environment_variables.items():
command_options.append(
"-e='{env_var_name}={env_var_value}'".format(
env_var_name=env_var_name,
env_var_value=env_var_value,
),
)
# Set output format, of podman command, to json
command_options.extend(["--format", "json"])
# Full command build, with option included
# Base of the command
command = [
module.params["executable"],
"generate",
"systemd",
]
# Add the options to the commande
command.extend(command_options)
# Add pod or container name to the command
command.append(module.params["name"])
# Build the string version of the command, only for module return
command_str = " ".join(command)
# Run the podman command to generated systemd .service unit(s) content
return_code, stdout, stderr = module.run_command(command)
# In case of error in running the command
if return_code != 0:
# Print information about the error and return and empty dictionary
message = "Error generating systemd .service unit(s)."
message += " Command executed: {command_str}"
message += " Command returned with code: {return_code}."
message += " Error message: {stderr}."
module.fail_json(
msg=message.format(
command_str=command_str,
return_code=return_code,
stderr=stderr,
),
changed=changed,
systemd_units={},
podman_command=command_str,
)
# In case of command execution success, its stdout is a json
# dictionary. This dictionary is all the generated systemd units.
# Each key value pair is one systemd unit. The key is the unit name
# and the value is the unit content.
# Load the returned json dictionary as a python dictionary
systemd_units = json.loads(stdout)
# Write the systemd .service unit(s) content to file(s), if
# requested
if module.params["dest"]:
try:
systemd_units_dest = module.params["dest"]
# If destination don't exist
if not os.path.exists(systemd_units_dest):
# If not in check mode, make it
if not module.check_mode:
os.makedirs(systemd_units_dest)
changed = True
# If destination exist but not a directory
if not os.path.isdir(systemd_units_dest):
# Stop and tell user that the destination is not a directory
message = "Destination {systemd_units_dest} is not a directory."
message += " Can't save systemd unit files in."
module.fail_json(
msg=message.format(
systemd_units_dest=systemd_units_dest,
),
changed=changed,
systemd_units=systemd_units,
podman_command=command_str,
)
# Write each systemd unit, if needed
for unit_name, unit_content in systemd_units.items():
# Build full path to unit file
unit_file_name = unit_name + ".service"
unit_file_full_path = os.path.join(
systemd_units_dest,
unit_file_name,
)
if module.params["force"]:
# Force to replace the existing unit file
need_to_write_file = True
else:
# See if we need to write the unit file, default yes
need_to_write_file = bool(compare_systemd_file_content(unit_file_full_path, unit_content))
# Write the file, if needed
if need_to_write_file:
with open(unit_file_full_path, "w") as unit_file:
# If not in check mode, write the file
if not module.check_mode:
unit_file.write(unit_content)
changed = True
except Exception as exception:
# When exception occurs while trying to write units file
message = "PODMAN-GENERATE-SYSTEMD-DEBUG: "
message += "Error writing systemd units files: "
message += "{exception}"
module.log(
message.format(exception=exception),
)
# Return the systemd .service unit(s) content
return changed, systemd_units, command_str
def run_module():
"""Run the module on the target"""
# Build the list of parameters user can use
module_parameters = {
"name": {
"type": "str",
"required": True,
},
"dest": {
"type": "path",
"required": False,
},
"new": {
"type": "bool",
"required": False,
"default": False,
},
"force": {
"type": "bool",
"required": False,
"default": False,
},
"restart_policy": {
"type": "str",
"required": False,
"choices": RESTART_POLICY_CHOICES,
},
"restart_sec": {
"type": "int",
"required": False,
},
"start_timeout": {
"type": "int",
"required": False,
},
"stop_timeout": {
"type": "int",
"required": False,
},
"env": {
"type": "dict",
"required": False,
},
"use_names": {
"type": "bool",
"required": False,
"default": True,
},
"container_prefix": {
"type": "str",
"required": False,
},
"pod_prefix": {
"type": "str",
"required": False,
},
"separator": {
"type": "str",
"required": False,
},
"no_header": {
"type": "bool",
"required": False,
"default": False,
},
"after": {
"type": "list",
"elements": "str",
"required": False,
},
"wants": {
"type": "list",
"elements": "str",
"required": False,
},
"requires": {
"type": "list",
"elements": "str",
"required": False,
},
"executable": {
"type": "str",
"required": False,
"default": "podman",
},
}
# Build result dictionary
result = {
"changed": False,
"systemd_units": {},
"podman_command": "",
}
# Build the Ansible Module
module = AnsibleModule(argument_spec=module_parameters, supports_check_mode=True)
# Generate the systemd units
state_changed, systemd_units, podman_command = generate_systemd(module)
result["changed"] = state_changed
result["systemd_units"] = systemd_units
result["podman_command"] = podman_command
# Return the result
module.exit_json(**result)
def main():
"""Main function of this script."""
run_module()
if __name__ == "__main__":
main()