#!/usr/bin/env python3
# -*- coding: utf-8; py-indent-offset: 4 -*-
#
# Author:  Linuxfabrik GmbH, Zurich, Switzerland
# Contact: info (at) linuxfabrik (dot) ch
#          https://www.linuxfabrik.ch/
# License: The Unlicense, see LICENSE file.

# https://github.com/Linuxfabrik/monitoring-plugins/blob/main/CONTRIBUTING.md

"""See the check's README for more details."""

import argparse
import re
import sys

import lib.args
import lib.base
import lib.human
import lib.lftest
import lib.shell
from lib.globals import STATE_CRIT, STATE_OK, STATE_UNKNOWN, STATE_WARN

__author__ = 'Linuxfabrik GmbH, Zurich/Switzerland'
__version__ = '2026041404'

DESCRIPTION = """Displays system-wide Docker information including container counts (running,
paused, stopped), image count, storage and logging driver, Docker version, available
CPUs, and total memory. Also monitors the Docker daemon stderr for warnings and errors.
Individual stderr lines can be filtered out with --ignore (e.g. the "WARNING: No swap
limit support" message on hosts where the kernel does not expose swap accounting). For
Podman, use the podman-info check instead.
Requires root or sudo."""


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(description=DESCRIPTION)

    parser.add_argument(
        '-V',
        '--version',
        action='version',
        version=f'%(prog)s: v{__version__} by {__author__}',
    )

    parser.add_argument(
        '--always-ok',
        help=lib.args.help('--always-ok'),
        dest='ALWAYS_OK',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--ignore',
        help='Ignore stderr lines matching this Python regular expression. '
        'Case-sensitive by default; use `(?i)` for case-insensitive matching. '
        'Can be specified multiple times. '
        'Example: `--ignore="No swap limit support"` to suppress the Docker '
        'warning on kernels without swap accounting. '
        'Example: `--ignore="(?i)bridge-nf-call"` (case-insensitive) to '
        'suppress both `bridge-nf-call-iptables` and `bridge-nf-call-ip6tables` '
        'warnings on Debian hosts. '
        'Default: %(default)s',
        dest='IGNORE',
        action='append',
        default=None,
    )

    parser.add_argument(
        '--test',
        help=lib.args.help('--test'),
        dest='TEST',
        type=lib.args.csv,
    )

    args, _ = parser.parse_known_args()
    return args


def main():
    """The main function. This is where the magic happens."""

    # parse the command line
    try:
        args = parse_args()
    except SystemExit:
        sys.exit(STATE_UNKNOWN)

    if args.IGNORE is None:
        args.IGNORE = []

    # compile ignore patterns (case-sensitive by default, matching the
    # lib.args convention for --match / --ignore-regex; the user can
    # opt into case-insensitive matching with the inline `(?i)` flag).
    try:
        ignore_patterns = [re.compile(p) for p in args.IGNORE]
    except re.error as e:
        lib.base.cu(f'Invalid regular expression: {e}')

    # fetch data
    if args.TEST is None:
        stdout, stderr, retc = lib.base.coe(
            lib.shell.shell_exec('docker info'),
        )
        if retc != 0:
            lib.base.oao(f'{stderr}\n{stdout}', STATE_CRIT)
        if 'server version:' not in stdout.lower():
            lib.base.cu(
                'Unable to parse docker info output.'
                ' If you are using Podman, use the podman-info check instead.'
            )
    else:
        # do not call the command, put in test data
        stdout, stderr, retc = lib.lftest.test(args.TEST)

    # init some vars
    msg = ''
    state = STATE_OK
    perfdata = ''
    containers = containers_paused = containers_running = ''
    containers_stopped = cpus = images = logging_driver = ''
    memory_human = registry = storage_driver = ver = ''
    warn, crit = '', ''

    # analyze data - extract values from docker info text output
    for row in stdout.strip().split('\n'):
        lcrow = row.lower()
        if ' containers: ' in lcrow:
            containers = lcrow.replace('containers: ', '').strip()
        if '  paused: ' in lcrow:
            containers_paused = lcrow.replace('paused: ', '').strip()
        if '  running: ' in lcrow:
            containers_running = lcrow.replace('running: ', '').strip()
        if '  stopped: ' in lcrow:
            containers_stopped = lcrow.replace('stopped: ', '').strip()
        if ' cpus: ' in lcrow:
            cpus = lcrow.replace('cpus: ', '').strip()
        if ' images: ' in lcrow:
            images = lcrow.replace('images: ', '').strip()
        if ' logging driver: ' in lcrow:
            logging_driver = lcrow.replace('logging driver: ', '').strip()
        if ' total memory: ' in lcrow:
            memory_human = row.replace('Total Memory: ', '').strip()
        if ' registry: ' in lcrow:
            registry = lcrow.replace('registry: ', '').strip()
        if ' storage driver: ' in lcrow:
            storage_driver = lcrow.replace('storage driver: ', '').strip()
        if ' server version: ' in lcrow:
            ver = lcrow.replace('server version: ', '').strip()

    # check stderr for warnings and errors, skipping lines matched by
    # --ignore (#834: let admins suppress boilerplate docker warnings
    # like "WARNING: No swap limit support" that cannot be silenced in
    # the daemon config).
    for row in stderr.strip().split('\n'):
        if any(pattern.search(row) for pattern in ignore_patterns):
            continue
        lcrow = row.lower()
        if 'warning: ' in lcrow:
            warn += f'{row}, '
            state = lib.base.get_worst(state, STATE_WARN)
        if 'error: ' in lcrow:
            crit += f'{row}, '
            state = lib.base.get_worst(state, STATE_CRIT)

    # build perfdata
    perfdata += lib.base.get_perfdata(
        'containers',
        containers,
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'containers_paused',
        containers_paused,
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'containers_running',
        containers_running,
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'containers_stopped',
        containers_stopped,
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'cpu',
        cpus,
        _min=0,
    )
    perfdata += lib.base.get_perfdata(
        'images',
        images,
        _min=0,
    )
    if memory_human:
        perfdata += lib.base.get_perfdata(
            'ram',
            lib.human.human2bytes(memory_human),
            uom='B',
            _min=0,
        )

    # create output
    if crit:

        # build the message
        msg += f'{crit}'
    if warn:
        msg += f'{warn}'
    if containers != '':
        msg += f'{containers} Containers'
    if containers_running != '':
        msg += (
            f' ({containers_running} running,'
            f' {containers_paused} paused,'
            f' {containers_stopped} stopped)'
        )
    if images != '':
        msg += f', {images} Images'
    if storage_driver != '':
        msg += f', Storage Driver: {storage_driver}'
    if logging_driver != '':
        msg += f', Logging Driver: {logging_driver}'
    if registry != '':
        msg += f', Registry: {registry}'
    msg += f', Docker v{ver}'
    msg += f', {cpus} CPUs'
    msg += f', {memory_human} Memory'

    # over and out
    lib.base.oao(msg, state, perfdata, always_ok=args.ALWAYS_OK)


if __name__ == '__main__':
    try:
        main()
    except Exception:
        lib.base.cu()
