line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::EC2Cssh; |
2
|
|
|
|
|
|
|
$App::EC2Cssh::VERSION = '0.006'; |
3
|
1
|
|
|
1
|
|
1011
|
use Moose; |
|
1
|
|
|
|
|
352934
|
|
|
1
|
|
|
|
|
6
|
|
4
|
|
|
|
|
|
|
|
5
|
|
|
|
|
|
|
=head1 NAME |
6
|
|
|
|
|
|
|
|
7
|
|
|
|
|
|
|
App::EC2Cssh - Package for ec2-cssh CLI application |
8
|
|
|
|
|
|
|
|
9
|
|
|
|
|
|
|
=head1 SYNOSPSIS |
10
|
|
|
|
|
|
|
|
11
|
|
|
|
|
|
|
See L<ec2-cssh> |
12
|
|
|
|
|
|
|
|
13
|
|
|
|
|
|
|
=cut |
14
|
|
|
|
|
|
|
|
15
|
1
|
|
|
1
|
|
5407
|
use autodie qw/:all/; |
|
1
|
|
|
|
|
11124
|
|
|
1
|
|
|
|
|
4
|
|
16
|
1
|
|
|
1
|
|
15613
|
use Cwd; |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
49
|
|
17
|
1
|
|
|
1
|
|
4
|
use File::Spec; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
18
|
|
18
|
1
|
|
|
1
|
|
663
|
use IO::Socket::SSL; |
|
1
|
|
|
|
|
54077
|
|
|
1
|
|
|
|
|
8
|
|
19
|
1
|
|
|
1
|
|
965
|
use Net::Amazon::EC2; |
|
1
|
|
|
|
|
1440340
|
|
|
1
|
|
|
|
|
39
|
|
20
|
1
|
|
|
1
|
|
567
|
use Safe; |
|
1
|
|
|
|
|
27135
|
|
|
1
|
|
|
|
|
48
|
|
21
|
1
|
|
|
1
|
|
544
|
use Text::Template; |
|
1
|
|
|
|
|
2017
|
|
|
1
|
|
|
|
|
39
|
|
22
|
|
|
|
|
|
|
|
23
|
1
|
|
|
1
|
|
396
|
use IO::Pipe; |
|
1
|
|
|
|
|
813
|
|
|
1
|
|
|
|
|
26
|
|
24
|
1
|
|
|
1
|
|
813
|
use AnyEvent; |
|
1
|
|
|
|
|
3765
|
|
|
1
|
|
|
|
|
33
|
|
25
|
|
|
|
|
|
|
|
26
|
1
|
|
|
1
|
|
401
|
use Log::Any qw/$log/; |
|
1
|
|
|
|
|
5711
|
|
|
1
|
|
|
|
|
6
|
|
27
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# Config stuff. |
29
|
|
|
|
|
|
|
has 'config' => ( is => 'ro', isa => 'HashRef', lazy_build => 1); |
30
|
|
|
|
|
|
|
has 'config_file' => ( is => 'ro' , isa => 'Maybe[Str]'); |
31
|
|
|
|
|
|
|
has 'config_files' => ( is => 'ro' , isa => 'ArrayRef[Str]' , lazy_build => 1); |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
# Run options stuff |
34
|
|
|
|
|
|
|
has 'set' => ( is => 'ro' , isa => 'Str', required => 1 ); |
35
|
|
|
|
|
|
|
has 'demux_command' => ( is => 'ro', isa => 'Maybe[Str]', required => 0, predicate => 'has_demux_command' ); |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
# Operational stuff. |
38
|
|
|
|
|
|
|
has 'ec2' => ( is => 'ro', isa => 'Net::Amazon::EC2', lazy_build => 1); |
39
|
|
|
|
|
|
|
|
40
|
|
|
|
|
|
|
|
41
|
|
|
|
|
|
|
sub _build_config{ |
42
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
43
|
0
|
|
|
|
|
|
my $config = {}; |
44
|
0
|
|
|
|
|
|
foreach my $file (reverse @{$self->config_files()} ){ |
|
0
|
|
|
|
|
|
|
45
|
0
|
|
|
|
|
|
$log->info("Loading $file.."); |
46
|
0
|
|
|
|
|
|
my $file_config = do $file; |
47
|
|
|
|
|
|
|
|
48
|
0
|
0
|
|
|
|
|
my $ec2_config = { %{ $config->{ec2_config} || {} } , %{ $file_config->{ec2_config} || {} } }; |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
49
|
0
|
0
|
|
|
|
|
my $ec2_sets = { %{ $config->{ec2_sets} || {} } , %{ $file_config->{ec2_sets} || {} } }; |
|
0
|
0
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
50
|
0
|
|
|
|
|
|
$config = { %{$config} , %{$file_config} , 'ec2_config' => $ec2_config , ec2_sets => $ec2_sets }; |
|
0
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
51
|
|
|
|
|
|
|
} |
52
|
|
|
|
|
|
|
|
53
|
0
|
|
|
|
|
|
$log->info("Available sets: " .( join(', ', sort keys %{$config->{ec2_sets}}))); |
|
0
|
|
|
|
|
|
|
54
|
0
|
|
|
|
|
|
return $config; |
55
|
|
|
|
|
|
|
} |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
sub _build_config_files{ |
58
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
59
|
|
|
|
|
|
|
my @candidates = ( |
60
|
|
|
|
|
|
|
( $self->config_file() ? $self->config_file() : () ), |
61
|
|
|
|
|
|
|
File::Spec->catfile( getcwd() , '.ec2cssh.conf' ), |
62
|
0
|
0
|
|
|
|
|
File::Spec->catfile( $ENV{HOME} , '.ec2cssh.conf' ), |
63
|
|
|
|
|
|
|
File::Spec->catfile( '/' , 'etc' , 'ec2ssh.conf' ) |
64
|
|
|
|
|
|
|
); |
65
|
0
|
|
|
|
|
|
my @files = (); |
66
|
0
|
|
|
|
|
|
foreach my $candidate ( @candidates ){ |
67
|
0
|
0
|
|
|
|
|
if( -r $candidate ){ |
68
|
0
|
|
|
|
|
|
$log->info("Found config file '$candidate'"); |
69
|
0
|
|
|
|
|
|
push @files , $candidate; |
70
|
|
|
|
|
|
|
} |
71
|
|
|
|
|
|
|
} |
72
|
0
|
0
|
|
|
|
|
unless( @files ){ |
73
|
0
|
|
|
|
|
|
die "Cannot find any config files amongst ".join(', ' , @candidates )."\n"; |
74
|
|
|
|
|
|
|
} |
75
|
0
|
|
|
|
|
|
return \@files; |
76
|
|
|
|
|
|
|
} |
77
|
|
|
|
|
|
|
|
78
|
|
|
|
|
|
|
sub _build_ec2{ |
79
|
0
|
|
|
0
|
|
|
my ($self) = @_; |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
# Hack so we never verify Amazon's host. Whilst still keeping HTTPS |
82
|
0
|
|
|
0
|
|
|
IO::Socket::SSL::set_defaults( SSL_verify_callback => sub{ return 1; } ); |
|
0
|
|
|
|
|
|
|
83
|
0
|
0
|
|
|
|
|
my $ec2 = Net::Amazon::EC2->new({ %{ $self->config()->{ec2_config} || die "No ec2_config in config\n" } , ssl => 1 } ); |
|
0
|
|
|
|
|
|
|
84
|
0
|
|
|
|
|
|
return $ec2; |
85
|
|
|
|
|
|
|
} |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
sub main{ |
88
|
0
|
|
|
0
|
0
|
|
my ($self) = @_; |
89
|
|
|
|
|
|
|
|
90
|
0
|
|
|
|
|
|
my @hosts; |
91
|
0
|
|
|
|
|
|
my %hostnames = (); |
92
|
0
|
|
|
|
|
|
$log->info("Listing instances for set='".$self->set()."'"); |
93
|
|
|
|
|
|
|
|
94
|
0
|
|
|
|
|
|
my $set_config = {}; |
95
|
0
|
0
|
|
|
|
|
if( $self->set() ){ |
96
|
0
|
|
0
|
|
|
|
$set_config = $self->config()->{ec2_sets}->{$self->set()} || die "No ec2_set '".$self->set()."' defined in config\n"; |
97
|
|
|
|
|
|
|
} |
98
|
|
|
|
|
|
|
|
99
|
0
|
|
|
|
|
|
my $reservation_infos = $self->ec2->describe_instances( %{ $set_config } ) ; |
|
0
|
|
|
|
|
|
|
100
|
0
|
|
|
|
|
|
foreach my $ri ( @$reservation_infos ){ |
101
|
0
|
|
|
|
|
|
my $instances = $ri->instances_set(); |
102
|
0
|
|
|
|
|
|
foreach my $instance ( @$instances ){ |
103
|
0
|
|
|
|
|
|
my $host = $instance->dns_name(); |
104
|
0
|
0
|
|
|
|
|
unless( $host ){ |
105
|
0
|
|
|
|
|
|
$log->warn("Instance ".$instance->instance_id()." does not have a dns_name. Skipping"); |
106
|
0
|
|
|
|
|
|
next; |
107
|
|
|
|
|
|
|
} |
108
|
0
|
|
|
|
|
|
$log->debug("Adding host $host"); |
109
|
0
|
|
|
|
|
|
push @hosts , $host; |
110
|
|
|
|
|
|
|
|
111
|
0
|
0
|
|
|
|
|
if( my $tagset = $instance->tag_set() ){ |
112
|
0
|
|
|
|
|
|
foreach my $tag ( @$tagset ){ |
113
|
0
|
|
0
|
|
|
|
$log->trace("Host has tag: ".$tag->key().':'.( $tag->value() // 'UNDEF' )); |
114
|
0
|
0
|
|
|
|
|
if( $tag->key() eq 'Name' ){ |
115
|
0
|
|
|
|
|
|
$log->debug("Host $host name is ".$tag->value()); |
116
|
0
|
|
|
|
|
|
$hostnames{$host} = $tag->value(); |
117
|
|
|
|
|
|
|
} |
118
|
|
|
|
|
|
|
} |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
} |
121
|
|
|
|
|
|
|
} |
122
|
|
|
|
|
|
|
|
123
|
0
|
|
|
|
|
|
$log->info("Got ".scalar( @hosts )." hosts"); |
124
|
0
|
0
|
|
|
|
|
if( $self->has_demux_command() ){ |
125
|
0
|
|
|
|
|
|
return $self->do_demux_command( \@hosts , \%hostnames ); |
126
|
|
|
|
|
|
|
} |
127
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
# No demux command, just carry on using the configured command for multiple hosts. |
129
|
|
|
|
|
|
|
my $tmpl = Text::Template->new( TYPE => 'STRING', |
130
|
0
|
|
0
|
|
|
|
SOURCE => $self->config()->{command} || die "Missing command in config\n" |
131
|
|
|
|
|
|
|
); |
132
|
0
|
0
|
|
|
|
|
unless( $tmpl->compile() ){ |
133
|
0
|
|
|
|
|
|
die "Cannot compile template from '".$self->config()->{command}."' ERROR:".$Text::Template::ERROR."\n"; |
134
|
|
|
|
|
|
|
} |
135
|
|
|
|
|
|
|
|
136
|
0
|
|
|
|
|
|
my $command = $tmpl->fill_in( SAFE => Safe->new(), |
137
|
|
|
|
|
|
|
HASH => { |
138
|
|
|
|
|
|
|
hosts => \@hosts, |
139
|
|
|
|
|
|
|
hostnames => \%hostnames, |
140
|
|
|
|
|
|
|
} |
141
|
|
|
|
|
|
|
); |
142
|
0
|
|
|
|
|
|
$log->info("Will do '".substr($command, 0, 80)."..'"); |
143
|
0
|
0
|
|
|
|
|
if( $log->is_debug() ){ |
144
|
0
|
|
|
|
|
|
$log->debug($command); |
145
|
|
|
|
|
|
|
} |
146
|
0
|
|
|
|
|
|
my $sys_return = system( $command ); |
147
|
0
|
|
|
|
|
|
$log->info("Done (returned $sys_return)"); |
148
|
0
|
|
|
|
|
|
return $sys_return; |
149
|
|
|
|
|
|
|
} |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
$| = 1; |
152
|
|
|
|
|
|
|
|
153
|
|
|
|
|
|
|
sub do_demux_command{ |
154
|
0
|
|
|
0
|
0
|
|
my ($self, $hosts, $hostnames) = @_; |
155
|
|
|
|
|
|
|
|
156
|
0
|
|
|
|
|
|
$log->info("Will do ".$self->demux_command()." on each of the hosts"); |
157
|
|
|
|
|
|
|
|
158
|
0
|
|
|
|
|
|
my $tmpl = Text::Template->new( TYPE => 'STRING', |
159
|
|
|
|
|
|
|
SOURCE => $self->demux_command() ); |
160
|
|
|
|
|
|
|
|
161
|
0
|
|
|
|
|
|
my @finished = (); |
162
|
0
|
|
|
|
|
|
foreach my $host ( @$hosts ){ |
163
|
0
|
|
|
|
|
|
my $hostname = $hostnames->{$host}; |
164
|
0
|
|
|
|
|
|
my $command = $tmpl->fill_in( HASH => { host => $host , hostname => $hostname } ); |
165
|
0
|
|
|
|
|
|
$log->debug("Will do ".$command); |
166
|
0
|
|
|
|
|
|
my $io_h = IO::Pipe->new()->reader( $command ); |
167
|
0
|
|
|
|
|
|
my $w; |
168
|
0
|
|
0
|
|
|
|
my $toprint = $hostname || $host; |
169
|
0
|
|
|
|
|
|
my $finished = AnyEvent->condvar(); |
170
|
0
|
|
|
|
|
|
push @finished , $finished; |
171
|
|
|
|
|
|
|
$w = AnyEvent->io( fh => $io_h, |
172
|
|
|
|
|
|
|
poll => 'r', |
173
|
|
|
|
|
|
|
cb => sub{ |
174
|
0
|
|
|
0
|
|
|
my $line = <$io_h>; |
175
|
0
|
0
|
|
|
|
|
unless( $line ){ |
176
|
0
|
|
|
|
|
|
undef $w; |
177
|
0
|
|
|
|
|
|
$finished->send(); |
178
|
0
|
|
|
|
|
|
return; |
179
|
|
|
|
|
|
|
} |
180
|
0
|
|
|
|
|
|
print "$toprint: ".$line; |
181
|
0
|
|
|
|
|
|
}); |
182
|
|
|
|
|
|
|
} |
183
|
|
|
|
|
|
|
|
184
|
0
|
|
|
|
|
|
map{ $_->recv() } @finished; |
|
0
|
|
|
|
|
|
|
185
|
0
|
|
|
|
|
|
return 0; |
186
|
|
|
|
|
|
|
} |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable(); |