diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 48513a3..be13912 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,9 +1,8 @@ --- -# https://docs.github.com/en/actions/reference/events-that-trigger-workflows - name: CI on: + # https://docs.github.com/en/actions/reference/events-that-trigger-workflows # Trigger the workflow on push or pull request, # but only for the main branch pull_request: @@ -16,7 +15,7 @@ on: branches: - master schedule: - - cron: "0 2 * * 0" + - cron: "10 2 * * 0" defaults: run: @@ -42,7 +41,49 @@ jobs: # VALIDATE_MARKDOWN: trues VALIDATE_YAML: true - test: + arch_standard: + name: "${{ matrix.image }} / python: ${{ matrix.python-version }}, ansible: ${{ matrix.ansible-version }}" + needs: + - lint + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + image: + - archlinux:latest + python-version: + - '3.8' + - '3.9' + ansible-version: + - '2.9' + - '2.10' + + steps: + - name: check out the codebase. + uses: actions/checkout@v2 + with: + path: 'ansible-logrotate' + + - name: set up python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: install dependencies + run: | + python -m pip install --upgrade pip + pip install -r test-requirements.txt + + - name: test default + run: | + tox -e py$(printf "${{ matrix.python-version }}" | tr -d '.')-ansible$(printf "${{ matrix.ansible-version }}" | tr -d '.') -- \ + molecule test + env: + PY_COLORS: '1' + ANSIBLE_FORCE_COLOR: '1' + DISTRIBUTION: ${{ matrix.image }} + + deb_standard: name: "${{ matrix.image }} / python: ${{ matrix.python-version }}, ansible: ${{ matrix.ansible-version }}" needs: - lint @@ -55,10 +96,52 @@ jobs: - debian:10 - ubuntu:18.04 - ubuntu:20.04 + python-version: + - '3.8' + - '3.9' + ansible-version: + - '2.9' + - '2.10' + + steps: + - name: check out the codebase. + uses: actions/checkout@v2 + with: + path: 'ansible-logrotate' + + - name: set up python ${{ matrix.python-version }} + uses: actions/setup-python@v2 + with: + python-version: ${{ matrix.python-version }} + + - name: install dependencies + run: | + python -m pip install --upgrade pip + pip install -r test-requirements.txt + + - name: test default + run: | + tox -e py$(printf "${{ matrix.python-version }}" | tr -d '.')-ansible$(printf "${{ matrix.ansible-version }}" | tr -d '.') -- \ + molecule test + env: + PY_COLORS: '1' + ANSIBLE_FORCE_COLOR: '1' + DISTRIBUTION: ${{ matrix.image }} + + rpm_standard: + name: "${{ matrix.image }} / python: ${{ matrix.python-version }}, ansible: ${{ matrix.ansible-version }}" + needs: + - lint + runs-on: ubuntu-18.04 + strategy: + fail-fast: false + matrix: + image: + - centos:7 - centos:8 + - oraclelinux:7 - oraclelinux:8 python-version: - - '3.7' - '3.8' - '3.9' ansible-version: @@ -81,10 +164,10 @@ jobs: python -m pip install --upgrade pip pip install -r test-requirements.txt - - name: test with tox + - name: test default run: | - tox -e py$(printf "${{ matrix.python-version }}" | tr -d '.')-ansible$(printf "${{ matrix.ansible-version }}" | tr -d '.') \ - -- molecule test + tox -e py$(printf "${{ matrix.python-version }}" | tr -d '.')-ansible$(printf "${{ matrix.ansible-version }}" | tr -d '.') -- \ + molecule test env: PY_COLORS: '1' ANSIBLE_FORCE_COLOR: '1' @@ -93,7 +176,9 @@ jobs: publish: if: github.ref == 'refs/heads/master' needs: - - test + - arch_standard + - deb_standard + - rpm_standard runs-on: ubuntu-18.04 steps: - name: galaxy diff --git a/README.md b/README.md index acf53f5..52452d2 100644 --- a/README.md +++ b/README.md @@ -13,22 +13,46 @@ specifying a list of directives. [issues]: https://github.com/bodsch/ansible-logrotate/issues?q=is%3Aopen+is%3Aissue [releases]: https://github.com/bodsch/ansible-logrotate/releases -## tested operating systems + +## Requirements & Dependencies + +None + +### Operating systems + +Tested on * Debian 9 / 10 * Ubuntu 18.04 / 20.04 -* CentOS 8 -* Oracle Linux 8 +* CentOS 7 / 8 +* Oracle Linux 7 / 8 +* Arch Linux -## Requirements +## usage -None - -## Role Variables +```yaml +logrotate_global: + rotate_log: weekly + rotate_size: '' + su_user: '' + su_group: '' + rotate: 2 + create: true + dateext: true + compress: true + tabooext: [] + archive_directory: '' + +logrotate_conf_dir: "/etc/logrotate.d" + +logrotate_scripts: {} + +logroate_disable_systemd: true +``` -**logrotate_scripts**: A list of logrotate scripts and the directives to use for the rotation. +### **logrotate_scripts**: A dictionary of logrotate scripts and the directives to use for the rotation. -* `name` - The name of the script that goes into /etc/logrotate.d/ +* `state` - create (`present`) or remove (`absent`) configuration. default: `present` * `path` - Path to point logrotate to for the log rotation * `paths` - A list of paths to point logrotate to for the log rotation. * `options` - List of directives for logrotate, view the logrotate man page for specifics @@ -36,68 +60,42 @@ None ```yaml logrotate_scripts: - - name: rails - path: "/srv/current/log/*.log" + audit: + path: /var/log/audit/audit.log options: - weekly - - size 25M + - rotate 4 - missingok - - compress + - notifempty - delaycompress - - copytruncate + scripts: + postrotate: /etc/init.d/auditd restart2> /dev/null ``` ```yaml logrotate_scripts: - - name: rails + nginx: paths: - - "/srv/current/scare.log" - - "/srv/current/hide.log" + - /var/log/nginx/*/*.log + - /var/log/nginx/*.log options: - weekly - - size 25M + - rotate 2 - missingok + - notifempty - compress - - delaycompress - - copytruncate + - sharedscripts + - create 0644 http log + - su root http + scripts: + postrotate: test ! -r /run/nginx.pid || kill -USR1 $(cat /run/nginx.pid) ``` -## Dependencies - -None - ## Example Playbook -Setting up logrotate for additional Nginx logs, with postrotate script. +see into [molecule test](molecule/default/converge.yml) and [configuration](molecule/default/group_vars/all/vars.yml) + -```yaml -- hosts: all - vars: - logrotate_scripts: - - name: nginx-options - path: /var/log/nginx/options.log - options: - - daily - - weekly - - size 25M - - rotate 7 - - missingok - - compress - - delaycompress - - copytruncate - - - name: nginx-scripts - path: /var/log/nginx/scripts.log - options: - - daily - - weekly - - size 25M - scripts: - postrotate: "echo test" - - roles: - - ansible-logrotate -``` ## Tests diff --git a/defaults/main.yml b/defaults/main.yml index a91f016..24c0f40 100644 --- a/defaults/main.yml +++ b/defaults/main.yml @@ -1,18 +1,84 @@ --- logrotate_global: + # frequency + # - hourly + # Log files are rotated every hour. + # Note that usually logrotate is configured to be run by cron daily. + # You have to change this configuration and run logrotate hourly to be able + # to really rotate logs hourly. + # - daily + # Log files are rotated every day. + # - weekly + # Log files are rotated once each weekday, + # or if the date is advanced by at least 7 days since the last rotation (while ignoring the exact time). + # The weekday interpretation is following: + # 0 means Sunday, + # 1 means Monday, + # ..., + # 6 means Saturday; + # the special value 7 means each 7 days, irrespectively of weekday. + # Defaults to 0 if the weekday argument is omitted. + # - monthly + # Log files are rotated the first time logrotate is run in a month (this is normally on the first day of the month). + # - yearly + # Log files are rotated if the current year is not the same as the last rotation. + # rotate_log: weekly - rotate_size: 20M + # restrict maximum size of log files + # Log files are rotated only if they grow bigger than size bytes. + # If size is followed by k, the size is assumed to be in kilobytes. + # If the M is used, the size is in megabytes, and if G is used, the size is in gigabytes. + # So size 100, size 100k, size 100M and size 100G are all valid. + # This option is mutually exclusive with the time interval options, + # and it causes log files to be rotated without regard for the last rotation time, + # if specified after the time criteria (the last specified option takes the precedence). + rotate_size: '' # 20M + # Rotate log files set under this user and group instead of using default user/group (usually root) + # su_user specifies the user used for rotation and + # su_group specifies the group used for rotation su_user: '' su_group: '' + # Log files are rotated count times before being removed or mailed to the address specified in a mail directive. + # If count is 0, old versions are removed rather than rotated. + # If count is -1, old logs are not removed at all, except they are affected by maxage + # (use with caution, may waste performance and disk space). rotate: 2 + # create new (empty) log files after rotating old ones create: true + # use date as a suffix of the rotated file dateext: true + # if you want your log files compressed compress: true + # taboo extension list + # At startup, the taboo extension list , + # v, .cfsaved, .disabled, .dpkg-bak, .dpkg-del, .dpkg-dist, + # .dpkg-new, .dpkg-old, .rhn-cfg-tmp-*, .rpmnew, .rpmorig, + # .rpmsave, .swp, .ucf-dist, .ucf-new, .ucf-old, ~ + # for arch based distribution, you can add her: + # .pacorig, .pacnew, .pacsave tabooext: [] + # Logs are moved into directory for rotation + # e.g. /var/log/archive + archive_directory: '' logrotate_conf_dir: "/etc/logrotate.d" -logrotate_scripts: [] + +logrotate_scripts: {} +# audit: +# path: /var/log/audit/audit.log +# description: | +# rotate all audit logs +# options: +# - weekly +# - rotate 4 +# - missingok +# - notifempty +# - delaycompress +# scripts: +# prerotate: systemctl stop auditd.service > /dev/null +# postrotate: systemctl start auditd.service > /dev/null +# foo: failed logroate_disable_systemd: true diff --git a/filter_plugins/types.py b/filter_plugins/types.py new file mode 100644 index 0000000..fa61aeb --- /dev/null +++ b/filter_plugins/types.py @@ -0,0 +1,25 @@ +# python 3 headers, required if submitting to Ansible + +from __future__ import (absolute_import, print_function) +__metaclass__ = type + +from ansible.utils.display import Display + +display = Display() + + +class FilterModule(object): + """ + Ansible file jinja2 tests + """ + + def filters(self): + return { + 'type': self.var_type, + } + + def var_type(self, var): + ''' + Get the type of a variable + ''' + return type(var).__name__ diff --git a/meta/main.yml b/meta/main.yml index cc63443..0e2354c 100644 --- a/meta/main.yml +++ b/meta/main.yml @@ -1,4 +1,5 @@ --- + galaxy_info: role_name: logrotate namespace: bodsch @@ -8,22 +9,30 @@ galaxy_info: license: Apache min_ansible_version: 2.8 platforms: - - name: Debian - versions: - # 9 - - etch - # 10 - - buster - name: Ubuntu versions: + # 16.04 + - cosmic # 18.04 - bionic # 20.04 - focal + - name: Debian + versions: + # 9 + - etch + # 10 + - buster - name: EL versions: + - 7 - 8 + - name: ArchLinux galaxy_tags: - system + - logfile + - rotate dependencies: [] + +... diff --git a/molecule/default/group_vars/all/vars.yml b/molecule/default/group_vars/all/vars.yml index 25acd6f..8627bbd 100644 --- a/molecule/default/group_vars/all/vars.yml +++ b/molecule/default/group_vars/all/vars.yml @@ -1,53 +1,59 @@ --- -logrotate_global_dateext: false +logrotate_global: + dateext: false + rotate_size: 20M logrotate_scripts: audit: - - path: /var/log/audit/audit.log - description: | - rotate all audit logs - options: - - weekly - - rotate 4 - - missingok - - notifempty - - delaycompress - scripts: - prerotate: systemctl stop auditd.service > /dev/null - postrotate: systemctl start auditd.service > /dev/null - foo: failed + path: /var/log/audit/audit.log + description: | + rotate all audit logs + options: + - weekly + - rotate 4 + - missingok + - notifempty + - delaycompress + scripts: + prerotate: systemctl stop auditd.service > /dev/null + postrotate: systemctl start auditd.service > /dev/null + foo: failed system: - - path: /var/log/wtmp - options: - - monthly - - create 0664 root utmp - - minsize 1M - - rotate 1 - - - path: /var/log/btmp - options: - - missingok - - monthly - - create 0600 root utmp - - rotate 1 + paths: + - /var/log/wtmp + - /var/log/btmp + options: + - monthly + - create 0664 root utmp + - minsize 1M + - rotate 1 icinga2: - - paths: - - /var/log/icinga2/icinga2.log - - /var/log/icinga2/debug.log - options: - - weekly - - rotate 2 - - missingok - - notifempty - - compress - - delaycompress - - create 644 icinga icinga - scripts: - postrotate: /bin/kill -USR1 $(cat /run/icinga2/icinga2.pid 2> /dev/null) 2> /dev/null || true + paths: + - /var/log/icinga2/icinga2.log + - /var/log/icinga2/debug.log + options: + - weekly + - rotate 2 + - missingok + - notifempty + - compress + - delaycompress + - create 644 icinga icinga + scripts: + postrotate: /bin/kill -USR1 $(cat /run/icinga2/icinga2.pid 2> /dev/null) 2> /dev/null || true nofunc: - - options: - - daily + options: + - daily + + noexists: + description: | + this logrotate schould be absent + state: absent + options: + - daily + +... diff --git a/tasks/configure.yaml b/tasks/configure.yaml new file mode 100644 index 0000000..db6ea7a --- /dev/null +++ b/tasks/configure.yaml @@ -0,0 +1,51 @@ +--- + +- name: create logrotate.conf + template: + src: logrotate.conf.j2 + dest: /etc/logrotate.conf + mode: 0644 + +- name: create directory {{ logrotate_conf_dir }} + file: + path: "{{ logrotate_conf_dir }}" + state: directory + mode: 0755 + +- name: create directory {{ logrotate_global.archive_directory }} + file: + path: "{{ logrotate_global.archive_directory }}" + state: directory + mode: 0755 + when: + - logrotate_global.archive_directory is defined + - logrotate_global.archive_directory | length > 0 + +- name: create logrotate.d configs + template: + src: logrotate.d.j2 + dest: "{{ logrotate_conf_dir }}/{{ item.key }}" + mode: 0644 + loop: + "{{ logrotate_scripts | dict2items }}" + loop_control: + label: "{{ item.key }}" + when: + - logrotate_scripts is defined + - logrotate_scripts | length > 0 + - item.value.state | default('present') == 'present' + +- name: remove logrotate.d configs + file: + dest: "{{ logrotate_conf_dir }}/{{ item.key }}" + state: absent + loop: + "{{ logrotate_scripts | dict2items }}" + loop_control: + label: "{{ item.key }}" + when: + - logrotate_scripts is defined + - logrotate_scripts | length > 0 + - item.value.state | default('present') == 'absent' + +... diff --git a/tasks/cron.yaml b/tasks/cron.yaml new file mode 100644 index 0000000..806c59a --- /dev/null +++ b/tasks/cron.yaml @@ -0,0 +1,15 @@ +--- + +- name: ensure that /etc/cron.daily is present + file: + state: directory + path: /etc/cron.daily + mode: 0755 + +- name: write cron.daily + template: + src: cron_logrotate.j2 + dest: /etc/cron.daily/logrotate + mode: 0755 + +... diff --git a/tasks/install.yaml b/tasks/install.yaml new file mode 100644 index 0000000..3879165 --- /dev/null +++ b/tasks/install.yaml @@ -0,0 +1,8 @@ +--- + +- name: install logrotate + package: + name: logrotate + state: present + +... diff --git a/tasks/main.yml b/tasks/main.yml index 5e6076e..2d5693e 100644 --- a/tasks/main.yml +++ b/tasks/main.yml @@ -1,60 +1,29 @@ --- -- name: update package cache - package: - update_cache: true - -- name: install logrotate - package: - name: logrotate - state: present - -- name: create logrotate.conf - template: - src: logrotate.conf.j2 - dest: /etc/logrotate.conf - mode: 0644 - -- name: create logrotate.d configs - template: - src: logrotate.d.j2 - dest: "{{ logrotate_conf_dir }}/{{ item.key }}" - mode: 0644 - loop: - "{{ logrotate_scripts | dict2items }}" - when: - - logrotate_scripts is defined - - logrotate_scripts | length > 0 - -- name: systemd service handling - block: - - name: set systemd states - set_fact: - systemd_state: "{{ 'stopped' if logroate_disable_systemd else 'omit' }}" - systemd_enabled: "{{ 'false' if logroate_disable_systemd else 'true' }}" - - - name: handle systemd timer unit is stopped and disabled - service: - name: logrotate.timer - state: "{{ systemd_state }}" - enabled: "{{ systemd_enabled }}" - failed_when: false - when: - - not ansible_distribution_major_version == 7 - when: - - ansible_service_mgr | lower == "systemd" - - logroate_disable_systemd - -- name: ensure that /etc/cron.daily is present - file: - state: directory - path: /etc/cron.daily - mode: 0755 - -- name: write cron.daily - template: - src: cron_logrotate.j2 - dest: /etc/cron.daily/logrotate - mode: 0755 +- include: prepare.yaml + become: true + tags: + - logrotate_prepare + - logrotate_configure + +- include: install.yaml + become: true + tags: + - logrotate_install + +- include: configure.yaml + become: true + tags: + - logrotate_configure + +- name: handle systemd + include_tasks: systemd.yaml + tags: + - logrotate_systemd + +- name: create cron job + include_tasks: cron.yaml + tags: + - logrotate_cron ... diff --git a/tasks/prepare.yaml b/tasks/prepare.yaml new file mode 100644 index 0000000..f39382d --- /dev/null +++ b/tasks/prepare.yaml @@ -0,0 +1,12 @@ +--- + +- name: update package cache + package: + update_cache: true + +- name: merge logrotate global configuration between defaults and custom + set_fact: + logrotate_global: "{{ logrotate_defaults_global | + combine( logrotate_global, recursive=True ) }}" + +... diff --git a/tasks/systemd.yaml b/tasks/systemd.yaml new file mode 100644 index 0000000..e528d37 --- /dev/null +++ b/tasks/systemd.yaml @@ -0,0 +1,22 @@ +--- + +- name: systemd service handling + block: + - name: set systemd states + set_fact: + systemd_state: "{{ 'stopped' if logroate_disable_systemd else 'omit' }}" + systemd_enabled: "{{ 'false' if logroate_disable_systemd else 'true' }}" + + - name: handle systemd timer unit is stopped and disabled + service: + name: logrotate.timer + state: "{{ systemd_state }}" + enabled: "{{ systemd_enabled }}" + failed_when: false + when: + - not ansible_distribution_major_version == 7 + when: + - ansible_service_mgr | lower == "systemd" + - logroate_disable_systemd + +... diff --git a/templates/cron_logrotate.j2 b/templates/cron_logrotate.j2 index e4f19f8..1e1a363 100644 --- a/templates/cron_logrotate.j2 +++ b/templates/cron_logrotate.j2 @@ -14,13 +14,15 @@ if [ -z "${LOGROTATE}" ]; then fi # this cronjob persists removals (but not purges) -if [ ! -x /usr/sbin/logrotate ]; then +if [ ! -x ${LOGROTATE} ]; then exit 0 fi -/usr/sbin/logrotate /etc/logrotate.conf +${LOGROTATE} /etc/logrotate.conf EXITVALUE=$? -if [ $EXITVALUE != 0 ]; then +if [ $EXITVALUE != 0 ] +then /usr/bin/logger -t logrotate "ALERT exited abnormally with [$EXITVALUE]" fi + exit $EXITVALUE diff --git a/templates/logrotate.conf.j2 b/templates/logrotate.conf.j2 index 1f4f29a..a806c69 100644 --- a/templates/logrotate.conf.j2 +++ b/templates/logrotate.conf.j2 @@ -3,7 +3,8 @@ # see "man logrotate" for details # rotate log files weekly -{% if logrotate_global.rotate_log is defined and logrotate_global.rotate_log in rotate_log_attr %} +{% if logrotate_global.rotate_log is defined and + logrotate_global.rotate_log in rotate_log_attr %} {{ logrotate_global.rotate_log }} {% else %} weekly @@ -21,21 +22,26 @@ su {{ logrotate_global.su_user }} {{ logrotate_global.su_group }} # keep 4 weeks worth of backlogs rotate {{ logrotate_global.rotate | default('2') }} +{% if logrotate_global.rotate_size is defined and + logrotate_global.rotate_size | length != 0 %} # restrict maximum size of log files -#size 20M - -# create new (empty) log files after rotating old ones -create +size {{ logrotate_global.rotate_size | default('20M') }} +{% endif %} +{% if logrotate_global.archive_directory is defined and + logrotate_global.archive_directory | length > 0 %} # Logs are moved into directory for rotation -# olddir /var/log/archive +olddir {{ logrotate_global.archive_directory }} +{% endif %} -{% if logrotate_global.create is defined and logrotate_global.create | bool == true -%} +{% if logrotate_global.create is defined and + logrotate_global.create | bool == true -%} # create new (empty) log files after rotating old ones create {% endif %} -{% if logrotate_global.dateext is defined and logrotate_global.dateext | bool == true -%} +{% if logrotate_global.dateext is defined and + logrotate_global.dateext | bool == true -%} # use date as a suffix of the rotated file dateext {% endif %} @@ -45,8 +51,10 @@ dateext compress {% endif %} -{% if logrotate_global.tabooext is defined and logrotate_global.tabooext != '' %} -tabooext + {{logrotate_global.tabooext | join(' ')}} +{% if logrotate_global.tabooext is defined and + logrotate_global.tabooext | type == 'list' and + logrotate_global.tabooext | count >= 1 %} +tabooext + {{ logrotate_global.tabooext | join(' ') }} {# .pacorig .pacnew .pacsave #} {% endif %} diff --git a/templates/logrotate.d.j2 b/templates/logrotate.d.j2 index 168c9df..a3cc500 100644 --- a/templates/logrotate.d.j2 +++ b/templates/logrotate.d.j2 @@ -1,32 +1,31 @@ # {{ ansible_managed }} -{% set scripts_attr = ['postrotate', 'prerotate'] %} -{% for v in item.value -%} +{% set scripts_attr = ['postrotate', 'prerotate'] %} -{%- if v.description is defined %} -# {{ v.description }} +{%- if item.value.description is defined %} +# {{ item.value.description }} {% endif -%} -{% if 'path' in v or 'paths' in v -%} +{% if item.value.path is defined or item.value.paths is defined %} -{% if 'path' in v %} -{{ v.path }} -{% elif 'paths' in v -%} -{% for path in v.paths -%} +{% if item.value.path is defined %} +{{ item.value.path }} +{% elif item.value.paths %} +{% for path in item.value.paths -%} {{ path }} {% endfor -%} -{% endif -%} +{% endif %} { -{% if v.options is defined %} +{% if item.value.options is defined %} {# all logrotate options #} - {% for option in v.options -%} + {% for option in item.value.options -%} {{ option }} {% endfor -%} {% endif %} -{% if v.scripts is defined %} +{% if item.value.scripts is defined %} {# pre- or postrotate scripts #} -{% for name, script in v.scripts.items() %} +{% for name, script in item.value.scripts.items() %} {% if name in scripts_attr %} {{ name }} {{ script }} @@ -34,7 +33,6 @@ {% endif %} {% endfor %} {% endif %} -} +} {% endif %} -{% endfor %} diff --git a/vars/main.yml b/vars/main.yml index c81cf5b..fd67eab 100644 --- a/vars/main.yml +++ b/vars/main.yml @@ -1,3 +1,15 @@ --- +logrotate_defaults_global: + rotate_log: weekly + rotate_size: '' + su_user: '' + su_group: '' + rotate: 0 + create: true + dateext: true + compress: true + tabooext: [] + archive_directory: '' + ...