line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package App::EC2Cssh; |
2
|
|
|
|
|
|
|
$App::EC2Cssh::VERSION = '0.005'; |
3
|
1
|
|
|
1
|
|
960
|
use Moose; |
|
1
|
|
|
|
|
295216
|
|
|
1
|
|
|
|
|
5
|
|
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
|
|
5290
|
use autodie qw/:all/; |
|
1
|
|
|
|
|
11058
|
|
|
1
|
|
|
|
|
5
|
|
16
|
1
|
|
|
1
|
|
15955
|
use Cwd; |
|
1
|
|
|
|
|
12
|
|
|
1
|
|
|
|
|
50
|
|
17
|
1
|
|
|
1
|
|
3
|
use File::Spec; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
20
|
|
18
|
1
|
|
|
1
|
|
655
|
use IO::Socket::SSL; |
|
1
|
|
|
|
|
56295
|
|
|
1
|
|
|
|
|
10
|
|
19
|
1
|
|
|
1
|
|
932
|
use Net::Amazon::EC2; |
|
1
|
|
|
|
|
1452133
|
|
|
1
|
|
|
|
|
38
|
|
20
|
1
|
|
|
1
|
|
600
|
use Safe; |
|
1
|
|
|
|
|
27207
|
|
|
1
|
|
|
|
|
45
|
|
21
|
1
|
|
|
1
|
|
559
|
use Text::Template; |
|
1
|
|
|
|
|
2010
|
|
|
1
|
|
|
|
|
39
|
|
22
|
|
|
|
|
|
|
|
23
|
1
|
|
|
1
|
|
374
|
use IO::Pipe; |
|
1
|
|
|
|
|
755
|
|
|
1
|
|
|
|
|
23
|
|
24
|
1
|
|
|
1
|
|
837
|
use AnyEvent; |
|
1
|
|
|
|
|
3695
|
|
|
1
|
|
|
|
|
31
|
|
25
|
|
|
|
|
|
|
|
26
|
1
|
|
|
1
|
|
437
|
use Log::Any qw/$log/; |
|
1
|
|
|
|
|
5584
|
|
|
1
|
|
|
|
|
5
|
|
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
|
|
|
|
|
|
$log->info("Listing instances for set='".$self->set()."'"); |
92
|
|
|
|
|
|
|
|
93
|
0
|
|
|
|
|
|
my $set_config = {}; |
94
|
0
|
0
|
|
|
|
|
if( $self->set() ){ |
95
|
0
|
|
0
|
|
|
|
$set_config = $self->config()->{ec2_sets}->{$self->set()} || die "No ec2_set '".$self->set()."' defined in config\n"; |
96
|
|
|
|
|
|
|
} |
97
|
|
|
|
|
|
|
|
98
|
0
|
|
|
|
|
|
my $reservation_infos = $self->ec2->describe_instances( %{ $set_config } ) ; |
|
0
|
|
|
|
|
|
|
99
|
0
|
|
|
|
|
|
foreach my $ri ( @$reservation_infos ){ |
100
|
0
|
|
|
|
|
|
my $instances = $ri->instances_set(); |
101
|
0
|
|
|
|
|
|
foreach my $instance ( @$instances ){ |
102
|
0
|
0
|
|
|
|
|
if( my $tagset = $instance->tag_set() ){ |
103
|
0
|
|
|
|
|
|
foreach my $tag ( @$tagset ){ |
104
|
0
|
|
0
|
|
|
|
$log->trace("Host has tag: ".$tag->key().':'.( $tag->value() // 'UNDEF' )); |
105
|
|
|
|
|
|
|
} |
106
|
|
|
|
|
|
|
} |
107
|
0
|
0
|
|
|
|
|
if( my $host = $instance->dns_name() ){ |
108
|
0
|
|
|
|
|
|
$log->debug("Adding host $host"); |
109
|
0
|
|
|
|
|
|
push @hosts , $host; |
110
|
|
|
|
|
|
|
}else{ |
111
|
0
|
|
|
|
|
|
$log->warn("Instance ".$instance->instance_id()." does not have a dns_name. Skipping"); |
112
|
|
|
|
|
|
|
} |
113
|
|
|
|
|
|
|
} |
114
|
|
|
|
|
|
|
} |
115
|
|
|
|
|
|
|
|
116
|
0
|
|
|
|
|
|
$log->info("Got ".scalar( @hosts )." hosts"); |
117
|
0
|
0
|
|
|
|
|
if( $self->has_demux_command() ){ |
118
|
0
|
|
|
|
|
|
return $self->do_demux_command( \@hosts ); |
119
|
|
|
|
|
|
|
} |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
# No demux command, just carry on using the configured command for multiple hosts. |
122
|
|
|
|
|
|
|
my $tmpl = Text::Template->new( TYPE => 'STRING', |
123
|
0
|
|
0
|
|
|
|
SOURCE => $self->config()->{command} || die "Missing command in config\n" |
124
|
|
|
|
|
|
|
); |
125
|
0
|
0
|
|
|
|
|
unless( $tmpl->compile() ){ |
126
|
0
|
|
|
|
|
|
die "Cannot compile template from '".$self->config()->{command}."' ERROR:".$Text::Template::ERROR."\n"; |
127
|
|
|
|
|
|
|
} |
128
|
|
|
|
|
|
|
|
129
|
0
|
|
|
|
|
|
my $command = $tmpl->fill_in( SAFE => Safe->new(), |
130
|
|
|
|
|
|
|
HASH => { |
131
|
|
|
|
|
|
|
hosts => \@hosts |
132
|
|
|
|
|
|
|
} |
133
|
|
|
|
|
|
|
); |
134
|
0
|
|
|
|
|
|
$log->info("Will do '".substr($command, 0, 80)."..'"); |
135
|
0
|
0
|
|
|
|
|
if( $log->is_debug() ){ |
136
|
0
|
|
|
|
|
|
$log->debug($command); |
137
|
|
|
|
|
|
|
} |
138
|
0
|
|
|
|
|
|
my $sys_return = system( $command ); |
139
|
0
|
|
|
|
|
|
$log->info("Done (returned $sys_return)"); |
140
|
0
|
|
|
|
|
|
return $sys_return; |
141
|
|
|
|
|
|
|
} |
142
|
|
|
|
|
|
|
|
143
|
|
|
|
|
|
|
$| = 1; |
144
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub do_demux_command{ |
146
|
0
|
|
|
0
|
0
|
|
my ($self, $hosts) = @_; |
147
|
|
|
|
|
|
|
|
148
|
0
|
|
|
|
|
|
my $tmpl = Text::Template->new( TYPE => 'STRING', |
149
|
|
|
|
|
|
|
SOURCE => $self->demux_command() ); |
150
|
|
|
|
|
|
|
|
151
|
0
|
|
|
|
|
|
my @finished = (); |
152
|
0
|
|
|
|
|
|
foreach my $host ( @$hosts ){ |
153
|
0
|
|
|
|
|
|
my $command = $tmpl->fill_in( HASH => { host => $host } ); |
154
|
0
|
|
|
|
|
|
$log->info("Will do ".$command); |
155
|
0
|
|
|
|
|
|
my $io_h = IO::Pipe->new()->reader( $command ); |
156
|
0
|
|
|
|
|
|
my $w; |
157
|
0
|
|
|
|
|
|
my $finished = AnyEvent->condvar(); |
158
|
0
|
|
|
|
|
|
push @finished , $finished; |
159
|
|
|
|
|
|
|
$w = AnyEvent->io( fh => $io_h, |
160
|
|
|
|
|
|
|
poll => 'r', |
161
|
|
|
|
|
|
|
cb => sub{ |
162
|
0
|
|
|
0
|
|
|
my $line = <$io_h>; |
163
|
0
|
0
|
|
|
|
|
unless( $line ){ |
164
|
0
|
|
|
|
|
|
undef $w; |
165
|
0
|
|
|
|
|
|
$finished->send(); |
166
|
0
|
|
|
|
|
|
return; |
167
|
|
|
|
|
|
|
} |
168
|
0
|
|
|
|
|
|
print $host.": ".$line; |
169
|
0
|
|
|
|
|
|
}); |
170
|
|
|
|
|
|
|
} |
171
|
|
|
|
|
|
|
|
172
|
0
|
|
|
|
|
|
map{ $_->recv() } @finished; |
|
0
|
|
|
|
|
|
|
173
|
0
|
|
|
|
|
|
return 0; |
174
|
|
|
|
|
|
|
} |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
__PACKAGE__->meta->make_immutable(); |