#!/usr/bin/perl -w

=head1 NAME
      
      direct-create-beta -- создает "бету" Директа
 
=head1 SYNOPSIS

    Для обычных разработческих бет: 

        direct-create-beta
        direct-create-beta -b RBACDirect-separated
        direct-create-beta -p 8453
        direct-create-beta -p 8453 -b RBACDirect-separated -n DIRECT-12345

    Поднять сервисы:

        direct-create-beta --infoblock
        direct-create-beta --infoblock branch:use-agency_client_id

        direct-create-beta --dna

        direct-create-beta --java-svn app:web
        direct-create-beta --java-svn app:web,app:intapi,revision:2900000
        direct-create-beta --java-svn app:web,rp:599164
        direct-create-beta --java-svn app:web,rp:599164,no-sandbox:1
        direct-create-beta --java-svn app:web,branch:DIRECT-61550,revision:2900000

        direct-create-beta --arc --java app:jobs

        direct-create-beta --canvas TAG:master,VIDEO_SEARCH:1,BRANCH:master,BUILD:1

    Для "автоматических" бет есть интерфейс http://direct-dev.yandex-team.ru/autobeta/ 

    Только выбрать свободный порт:

        direct-create-beta --get-port-only
        direct-create-beta -g
        Напечатает порт и выйдет. Полезно для сценария "создать бету, перейти в нее и там сделать что-то супер-кастомное".
  
=head1 DESCRIPTION

    Создает "бету" Директа: чекаутит рабочую копию в каталог /var/www/beta.<имя беты>.<порт> + запускает apache на указанном порту

    После создания бета доступна по адресу
    https://<номер>.beta[123].direct.yandex.ru
    Между beta1, beta2, beta3 работает автоматическое перенаправление в соответствии с номером беты.
    Распределение такое:
     8000 ..  8999 -- beta1
     9000 ..  9999 -- beta2
    10000 .. 10999 -- beta3

    
    Опции:
        -h, --help 
            показать справку и завершиться
        -p, --port <port_number>
            (необязательно) номер порта, на котором запустить бету
            если не указан -- будет выбран один из свободных портов, зарезервированных за текущим пользователем
        -b, --branch <branch_name>
            (необязательно) бранч, на который должна смотреть бета
        -r, --rev, --revision <revision>
            номер ревизии, которую нужно зачекаутить
        -n, --name <name>
            (необязательно) "имя" беты. 
            Добавится к имени владельца в имени каталога /var/www/beta.<login>-<name>.<port>
        -y, --yandex-lib 
            если ключ присутствует -- в каталог yandex-lib в рабочей копии будет зачекаучен yandex-lib
        -d, --database <database name>
            (необязательно) база данных на которую должна смотреть бета
        -a, --arc
            использовать arc, вместо svn
        Запуск сервисов на бете:
        --infoblock
            http-инфоблок (сделает beta-ctl setup infoblock)
        --dna
            новый интерфейс Директа (сделает beta-ctl setup dna)
        --js-templater
            пока ничего не делает (js-templater запускается всегда), но в будущем js-templater можно будет включать/отключать (опцией --no-js-templater)
        --java-svn
            java-сервисы из рабочей копии Аркадии (сделает direct-mk java-init)
        --canvas
            поднимает canvas в докерных контейнерах (сделает beta-ctl setup canvas). Параметры в beta-ctl передаются через переменные окружения.
            Параметры:
                * TAG           - тег докерного образа (e.g. develop, master, pr-1234, 1.2.3)
                * VIDEO_SEARCH  - включать ли поиск по видео (требует для работы 5gb модель, по умолчанию выключен). Возможные значения: 0, 1.
                * BRANCH        - используемая ветка из репозитория.
                * BUILD         - собирать ли образ из локальной копии. Если тег не задан, создаётся образ с тегом canvas_beta_$beta_port.
                                  По умолчанию образ не собирается. Возможные значения: 0, 1.

        Можно пробросить параметры в команду запуска сервиса: например, --java-svn revision:2900000,branch:DIRECT-12345 запустит
        direct-mk java-init -- --revision=2900000 --branch=DIRECT-12345; --java-svn rp:599164 наложит патч, выложенный на ревью 599164

        Можно отключить запуск сервиса (если он включен по умолчанию) соответствующей опцией в префиксом --no-, например, --no-infoblock
        Пока никакие сервисы по умолчанию не включены.

=head1 COMMENTS

=cut

# $Id:$

