00208d2b05b317bb5812080445b20eb5c1ae2c81
[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   } elsif ($code = $self->can("every_$cmd")) {
84     return $self->_run_every($cmd, $code, @argv);
85   }
86   $self->stderr->print("No such command: ${cmd}\n");
87   return $self->local_help;
88 }
89
90 sub _load_file {
91   my ($self, $file) = @_;
92   $self->_load_file_in_my_script($file);
93 }
94
95 sub local_help {
96   my ($self) = @_;
97   $self->stderr->print("Help unimplemented\n");
98 }
99
100 sub _maybe_parse_options {
101   my ($self, $code, $argv) = @_;
102   if (my $proto = prototype($code)) {
103     $self->_parse_options($proto, $argv);
104   } else {
105     {};
106   }
107 }
108
109 sub _run_local {
110   my ($self, $cmd, $code, @argv) = @_;
111   my $opt = $self->_maybe_parse_options($code, \@argv);
112   $self->$code($opt, @argv);
113 }
114
115 sub _run_each {
116   my ($self, $cmd, $code, @argv) = @_;
117   my @targets = $self->_host_list_for($cmd);
118   unless (@targets) {
119     $self->stderr->print("No targets for ${cmd}\n");
120     return;
121   }
122   my $opt = $self->_maybe_parse_options($code, \@argv);
123   $self->local_client->ensure(connector => 'Tak::ConnectorService');
124   foreach my $target (@targets) {
125     my $remote = $self->_connection_to($target);
126     $self->$code($remote, $opt, @argv);
127   }
128 }
129
130 sub _run_every {
131   my ($self, $cmd, $code, @argv) = @_;
132   my @targets = $self->_host_list_for($cmd);
133   unless (@targets) {
134     $self->stderr->print("No targets for ${cmd}\n");
135     return;
136   }
137   my $opt = $self->_maybe_parse_options($code, \@argv);
138   $self->local_client->ensure(connector => 'Tak::ConnectorService');
139   my @remotes = map $self->_connection_to($_), @targets;
140   $self->$code(\@remotes, $opt, @argv);
141 }
142
143 sub _host_list_for {
144   my ($self, $command) = @_;
145   my @host_spec = map split(' ', $_), @{$self->options->{host}};
146 }
147
148 sub _connection_to {
149   my ($self, $target) = @_;
150   log_debug { "Connecting to ${target}" };
151   my @path = $self->local_client->do(
152     connector => create => $target, log_level => $self->log_level
153   );
154   my ($local, $remote) =
155     map $self->local_client->curry(connector => connection => @path => $_),
156       qw(local remote);
157   $local->ensure(module_sender => 'Tak::ModuleSender');
158   $remote->ensure(
159     module_loader => 'Tak::ModuleLoader',
160     expose => { module_sender => [ 'remote', 'module_sender' ] }
161   );
162   $remote->do(module_loader => 'enable');
163   log_debug { "Setup connection to ${target}" };
164   Tak::Client::RemoteRouter->new(
165     %$remote, host => $target
166   );
167 }
168
169 1;