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