use strict;
use Cwd;
use Getopt::Long;
use List::MoreUtils qw{ firstval }; 
use Time::HiRes qw//;
use JSON;

use BetaPorts;
use ProjectSpecific;

use Yandex::Shell;
use Yandex::MetricsLogSender;

use utf8;
use open ':locale';


#.......................................................................................
my $DT = ProjectSpecific::svn_url('trunk');
my $DB = ProjectSpecific::svn_url('branches');
my $yandex_lib = "svn+ssh://svn.yandex.ru/direct-utils/yandex-lib";
#.......................................................................................

# Опции запуска сервисов формируются из этого массива.
# Если добавляете/удаляете элементы массива, обновите документацию.
our @BETA_SERVICES = qw/canvas infoblock dna js-templater java-svn/;    # java-svn не соответствует сервису в beta-ctl и кодирует на самом деле и сервисы, и источник (рабочая копия Аркадии)
                                                                        # источник может быть и другим -- hg, ресурс из Sandbox
                                                                        # в будущем будет более тонкая настройка, java-svn может остаться shortcut'ом. А может и не остаться.
my $opt = parse_options();

if ($opt->{services}->{'java-svn'} && !defined $opt->{services}->{'java-svn'}->{app}) {
    die "You should specify java applications to run locally: use --java-svn app:APP1,app:APP2, where APP can be web, intapi, api5, canvas-backend";
}

# собираем название каталога
my $path;
if ($opt->{autobeta}) {
    $path=compose_autobeta_dirname(port => $opt->{port}, relative_svn_path => $opt->{relative_svn_path});
} else {
    $path="/var/www/" . ($ProjectSpecific::PROJECT eq "Directmod" ? "mod" : "beta") . ".$opt->{name}.$opt->{port}";
}

my $url = ProjectSpecific::beta_url($opt->{port});
print "path: $path\nurl:  $url\n\n";

my $start_time = Time::HiRes::time();
my $metrics = new Yandex::MetricsLogSender('direct-create-beta', {
    path => $path,
    opt => to_json({map {$_ => $opt->{$_}} grep {defined $opt->{$_}} keys %$opt}),
});

my $group = ProjectSpecific::get_project_data('autobeta_user'); # группа 'ppc', 'mod' или 'geocontext' в зависимости от проекта, совпадает с именем пользователя, для которого создаём автобеты
                                                                # TDOD наверное, лучше название группы сделать отдельной сущностью в ProjectSpecific
if (!$opt->{arc}) {
    my $svn_url = $opt->{svn_url};
    $svn_url .= '@' . $opt->{rev} if $opt->{rev};
    print "direct-svn-checkout $svn_url\n";
    yash_system "direct-svn-checkout", $svn_url, $path;  # если direct-svn-checkout завершится неудачно, то он удалит за собой $path
    `chgrp $group $path`;
    if (! $opt->{autobeta}) {
        `chmod 0775 $path`;
    }
} else {
    yash_system('mkdir', '-p', "$path/arcadia", "$path/store");
    print "arc --update\n";
    yash_system(qw(arc --update));
    $ENV{ARC_ALLOW_WRITE_TO_ALL} = 1;
    print "arc mount -m $path/arcadia -S $path/store --object-store /var/www/arc-object-store\n";   
    yash_system(qw(arc mount), '-m', "$path/arcadia", '-S', "$path/store", qw(--object-store /var/www/arc-object-store));
}

chdir $path or die "can't cd to $path: $!";

if (!$opt->{arc} && $opt->{co_yandex_lib}){
    print "svn co $yandex_lib yandex-lib\n";
    yash_system("svn co $yandex_lib yandex-lib");
}

my @beta_httpd_conf_opts = (
    '--user', $opt->{me},
);
if (!$opt->{arc} && $opt->{co_yandex_lib}) {
    push @beta_httpd_conf_opts, '--yandex-lib-local';
}

