#!/usr/lib64/linuxfabrik-monitoring-plugins/venv/bin/python
# -*- 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.lftest
import lib.shell
import lib.version
from lib.globals import STATE_UNKNOWN, STATE_WARN

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

DESCRIPTION = """Checks the installed Red Hat OpenJDK version against the endoflife.date API and alerts if
the version is end-of-life or if newer major, minor, or patch releases are available.
By default, alerts 30 days before the official EOL date. The offset is configurable."""

DEFAULT_CHECK_MAJOR = False
DEFAULT_CHECK_MINOR = False
DEFAULT_CHECK_PATCH = False
DEFAULT_INSECURE = False
DEFAULT_NO_PROXY = False
DEFAULT_OFFSET_EOL = -30  # days
DEFAULT_PATH = '/usr/bin/java'
DEFAULT_TIMEOUT = 8
DEFAULT_UNREACHABLE_SEVERITY = 'ok'


def parse_args():
    """Parse command line arguments using argparse."""
    parser = argparse.ArgumentParser(
        description=DESCRIPTION,
        epilog=lib.args.epilog(__file__),
        formatter_class=lib.args.HelpFormatter,
    )

    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(
        '--check-major',
        help=lib.args.help('--check-major'),
        dest='CHECK_MAJOR',
        action='store_true',
        default=DEFAULT_CHECK_MAJOR,
    )

    parser.add_argument(
        '--check-minor',
        help=lib.args.help('--check-minor'),
        dest='CHECK_MINOR',
        action='store_true',
        default=DEFAULT_CHECK_MINOR,
    )

    parser.add_argument(
        '--check-patch',
        help=lib.args.help('--check-patch'),
        dest='CHECK_PATCH',
        action='store_true',
        default=DEFAULT_CHECK_PATCH,
    )

    parser.add_argument(
        '--insecure',
        help=lib.args.help('--insecure'),
        dest='INSECURE',
        action='store_true',
        default=DEFAULT_INSECURE,
    )

    parser.add_argument(
        '--no-perfdata',
        help=lib.args.help('--no-perfdata'),
        dest='NO_PERFDATA',
        action='store_true',
        default=False,
    )

    parser.add_argument(
        '--no-proxy',
        help=lib.args.help('--no-proxy'),
        dest='NO_PROXY',
        action='store_true',
        default=DEFAULT_NO_PROXY,
    )

    parser.add_argument(
        '--offset-eol',
        help=lib.args.help('--offset-eol') + ' Default: %(default)s days',
        dest='OFFSET_EOL',
        type=int,
        default=DEFAULT_OFFSET_EOL,
    )

    parser.add_argument(
        '--path',
        help='Local path to your Java binary. Default: %(default)s',
        dest='PATH',
        default=DEFAULT_PATH,
    )

    parser.add_argument(
        '--proxy',
        help=lib.args.help('--proxy'),
        dest='PROXY',
        default=None,
    )

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

    parser.add_argument(
        '--timeout',
        help=lib.args.help('--timeout') + ' Default: %(default)s (seconds)',
        dest='TIMEOUT',
        type=int,
        default=DEFAULT_TIMEOUT,
    )

    parser.add_argument(
        '--unreachable-severity',
        help=lib.args.help('--unreachable-severity') + ' Default: %(default)s',
        dest='UNREACHABLE_SEVERITY',
        choices=['ok', 'warn', 'crit', 'unknown'],
        default=DEFAULT_UNREACHABLE_SEVERITY,
    )

    args, _ = parser.parse_known_args()
    return args


def get_installed_version(
    path, test_arg=None, timeout=DEFAULT_TIMEOUT, always_ok=False
):
    if test_arg is None:
        cmd = [path, '-version']
        success, result = lib.shell.shell_exec(cmd, timeout=timeout)
        if not success:
            if result.startswith('Timeout after'):
                lib.base.oao(
                    f'Timeout after {timeout}s while running `{" ".join(cmd)}`.',
                    STATE_WARN,
                    always_ok=always_ok,
                )
            return ''
        stdout = result[1].strip()  # we get the version info on STDERR :facepalm:
    else:
        _, stderr, _ = lib.lftest.test(test_arg)
        stdout = stderr.strip()  # the version info lives on STDERR
    # where to find the version number in output?
    # The first line of the banner is `<launcher_name> version "<java_version>"`,
    # followed by the version date and "LTS" on 10+ (jdk8u:
    # jdk/src/share/classes/sun/misc/Version.java.template, jdk9+:
    # src/java.base/share/classes/java/lang/VersionProps.java.template):
    #   openjdk version "1.8.0_504"
    #   openjdk version "21.0.12.1" 2026-08-18 LTS
    #   openjdk version "27-ea" 2026-09-15
    # Red Hat adds its build tag only to the two lines below it. The JVM and the
    # launcher print "Picked up JAVA_TOOL_OPTIONS: ...", "Picked up _JAVA_OPTIONS: ..."
    # and "NOTE: Picked up JDK_JAVA_OPTIONS: ..." to STDERR before the banner, and
    # those values may contain quoted strings with digits, so the regex is anchored
    # on the banner line and expects the quoted value to start with a digit.
    # Verified against OpenJDK 1.8.0_504, 11.0.25, 17.0.20.1, 21.0.12.1 (ubi9) and
    # 25.0.4.1, 27-ea (Fedora 43), jdk8u512-b07 and jdk-28+19 source.
    version_regex = r'^\S+ version "(\d[^"\s]*)"'
    try:
        stdout = re.search(version_regex, stdout, re.MULTILINE)
        return stdout.group(1).strip()
    except Exception:
        return ''


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)

    # fetch data
    installed_version = get_installed_version(
        args.PATH, args.TEST, timeout=args.TIMEOUT, always_ok=args.ALWAYS_OK
    )
    if not installed_version:
        lib.base.cu(
            f'Java not found at `{args.PATH}`. Use --path to point at the binary.'
        )

    # init some vars
    perfdata = ''
    # lib.version reads a suffix as version parts (4.5rc1 -> 4.51), use the core
    # Java 8 writes its update number as `1.8.0_504`, which counts as a fourth
    # version part, so `8u504` and `8u472` stay apart in the perfdata
    installed_core = re.match(
        r'\d+(?:\.\d+)*', installed_version.replace('_', '.')
    ).group()

    # analyze data
    state, eol = lib.version.check_eol(
        'https://endoflife.date/api/redhat-build-of-openjdk.json',
        installed_core,
        offset_eol=args.OFFSET_EOL,
        check_major=args.CHECK_MAJOR,
        check_minor=args.CHECK_MINOR,
        check_patch=args.CHECK_PATCH,
        insecure=args.INSECURE,
        no_proxy=args.NO_PROXY,
        proxy=args.PROXY,
        timeout=args.TIMEOUT,
        unreachable_severity=args.UNREACHABLE_SEVERITY,
        # where the distribution maintains the software, its life cycle counts
        package_path=args.PATH if args.TEST is None else None,
    )

    # build the message
    msg = f'Java (Red Hat build of OpenJDK) v{installed_version} ({eol})'

    perfdata += lib.base.get_perfdata(
        'openjdk-redhat-version',
        lib.version.version2float(installed_core),
        uom=None,
        _min=0,
    )

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


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