package Tak::Script; use Getopt::Long qw(GetOptionsFromArray :config posix_defaults bundling); use Config::Settings; use IO::Handle; use Tak::Client::Router; use Tak::Client::RemoteRouter; use Tak::Router; use Log::Contextual qw(:log); use Log::Contextual::SimpleLogger; use Moo; with 'Tak::Role::ScriptActions'; has options => (is => 'ro', required => 1); has env => (is => 'ro', required => 1); has log_level => (is => 'rw'); has stdin => (is => 'lazy'); has stdout => (is => 'lazy'); has stderr => (is => 'lazy'); sub _build_stdin { shift->env->{stdin} } sub _build_stdout { shift->env->{stdout} } sub _build_stderr { shift->env->{stderr} } has config => (is => 'lazy'); sub _build_config { my ($self) = @_; my $file = $self->options->{config} || '.tak/default.conf'; if (-e $file) { Config::Settings->new->parse_file($file); } else { {}; } } has local_client => (is => 'lazy'); sub _build_local_client { my ($self) = @_; Tak::Client::Router->new(service => Tak::Router->new); } sub BUILD { shift->setup_logger; } sub setup_logger { my ($self) = @_; my @level_names = qw(fatal error warn info debug trace); my $options = $self->options; my $level = 2 + ($options->{verbose}||0) - ($options->{quiet}||0); my $upto = $level_names[$level]; $self->log_level($upto); Log::Contextual::set_logger( Log::Contextual::SimpleLogger->new({ levels_upto => $upto, coderef => sub { print STDERR ' ', @_ }, }) ); } sub _parse_options { my ($self, $string, $argv) = @_; my @spec = split ';', $string; my %opt; GetOptionsFromArray($argv, \%opt, @spec); return \%opt; } sub run { my ($self) = @_; my @argv = @{$self->env->{argv}}; unless (@argv && $argv[0]) { return $self->local_help; } my $cmd = shift(@argv); $cmd =~ s/-/_/g; if (my $code = $self->can("local_$cmd")) { return $self->_run($cmd, $code, @argv); } elsif ($code = $self->can("each_$cmd")) { return $self->_run_each($cmd, $code, @argv); } elsif ($code = $self->can("every_$cmd")) { return $self->_run_every($cmd, $code, @argv); } $self->stderr->print("No such command: ${cmd}\n"); return $self->local_help; } sub _load_file { my ($self, $file) = @_; $self->_load_file_in_my_script($file); } sub local_help { my ($self) = @_; $self->stderr->print("Help unimplemented\n"); } sub _maybe_parse_options { my ($self, $code, $argv) = @_; if (my $proto = prototype($code)) { $self->_parse_options($proto, $argv); } else { {}; } } sub _run_local { my ($self, $cmd, $code, @argv) = @_; my $opt = $self->_maybe_parse_options($code, \@argv); $self->$code($opt, @argv); } sub _run_each { my ($self, $cmd, $code, @argv) = @_; my @targets = $self->_host_list_for($cmd); unless (@targets) { $self->stderr->print("No targets for ${cmd}\n"); return; } my $opt = $self->_maybe_parse_options($code, \@argv); $self->local_client->ensure(connector => 'Tak::ConnectorService'); foreach my $target (@targets) { my $remote = $self->_connection_to($target); $self->$code($remote, $opt, @argv); } } sub _run_every { my ($self, $cmd, $code, @argv) = @_; my @targets = $self->_host_list_for($cmd); unless (@targets) { $self->stderr->print("No targets for ${cmd}\n"); return; } my $opt = $self->_maybe_parse_options($code, \@argv); $self->local_client->ensure(connector => 'Tak::ConnectorService'); my @remotes = map $self->_connection_to($_), @targets; $self->$code(\@remotes, $opt, @argv); } sub _host_list_for { my ($self, $command) = @_; my @host_spec = map split(' ', $_), @{$self->options->{host}}; unshift(@host_spec, '-') if $self->options->{local}; return @host_spec; } sub _connection_to { my ($self, $target) = @_; log_debug { "Connecting to ${target}" }; my @path = $self->local_client->do( connector => create => $target, log_level => $self->log_level ); my ($local, $remote) = map $self->local_client->curry(connector => connection => @path => $_), qw(local remote); $local->ensure(module_sender => 'Tak::ModuleSender'); $remote->ensure( module_loader => 'Tak::ModuleLoader', expose => { module_sender => [ 'remote', 'module_sender' ] } ); $remote->do(module_loader => 'enable'); log_debug { "Setup connection to ${target}" }; Tak::Client::RemoteRouter->new( %$remote, host => $target ); } 1;