if ( $ProjectSpecific::PROJECT eq 'Geocontext' ) {
    yash_system 'maintenance/beta_httpd_conf.pl', '--init', @beta_httpd_conf_opts;
} else {
    # Direct, Directmod
    yash_system('direct-mk', 'beta_postcreate', '--', @beta_httpd_conf_opts);
    if ($ProjectSpecific::PROJECT eq 'Direct') {
        my $beta_ctl = '/usr/local/bin/beta-ctl';
        for my $s (keys %{$opt->{services}}) {
            my %params = %{ $opt->{services}->{$s} || {} };
            if ($s eq 'java-svn') {
                yash_system 'direct-mk', 'java-init', '--', (map { $_ eq 'no-sandbox' ? "--$_" : "--$_=$params{$_}" } keys %params);
            } elsif ($s eq 'js-templater') {
                # ничего не делаем
                # когда совсем избавимся от запуска js-templater через direct-mk (см. комментарии TODO в $DT/etc/quasi-make/Beta.pm), можно переместить beta-ctl setup js-templater сюда.
                # на первое время запускать его по умолчанию (добавить в %services_to_setup в parse_options), есть opt-out в виде опции --no-js-templater
            } elsif ($s eq 'canvas') {
                yash_system 'env', (map { "CANVAS_$_=$params{$_}" } keys %params), $beta_ctl, 'setup', $s;
            } else {
                yash_system $beta_ctl, 'setup', $s, (map { "--$_=$params{$_}" } keys %params);
            }
        }
    }
}

if ($opt->{database}) {
    yash_system 'direct-mk', $opt->{database};
}

$metrics->add(time => Time::HiRes::time()-$start_time);
$metrics->flush();

print "\n\npath: $path\nurl:  $url\n\n";

exit;
#.......................................................................................


=head2 parse_options
    
    возвращает ссылку на хеш с опциями из командной строки

=cut

