From 9cfd12e7454746b548b2c128e3951f68eea9615f Mon Sep 17 00:00:00 2001 From: Daniel Akulenok Date: Fri, 5 Sep 2025 22:49:16 +0200 Subject: [PATCH] init --- README.md | 273 ++++++++ defaults/main.yml | 603 ++++++++++++++++++ handlers/main.yml | 8 + meta/main.yml | 24 + meta/requirements.yml | 5 + molecule/default/converge.yml | 17 + molecule/default/molecule.yml | 30 + molecule/default/prepare.yml | 28 + molecule/default/requirements.yml | 3 + .../__pycache__/test_default.cpython-313.pyc | Bin 0 -> 2463 bytes molecule/default/tests/test_default.py | 34 + tasks/configure.yml | 58 ++ tasks/containers.yml | 241 +++++++ tasks/install.yml | 14 + tasks/main.yml | 48 ++ tasks/networks.yml | 25 + tasks/pods.yml | 66 ++ tasks/services.yml | 32 + tasks/volumes.yml | 19 + templates/policy.json.j2 | 37 ++ templates/registries.conf.j2 | 54 ++ templates/storage.conf.j2 | 13 + vars/Debian.yml | 14 + 23 files changed, 1646 insertions(+) create mode 100644 README.md create mode 100644 defaults/main.yml create mode 100644 handlers/main.yml create mode 100644 meta/main.yml create mode 100644 meta/requirements.yml create mode 100644 molecule/default/converge.yml create mode 100644 molecule/default/molecule.yml create mode 100644 molecule/default/prepare.yml create mode 100644 molecule/default/requirements.yml create mode 100644 molecule/default/tests/__pycache__/test_default.cpython-313.pyc create mode 100644 molecule/default/tests/test_default.py create mode 100644 tasks/configure.yml create mode 100644 tasks/containers.yml create mode 100644 tasks/install.yml create mode 100644 tasks/main.yml create mode 100644 tasks/networks.yml create mode 100644 tasks/pods.yml create mode 100644 tasks/services.yml create mode 100644 tasks/volumes.yml create mode 100644 templates/policy.json.j2 create mode 100644 templates/registries.conf.j2 create mode 100644 templates/storage.conf.j2 create mode 100644 vars/Debian.yml diff --git a/README.md b/README.md new file mode 100644 index 0000000..88af9ad --- /dev/null +++ b/README.md @@ -0,0 +1,273 @@ +Podman +====== + +This Ansible role installs and configures Podman container runtime, and provides comprehensive container, pod, network, and volume management capabilities. + +Requirements +------------ + +- Ansible 2.11 or higher +- Target systems: Ubuntu 20.04+, Debian 11+ +- containers.podman collection (for container management tasks) + +Role Variables +-------------- + +### Installation Variables + +* `podman_install_from_repo`: Install from official repositories (default: `true`) +* `podman_packages`: List of core Podman packages to install +* `podman_additional_packages`: Additional packages for full container support + +### Configuration Variables + +* `podman_configure_registries`: Configure container registries (default: `true`) +* `podman_registries_conf_path`: Path to registries configuration (default: `/etc/containers/registries.conf`) +* `podman_registries_additional`: Additional registry configurations for special cases + +**Note**: Registry configuration is now unified with the image signature policy through `podman_policy_trusted_registries`. Each registry in the policy configuration includes both security settings (signature verification) and registry behavior (insecure, blocked, unqualified search). + +* `podman_configure_storage`: Configure storage settings (default: `true`) +* `podman_storage_conf_path`: Path to storage configuration (default: `/etc/containers/storage.conf`) +* `podman_storage_driver`: Storage driver to use (default: `overlay`) +* `podman_storage_runroot`: Runtime storage path (default: `/run/containers/storage`) +* `podman_storage_graphroot`: Persistent storage path (default: `/var/lib/containers/storage`) + +* `podman_configure_policy`: Configure container policy (default: `true`) +* `podman_policy_path`: Path to policy configuration (default: `/etc/containers/policy.json`) + +### Image Signature Policy Variables + +* `podman_policy_default_type`: Default policy for unlisted registries (`"insecureAcceptAnything"` or `"reject"`) +* `podman_policy_reject_unknown_registries`: Reject images from unlisted registries (default: `false`) +* `podman_policy_trusted_registries`: Unified registry configuration for both policy and registries.conf + +Each registry in `podman_policy_trusted_registries` supports: + +**Security Policy Options:** +- `type`: Verification type (`"insecureAcceptAnything"`, `"signedBy"`, `"reject"`) +- `keyPath`: Path to GPG key file (for `signedBy` type) +- `keyData`: Inline GPG key data (alternative to `keyPath`) + +**Registry Configuration Options:** +- `insecure`: Allow insecure (HTTP) connections (default: `false`) +- `blocked`: Block access to this registry (default: `false`) +- `unqualified_search`: Include in unqualified image searches (default: `true`) +- `mirror`: List of mirror registries for redundancy/performance + +```yaml +# Unified registry and policy configuration +podman_policy_trusted_registries: + - registry: "docker.io" + # Policy settings + type: "insecureAcceptAnything" + # Registry settings + insecure: false + blocked: false + unqualified_search: true + + - registry: "internal-registry.company.com" + # Policy settings + type: "signedBy" + keyPath: "/etc/pki/containers/company.gpg" + # Registry settings + insecure: false + blocked: false + unqualified_search: true + # Mirror configuration + mirror: + - location: "backup-registry.company.com" + insecure: false + +# Development configuration (default) +podman_policy_default_type: "insecureAcceptAnything" +podman_policy_reject_unknown_registries: false + +# Production configuration with signature verification +podman_policy_default_type: "reject" +podman_policy_reject_unknown_registries: true +``` + +### Service Variables + +* `podman_enable_socket`: Enable Podman socket service (default: `false`) +* `podman_enable_api_service`: Enable Podman API service (default: `false`) + +### Container Management Variables + +```yaml +podman_containers: + - name: nginx + image: docker.io/nginx:latest + state: started + ports: + - "8080:80" + volumes: + - "/etc/nginx/conf.d:/etc/nginx/conf.d:ro" + env: + NGINX_HOST: example.com + restart_policy: always + user: nginx + networks: + - podman + labels: + app: webserver + version: "1.0" +``` + +### Network Management Variables + +```yaml +podman_networks: + - name: app-network + driver: bridge + subnet: "10.89.0.0/24" + gateway: "10.89.0.1" + state: present + internal: false + disable_dns: false + dns: + - "8.8.8.8" + options: + mtu: 1500 + vlan: 100 + ipam_driver: "host-local" + interface_name: "podman1" + route: + - "10.10.0.0/16,192.168.1.1" + - name: macvlan-net + driver: macvlan + macvlan: "eth0" + subnet: "192.168.1.0/24" + - name: ipv6-net + driver: bridge + subnet: "fd00::/64" + ipv6: true + recreate: false +``` + +### Volume Management Variables + +```yaml +podman_volumes: + - name: app-data + state: present + driver: local + labels: + environment: production + backup: daily + options: + - "device=/dev/sdb1" + - "type=ext4" + - "o=rw" + - name: tmpfs-volume + state: present + driver: tmpfs + options: + - "tmpfs-size=100m" + - "tmpfs-mode=1777" + - name: quadlet-volume + state: quadlet + driver: local + quadlet_filename: "custom-volume" + quadlet_file_mode: "0640" + quadlet_options: + - "Group=192" + - "Copy=true" + recreate: false + debug: false +``` + +### Pod Management Variables + +```yaml +podman_pods: + - name: webapp-pod + state: started + ports: + - "8080:80" + - "3306:3306" + networks: + - frontend + hostname: webapp + dns: + - "8.8.8.8" + labels: + app: webapp + tier: frontend + volumes: + - "webapp-data:/data" + infra: true + infra_image: "k8s.gcr.io/pause:3.1" + memory: "2g" + cpu_shares: "1024" + security_opt: + - "seccomp=unconfined" + add_host: + - "database.local:127.0.0.1" + share: "net,ipc" + userns: "auto" + device: + - "/dev/sda:/dev/xvda:rwm" + sysctl: + net.core.somaxconn: "1024" + exit_policy: "stop" + - name: quadlet-pod + state: quadlet + quadlet_filename: "custom-pod" + quadlet_options: + - "AutoUpdate=registry" + generate_systemd: + path: "/etc/systemd/system" + restart_policy: "always" +``` + +Dependencies +------------ + +* `containers.podman` collection for container management tasks + +Example Playbook +---------------- + +```yaml +- hosts: servers + roles: + - role: keepit.podman + vars: + podman_enable_socket: true + podman_containers: + - name: web-server + image: nginx:latest + state: started + ports: + - "80:80" + volumes: + - "/var/www/html:/usr/share/nginx/html:ro" + podman_networks: + - name: web-network + subnet: "172.20.0.0/16" +``` + +Tags +---- + +* `podman` - Run all tasks +* `podman-install` - Install packages only +* `podman-configure` - Configure Podman only +* `podman-services` - Manage services only +* `podman-networks` - Manage networks only +* `podman-volumes` - Manage volumes only +* `podman-pods` - Manage pods only +* `podman-containers` - Manage containers only + +License +------- + +MIT + +Author Information +------------------ + +Daniel Akulenok +Keepit A/S diff --git a/defaults/main.yml b/defaults/main.yml new file mode 100644 index 0000000..7ee6eaf --- /dev/null +++ b/defaults/main.yml @@ -0,0 +1,603 @@ +--- +# defaults file for podman + +# Package installation +podman_install_from_repo: true +podman_packages: + - podman + - crun + +# Podman configuration +podman_configure_registries: true +podman_registries_conf_path: /etc/containers/registries.conf + +# Additional registries not included in policy configuration +# Use this for registries that need special mirror or proxy configuration +podman_registries_additional: [] +# Example: +# podman_registries_additional: +# - location: "internal-mirror.company.com" +# insecure: false +# blocked: false +# mirror: +# - location: "docker.io" +# insecure: false + +# Storage configuration +podman_configure_storage: true +podman_storage_conf_path: /etc/containers/storage.conf +podman_storage_driver: overlay +podman_storage_runroot: /run/containers/storage +podman_storage_graphroot: /var/lib/containers/storage + +# Policy configuration +podman_configure_policy: true +podman_policy_path: /etc/containers/policy.json + +# Image signature policy settings +# Default policy type: "insecureAcceptAnything" for development, "reject" for production +podman_policy_default_type: "insecureAcceptAnything" + +# Whether to reject images from unknown/unlisted registries +podman_policy_reject_unknown_registries: false + +# Trusted registries configuration +# Each registry can have different trust levels and signature requirements +# Plus registries.conf configuration options +podman_policy_trusted_registries: + # Docker Hub official images (library namespace) + - registry: "docker.io/library" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: false # Don't search library namespace for unqualified images + + # Docker Hub (all namespaces) - use with caution in production + - registry: "docker.io" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: true # Primary search registry + + # Red Hat's official registry + - registry: "registry.redhat.io" + type: "insecureAcceptAnything" + # For production with signature verification: + # type: "signedBy" + # keyPath: "/etc/pki/containers/redhat.gpg" + # Registry configuration + insecure: false + blocked: false + unqualified_search: true + + # Quay.io - Red Hat's container registry + - registry: "quay.io" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: true + + # Google Container Registry + - registry: "gcr.io" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: true + + # Microsoft Container Registry + - registry: "mcr.microsoft.com" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: true + + # Amazon ECR Public + - registry: "public.ecr.aws" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: false # Not commonly used for unqualified searches + + # GitHub Container Registry + - registry: "ghcr.io" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: false # Not commonly used for unqualified searches + + # GitLab Container Registry + - registry: "registry.gitlab.com" + type: "insecureAcceptAnything" + # Registry configuration + insecure: false + blocked: false + unqualified_search: false # Not commonly used for unqualified searches + +# Example production configuration with signature verification: +# podman_policy_default_type: "reject" +# podman_policy_reject_unknown_registries: true +# podman_policy_trusted_registries: +# # Internal company registry with signature verification +# - registry: "internal-registry.company.com" +# type: "signedBy" +# keyPath: "/etc/pki/containers/company.gpg" +# # Registry configuration +# insecure: false +# blocked: false +# unqualified_search: true +# # Optional: mirror configuration +# mirror: +# - location: "backup-registry.company.com" +# insecure: false +# +# # Red Hat registry with signature verification +# - registry: "registry.redhat.io" +# type: "signedBy" +# keyPath: "/etc/pki/containers/redhat.gpg" +# # Registry configuration +# insecure: false +# blocked: false +# unqualified_search: true +# +# # Docker Hub official images only (more restrictive) +# - registry: "docker.io/library" +# type: "insecureAcceptAnything" +# # Registry configuration +# insecure: false +# blocked: false +# unqualified_search: false +# +# # Specific trusted namespaces +# - registry: "quay.io/company" +# type: "signedBy" +# keyPath: "/etc/pki/containers/company.gpg" +# # Registry configuration +# insecure: false +# blocked: false +# unqualified_search: false + +# Service management +podman_enable_socket: false +podman_enable_api_service: false + +# Container management +podman_containers: [] +# Example container configurations: +# podman_containers: +# # Basic web server container +# - name: nginx +# image: docker.io/nginx:latest +# state: started +# ports: +# - "8080:80" +# volumes: +# - "/etc/nginx/conf.d:/etc/nginx/conf.d:ro" +# env: +# NGINX_HOST: example.com +# restart_policy: always +# user: nginx +# networks: +# - podman +# labels: +# app: webserver +# version: "1.0" +# working_dir: /app +# +# # Advanced container with comprehensive configuration +# - name: app-server +# image: registry.example.com/myapp:v1.2.3 +# state: started +# # Basic runtime options +# command: ["/app/start.sh", "--config", "/etc/app/config.yaml"] +# entrypoint: "/entrypoint.sh" +# user: "1000:1000" +# working_dir: /app +# hostname: app-server +# detach: true +# interactive: false +# tty: false +# auto_remove: false +# privileged: false +# read_only: false +# read_only_tmpfs: true +# init: true +# +# # Networking configuration +# networks: +# - frontend +# - backend +# ports: +# - "8080:8080" +# - "127.0.0.1:8081:8081/tcp" +# publish_all: false +# expose: +# - "9000" +# ip: "10.88.0.100" +# mac_address: "02:42:ac:11:00:02" +# network_aliases: +# - app +# - api +# dns: +# - "8.8.8.8" +# - "1.1.1.1" +# dns_options: "ndots:2" +# dns_search: +# - "example.com" +# - "local" +# add_hosts: +# database: "192.168.1.100" +# cache: "192.168.1.101" +# no_hosts: false +# +# # Storage and volumes +# volumes: +# - "/data/app:/app/data:rw" +# - "app-config:/etc/app:ro" +# volumes_from: +# - data-container +# mounts: +# - "type=bind,source=/host/path,destination=/container/path,ro" +# tmpfs: +# /tmp: "rw,size=100m,mode=1777" +# /var/cache: "rw,size=50m" +# +# # Environment variables +# env: +# DATABASE_URL: "postgresql://user:pass@db:5432/myapp" +# REDIS_URL: "redis://cache:6379/0" +# LOG_LEVEL: "info" +# FEATURE_FLAGS: "new_ui,api_v2" +# env_files: +# - "/etc/app/.env" +# env_host: false +# env_merge: +# PATH: "/app/bin:$PATH" +# unsetenv: +# - "TEMP_VAR" +# +# # Resource constraints +# memory: "2g" +# memory_reservation: "1g" +# memory_swap: "4g" +# memory_swappiness: 60 +# cpus: "1.5" +# cpu_shares: 1024 +# cpu_period: 100000 +# cpu_quota: 150000 +# cpuset_cpus: "0-1" +# cpuset_mems: "0" +# blkio_weight: 500 +# blkio_weight_device: +# "/dev/sda": 600 +# oom_kill_disable: false +# oom_score_adj: 500 +# pids_limit: "1000" +# +# # Device access +# devices: +# - "/dev/nvidia0:/dev/nvidia0:rwm" +# - "/dev/sda:/dev/xvda:ro" +# device_read_bps: +# - "/dev/sda:1mb" +# device_write_bps: +# - "/dev/sda:1mb" +# device_read_iops: +# - "/dev/sda:1000" +# device_write_iops: +# - "/dev/sda:1000" +# gpus: "all" +# +# # Security configuration +# security_opt: +# - "seccomp=unconfined" +# - "label=type:container_runtime_t" +# cap_add: +# - "NET_ADMIN" +# - "SYS_TIME" +# cap_drop: +# - "MKNOD" +# - "AUDIT_WRITE" +# userns: "host" +# uidmap: +# - "0:1000:1000" +# gidmap: +# - "0:1000:1000" +# subuidname: "myuser" +# subgidname: "myuser" +# groups: +# - "docker" +# - "audio" +# +# # Namespaces +# ipc_mode: "container:other-container" +# pid_mode: "host" +# uts: "host" +# cgroupns: "host" +# +# # Cgroups +# cgroups: "enabled" +# cgroup_parent: "/system.slice" +# cgroup_conf: +# "memory.swappiness": "10" +# +# # System configuration +# sysctl: +# net.core.somaxconn: "1024" +# kernel.shm_rmid_forced: "1" +# systemd: "true" +# ulimits: +# - "nofile=65536:65536" +# - "nproc=4096:4096" +# umask: "0027" +# +# # Shared memory +# shm_size: "128m" +# shm_size_systemd: "64m" +# +# # Pod integration +# pod: "app-pod" +# +# # Logging +# log_driver: "journald" +# log_options: +# max_size: "10mb" +# max_files: "3" +# tag: "app-server" +# log_level: "info" +# +# # Health checks +# healthcheck: "curl -f http://localhost:8080/health || exit 1" +# healthcheck_interval: "30s" +# healthcheck_timeout: "10s" +# healthcheck_start_period: "60s" +# healthcheck_retries: 3 +# healthcheck_failure_action: "restart" +# no_healthcheck: false +# +# # Startup health checks +# health_startup_cmd: "curl -f http://localhost:8080/ready || exit 1" +# health_startup_interval: "5s" +# health_startup_timeout: "3s" +# health_startup_retries: 20 +# health_startup_success: 1 +# +# # Metadata +# labels: +# app: "myapp" +# version: "1.2.3" +# environment: "production" +# maintainer: "team@example.com" +# annotations: +# "org.opencontainers.image.source": "https://github.com/example/myapp" +# +# # Container lifecycle +# restart_policy: "on-failure:3" +# restart_time: "10s" +# stop_signal: 15 +# stop_time: "30s" +# stop_timeout: 30 +# timeout: 0 +# +# # Image options +# pull: "missing" +# image_strict: false +# arch: "amd64" +# platform: "linux/amd64" +# +# # Advanced options +# timezone: "UTC" +# requires: +# - "database-container" +# +# # Systemd integration +# generate_systemd: +# path: "/etc/systemd/system" +# restart_policy: "always" +# stop_timeout: 120 +# names: true +# container_prefix: "container" +# new: false +# no_header: false +# wants: +# - "network-online.target" +# after: +# - "network-online.target" +# requires: +# - "postgresql.service" +# +# # Control options +# recreate: false +# force_restart: false +# debug: false +# +# # Database container with quadlet +# - name: postgres +# image: docker.io/postgres:15 +# state: quadlet +# env: +# POSTGRES_DB: myapp +# POSTGRES_USER: appuser +# POSTGRES_PASSWORD: secretpass +# volumes: +# - "postgres-data:/var/lib/postgresql/data" +# ports: +# - "5432:5432" +# networks: +# - backend +# memory: "4g" +# cpu_shares: 2048 +# healthcheck: "pg_isready -U appuser -d myapp" +# healthcheck_interval: "10s" +# healthcheck_timeout: "5s" +# healthcheck_retries: 5 +# quadlet_dir: "/etc/containers/systemd" +# quadlet_filename: "postgres-db" +# quadlet_file_mode: "0640" +# quadlet_options: +# - "AutoUpdate=registry" +# - "Pull=newer" +# - | +# [Install] +# WantedBy=default.target +# +# # Privileged system container +# - name: monitoring-agent +# image: quay.io/prometheus/node-exporter:latest +# state: started +# privileged: true +# read_only: true +# pid_mode: "host" +# networks: +# - host +# volumes: +# - "/proc:/host/proc:ro" +# - "/sys:/host/sys:ro" +# - "/:/rootfs:ro" +# command: +# - "--path.procfs=/host/proc" +# - "--path.sysfs=/host/sys" +# - "--collector.filesystem.ignored-mount-points" +# ports: +# - "9100:9100" +# restart_policy: "always" +# user: "nobody" +# cap_drop: +# - "ALL" +# security_opt: +# - "no-new-privileges=true" + +# Network management +podman_networks: [] +# Example network configuration: +# podman_networks: +# - name: app-network +# driver: bridge +# subnet: "10.89.0.0/24" +# gateway: "10.89.0.1" +# state: present +# - name: frontend-network +# driver: bridge +# subnet: "172.20.0.0/16" +# gateway: "172.20.0.1" +# internal: true +# disable_dns: false +# dns: +# - "8.8.8.8" +# - "8.8.4.4" +# options: +# mtu: 1500 +# vlan: 100 +# - name: macvlan-network +# driver: macvlan +# macvlan: "eth0" +# subnet: "192.168.1.0/24" +# gateway: "192.168.1.1" +# ipam_driver: "host-local" +# - name: ipv6-network +# driver: bridge +# subnet: "fd00::/64" +# gateway: "fd00::1" +# ipv6: true +# route: +# - "10.10.0.0/16,192.168.1.1" + +# Volume management +podman_volumes: [] +# Example volume configuration: +# podman_volumes: +# - name: app-data +# state: present +# driver: local +# - name: database-volume +# state: present +# driver: local +# labels: +# environment: production +# backup: daily +# options: +# - "device=/dev/sdb1" +# - "type=ext4" +# - "o=rw" +# - name: tmpfs-volume +# state: present +# driver: tmpfs +# options: +# - "tmpfs-size=100m" +# - "tmpfs-mode=1777" +# - name: quadlet-volume +# state: quadlet +# driver: local +# quadlet_filename: "custom-volume" +# quadlet_file_mode: "0640" +# quadlet_options: +# - "Group=192" +# - "Copy=true" +# recreate: false +# debug: false + +# Pod management +podman_pods: [] +# Example pod configuration: +# podman_pods: +# - name: webapp-pod +# state: started +# ports: +# - "8080:80" +# - "3306:3306" +# networks: +# - frontend +# hostname: webapp +# dns: +# - "8.8.8.8" +# - "8.8.4.4" +# labels: +# app: webapp +# tier: frontend +# volumes: +# - "webapp-data:/data" +# infra: true +# infra_image: "k8s.gcr.io/pause:3.1" +# - name: database-pod +# state: created +# networks: +# - backend +# security_opt: +# - "seccomp=unconfined" +# memory: "2g" +# cpu_shares: "1024" +# add_host: +# - "database.local:127.0.0.1" +# share: "net,ipc" +# userns: "auto" +# - name: monitoring-pod +# state: started +# ports: +# - "9090:9090" +# device: +# - "/dev/sda:/dev/xvda:rwm" +# sysctl: +# net.core.somaxconn: "1024" +# shm_size: "128m" +# exit_policy: "stop" +# - name: quadlet-pod +# state: quadlet +# ports: +# - "4444:5555" +# volumes: +# - "/var/run/docker.sock:/var/run/docker.sock" +# quadlet_dir: "/etc/containers/systemd" +# quadlet_filename: "custom-pod" +# quadlet_file_mode: "0640" +# quadlet_options: +# - "AutoUpdate=registry" +# - "Pull=newer" +# generate_systemd: +# path: "/etc/systemd/system" +# restart_policy: "always" diff --git a/handlers/main.yml b/handlers/main.yml new file mode 100644 index 0000000..370d646 --- /dev/null +++ b/handlers/main.yml @@ -0,0 +1,8 @@ +--- +# handlers file for podman + +- name: Restart podman + ansible.builtin.systemd: + name: podman + state: restarted + listen: restart podman diff --git a/meta/main.yml b/meta/main.yml new file mode 100644 index 0000000..cd70319 --- /dev/null +++ b/meta/main.yml @@ -0,0 +1,24 @@ +--- +galaxy_info: + role_name: podman + author: Daniel Akulenok + namespace: keepit + description: Install and configure Podman container runtime and manage containers + company: Keepit A/S + license: MIT + min_ansible_version: "2.11" + platforms: + - name: Ubuntu + versions: + - focal + - jammy + - name: Debian + versions: + - bullseye + - bookworm + galaxy_tags: + - podman + - containers + - docker + - oci +dependencies: [] diff --git a/meta/requirements.yml b/meta/requirements.yml new file mode 100644 index 0000000..2c10421 --- /dev/null +++ b/meta/requirements.yml @@ -0,0 +1,5 @@ +--- +# Collection requirements for the podman role +collections: + - name: containers.podman + version: ">=1.10.0" diff --git a/molecule/default/converge.yml b/molecule/default/converge.yml new file mode 100644 index 0000000..998a1b0 --- /dev/null +++ b/molecule/default/converge.yml @@ -0,0 +1,17 @@ +--- +- name: Converge - apply podman role + hosts: all + become: true + vars: + podman_install_from_repo: true + podman_packages: + - podman + podman_configure_registries: true + podman_configure_storage: true + podman_configure_policy: true + podman_enable_socket: false + podman_enable_api_service: false + podman_policy_default_type: "insecureAcceptAnything" + podman_policy_trusted_registries: [] + roles: + - name: podman diff --git a/molecule/default/molecule.yml b/molecule/default/molecule.yml new file mode 100644 index 0000000..b319cd8 --- /dev/null +++ b/molecule/default/molecule.yml @@ -0,0 +1,30 @@ +--- +dependency: + name: galaxy +driver: + name: podman +platform_defaults: &platform_defaults + tmpfs: + - /tmp + - /run/lock + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup:ro + command: /lib/systemd/systemd +platforms: + - name: debian-bullseye + image: docker.io/jrei/systemd-debian:12 + <<: *platform_defaults + - name: debian-bullseye + image: docker.io/jrei/systemd-debian:13 + <<: *platform_defaults + +provisioner: + name: ansible + lint: + name: ansible-lint +verifier: + name: ansible +lint: | + set -e + ansible-lint . + yamllint . diff --git a/molecule/default/prepare.yml b/molecule/default/prepare.yml new file mode 100644 index 0000000..febac6e --- /dev/null +++ b/molecule/default/prepare.yml @@ -0,0 +1,28 @@ +--- +- name: Prepare instance for podman role testing + hosts: all + become: true + tasks: + - name: Ensure apt cache is up-to-date + ansible.builtin.apt: + update_cache: yes + cache_valid_time: 3600 + + - name: Install prerequisites used by the role/tests + ansible.builtin.apt: + name: + - apt-transport-https + - ca-certificates + - gnupg + - lsb-release + - curl + state: present + update_cache: false + + - name: Install python3-venv and python3-pip for testinfra + ansible.builtin.apt: + name: + - python3-pip + - python3-venv + state: present + update_cache: false diff --git a/molecule/default/requirements.yml b/molecule/default/requirements.yml new file mode 100644 index 0000000..25a97a4 --- /dev/null +++ b/molecule/default/requirements.yml @@ -0,0 +1,3 @@ +--- +collections: + - name: containers.podman diff --git a/molecule/default/tests/__pycache__/test_default.cpython-313.pyc b/molecule/default/tests/__pycache__/test_default.cpython-313.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae22792d8e160f336dabb8c58732ec26e7d56f40 GIT binary patch literal 2463 zcmc&$%}*pn6tDhZm=C^o5J8Z&#UM<=&?M-FzzX7sk&hj+GqTZSlTI^T3=K2gld2v9 zbAr=K_Q0CBQR6|c-1OvbV&XsGp-CSgy+;qmlSFQweAOS!3?Z28K|Ap3)vH%uzgO?~ z=Ayklj^O+A@$*enMCdox3BQ&=x%d^7rznr|+%h75%Mtz|pNoX9^)?r+>!FrQgj&6Q zh;nW9UZ~|dL#^KC`FAAxO_N%#P}we0b_hvw@5`HJ8OxfwEoV#(%c?>3^%72R zV_eZKnV2O^Wq(t-44p!?1m1W7)K7C@+tCo}c~{ zA#KFNT!6^1b+3HyahI`ylS>G^u6f-ZXtFtGVz$;5#44TM!G!9jK^W$>p1^FFmPD$C zp#t2~Qc83M=(#*8xDje;m=Lcut5%99F_`EIg|Zfejj+UV2O5E1*A12IDR>t`1a|TY zc(A0QcR~0tj~^D`{<@_8L*tlEWGzFw2nkoiCz8eFRvVGdXu zc=zg8uN(%y#$Q*z0{-whTWsR&mMj^B1^tv?uIyQxrZFj{3(|;Qu9(D<7;s~BkIpb< zykx4{s9C|r=+F((8A_!_YEnZi!*Bur@2DkRla}(U*;EVZWn1@wa7C)uFkwVM01T)l z9+!}QX6Ob%Su#tKskb#Jqi=*9+7$E%iJqT@kfQW#}o z;>Iq=-4@Tm?YQF6KJS;ZoIjxz8#_8N4Ob{)%Z-6hSTUq-=jBSi!7ET6eR}y$*j>F# zAHy#NrhD8Qy<0XlsD-m64K`Nu={ksWf#bLrDE0ykpGQ#l>tD%V$fwDPljOu{a`q%S zdnQI6EgdX9${u9x?lDJ9UkH3R|2v;JNPWKG@X7NQ#C81@=eZ9!CYL?=p(o$xF5)8R G)A2uvVje31 literal 0 HcmV?d00001 diff --git a/molecule/default/tests/test_default.py b/molecule/default/tests/test_default.py new file mode 100644 index 0000000..f6e1f3f --- /dev/null +++ b/molecule/default/tests/test_default.py @@ -0,0 +1,34 @@ +import os +import testinfra.utils.ansible_runner + +# Get hosts from Testinfra inventory created by Molecule +testinfra_hosts = testinfra.utils.ansible_runner.AnsibleRunner( + os.environ.get('MOLECULE_INVENTORY_FILE') +).get_hosts('all') + + +def test_podman_package_installed(host): + pkg = host.package("podman") + assert pkg.is_installed, "podman package should be installed" + + +def test_podman_binary_executes(host): + cmd = host.run("podman --version") + assert cmd.rc == 0, f"podman not runnable: {cmd.stderr or cmd.stdout}" + + +def test_containers_conf_exists(host): + f = host.file("/etc/containers/registries.conf") + assert f.exists, "/etc/containers/registries.conf should exist" + + +def test_policy_json_exists(host): + f = host.file("/etc/containers/policy.json") + assert f.exists, "/etc/containers/policy.json should exist" + + +def test_podman_config_file_valid_json(host): + f = host.file("/etc/containers/policy.json") + assert f.exists and f.size > 0 + cmd = host.run("python3 -c 'import json,sys;json.load(open(\"/etc/containers/policy.json\"))'") + assert cmd.rc == 0, "policy.json should be valid JSON" diff --git a/tasks/configure.yml b/tasks/configure.yml new file mode 100644 index 0000000..6cb154f --- /dev/null +++ b/tasks/configure.yml @@ -0,0 +1,58 @@ +--- +# Configure Podman + +- name: Ensure containers configuration directory exists + ansible.builtin.file: + path: /etc/containers + state: directory + owner: root + group: root + mode: "0755" + +- name: Configure container registries + ansible.builtin.template: + src: registries.conf.j2 + dest: "{{ podman_registries_conf_path }}" + owner: root + group: root + mode: "0644" + backup: true + when: podman_configure_registries + notify: + - restart podman + +- name: Configure container storage + ansible.builtin.template: + src: storage.conf.j2 + dest: "{{ podman_storage_conf_path }}" + owner: root + group: root + mode: "0644" + backup: true + when: podman_configure_storage + notify: + - restart podman + +- name: Configure container policy + ansible.builtin.template: + src: policy.json.j2 + dest: "{{ podman_policy_path }}" + owner: root + group: root + mode: "0644" + backup: true + when: podman_configure_policy + notify: + - restart podman + +- name: Ensure storage directories exist + ansible.builtin.file: + path: "{{ item }}" + state: directory + owner: root + group: root + mode: "0755" + loop: + - "{{ podman_storage_runroot }}" + - "{{ podman_storage_graphroot }}" + when: podman_configure_storage diff --git a/tasks/containers.yml b/tasks/containers.yml new file mode 100644 index 0000000..a589b0b --- /dev/null +++ b/tasks/containers.yml @@ -0,0 +1,241 @@ +--- +# Manage Podman containers + +- name: Manage Podman containers + containers.podman.podman_container: + name: "{{ item.name }}" + image: "{{ item.image }}" + state: "{{ item.state | default('present') }}" + + # Basic container configuration + command: "{{ item.command | default(omit) }}" + entrypoint: "{{ item.entrypoint | default(omit) }}" + user: "{{ item.user | default(omit) }}" + workdir: "{{ item.working_dir | default(omit) }}" + hostname: "{{ item.hostname | default(omit) }}" + + # Container runtime options + detach: "{{ item.detach | default(true) }}" + interactive: "{{ item.interactive | default(false) }}" + tty: "{{ item.tty | default(false) }}" + rm: "{{ item.auto_remove | default(false) }}" + rmi: "{{ item.remove_image | default(false) }}" + privileged: "{{ item.privileged | default(false) }}" + read_only: "{{ item.read_only | default(false) }}" + read_only_tmpfs: "{{ item.read_only_tmpfs | default(true) }}" + init: "{{ item.init | default(false) }}" + init_path: "{{ item.init_path | default(omit) }}" + + # Networking + network: "{{ item.networks | default(omit) }}" + publish: "{{ item.ports | default(omit) }}" + publish_all: "{{ item.publish_all | default(false) }}" + expose: "{{ item.expose | default(omit) }}" + ip: "{{ item.ip | default(omit) }}" + ip6: "{{ item.ip6 | default(omit) }}" + mac_address: "{{ item.mac_address | default(omit) }}" + network_aliases: "{{ item.network_aliases | default(omit) }}" + dns: "{{ item.dns | default(omit) }}" + dns_option: "{{ item.dns_options | default(omit) }}" + dns_search: "{{ item.dns_search | default(omit) }}" + etc_hosts: "{{ item.add_hosts | default(omit) }}" + no_hosts: "{{ item.no_hosts | default(false) }}" + + # Storage and volumes + volume: "{{ item.volumes | default(omit) }}" + volumes_from: "{{ item.volumes_from | default(omit) }}" + mount: "{{ item.mounts | default(omit) }}" + tmpfs: "{{ item.tmpfs | default(omit) }}" + image_volume: "{{ item.image_volume | default(omit) }}" + + # Environment variables + env: "{{ item.env | default(omit) }}" + env_file: "{{ item.env_files | default(omit) }}" + env_host: "{{ item.env_host | default(false) }}" + env_merge: "{{ item.env_merge | default(omit) }}" + unsetenv: "{{ item.unsetenv | default(omit) }}" + unsetenv_all: "{{ item.unsetenv_all | default(false) }}" + + # Resource constraints + memory: "{{ item.memory | default(omit) }}" + memory_reservation: "{{ item.memory_reservation | default(omit) }}" + memory_swap: "{{ item.memory_swap | default(omit) }}" + memory_swappiness: "{{ item.memory_swappiness | default(omit) }}" + kernel_memory: "{{ item.kernel_memory | default(omit) }}" + cpus: "{{ item.cpus | default(omit) }}" + cpu_shares: "{{ item.cpu_shares | default(omit) }}" + cpu_period: "{{ item.cpu_period | default(omit) }}" + cpu_quota: "{{ item.cpu_quota | default(omit) }}" + cpu_rt_period: "{{ item.cpu_rt_period | default(omit) }}" + cpu_rt_runtime: "{{ item.cpu_rt_runtime | default(omit) }}" + cpuset_cpus: "{{ item.cpuset_cpus | default(omit) }}" + cpuset_mems: "{{ item.cpuset_mems | default(omit) }}" + blkio_weight: "{{ item.blkio_weight | default(omit) }}" + blkio_weight_device: "{{ item.blkio_weight_device | default(omit) }}" + oom_kill_disable: "{{ item.oom_kill_disable | default(false) }}" + oom_score_adj: "{{ item.oom_score_adj | default(omit) }}" + pids_limit: "{{ item.pids_limit | default(omit) }}" + + # Device access + device: "{{ item.devices | default(omit) }}" + device_cgroup_rule: "{{ item.device_cgroup_rule | default(omit) }}" + device_read_bps: "{{ item.device_read_bps | default(omit) }}" + device_read_iops: "{{ item.device_read_iops | default(omit) }}" + device_write_bps: "{{ item.device_write_bps | default(omit) }}" + device_write_iops: "{{ item.device_write_iops | default(omit) }}" + gpus: "{{ item.gpus | default(omit) }}" + + # Security options + security_opt: "{{ item.security_opt | default(omit) }}" + cap_add: "{{ item.cap_add | default(omit) }}" + cap_drop: "{{ item.cap_drop | default(omit) }}" + seccomp_policy: "{{ item.seccomp_policy | default(omit) }}" + userns: "{{ item.userns | default(omit) }}" + uidmap: "{{ item.uidmap | default(omit) }}" + gidmap: "{{ item.gidmap | default(omit) }}" + subuidname: "{{ item.subuidname | default(omit) }}" + subgidname: "{{ item.subgidname | default(omit) }}" + group_add: "{{ item.groups | default(omit) }}" + group_entry: "{{ item.group_entry | default(omit) }}" + passwd: "{{ item.passwd | default(omit) }}" + passwd_entry: "{{ item.passwd_entry | default(omit) }}" + + # Namespaces + ipc: "{{ item.ipc_mode | default(omit) }}" + pid: "{{ item.pid_mode | default(omit) }}" + uts: "{{ item.uts | default(omit) }}" + cgroupns: "{{ item.cgroupns | default(omit) }}" + + # Cgroups + cgroups: "{{ item.cgroups | default(omit) }}" + cgroup_parent: "{{ item.cgroup_parent | default(omit) }}" + cgroup_conf: "{{ item.cgroup_conf | default(omit) }}" + + # System configuration + sysctl: "{{ item.sysctl | default(omit) }}" + systemd: "{{ item.systemd | default(omit) }}" + ulimit: "{{ item.ulimits | default(omit) }}" + umask: "{{ item.umask | default(omit) }}" + + # Shared memory and tmpfs + shm_size: "{{ item.shm_size | default(omit) }}" + shm_size_systemd: "{{ item.shm_size_systemd | default(omit) }}" + + # Pods + pod: "{{ item.pod | default(omit) }}" + pod_id_file: "{{ item.pod_id_file | default(omit) }}" + + # Logging + log_driver: "{{ item.log_driver | default(omit) }}" + log_opt: "{{ item.log_options | default(omit) }}" + log_level: "{{ item.log_level | default(omit) }}" + + # Health checks + healthcheck: "{{ item.healthcheck | default(omit) }}" + healthcheck_interval: "{{ item.healthcheck_interval | default(omit) }}" + healthcheck_timeout: "{{ item.healthcheck_timeout | default(omit) }}" + healthcheck_start_period: "{{ item.healthcheck_start_period | default(omit) }}" + healthcheck_retries: "{{ item.healthcheck_retries | default(omit) }}" + healthcheck_failure_action: "{{ item.healthcheck_failure_action | default(omit) }}" + no_healthcheck: "{{ item.no_healthcheck | default(false) }}" + + # Startup health checks + health_startup_cmd: "{{ item.health_startup_cmd | default(omit) }}" + health_startup_interval: "{{ item.health_startup_interval | default(omit) }}" + health_startup_timeout: "{{ item.health_startup_timeout | default(omit) }}" + health_startup_retries: "{{ item.health_startup_retries | default(omit) }}" + health_startup_success: "{{ item.health_startup_success | default(omit) }}" + + # Metadata and labels + label: "{{ item.labels | default(omit) }}" + label_file: "{{ item.label_file | default(omit) }}" + annotation: "{{ item.annotations | default(omit) }}" + + # Container lifecycle + restart_policy: "{{ item.restart_policy | default('no') }}" + restart_time: "{{ item.restart_time | default(omit) }}" + stop_signal: "{{ item.stop_signal | default(omit) }}" + stop_time: "{{ item.stop_time | default(omit) }}" + stop_timeout: "{{ item.stop_timeout | default(omit) }}" + timeout: "{{ item.timeout | default(omit) }}" + + # Pull and image options + pull: "{{ item.pull | default('missing') }}" + image_strict: "{{ item.image_strict | default(false) }}" + arch: "{{ item.arch | default(omit) }}" + os: "{{ item.os | default(omit) }}" + platform: "{{ item.platform | default(omit) }}" + variant: "{{ item.variant | default(omit) }}" + + # Registry and authentication + authfile: "{{ item.authfile | default(omit) }}" + tls_verify: "{{ item.tls_verify | default(omit) }}" + decryption_key: "{{ item.decryption_key | default(omit) }}" + + # File operations + cidfile: "{{ item.cidfile | default(omit) }}" + conmon_pidfile: "{{ item.conmon_pidfile | default(omit) }}" + pid_file: "{{ item.pid_file | default(omit) }}" + + # Special options + attach: "{{ item.attach | default(omit) }}" + detach_keys: "{{ item.detach_keys | default(omit) }}" + sig_proxy: "{{ item.sig_proxy | default(true) }}" + http_proxy: "{{ item.http_proxy | default(true) }}" + + # Advanced options + chrootdirs: "{{ item.chrootdirs | default(omit) }}" + hooks_dir: "{{ item.hooks_dir | default(omit) }}" + hostuser: "{{ item.hostuser | default(omit) }}" + init_ctr: "{{ item.init_ctr | default(omit) }}" + personality: "{{ item.personality | default(omit) }}" + preserve_fd: "{{ item.preserve_fd | default(omit) }}" + preserve_fds: "{{ item.preserve_fds | default(omit) }}" + rdt_class: "{{ item.rdt_class | default(omit) }}" + requires: "{{ item.requires | default(omit) }}" + rootfs: "{{ item.rootfs | default(false) }}" + sdnotify: "{{ item.sdnotify | default(omit) }}" + secrets: "{{ item.secrets | default(omit) }}" + timezone: "{{ item.timezone | default(omit) }}" + + # Retry options + retry: "{{ item.retry | default(omit) }}" + retry_delay: "{{ item.retry_delay | default(omit) }}" + + # Systemd generation + generate_systemd: "{{ item.generate_systemd | default(omit) }}" + + # Quadlet options + quadlet_dir: "{{ item.quadlet_dir | default(omit) }}" + quadlet_filename: "{{ item.quadlet_filename | default(omit) }}" + quadlet_file_mode: "{{ item.quadlet_file_mode | default(omit) }}" + quadlet_options: "{{ item.quadlet_options | default(omit) }}" + + # Control options + cmd_args: "{{ item.cmd_args | default(omit) }}" + executable: "{{ item.executable | default('podman') }}" + recreate: "{{ item.recreate | default(false) }}" + force_restart: "{{ item.force_restart | default(false) }}" + force_delete: "{{ item.force_delete | default(true) }}" + delete_depend: "{{ item.delete_depend | default(false) }}" + delete_time: "{{ item.delete_time | default(omit) }}" + delete_volumes: "{{ item.delete_volumes | default(false) }}" + debug: "{{ item.debug | default(false) }}" + loop: "{{ podman_containers }}" + loop_control: + label: "{{ item.name }}" + register: podman_container_results + +- name: Enable and start container systemd services + ansible.builtin.systemd: + name: "container-{{ item.item.name }}" + enabled: true + state: started + daemon_reload: true + loop: "{{ podman_container_results.results }}" + loop_control: + label: "{{ item.item.name }}" + when: + - item.item.generate_systemd is defined + - item.item.generate_systemd + - item.item.state | default('present') in ['present', 'started'] diff --git a/tasks/install.yml b/tasks/install.yml new file mode 100644 index 0000000..bdb40de --- /dev/null +++ b/tasks/install.yml @@ -0,0 +1,14 @@ +--- +# Install Podman packages + +- name: Update package cache (Debian/Ubuntu) + ansible.builtin.apt: + cache_valid_time: 3600 + when: ansible_os_family == "Debian" + +- name: Install Podman and related packages + ansible.builtin.package: + name: "{{ podman_packages }}" + state: present + notify: + - restart podman diff --git a/tasks/main.yml b/tasks/main.yml new file mode 100644 index 0000000..a560e4f --- /dev/null +++ b/tasks/main.yml @@ -0,0 +1,48 @@ +--- +# tasks file for podman + +- name: Install Podman packages + ansible.builtin.include_tasks: install.yml + tags: + - podman + - podman-install + +- name: Configure Podman + ansible.builtin.include_tasks: configure.yml + tags: + - podman + - podman-configure + +- name: Manage Podman services + ansible.builtin.include_tasks: services.yml + tags: + - podman + - podman-services + +- name: Manage Podman networks + ansible.builtin.include_tasks: networks.yml + when: podman_networks | length > 0 + tags: + - podman + - podman-networks + +- name: Manage Podman volumes + ansible.builtin.include_tasks: volumes.yml + when: podman_volumes | length > 0 + tags: + - podman + - podman-volumes + +- name: Manage Podman pods + ansible.builtin.include_tasks: pods.yml + when: podman_pods | length > 0 + tags: + - podman + - podman-pods + +- name: Manage Podman containers + ansible.builtin.include_tasks: containers.yml + when: podman_containers | length > 0 + tags: + - podman + - podman-containers diff --git a/tasks/networks.yml b/tasks/networks.yml new file mode 100644 index 0000000..1fae6ff --- /dev/null +++ b/tasks/networks.yml @@ -0,0 +1,25 @@ +--- +# Manage Podman networks + +- name: Manage Podman networks + containers.podman.podman_network: + name: "{{ item.name }}" + state: "{{ item.state | default('present') }}" + driver: "{{ item.driver | default('bridge') }}" + subnet: "{{ item.subnet | default(omit) }}" + gateway: "{{ item.gateway | default(omit) }}" + ip_range: "{{ item.ip_range | default(omit) }}" + disable_dns: "{{ item.disable_dns | default(false) }}" + internal: "{{ item.internal | default(false) }}" + opt: "{{ item.options | default(omit) }}" + dns: "{{ item.dns | default(omit) }}" + interface_name: "{{ item.interface_name | default(omit) }}" + ipam_driver: "{{ item.ipam_driver | default(omit) }}" + ipv6: "{{ item.ipv6 | default(false) }}" + macvlan: "{{ item.macvlan | default(omit) }}" + net_config: "{{ item.net_config | default(omit) }}" + route: "{{ item.route | default(omit) }}" + recreate: "{{ item.recreate | default(false) }}" + loop: "{{ podman_networks }}" + loop_control: + label: "{{ item.name }}" diff --git a/tasks/pods.yml b/tasks/pods.yml new file mode 100644 index 0000000..790026e --- /dev/null +++ b/tasks/pods.yml @@ -0,0 +1,66 @@ +--- +# Manage Podman pods + +- name: Manage Podman pods + containers.podman.podman_pod: + name: "{{ item.name }}" + state: "{{ item.state | default('created') }}" + publish: "{{ item.ports | default(omit) }}" + network: "{{ item.networks | default(omit) }}" + volume: "{{ item.volumes | default(omit) }}" + label: "{{ item.labels | default(omit) }}" + hostname: "{{ item.hostname | default(omit) }}" + infra: "{{ item.infra | default(true) }}" + infra_image: "{{ item.infra_image | default(omit) }}" + infra_command: "{{ item.infra_command | default(omit) }}" + infra_name: "{{ item.infra_name | default(omit) }}" + add_host: "{{ item.add_host | default(omit) }}" + dns: "{{ item.dns | default(omit) }}" + dns_opt: "{{ item.dns_opt | default(omit) }}" + dns_search: "{{ item.dns_search | default(omit) }}" + ip: "{{ item.ip | default(omit) }}" + ip6: "{{ item.ip6 | default(omit) }}" + mac_address: "{{ item.mac_address | default(omit) }}" + no_hosts: "{{ item.no_hosts | default(false) }}" + share: "{{ item.share | default(omit) }}" + share_parent: "{{ item.share_parent | default(omit) }}" + userns: "{{ item.userns | default(omit) }}" + uidmap: "{{ item.uidmap | default(omit) }}" + gidmap: "{{ item.gidmap | default(omit) }}" + subuidname: "{{ item.subuidname | default(omit) }}" + subgidname: "{{ item.subgidname | default(omit) }}" + security_opt: "{{ item.security_opt | default(omit) }}" + memory: "{{ item.memory | default(omit) }}" + memory_swap: "{{ item.memory_swap | default(omit) }}" + cpu_shares: "{{ item.cpu_shares | default(omit) }}" + cpus: "{{ item.cpus | default(omit) }}" + cpuset_cpus: "{{ item.cpuset_cpus | default(omit) }}" + cpuset_mems: "{{ item.cpuset_mems | default(omit) }}" + blkio_weight: "{{ item.blkio_weight | default(omit) }}" + blkio_weight_device: "{{ item.blkio_weight_device | default(omit) }}" + device: "{{ item.device | default(omit) }}" + device_read_bps: "{{ item.device_read_bps | default(omit) }}" + device_write_bps: "{{ item.device_write_bps | default(omit) }}" + shm_size: "{{ item.shm_size | default(omit) }}" + shm_size_systemd: "{{ item.shm_size_systemd | default(omit) }}" + sysctl: "{{ item.sysctl | default(omit) }}" + cgroup_parent: "{{ item.cgroup_parent | default(omit) }}" + pid: "{{ item.pid | default(omit) }}" + uts: "{{ item.uts | default(omit) }}" + network_alias: "{{ item.network_alias | default(omit) }}" + volumes_from: "{{ item.volumes_from | default(omit) }}" + exit_policy: "{{ item.exit_policy | default(omit) }}" + restart_policy: "{{ item.restart_policy | default(omit) }}" + pod_id_file: "{{ item.pod_id_file | default(omit) }}" + label_file: "{{ item.label_file | default(omit) }}" + gpus: "{{ item.gpus | default(omit) }}" + generate_systemd: "{{ item.generate_systemd | default(omit) }}" + quadlet_dir: "{{ item.quadlet_dir | default(omit) }}" + quadlet_filename: "{{ item.quadlet_filename | default(omit) }}" + quadlet_file_mode: "{{ item.quadlet_file_mode | default(omit) }}" + quadlet_options: "{{ item.quadlet_options | default(omit) }}" + recreate: "{{ item.recreate | default(false) }}" + debug: "{{ item.debug | default(false) }}" + loop: "{{ podman_pods }}" + loop_control: + label: "{{ item.name }}" diff --git a/tasks/services.yml b/tasks/services.yml new file mode 100644 index 0000000..13ff076 --- /dev/null +++ b/tasks/services.yml @@ -0,0 +1,32 @@ +--- +# Manage Podman services + +- name: Enable and start Podman socket + ansible.builtin.systemd: + name: podman.socket + enabled: true + state: started + daemon_reload: true + when: podman_enable_socket + +- name: Disable Podman socket + ansible.builtin.systemd: + name: podman.socket + enabled: false + state: stopped + when: not podman_enable_socket + +- name: Enable and start Podman API service + ansible.builtin.systemd: + name: podman.service + enabled: true + state: started + daemon_reload: true + when: podman_enable_api_service + +- name: Disable Podman API service + ansible.builtin.systemd: + name: podman.service + enabled: false + state: stopped + when: not podman_enable_api_service diff --git a/tasks/volumes.yml b/tasks/volumes.yml new file mode 100644 index 0000000..7ef76fd --- /dev/null +++ b/tasks/volumes.yml @@ -0,0 +1,19 @@ +--- +# Manage Podman volumes + +- name: Manage Podman volumes + containers.podman.podman_volume: + name: "{{ item.name }}" + state: "{{ item.state | default('present') }}" + driver: "{{ item.driver | default('local') }}" + options: "{{ item.options | default(omit) }}" + label: "{{ item.labels | default(omit) }}" + debug: "{{ item.debug | default(false) }}" + recreate: "{{ item.recreate | default(false) }}" + quadlet_dir: "{{ item.quadlet_dir | default(omit) }}" + quadlet_filename: "{{ item.quadlet_filename | default(omit) }}" + quadlet_file_mode: "{{ item.quadlet_file_mode | default(omit) }}" + quadlet_options: "{{ item.quadlet_options | default(omit) }}" + loop: "{{ podman_volumes }}" + loop_control: + label: "{{ item.name }}" diff --git a/templates/policy.json.j2 b/templates/policy.json.j2 new file mode 100644 index 0000000..0e0baa1 --- /dev/null +++ b/templates/policy.json.j2 @@ -0,0 +1,37 @@ +{ + "default": [ + { + "type": "{{ podman_policy_default_type }}" + } + ], + "transports": { + "docker-daemon": { + "": [ + { + "type": "insecureAcceptAnything" + } + ] + }, + "docker": { +{% for registry in podman_policy_trusted_registries %} + "{{ registry.registry }}": [ + { + "type": "{{ registry.type }}"{% if registry.keyPath is defined %}, + "keyType": "{{ registry.keyType | default('GPGKeys') }}", + "keyPath": "{{ registry.keyPath }}"{% endif %}{% if registry.keyData is defined %}, + "keyType": "{{ registry.keyType | default('GPGKeys') }}", + "keyData": "{{ registry.keyData }}"{% endif %}{% if registry.signedIdentity is defined %}, + "signedIdentity": {{ registry.signedIdentity | to_json }}{% endif %} + } + ]{% if not loop.last %},{% endif %} +{% endfor %} +{% if podman_policy_reject_unknown_registries %} + "": [ + { + "type": "reject" + } + ] +{% endif %} + } + } +} diff --git a/templates/registries.conf.j2 b/templates/registries.conf.j2 new file mode 100644 index 0000000..eae27c2 --- /dev/null +++ b/templates/registries.conf.j2 @@ -0,0 +1,54 @@ +# Configuration file for container registries +# {{ ansible_managed }} + +# Unqualified image search registries +# These registries will be searched when pulling images without a registry prefix +unqualified-search-registries = [ +{% set registries = podman_policy_trusted_registries | selectattr('unqualified_search', 'defined') | selectattr('unqualified_search') | map(attribute='registry') | list %} +{% if registries | length == 0 %} +{% set registries = podman_policy_trusted_registries | map(attribute='registry') | list %} +{% endif %} +{% set base_registries = registries | map('regex_replace', '^([^/]+).*', '\\1') | list %} +{% for registry in base_registries | unique %} + "{{ registry }}"{% if not loop.last %},{% endif %} +{% endfor %} +] + +{% for registry_config in podman_policy_trusted_registries %} +# Registry: {{ registry_config.registry }} +[[registry]] +location = "{{ registry_config.registry }}" +insecure = {{ registry_config.insecure | default(false) | lower }} +blocked = {{ registry_config.blocked | default(false) | lower }} +{% if registry_config.mirror is defined %} + +# Mirror configuration for {{ registry_config.registry }} +{% for mirror in registry_config.mirror %} +[[registry.mirror]] +location = "{{ mirror.location }}" +insecure = {{ mirror.insecure | default(false) | lower }} +{% endfor %} +{% endif %} +{% if registry_config.prefix is defined %} +prefix = "{{ registry_config.prefix }}" +{% endif %} + +{% endfor %} + +# Additional registry configurations +{% if podman_registries_additional is defined %} +{% for registry in podman_registries_additional %} +[[registry]] +location = "{{ registry.location }}" +insecure = {{ registry.insecure | default(false) | lower }} +blocked = {{ registry.blocked | default(false) | lower }} +{% if registry.mirror is defined %} +{% for mirror in registry.mirror %} +[[registry.mirror]] +location = "{{ mirror.location }}" +insecure = {{ mirror.insecure | default(false) | lower }} +{% endfor %} +{% endif %} + +{% endfor %} +{% endif %} diff --git a/templates/storage.conf.j2 b/templates/storage.conf.j2 new file mode 100644 index 0000000..ec64fe1 --- /dev/null +++ b/templates/storage.conf.j2 @@ -0,0 +1,13 @@ +# Storage configuration for containers +# {{ ansible_managed }} + +[storage] +driver = "{{ podman_storage_driver }}" +runroot = "{{ podman_storage_runroot }}" +graphroot = "{{ podman_storage_graphroot }}" + +[storage.options] +additionalimagestores = [] + +[storage.options.overlay] +mountopt = "nodev,metacopy=on" diff --git a/vars/Debian.yml b/vars/Debian.yml new file mode 100644 index 0000000..2f6e0fa --- /dev/null +++ b/vars/Debian.yml @@ -0,0 +1,14 @@ +--- +# OS-specific variables for Debian/Ubuntu + +# Package names may vary between distributions +podman_packages: + - podman + - buildah + - skopeo + +podman_additional_packages: + - crun + - fuse-overlayfs + - slirp4netns + - uidmap