root/lang/perl/Archer/lib/Archer/Shell.pm @ 3705

Revision 3705, 7.2 kB (checked in by tokuhirom, 5 years ago)

r3658@mnk (orig r308): tokuhiro | 2007-03-05 21:18:22 -0800
I want to use onscripter!


Line 
1package Archer::Shell;
2use strict;
3use warnings;
4use Net::SSH;
5use Term::ReadLine;
6use POSIX;
7use File::HomeDir;
8use Path::Class;
9
10sub new {
11    my ( $class, $args ) = @_;
12
13    return bless {%$args}, $class;
14}
15
16sub run_loop {
17    my ( $self, ) = @_;
18
19    # initialize parallel manager.
20    $self->{parallel} = $self->{context}->{config}->{global}->{parallel}
21        || 'Archer::Parallel::ForkManager';
22    $self->{parallel}->use or die $@;
23
24    # initialize readline library.
25    my $term = Term::ReadLine->new('Archer');
26
27    my $HISTFILE = file( File::HomeDir->my_home, "/.archer_shell_history" );
28    my $HISTSIZE = 256;
29
30   # this won't work with Term::ReadLine::Perl
31   # If there is Term::ReadLine::Gnu, be sure to do : export "PERL_RL=Gnu o=0"
32    eval { $term->stifle_history($HISTSIZE); };
33
34    if (@!) {
35        $self->{context}
36            ->log( 'debug' => "You will need Term::ReadLine::Gnu" );
37    }
38    else {
39        if ( -f $HISTFILE ) {
40            $term->ReadHistory($HISTFILE)
41                or $self->{context}
42                ->log( 'warn' => "cannot read history file: $!" );
43        }
44    }
45
46    while ( defined( my $line = $term->readline('archer> ') ) ) {
47        next if $line =~ /^\s*$/;
48        $self->catch_run($line);
49    }
50
51    print "\n";
52
53    eval { $term->WriteHistory($HISTFILE); };
54    if (@!) {
55        $self->{context}
56            ->log( 'debug' => "perlsh: cannot write history file: $!" );
57    }
58
59}
60
61sub catch_run {
62    my ( $self, $cmd ) = @_;
63
64    if ( $cmd =~ /^on\s+/ ) {
65        if ( $cmd =~ /^on\s(.*)\sdo\s(.*)$/ ) {
66            $self->process_host( $1, $2 );
67        }
68        else {
69            print "[WARNING] error in your syntax, see help\n";
70        }
71    }
72    elsif ( $cmd =~ /^with\s+/ ) {
73        if ( $cmd =~ /^with\s(.*)\sdo\s(.*)$/ ) {
74            $self->process_role( $1, $2 );
75        }
76        else {
77            print "[WARNING] error in your syntax, see help\n";
78        }
79    }
80    elsif ( $cmd =~ /^help/ ) {
81        $self->help();
82    }
83    elsif ( $cmd =~ /^(quit|exit)/ ) {
84        print "bye bye\n";
85        exit;
86    }
87    elsif ( $cmd =~ /^!/ ) {
88        if ( $cmd =~ /^!(\w+)\s?(on|with)?\s?(.*)?$/ ) {
89            my $task     = $1;
90            my $action   = $2;
91            my $machines = $3;
92            if (   defined $action
93                && defined $machines
94                && length($machines) < 1 )
95            {
96                return print "[WARNING] error in your syntax, see help\n";
97            }
98            my $executed = 0;
99            for my $plugin ( @{ $self->{config}->{tasks}->{process} } ) {
100                next if $plugin->{name} ne $task;
101                $executed = 1;
102                if ( defined $action ) {
103                    if ( $action eq "on" ) {
104                        my @hosts = split " ", $machines;
105                        for my $host (@hosts) {
106                            $self->process_task( $plugin, $host );
107                        }
108                    }
109                    else {
110                        my @roles = split " ", $machines;
111                        for my $role (@roles) {
112                            for my $host ( @{ $self->{servers}->{$role} } ) {
113                                $self->process_task( $plugin, $host );
114                            }
115                        }
116                    }
117                }
118                else {
119                    for my $host (
120                        @{ $self->{servers}->{ $plugin->{config}->{role} } } )
121                    {
122                        $self->process_task( $plugin, $host );
123                    }
124                }
125            }
126            if ( $executed == 0 ) {
127                print "[WARNING] unable to find the requested task: $task\n";
128            }
129        }
130        else {
131            print "[WARNING] error in your syntax\n";
132        }
133    }
134    else {
135        $self->process_command($cmd);
136    }
137}
138
139sub process_host {
140    my ( $self, $hosts, $cmd ) = @_;
141
142    my @hosts = split /\s/, $hosts;
143
144    # check if hosts are in our config.
145    for my $host (@hosts) {
146        for my $role ( keys %{ $self->{servers} } ) {
147            @hosts = grep ( /$host/, @{ $self->{servers}->{$role} } );
148        }
149    }
150
151    if (@hosts) {
152        $self->process_command( $cmd, \@hosts );
153    }
154}
155
156sub process_role {
157    my ( $self, $roles, $cmd ) = @_;
158
159    my @roles      = split /\s/, $roles;
160    my @hosts      = ();
161    my @inexistant = ();
162    for my $role (@roles) {
163        if ( !defined $self->{servers}->{$role} ) {
164            push( @inexistant, $role );
165            next;
166        }
167        for my $host ( @{ $self->{servers}->{$role} } ) {
168            push @hosts, $host;
169        }
170    }
171    if (@inexistant) {
172        print "[WARNING] inexisting role(s) for "
173            . join( ' ', @inexistant ) . "\n";
174    }
175    $self->process_command( $cmd, \@hosts );
176}
177
178sub process_command {
179    my ( $self, $cmd, $hosts ) = @_;
180    my $manager = $self->{parallel}->new;
181
182    if ( !$hosts ) {
183        for my $role (
184            keys
185            %{ $self->{config}->{projects}->{ $self->{context}->{project} } }
186            )
187        {
188            for my $host (
189                @{  $self->{config}->{projects}
190                        ->{ $self->{context}->{project} }->{$role}
191                }
192                )
193            {
194                push @{$hosts}, $host;
195            }
196        }
197    }
198
199    $manager->run(
200        {   elems    => $hosts,
201            callback => sub {
202                my $server = shift;
203                $self->callback( $server, $cmd );
204            },
205            num => $self->{context}->{parallel_num},
206        }
207    );
208}
209
210sub process_task {
211    my ( $self, $plugin, $host ) = @_;
212    my $class = "Archer::Plugin::$plugin->{module}";
213    $class->use or die $@;
214    $class->new(
215        {   config  => $plugin->{config},
216            project => $self->{context}->{project},
217            server  => $host
218        }
219    )->run( $self->{context} );
220}
221
222sub callback {
223    my ( $self, $server, $cmd ) = @_;
224
225    Net::SSH::sshopen2( $server, *READER, *WRITER, $cmd );
226    while (<READER>) {
227        chomp;
228        print "[$server] $_\n";
229    }
230    close READER;
231    close WRITER;
232}
233
234sub help {
235    my ($self) = @_;
236    my $help = <<HELP;
237 To quit, just type quit, exit, or press ctrl-D.
238 This shell is still experimental.
239
240 execute a command on all servers, just type it directly, like:
241
242archer> ping
243
244 To execute a command on a specific set of servers, specify an 'on' clause.
245 Note that if you specify more than one host name, they must be
246 space-delimited.
247
248archer> on app1.foo.com app2.foo.com do ping
249
250 To execute a command on all servers matching a set of roles:
251
252archer> with web db do ping
253
254 To execute an Archer task, prefix the name with a bang, by default it
255 will be executed only on the role applyed to this task.
256
257archer> !restart
258
259 To execute an Archer task on a specific set of servers:
260
261archer> !restart on app1.foo.com app2.foo.com
262
263 To execute an Archer task on all servers matching a set of roles:
264
265archer> !restart with web db
266
267HELP
268    print $help;
269}
270
2711;
272__END__
273
274=head1 NAME
275
276Archer::Shell - display shell prompt for remote servers.
277
278=head1 DESCRIPTION
279
280Shell prompt for remote servers.
281
282=head1 FILES
283
284    ~/.archer_shell_history
285
286=head1 AUTHORS
287
288    Gosuke Miyashita
289    Tokuhiro Matsuno
290
291=head1 SEE ALSO
292
293L<Term::ReadLine>
294
295=cut
Note: See TracBrowser for help on using the browser.