#!/usr/bin/perl

use strict;
use warnings;

use List::Util qw(first);

use PVE::Ceph::Tools;
use PVE::Cluster;
use PVE::RADOS;
use PVE::RPCEnvironment;

my $ceph_cfg_file = 'ceph.conf';
my $keyring_value = '/etc/pve/ceph/$cluster.$name.keyring';

sub try_adapt_cfg {
    my ($cfg) = @_;

    my $entity = 'client.crash';
    my $removed_key = 0;

    print("Checking whether the configuration for '$entity' needs to be updated.\n");

    my $add_keyring = sub {
        print("Setting keyring path to '$keyring_value'.\n");
        $cfg->{$entity}->{keyring} = $keyring_value;
    };

    if (!exists($cfg->{$entity})) {
        print("Adding missing section for '$entity'.\n");
        $add_keyring->();
        return 1;
    }

    if (exists($cfg->{$entity}->{key})) {
        print("Removing existing usage of key.\n");
        delete($cfg->{$entity}->{key});
        $removed_key = 1;
    }

    if (!exists($cfg->{$entity}->{keyring})) {
        print("Keyring path is missing from configuration.\n");
        $add_keyring->();
        return 1;
    }

    my $current_keyring_value = $cfg->{$entity}->{keyring};
    if ($current_keyring_value ne $keyring_value) {
        print("Current keyring path differs from expected path.\n");
        $add_keyring->();
        return 1;
    }

    return $removed_key;
}

sub main {
    # PVE::RADOS expects an active RPC Environment because it forks itself
    # and may want to clean up after
    my $rpcenv = PVE::RPCEnvironment->setup_default_cli_env();

    if (!PVE::Ceph::Tools::check_ceph_installed('ceph_bin', 1)) {
        print("Ceph is not installed. No action required.\n");
        exit 0;
    }

    my $ceph_cfg_path = PVE::Ceph::Tools::get_config('pve_ceph_cfgpath');
    if (PVE::Ceph::Tools::check_ceph_installed('ceph_mon', 1) && -f $ceph_cfg_path) {
        my $pve_ceph_cfgdir = PVE::Ceph::Tools::get_config('pve_ceph_cfgdir');
        if (!-d $pve_ceph_cfgdir) {
            File::Path::make_path($pve_ceph_cfgdir);
        }
    }

    eval { PVE::Ceph::Tools::check_ceph_inited(); };
    if ($@) {
        print("Ceph is not initialized. No action required.\n");
        exit 0;
    }

    my $rados = eval { PVE::RADOS->new() };
    my $ceph_crash_key_path = PVE::Ceph::Tools::get_config('pve_ceph_crash_key_path');

    my $inner_err = '';

    my $rval = PVE::Cluster::cfs_lock_file(
        $ceph_cfg_file,
        undef,
        sub {
            eval {
                my $cfg = PVE::Cluster::cfs_read_file($ceph_cfg_file);

                if (!defined($rados)) {
                    my $has_mon_host =
                        defined($cfg->{global}) && defined($cfg->{global}->{mon_host});
                    if ($has_mon_host && $cfg->{global}->{mon_host} ne '') {
                        die "Connection to RADOS failed even though a monitor is configured.\n"
                            . "Please verify whether your configuration in '$ceph_cfg_file' is correct.\n";
                    }

                    print(
                        "Connection to RADOS failed and no monitor is configured in '$ceph_cfg_file'.\n"
                            . "Assuming that things are fine. No action required.\n");
                    return;
                }

                my $updated_keyring =
                    PVE::Ceph::Tools::create_or_update_crash_keyring_file($rados);

                if ($updated_keyring) {
                    print("Keyring file '$ceph_crash_key_path' was updated.\n");
                }

                my $changed = try_adapt_cfg($cfg);

                if ($changed) {
                    print("Committing updated configuration to '$ceph_cfg_file'.\n");
                    PVE::Cluster::cfs_write_file($ceph_cfg_file, $cfg);
                    print("Successfully updated configuration for 'ceph-crash.service'.\n");
                } else {
                    print("Configuration in '$ceph_cfg_file' does not need to be updated.\n");
                }
            };
            $inner_err = $@;

            return 1;
        },
    );

    # cfs_lock_file sets $@ explicitly to undef
    my $err = $@ // '';

    my $has_err = !defined($rval) || $inner_err || $err;

    if ($has_err) {
        $err =~ s/\n*$//;
        $inner_err =~ s/\n*$//;

        if (!defined($rval)) {
            warn("Error while acquiring or releasing lock for '$ceph_cfg_file'.\n");
            warn("Error: $err\n") if $err ne '';
        }

        warn("Failed to configure keyring for 'ceph-crash.service'.\nError: $inner_err\n")
            if $inner_err ne '';

        exit 1;
    }
}

main();
