#!/usr/bin/perl

=head1 NAME

    clus-update-hosts - обновление локального кеша списков хостов для clus, 
    используя информацию из кондуктора

=head1 DESCRIPTION

Читаем конфиги из /etc/clus/update-hosts.d
Конфиги должны быть в формате YAML, пример:

file: /etc/yandex-direct/clus.hosts
rtrim: .yandex.ru
clus_groups:
  PPCDATA1: direct_group_ng_mysql_ppcdata1
  PERL: direct_group_front, direct_group_soap

Возможные параметры:
--cached - использование кешированного conductor api
--verbose - выводить лог работы
--timeout=10 - таймаут на запрос в api кондуктора
--confs-dir - директория с конфигами

=cut

use strict;
use warnings;

use Carp qw/croak/;
use Time::HiRes;
use POSIX qw/strftime/;
use List::MoreUtils qw/uniq/;
use File::Slurp qw/write_file/;
use Net::INET6Glue::INET_is_INET6;

use LWP::UserAgent;
use YAML;

use Getopt::Long;

my $CONFS_DIR = '/etc/clus/update-hosts.d';
my $CONDUCTOR_URL = "http://c.yandex-team.ru";
my $CACHED_API = 0;
my $TIMEOUT = 60;
my $VERBOSE = 0;
my $EXIT_CODE = 0;
GetOptions(
    "help" => \&usage_full,
    "verbose" => \$VERBOSE,

    "cached" => \$CACHED_API,
    "timeout=f" => \$TIMEOUT,

    "confs-dir=s" => \$CONFS_DIR,    
    ) || croak "Can't GetOptions: $!";

my @confs = get_confs();

if (@confs) {
    my $groups = parse_groups_data(get_groups_data());
    for my $conf (@confs) {
        process_conf($conf, $groups);
    }
} else {
    debug("No valid configs");
}

exit $EXIT_CODE;

sub get_groups_data {
    my $url = $CONDUCTOR_URL."/".($CACHED_API ? 'api-cached' : 'api')."/groups_export";
    debug("Get $url");

    my $ua = LWP::UserAgent->new(agent => "Direct $0", timeout => $TIMEOUT);
    my $resp = $ua->get($url);
    if (!$resp->is_success) {
        croak "Can't get $url: ".$resp->status_line;
    }

    return $resp->decoded_content;
}

sub parse_groups_data {
    my ($data) = @_;
    
    my %groups;
    for my $line (split /\n/, $data) {
        my ($group, $hosts_str, $email) = split /:/, $line;
        $groups{$group} = {
            hosts_str => $hosts_str,
            email => $email,
        };
    }

    return \%groups;
}

sub get_confs {
    opendir(my $dh, $CONFS_DIR) || croak "Can't opendir $CONFS_DIR: $!";
    my @confs;
    for my $fn (grep {-f} map {"$CONFS_DIR/$_"} grep {!/^\./ && !/\.dpkg/} readdir($dh)) {
        debug("read $fn");
        if (my $conf = eval { YAML::LoadFile($fn); }) {
            push @confs, {file => $fn, conf => $conf};
        } else {
            warn "Can't read yaml file: $fn: $@";
            $EXIT_CODE = 1;
        }
    }
    closedir($dh);
    return @confs;
}

sub process_conf {
    my ($conf, $groups) = @_;

    my $out = "# Generated by $0 at ".strftime("%Y-%m-%d:%H:%M:%S", localtime)."\n";
    $out .= "\n";

    my $clus_groups = $conf->{conf}->{clus_groups};
    for my $clus_group (keys %$clus_groups) {
        my @hosts;
        for my $c_group (split /\s*,\s*/, $clus_groups->{$clus_group}) {
            if (!exists $groups->{$c_group}) {
                warn "No $c_group in conductor";
                $EXIT_CODE = 1;
                next;
            }
            push @hosts, split /\s*,\s*/, $groups->{$c_group}->{hosts_str};
        }
        if ($conf->{conf}->{rtrim}){
            @hosts = map {s/\Q$conf->{conf}->{rtrim}\E$//; $_} @hosts;
        }
        @hosts = sort &uniq(@hosts);
        $out .= "host_group:$clus_group = ".join(', ', @hosts)."\n";
    }
    debug("Write data to $conf->{conf}->{file}");
    write_file($conf->{conf}->{file}, {atomic => 1}, $out);
}

sub usage_full {
    system("pod2text-utf8 <$0 | less -F");
    exit(0);
}

sub debug {
    return if !$VERBOSE;
    my $time = Time::HiRes::time;
    print STDERR sprintf(
        "%s.%03d %s\n", 
        strftime("%Y-%m-%d:%H:%M:%S", localtime($time)),
        int(1000*($time-int($time))),
        $_[0]
        );
}
