#!/usr/bin/env python3

# Copyright (c) 2022 SUSE LLC  All rights reserved.
#
# check_rmt_repos is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation, version 2 of
# the License.
#
# check_rmt_repos is distributed in the hope that it will
# be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with check_rmt_repos. If not, see
# <http://www.gnu.org/licenses/>.
#

import argparse
import json
import os
import subprocess
import sys
import csv
from io import StringIO

# Nagios states
OK = 0
WARNING = 1
CRITICAL = 2
UNKNOWN = 3


def get_enabled_rmt_repos():
    repo_info_cmd = ['rmt-cli', 'repos', 'list', '--csv']
    proc = subprocess.Popen(
        repo_info_cmd,
        stdout=subprocess.PIPE,
        stderr=subprocess.PIPE
    )
    avail_repo_data, errors = proc.communicate()

    if errors or proc.returncode != 0:
        print('Error executing "rmt-cli repos list"')
        sys.exit(CRITICAL)

    rmt_repos = {}
    f = StringIO(avail_repo_data.decode())
    f.readline()  # skip table headers
    csv_reader = csv.reader(f, delimiter=',')
    for repo_detail in csv_reader:
        id = repo_detail[0].strip()
        description = repo_detail[2].strip()
        rmt_repos[id] = description

    return rmt_repos


def get_rmt_cli_command():
    rmt_cli_pid = get_rmt_cli_pid()
    rmt_cli_cmd = None
    if rmt_cli_pid:
        rmt_cli_cmd = open('/proc/%s/cmdline' % rmt_cli_pid, 'r').read()
        rmt_cli_cmd = rmt_cli_cmd.replace('\x00', ' ')
    return rmt_cli_cmd


def get_rmt_cli_pid():
    rmt_cli_pid_cmd = ['ps', '-C', 'rmt-cli', '-o', 'pid=']
    rmt_cli_pid = subprocess.Popen(rmt_cli_pid_cmd, stdout=subprocess.PIPE)
    pid_data = rmt_cli_pid.communicate()
    return pid_data[0].strip().decode()


def get_mirrored_repos():
    mirrored_repos = {}
    for root, dirs, files in os.walk('/var/lib/rmt/public/repo'):
        for dirname in dirs:
            if dirname == 'repodata':
                mirrored_repos[root] = True
    return mirrored_repos


def update_prometheus_data(metrics):
    script = os.path.splitext(
        os.path.basename(node_exporter_path)
    )[0]
    info = ''
    with open(node_exporter_path, 'w') as prom_file:
        for key, metric in metrics.items():
            info = info + \
                '{script} {{status="{status}",repos="{repos}"}} {n_repos}\n'.format(
                    script=script,
                    status=key,
                    n_repos=metric.get('amount'),
                    repos=', '.join(metric.get('repos'))
                )

        prom_file.write(info)


# Set up command line argument parsing
argparse = argparse.ArgumentParser(description='Monitor repo setup for RMT')
argparse.add_argument(
    '-f', '--config-file',
    dest='config_file_path',
    default='/etc/rmt-utils/rmt_repository_config.json',
    help='Path to json config file for RMT repo configuration',
    metavar='CONFIG_FILE'
)
help_msg = 'A designator that will compare the repository configuration '
help_msg += '"framework" setting with the provided value'
argparse.add_argument(
    '--cloud',
    dest='framework',
    help=help_msg
)
args = argparse.parse_args()
node_exporter_path = '/var/lib/node_exporter/textfile_collector/check_rmt_repos.prom'
if not os.path.exists(os.path.dirname(node_exporter_path)):
    try:
        os.mkdir(os.path.dirname(node_exporter_path))
    except OSError:
        print('ERROR creating {}'.format(os.path.dirname(node_exporter_path)))


enabled_repos = get_enabled_rmt_repos()
mirrored_repos = get_mirrored_repos()

if not os.path.exists(args.config_file_path):
    print('Missing "%s"' % args.config_file_path)
    sys.exit(CRITICAL)

rmt_cli_command = get_rmt_cli_command()
if rmt_cli_command and 'mirror' in rmt_cli_command:
    # While the mirror process is running we do not complain about
    # missing directories. They are expected to show up at the end
    # of the mirror process.
    sys.exit(OK)

framework_to_use = None
if args.framework:
    framework_to_use = args.framework

configured_repo_ids = []
not_enabled = {'ids': [], 'descriptions': []}
missing_repo_dirs = []
extra_enabled_repos = {'ids': [], 'descriptions': []}
extra_mirrored_repos = []
config_repos = []
try:
    json_file = open(args.config_file_path)
    config_repos = json.load(json_file)
except:
    print('Could not open or read "%s"' % args.config_file_path)
    sys.exit(CRITICAL)

for config_repo in config_repos:
    repo_id = str(config_repo.get('id'))
    repo_framework = config_repo.get('framework')
    if repo_framework and repo_framework != framework_to_use:
        # Skip repos that are not configured for this framework
        continue
    configured_repo_ids.append(repo_id)
    if not enabled_repos.get(repo_id):
        not_enabled['ids'].append(repo_id)
        not_enabled['descriptions'].append(
            config_repo.get('description')
        )
    full_repo_path = '/var/lib/rmt/public/repo/' \
        + config_repo.get('location').strip('/')

    if mirrored_repos.get(full_repo_path):
        del mirrored_repos[full_repo_path]

    if not os.path.exists(full_repo_path):
        missing_repo_dirs.append(full_repo_path)

for repo_id, repo_description in enabled_repos.items():
    if repo_id not in configured_repo_ids:
        extra_enabled_repos['ids'].append(repo_id)
        extra_enabled_repos['descriptions'].append(repo_description)
extra_mirrored_repos = list(mirrored_repos.keys())

metrics = {
    'not_enabled': {
        'amount': len(not_enabled.get('ids')),
        'repos': not_enabled.get('ids')
    },
    'missing': {
        'amount': len(missing_repo_dirs),
        'repos': missing_repo_dirs
    },
    'extra_enabled': {
        'amount': len(extra_enabled_repos.get('ids')),
        'repos': extra_enabled_repos.get('ids')
    },
    'extra_mirrored': {
        'amount': len(extra_mirrored_repos),
        'repos': extra_mirrored_repos
    }
}
update_prometheus_data(metrics)


if not_enabled['ids']:
    print('Following expected repos are not enabled:')
    print('\t%s' % ' '.join(not_enabled['ids']))
    for description in not_enabled['descriptions']:
        print('\t%s' % description)

if missing_repo_dirs:
    print('Following repo directories are missing:')
    for missing_dir in missing_repo_dirs:
        print('\t%s' % missing_dir)

if extra_enabled_repos['ids']:
    print('Following enabled repos are extra:')
    print('\t%s' % ' '.join(extra_enabled_repos['ids']))
    for description in extra_enabled_repos['descriptions']:
        print('\t%s' % description)

if extra_mirrored_repos:
    print('Following mirrored repos are extra:')
    for extra_dir in extra_mirrored_repos:
        print('\t%s' % extra_dir)

if (
        not_enabled['ids'] or
        missing_repo_dirs or
        extra_enabled_repos['ids'] or
        extra_mirrored_repos
):
    sys.exit(CRITICAL)