sub parse_options
{
    my %O = (
        me => [getpwuid($<)]->[0],
        services => {},
    );

    # если сюда что-то добавится, нужно удалить строку "Пока никакие сервисы по умолчанию не включены." из pod-а
    my %services_to_setup;
    GetOptions (
        "h|help"       => \&usage,
        "p|port=s"     => \$O{port},
        "n|name=s"     => \$O{name},
        "b|branch=s"   => \$O{branch},
        "r|rev|revision=s"  => \$O{rev},
        "y|yandex-lib!"=> \$O{co_yandex_lib},
        "d|database=s"   => \$O{database},
        "g|get-port-only" => \$O{get_port_only},
        "a|arc"        => \$O{arc},
        "me=s"         => \$O{me},

        "autobeta"              => \$O{autobeta},
        "relative-svn-path=s"   => \$O{relative_svn_path},   # для автобеты используем --relative-svn-path вместо --branch для совместимости со старым воркером автобет (которому можно в принципе передать, например, $DRT/releases/release-...). Нужно будет привести к единообразию.

        (map {
                +"$_:s" => sub { $services_to_setup{$_[0]} = $_[1] },
                "no-$_" => sub { (my $name = $_[0]) =~ s/^no-//; delete $services_to_setup{$name} },
             } @BETA_SERVICES),
         
        "java:s" => sub { $services_to_setup{"java-svn"} = $_[1] },
    ) or die "can't parse options, stop\n";

    die "unbelievable! Don't know who am I" unless $O{me};

    die "autobeta creation is only available for robots" if $O{autobeta} && $O{me} ne ProjectSpecific::get_project_data('autobeta_user');
    die "relative svn path for autobeta is not given" if $O{autobeta} && !$O{relative_svn_path};
    if ($O{relative_svn_path}) {
        $O{relative_svn_path} =~ s{^(?!/)}{/};
    }

    die "--autobeta is not supported for arc" if $O{autobeta} && $O{arc};
    die "--relative-svn-path is not supported for arc" if $O{relative_svn_path} && $O{arc};

    die "--branch is currently not supported for arc" if $O{branch} && $O{arc};
    die "--revision is currently not supported for arc" if $O{revision} && $O{arc};
    die "--yandex-lib is currently not supported for arc" if $O{co_yandex_lib} && $O{arc};

    die "--branch, --relative-svn-path can't be used together, stop\n" if $O{branch} && $O{relative_svn_path};

    die "--port, --get-port-only can't be used together, stop\n" if $O{port} && $O{get_port_only};

    if ( !$O{port} ){
        # Если пользователь не указал порт -- выбираем автоматически, 
        # показываем и даем пару секунд на то, чтобы передумать и остановить скрипт
        $O{port} = eval{BetaPorts::get_free_beta(for => $O{autobeta} ? "autobeta" : $O{me})};
        if ( !$O{port} ) {
            die "Невозможно выбрать номер беты для пользователя $O{me}
Возможно, нет зарезервированных номеров, или они все уже заняты бетами

Детали:
$@\n";
        }
        if ( $O{get_port_only} ){
            print "$O{port}\n";
            exit 0;
        }

        print STDERR "Автоматически выбран номер беты $O{port}\n";
        sleep 2 unless $O{autobeta};    # создание автобеты из Табулы не умеем останавливать, так что в этом случае задержка не нужна
    }

    die "incorrect option --port $O{port}, stop" if $O{port} !~ /^\d{4,5}$/;

    my $reserved_ports = BetaPorts::get_reserved_ports(for => $O{autobeta} ? "autobeta" : $O{me});
    if( !grep { $_ == $O{port} } @$reserved_ports ){
        # пользователь пытается создать бету с незарегистрированным номером
        if ($O{autobeta}) {
            # для автобеты более уместно более короткое сообщение без инструкции
            die "port $O{port} is not a valid autobeta port\n";
        } else {
            die "

Бета номер $O{port} не зарезервирована за пользователем $O{me} на этом сервере.

Свой резерв можно посмотреть так:
  beta-ports.pl | grep $O{me}

Чтобы зарезервировать для себя новый диапазон портов, надо отредактировать файлы

  direct-utils/beta-ports/lib/BetaPorts.pm
  direct-utils/beta-ports/changes

, собрать пакет yandex-du-beta-ports и установить его по крайней мере на этот сервер (через beta-update).

За помощью обращайтесь к дежурному или другому разработчику из группы lena-san@.\n\n\n";
        }
    }

    if ( ! BetaPorts::is_port_free($O{port}) ){
        die "\n\nБета номер $O{port} уже существует, пожалуйста, выберите другой номер.\n\n";
    }

    if (!$O{arc}) {
        if ($O{autobeta} || $O{relative_svn_path}) {
            $O{svn_url} = ProjectSpecific::svn_url('root') . $O{relative_svn_path};
        } else {
            $O{svn_url} = $O{branch} && $O{branch} ne 'trunk' ? $DB."/".$O{branch} : $DT;
        }
        system("svn info $O{svn_url} > /dev/null") == 0 or die "wrong svn url $O{svn_url}";
    }

    if ( ! $O{name} ){
        $O{name} = $O{me};
    } elsif ( $O{name} !~ /^$O{me}/ ) {
        $O{name} = "$O{me}-$O{name}";
    }

    my %service_params;
    for my $s (keys %services_to_setup) {
        my $paramstr = $services_to_setup{$s};
        if ($paramstr) {
            my @params = split /,/, $paramstr;
            for my $p (@params) {
                my ($name, $value) = $p =~ /^([^:]+):(.+)$/;
                if (!defined $value) {
                    die "malformed param string for '$s', should be <name>:<value>[,<name>:<value>,...]\n";
                } else {
                    if (!defined $service_params{$s}->{$name}) {
                        $service_params{$s}->{$name} = $value; 
                    } else {
                        $service_params{$s}->{$name} .= ",$value"; 
                    }
                }
            }
        } else {
            $service_params{$s} = {};
        }
    }
    $O{services}->{$_} = $service_params{$_} for keys %service_params;
    if ($O{services} && %{ $O{services} } ) {
        print STDERR "Запустить с сервисами: " . join(', ', keys %{$O{services}}) . "\n";
    }

    return \%O;
}

=head2 compose_autobeta_dirname

    Генерирует имя директории автобеты.
    Скопировано из direct-utils/auto-betas/worker/bin/autobeta-worker.pl 

=cut

sub compose_autobeta_dirname {
    my %O = @_;

    return undef if !$O{relative_svn_path} || $O{port} !~ /^\d+$/;
    # сокращаем путь для удобства и совместимости с автобетами из старого репизтория
    $O{relative_svn_path} =~ s!^/branches/direct/perl!/branches!;
    $O{relative_svn_path} =~ s!^/trunk/arcadia/direct/perl!/trunk!;

    (my $branch = $O{relative_svn_path}) =~ s![/\.]!-!g;

    my $dirname;
    if ($ProjectSpecific::PROJECT eq 'Directmod') {
        $dirname = "$BetaPorts::BETAS_BASE_DIR/mod.auto$branch.$O{port}";
    } else {
        $dirname = "$BetaPorts::BETAS_BASE_DIR/beta.auto$branch.$O{port}";
    }
    return $dirname;
}



=head2 usage

    Печатает usage-сообщение

=cut

sub usage {
    system("podselect -section NAME -section SYNOPSIS -section DESCRIPTION $0 | pod2text-utf8");
    exit;
}

