b932feaad84e1fe20aa62d263b37e4e300e37bda
[scpubgit/Tak.git] / lib / Tak / Script.pm
1 package Tak::Script;
2
3 use Getopt::Long qw(GetOptionsFromArray :config posix_defaults bundling);
4 use Config::Settings;
5 use IO::Handle;
6 use Tak::Client::Router;
7 use Tak::Client::RemoteRouter;
8 use Tak::Router;
9 use Log::Contextual qw(:log);
10 use Log::Contextual::SimpleLogger;
11 use Moo;
12
13 has options => (is => 'ro', required => 1);
14 has env => (is => 'ro', required => 1);
15
16 has log_level => (is => 'rw');
17
18 has stdin => (is => 'lazy');
19 has stdout => (is => 'lazy');
20 has stderr => (is => 'lazy');
21
22 sub _build_stdin { shift->env->{stdin} }
23 sub _build_stdout { shift->env->{stdout} }
24 sub _build_stderr { shift->env->{stderr} }
25
26 has config => (is => 'lazy');
27
28 sub _build_config {
29   my ($self) = @_;
30   my $file = $self->options->{config} || '.tak/default.conf';
31   if (-e $file) {
32     Config::Settings->new->parse_file($file);
33   } else {
34     {};
35   }
36 }
37
38 has local_client => (is => 'lazy');
39
40 sub _build_local_client {
41   my ($self) = @_;
42   Tak::Client::Router->new(service => Tak::Router->new);
43 }
44
45 sub BUILD {
46   shift->setup_logger;
47 }
48
49 sub setup_logger {
50   my ($self) = @_;
51   my @level_names = qw(fatal error warn info debug trace);
52   my $options = $self->options;
53   my $level = 2 + ($options->{verbose}||0) - ($options->{quiet}||0);
54   my $upto = $level_names[$level];
55   $self->log_level($upto);
56   Log::Contextual::set_logger(
57     Log::Contextual::SimpleLogger->new({
58       levels_upto => $upto,
59       coderef => sub { print STDERR '<local> ', @_ },
60     })
61   );
62 }
63
64 sub _parse_options {
65   my ($self, $string, $argv) = @_;
66   my @spec = split ';', $string;
67   my %opt;
68   GetOptionsFromArray($argv, \%opt, @spec);
69   return \%opt;
70 }
71
72 sub run {
73   my ($self) = @_;
74   my @argv = @{$self->env->{argv}};
75   unless (@argv && $argv[0]) {
76     return $self->local_help;
77   }
78   my $cmd = shift(@argv);
79   if (my $code = $self->can("local_$cmd")) {
80     return $self->_run($cmd, $code, @argv);
81   } elsif ($code = $self->can("each_$cmd")) {
82     return $self->_run_each($cmd, $code, @argv);
83   }
84   $self->stderr->print("No such command: ${cmd}\n");
85   return $self->local_help;
86 }
87
88 sub _load_file {
89   my ($self, $file) = @_;
90   $self->_load_file_in_my_script($file);
91 }
92
93 sub local_help {
94   my ($self) = @_;
95   $self->stderr->print("Help unimplemented\n");
96 }
97
98 sub _maybe_parse_options {
99   my ($self, $code, $argv) = @_;
100   if (my $proto = prototype($code)) {
101     $self->_parse_options($proto, $argv);
102   } else {
103     {};
104   }
105 }
106
107 sub _run_local {
108   my ($self, $cmd, $code, @argv) = @_;
109   my $opt = $self->_maybe_parse_options($code, \@argv);
110   $self->$code($opt, @argv);
111 }
112
113 sub _run_each {
114   my ($self, $cmd, $code, @argv) = @_;
115   my @targets = $self->_host_list_for($cmd);
116   unless (@targets) {
117     $self->stderr->print("No targets for ${cmd}\n");
118     return;
119   }
120   my $opt = $self->_maybe_parse_options($code, \@argv);
121   if (my $prepare = $self->can("prepare_$cmd")) {
122     $self->$prepare($opt, @argv);
123   }
124   $self->local_client->ensure(connector => 'Tak::ConnectorService');
125   foreach my $target (@targets) {
126     my $remote = $self->_connection_to($target);
127     $self->$code($remote, $opt, @argv);
128   }
129 }
130
131 sub _host_list_for {
132   my ($self, $command) = @_;
133   my @host_spec = map split(' ', $_), @{$self->options->{host}};
134 }
135
136 sub _connection_to {
137   my ($self, $target) = @_;
138   log_debug { "Connecting to ${target}" };
139   my @path = $self->local_client->do(
140     connector => create => $target, log_level => $self->log_level
141   );
142   my ($local, $remote) =
143     map $self->local_client->curry(connector => connection => @path => $_),
144       qw(local remote);
145   $local->ensure(module_sender => 'Tak::ModuleSender');
146   $remote->ensure(
147     module_loader => 'Tak::ModuleLoader',
148     expose => { module_sender => [ 'remote', 'module_sender' ] }
149   );
150   $remote->do(module_loader => 'enable');
151   log_debug { "Setup connection to ${target}" };
152   Tak::Client::RemoteRouter->new(
153     %$remote, host => $target
154   );
155 }
156
157 1;