| line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
|
1
|
|
|
|
|
|
|
package Mojolicious::Command::deploy::heroku; |
|
2
|
1
|
|
|
1
|
|
27808
|
use Mojo::Base 'Mojolicious::Command'; |
|
|
0
|
|
|
|
|
|
|
|
|
0
|
|
|
|
|
|
|
|
3
|
|
|
|
|
|
|
|
|
4
|
|
|
|
|
|
|
#use IO::All 'io'; |
|
5
|
|
|
|
|
|
|
use File::Path 'make_path'; |
|
6
|
|
|
|
|
|
|
use File::Slurp qw/ slurp write_file /; |
|
7
|
|
|
|
|
|
|
use File::Spec; |
|
8
|
|
|
|
|
|
|
use Getopt::Long qw/ GetOptions :config no_auto_abbrev no_ignore_case /; |
|
9
|
|
|
|
|
|
|
use IPC::Cmd 'can_run'; |
|
10
|
|
|
|
|
|
|
use Mojo::IOLoop; |
|
11
|
|
|
|
|
|
|
use Mojo::UserAgent; |
|
12
|
|
|
|
|
|
|
use Mojolicious::Command::generate::heroku; |
|
13
|
|
|
|
|
|
|
use Mojolicious::Command::generate::makefile; |
|
14
|
|
|
|
|
|
|
use Net::Heroku; |
|
15
|
|
|
|
|
|
|
|
|
16
|
|
|
|
|
|
|
our $VERSION = 0.11; |
|
17
|
|
|
|
|
|
|
|
|
18
|
|
|
|
|
|
|
has tmpdir => sub { $ENV{MOJO_TMPDIR} || File::Spec->tmpdir }; |
|
19
|
|
|
|
|
|
|
has ua => sub { Mojo::UserAgent->new->ioloop(Mojo::IOLoop->singleton) }; |
|
20
|
|
|
|
|
|
|
has description => "Deploy Mojolicious app to Heroku.\n"; |
|
21
|
|
|
|
|
|
|
has opt => sub { {} }; |
|
22
|
|
|
|
|
|
|
has credentials_file => sub {"$ENV{HOME}/.heroku/credentials"}; |
|
23
|
|
|
|
|
|
|
has makefile => 'Makefile.PL'; |
|
24
|
|
|
|
|
|
|
has usage => <<"EOF"; |
|
25
|
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
usage: $0 deploy heroku [OPTIONS] |
|
27
|
|
|
|
|
|
|
|
|
28
|
|
|
|
|
|
|
# Create new app with randomly selected name and deploy |
|
29
|
|
|
|
|
|
|
$0 deploy heroku -c |
|
30
|
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
# Deploy to specified app and deploy (creates app if it does not exist) |
|
32
|
|
|
|
|
|
|
$0 deploy heroku -n friggin-ponycorns |
|
33
|
|
|
|
|
|
|
|
|
34
|
|
|
|
|
|
|
These options are available: |
|
35
|
|
|
|
|
|
|
-n, --appname Specify app name for deployment |
|
36
|
|
|
|
|
|
|
-a, --api-key Heroku API key (read from ~/.heroku/credentials by default). |
|
37
|
|
|
|
|
|
|
-c, --create Create app with randomly selected name |
|
38
|
|
|
|
|
|
|
-v, --verbose Verbose output (heroku response, git output) |
|
39
|
|
|
|
|
|
|
-h, --help This message |
|
40
|
|
|
|
|
|
|
EOF |
|
41
|
|
|
|
|
|
|
|
|
42
|
|
|
|
|
|
|
sub opt_spec { |
|
43
|
|
|
|
|
|
|
my $self = shift; |
|
44
|
|
|
|
|
|
|
my $opt = {}; |
|
45
|
|
|
|
|
|
|
|
|
46
|
|
|
|
|
|
|
return $opt |
|
47
|
|
|
|
|
|
|
if GetOptions( |
|
48
|
|
|
|
|
|
|
"appname|n=s" => sub { $opt->{name} = pop }, |
|
49
|
|
|
|
|
|
|
"api-key|a=s" => sub { $opt->{api_key} = pop }, |
|
50
|
|
|
|
|
|
|
"create|c" => sub { $opt->{create} = pop }, |
|
51
|
|
|
|
|
|
|
); |
|
52
|
|
|
|
|
|
|
} |
|
53
|
|
|
|
|
|
|
|
|
54
|
|
|
|
|
|
|
sub validate { |
|
55
|
|
|
|
|
|
|
my $self = shift; |
|
56
|
|
|
|
|
|
|
my $opt = shift; |
|
57
|
|
|
|
|
|
|
|
|
58
|
|
|
|
|
|
|
my @errors = |
|
59
|
|
|
|
|
|
|
map $_ . ' command not found' => |
|
60
|
|
|
|
|
|
|
grep !can_run($_) => qw/ git ssh ssh-keygen /; |
|
61
|
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
# Create or appname |
|
63
|
|
|
|
|
|
|
push @errors => '--create or --appname must be specified' |
|
64
|
|
|
|
|
|
|
if !defined $opt->{create} and !defined $opt->{name}; |
|
65
|
|
|
|
|
|
|
|
|
66
|
|
|
|
|
|
|
return @errors; |
|
67
|
|
|
|
|
|
|
} |
|
68
|
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
sub run { |
|
70
|
|
|
|
|
|
|
my $self = shift; |
|
71
|
|
|
|
|
|
|
|
|
72
|
|
|
|
|
|
|
# App home dir |
|
73
|
|
|
|
|
|
|
$self->ua->server->app($self->app); |
|
74
|
|
|
|
|
|
|
my $home_dir = $self->ua->server->app->home->to_string; |
|
75
|
|
|
|
|
|
|
|
|
76
|
|
|
|
|
|
|
# Command-line Options |
|
77
|
|
|
|
|
|
|
my $opt = $self->opt_spec(@_); |
|
78
|
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
# Validate |
|
80
|
|
|
|
|
|
|
my @errors = $self->validate($opt); |
|
81
|
|
|
|
|
|
|
die "\n" . join("\n" => @errors) . "\n" . $self->usage if @errors; |
|
82
|
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
# Net::Heroku |
|
84
|
|
|
|
|
|
|
my $h = $self->heroku_object($opt->{api_key} || $self->local_api_key); |
|
85
|
|
|
|
|
|
|
|
|
86
|
|
|
|
|
|
|
# Prepare |
|
87
|
|
|
|
|
|
|
$self->generate_makefile; |
|
88
|
|
|
|
|
|
|
$self->generate_herokufile; |
|
89
|
|
|
|
|
|
|
|
|
90
|
|
|
|
|
|
|
# SSH key permissions |
|
91
|
|
|
|
|
|
|
if (!remote_key_match($h)) { |
|
92
|
|
|
|
|
|
|
print "\nHeroku does not have any SSH keys stored for you."; |
|
93
|
|
|
|
|
|
|
my ($file, $key) = create_or_get_key(); |
|
94
|
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
print "\nUploading SSH public key $file\n"; |
|
96
|
|
|
|
|
|
|
$h->add_key(key => $key); |
|
97
|
|
|
|
|
|
|
} |
|
98
|
|
|
|
|
|
|
|
|
99
|
|
|
|
|
|
|
# Create |
|
100
|
|
|
|
|
|
|
my $res = verify_app( |
|
101
|
|
|
|
|
|
|
$h, |
|
102
|
|
|
|
|
|
|
config_app( |
|
103
|
|
|
|
|
|
|
$h, |
|
104
|
|
|
|
|
|
|
create_or_get_app($h, $opt), |
|
105
|
|
|
|
|
|
|
{BUILDPACK_URL => 'http://github.com/tempire/perloku.git'} |
|
106
|
|
|
|
|
|
|
) |
|
107
|
|
|
|
|
|
|
); |
|
108
|
|
|
|
|
|
|
|
|
109
|
|
|
|
|
|
|
print "Collecting all files in " |
|
110
|
|
|
|
|
|
|
. $self->app->home . " ..." |
|
111
|
|
|
|
|
|
|
. " (Ctrl-C to cancel)\n"; |
|
112
|
|
|
|
|
|
|
|
|
113
|
|
|
|
|
|
|
# Upload |
|
114
|
|
|
|
|
|
|
push_repo( |
|
115
|
|
|
|
|
|
|
fill_repo( |
|
116
|
|
|
|
|
|
|
$self->create_repo($home_dir, $self->tmpdir), |
|
117
|
|
|
|
|
|
|
$self->app->home->list_files |
|
118
|
|
|
|
|
|
|
), |
|
119
|
|
|
|
|
|
|
$res |
|
120
|
|
|
|
|
|
|
); |
|
121
|
|
|
|
|
|
|
} |
|
122
|
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
sub prompt { |
|
124
|
|
|
|
|
|
|
my ($message, @options) = @_; |
|
125
|
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
print "\n$message\n"; |
|
127
|
|
|
|
|
|
|
|
|
128
|
|
|
|
|
|
|
for (my $i = 0; $i < @options; $i++) { |
|
129
|
|
|
|
|
|
|
printf "\n%d) %s" => $i + 1, $options[$i]; |
|
130
|
|
|
|
|
|
|
} |
|
131
|
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
print "\n\n> "; |
|
133
|
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
my $response = ; |
|
135
|
|
|
|
|
|
|
chomp $response; |
|
136
|
|
|
|
|
|
|
|
|
137
|
|
|
|
|
|
|
return ($response |
|
138
|
|
|
|
|
|
|
&& $response =~ /^\d+$/ |
|
139
|
|
|
|
|
|
|
&& $response > 0 |
|
140
|
|
|
|
|
|
|
&& $response < @options + 1) |
|
141
|
|
|
|
|
|
|
? $options[$response - 1] |
|
142
|
|
|
|
|
|
|
: prompt($message, @options); |
|
143
|
|
|
|
|
|
|
} |
|
144
|
|
|
|
|
|
|
|
|
145
|
|
|
|
|
|
|
sub choose_key { |
|
146
|
|
|
|
|
|
|
return prompt |
|
147
|
|
|
|
|
|
|
"Which of the following keys would you like to use with Heroku?", |
|
148
|
|
|
|
|
|
|
ssh_keys(); |
|
149
|
|
|
|
|
|
|
} |
|
150
|
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
sub generate_key { |
|
152
|
|
|
|
|
|
|
print "\nGenerating an SSH public key...\n"; |
|
153
|
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
my $file = "id_rsa"; |
|
155
|
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
# Get/create dir |
|
157
|
|
|
|
|
|
|
#my $dir = io->dir("$ENV{HOME}/.ssh")->perms(0700)->mkdir; |
|
158
|
|
|
|
|
|
|
my $dir = File::Spec->catfile($ENV{HOME}, '.ssh'); |
|
159
|
|
|
|
|
|
|
make_path($dir, {mode => 0700}); |
|
160
|
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
# Generate RSA key |
|
162
|
|
|
|
|
|
|
my $path = File::Spec->catfile($dir, $file); |
|
163
|
|
|
|
|
|
|
`ssh-keygen -t rsa -N "" -f "$path" 2>&1`; |
|
164
|
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
return "$path.pub"; |
|
166
|
|
|
|
|
|
|
} |
|
167
|
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
sub ssh_keys { |
|
169
|
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
#return grep /\.pub$/ => io->dir("$ENV{HOME}/.ssh/")->all; |
|
171
|
|
|
|
|
|
|
opendir(my $dir => File::Spec->catfile($ENV{HOME}, '.ssh')) or return; |
|
172
|
|
|
|
|
|
|
return |
|
173
|
|
|
|
|
|
|
map File::Spec->catfile($ENV{HOME}, '.ssh', $_) => |
|
174
|
|
|
|
|
|
|
grep /\.pub$/ => readdir($dir); |
|
175
|
|
|
|
|
|
|
} |
|
176
|
|
|
|
|
|
|
|
|
177
|
|
|
|
|
|
|
|
|
178
|
|
|
|
|
|
|
sub create_or_get_key { |
|
179
|
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
#return io->file(ssh_keys() ? choose_key : generate_key)->slurp; |
|
181
|
|
|
|
|
|
|
my $file = ssh_keys() ? choose_key : generate_key; |
|
182
|
|
|
|
|
|
|
return $file, slurp $file; |
|
183
|
|
|
|
|
|
|
} |
|
184
|
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
sub generate_makefile { |
|
186
|
|
|
|
|
|
|
my $self = shift; |
|
187
|
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
my $command = Mojolicious::Command::generate::makefile->new; |
|
189
|
|
|
|
|
|
|
my $file = $self->app->home->rel_file($self->makefile); |
|
190
|
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
if (!file_exists($file)) { |
|
192
|
|
|
|
|
|
|
print "$file not found...generating\n"; |
|
193
|
|
|
|
|
|
|
return $command->run; |
|
194
|
|
|
|
|
|
|
} |
|
195
|
|
|
|
|
|
|
} |
|
196
|
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
sub generate_herokufile { |
|
198
|
|
|
|
|
|
|
my $self = shift; |
|
199
|
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
my $command = Mojolicious::Command::generate::heroku->new; |
|
201
|
|
|
|
|
|
|
|
|
202
|
|
|
|
|
|
|
if (!file_exists($command->file)) { |
|
203
|
|
|
|
|
|
|
print $command->file . " not found...generating\n"; |
|
204
|
|
|
|
|
|
|
return $command->run; |
|
205
|
|
|
|
|
|
|
} |
|
206
|
|
|
|
|
|
|
} |
|
207
|
|
|
|
|
|
|
|
|
208
|
|
|
|
|
|
|
sub file_exists { |
|
209
|
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
#return io(shift)->exists; |
|
211
|
|
|
|
|
|
|
return -e shift; |
|
212
|
|
|
|
|
|
|
} |
|
213
|
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
sub heroku_object { |
|
215
|
|
|
|
|
|
|
my ($self, $api_key) = @_; |
|
216
|
|
|
|
|
|
|
|
|
217
|
|
|
|
|
|
|
my $h; |
|
218
|
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
if (defined $api_key) { |
|
220
|
|
|
|
|
|
|
$h = Net::Heroku->new(api_key => $api_key); |
|
221
|
|
|
|
|
|
|
} |
|
222
|
|
|
|
|
|
|
else { |
|
223
|
|
|
|
|
|
|
my @credentials; |
|
224
|
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
while (!$h || $h->error) { |
|
226
|
|
|
|
|
|
|
@credentials = prompt_user_pass(); |
|
227
|
|
|
|
|
|
|
$h = Net::Heroku->new(@credentials); |
|
228
|
|
|
|
|
|
|
} |
|
229
|
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
$self->save_local_api_key($credentials[1], $h->ua->api_key); |
|
231
|
|
|
|
|
|
|
} |
|
232
|
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
return $h; |
|
234
|
|
|
|
|
|
|
} |
|
235
|
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
sub save_local_api_key { |
|
237
|
|
|
|
|
|
|
my ($self, $email, $api_key) = @_; |
|
238
|
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
#my $dir = io->dir("$ENV{HOME}/.heroku")->perms(0700)->mkdir; |
|
240
|
|
|
|
|
|
|
my $dir = "$ENV{HOME}/.heroku"; |
|
241
|
|
|
|
|
|
|
make_path($dir, {mode => 0700}); |
|
242
|
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
#return io("$dir/credentials")->print($email, "\n", $api_key, "\n"); |
|
244
|
|
|
|
|
|
|
return write_file "$dir/credentials", $email, "\n", $api_key, "\n"; |
|
245
|
|
|
|
|
|
|
} |
|
246
|
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
sub local_api_key { |
|
248
|
|
|
|
|
|
|
my $self = shift; |
|
249
|
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
return if !-T $self->credentials_file; |
|
251
|
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
#my $api_key = +(io->file($self->credentials_file)->slurp)[-1]; |
|
253
|
|
|
|
|
|
|
my $api_key = +(slurp $self->credentials_file)[-1]; |
|
254
|
|
|
|
|
|
|
chomp $api_key; |
|
255
|
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
return $api_key; |
|
257
|
|
|
|
|
|
|
} |
|
258
|
|
|
|
|
|
|
|
|
259
|
|
|
|
|
|
|
sub prompt_user_pass { |
|
260
|
|
|
|
|
|
|
print "\nPlease enter your Heroku credentials"; |
|
261
|
|
|
|
|
|
|
print "\n (Sign up for free at https://api.heroku.com/signup)"; |
|
262
|
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
print "\n\nEmail: "; |
|
264
|
|
|
|
|
|
|
my $email = ; |
|
265
|
|
|
|
|
|
|
chomp $email; |
|
266
|
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
print "Password: "; |
|
268
|
|
|
|
|
|
|
my $password = ; |
|
269
|
|
|
|
|
|
|
chomp $password; |
|
270
|
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
return (email => $email, password => $password); |
|
272
|
|
|
|
|
|
|
} |
|
273
|
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
sub create_repo { |
|
275
|
|
|
|
|
|
|
my ($self, $home_dir, $tmp_dir) = @_; |
|
276
|
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
my $git_dir = |
|
278
|
|
|
|
|
|
|
File::Spec->catfile($tmp_dir, 'mojo_deploy_git', int rand 1000); |
|
279
|
|
|
|
|
|
|
make_path($git_dir); |
|
280
|
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
my $r = { |
|
282
|
|
|
|
|
|
|
work_tree => $home_dir, |
|
283
|
|
|
|
|
|
|
git_dir => $git_dir, |
|
284
|
|
|
|
|
|
|
}; |
|
285
|
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
git($r, 'init'); |
|
287
|
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
return $r; |
|
289
|
|
|
|
|
|
|
} |
|
290
|
|
|
|
|
|
|
|
|
291
|
|
|
|
|
|
|
sub fill_repo { |
|
292
|
|
|
|
|
|
|
my ($r, $all_files) = @_; |
|
293
|
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
# .gitignore'd files |
|
295
|
|
|
|
|
|
|
my @ignore = |
|
296
|
|
|
|
|
|
|
git($r, 'ls-files' => '--others' => '-i' => '--exclude-standard'); |
|
297
|
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
my @files = |
|
299
|
|
|
|
|
|
|
grep { my $file = $_; $file if !grep $file =~ /$_\W*/ => @ignore } |
|
300
|
|
|
|
|
|
|
@$all_files; |
|
301
|
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
# Add files filtered by .gitignore |
|
303
|
|
|
|
|
|
|
print "Adding file $_\n" for @files; |
|
304
|
|
|
|
|
|
|
git($r, add => @files); |
|
305
|
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
git($r, commit => '-m' => '"Initial commit"'); |
|
307
|
|
|
|
|
|
|
print int(@files) . " files added\n\n"; |
|
308
|
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
return $r; |
|
310
|
|
|
|
|
|
|
} |
|
311
|
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
sub push_repo { |
|
313
|
|
|
|
|
|
|
my ($r, $res) = @_; |
|
314
|
|
|
|
|
|
|
|
|
315
|
|
|
|
|
|
|
git($r, remote => add => heroku => $res->{git_url}); |
|
316
|
|
|
|
|
|
|
git($r, push => '--force' => heroku => 'master'); |
|
317
|
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
return $r; |
|
319
|
|
|
|
|
|
|
} |
|
320
|
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
sub git { |
|
322
|
|
|
|
|
|
|
my $r = shift; |
|
323
|
|
|
|
|
|
|
my $cmd = |
|
324
|
|
|
|
|
|
|
"git -c core.autocrlf=false --work-tree=\"$r->{work_tree}\" --git-dir=\"$r->{git_dir}\" " |
|
325
|
|
|
|
|
|
|
. join " " => @_; |
|
326
|
|
|
|
|
|
|
return `$cmd`; |
|
327
|
|
|
|
|
|
|
} |
|
328
|
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
sub create_or_get_app { |
|
330
|
|
|
|
|
|
|
my ($h, $opt) = @_; |
|
331
|
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
# Attempt create |
|
333
|
|
|
|
|
|
|
my %params = defined $opt->{name} ? (name => $opt->{name}) : (); |
|
334
|
|
|
|
|
|
|
my $res = {$h->create(%params)}; |
|
335
|
|
|
|
|
|
|
my $error = $h->error; |
|
336
|
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
# Attempt retrieval |
|
338
|
|
|
|
|
|
|
$res = shift @{[grep $_->{name} eq $opt->{name} => $h->apps]} |
|
339
|
|
|
|
|
|
|
if $h->error and $h->error eq 'Name is already taken'; |
|
340
|
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
print "Upload failed for $opt->{name}: " . $error . "\n" and exit if !$res; |
|
342
|
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
return $res; |
|
344
|
|
|
|
|
|
|
} |
|
345
|
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
sub remote_key_match { |
|
347
|
|
|
|
|
|
|
my $h = pop; |
|
348
|
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
my %remote_keys = map { $_->{contents} => $_->{email} } $h->keys; |
|
350
|
|
|
|
|
|
|
my @local_keys = map substr(slurp($_), 0, -1) => ssh_keys(); |
|
351
|
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
#my @local_keys = map substr($_->all, 0, -1) => ssh_keys(); |
|
353
|
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
return grep defined $remote_keys{$_} => @local_keys; |
|
355
|
|
|
|
|
|
|
} |
|
356
|
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
sub config_app { |
|
358
|
|
|
|
|
|
|
my ($h, $res, $config) = @_; |
|
359
|
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
print "Configuration failed for app $res->{name}: " . $h->error . "\n" |
|
361
|
|
|
|
|
|
|
and exit |
|
362
|
|
|
|
|
|
|
if !$h->add_config(name => $res->{name}, %$config); |
|
363
|
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
return $res; |
|
365
|
|
|
|
|
|
|
} |
|
366
|
|
|
|
|
|
|
|
|
367
|
|
|
|
|
|
|
sub verify_app { |
|
368
|
|
|
|
|
|
|
my ($h, $res) = @_; |
|
369
|
|
|
|
|
|
|
|
|
370
|
|
|
|
|
|
|
# This is the way Heroku's official command-line client does it. |
|
371
|
|
|
|
|
|
|
|
|
372
|
|
|
|
|
|
|
for (0 .. 5) { |
|
373
|
|
|
|
|
|
|
last if $h->app_created(name => $res->{name}); |
|
374
|
|
|
|
|
|
|
sleep 1; |
|
375
|
|
|
|
|
|
|
print ' . '; |
|
376
|
|
|
|
|
|
|
} |
|
377
|
|
|
|
|
|
|
|
|
378
|
|
|
|
|
|
|
return $res; |
|
379
|
|
|
|
|
|
|
} |
|
380
|
|
|
|
|
|
|
|
|
381
|
|
|
|
|
|
|
1; |
|
382
|
|
|
|
|
|
|
|
|
383
|
|
|
|
|
|
|
=head1 NAME |
|
384
|
|
|
|
|
|
|
|
|
385
|
|
|
|
|
|
|
Mojolicious::Command::deploy::heroku - Deploy to Heroku |
|
386
|
|
|
|
|
|
|
|
|
387
|
|
|
|
|
|
|
=head1 USAGE |
|
388
|
|
|
|
|
|
|
|
|
389
|
|
|
|
|
|
|
script/my_app deploy heroku [OPTIONS] |
|
390
|
|
|
|
|
|
|
|
|
391
|
|
|
|
|
|
|
# Create new app with randomly selected name and deploy |
|
392
|
|
|
|
|
|
|
script/my_app deploy heroku --create |
|
393
|
|
|
|
|
|
|
|
|
394
|
|
|
|
|
|
|
# Create new app with randomly selected name and specified api key |
|
395
|
|
|
|
|
|
|
script/my_app deploy heroku --create --api-key 123412341234... |
|
396
|
|
|
|
|
|
|
|
|
397
|
|
|
|
|
|
|
# Deploy app (new or existing) with specified name |
|
398
|
|
|
|
|
|
|
script/my_app deploy heroku --name happy-cloud-1234 |
|
399
|
|
|
|
|
|
|
|
|
400
|
|
|
|
|
|
|
These options are available: |
|
401
|
|
|
|
|
|
|
-n, --appname Specify app for deployment |
|
402
|
|
|
|
|
|
|
-a, --api-key Heroku API key (read from ~/.heroku/credentials by default). |
|
403
|
|
|
|
|
|
|
-c, --create Create a new Heroku app |
|
404
|
|
|
|
|
|
|
-v, --verbose Verbose output (heroku response, git output) |
|
405
|
|
|
|
|
|
|
-h, --help This message |
|
406
|
|
|
|
|
|
|
|
|
407
|
|
|
|
|
|
|
=head1 DESCRIPTION |
|
408
|
|
|
|
|
|
|
|
|
409
|
|
|
|
|
|
|
L deploys a Mojolicious app to Heroku. |
|
410
|
|
|
|
|
|
|
|
|
411
|
|
|
|
|
|
|
*NOTE* The deploy command itself works on Windows, but the Heroku service does not reliably accept deployments from Windows. Your mileage may vary. |
|
412
|
|
|
|
|
|
|
|
|
413
|
|
|
|
|
|
|
*NOTE* This release works with Mojolicious versions 4.50 and above. For older Mojolicious versions, please use 0.10 or before. |
|
414
|
|
|
|
|
|
|
|
|
415
|
|
|
|
|
|
|
=head1 WORKFLOW |
|
416
|
|
|
|
|
|
|
|
|
417
|
|
|
|
|
|
|
=over 4 |
|
418
|
|
|
|
|
|
|
|
|
419
|
|
|
|
|
|
|
=item 1) B |
|
420
|
|
|
|
|
|
|
|
|
421
|
|
|
|
|
|
|
L |
|
422
|
|
|
|
|
|
|
|
|
423
|
|
|
|
|
|
|
=item 2) B |
|
424
|
|
|
|
|
|
|
|
|
425
|
|
|
|
|
|
|
mojo generate lite_app hello |
|
426
|
|
|
|
|
|
|
|
|
427
|
|
|
|
|
|
|
=item 3) B |
|
428
|
|
|
|
|
|
|
|
|
429
|
|
|
|
|
|
|
hello deploy heroku --create |
|
430
|
|
|
|
|
|
|
|
|
431
|
|
|
|
|
|
|
The deploy command creates a git repository of the B in /tmp, and then pushes it to a remote heroku repository. |
|
432
|
|
|
|
|
|
|
|
|
433
|
|
|
|
|
|
|
=back |
|
434
|
|
|
|
|
|
|
|
|
435
|
|
|
|
|
|
|
=head1 SEE ALSO |
|
436
|
|
|
|
|
|
|
|
|
437
|
|
|
|
|
|
|
L, |
|
438
|
|
|
|
|
|
|
L, |
|
439
|
|
|
|
|
|
|
L, |
|
440
|
|
|
|
|
|
|
L |
|
441
|
|
|
|
|
|
|
|
|
442
|
|
|
|
|
|
|
=head1 SOURCE |
|
443
|
|
|
|
|
|
|
|
|
444
|
|
|
|
|
|
|
L |
|
445
|
|
|
|
|
|
|
|
|
446
|
|
|
|
|
|
|
=head1 VERSION |
|
447
|
|
|
|
|
|
|
|
|
448
|
|
|
|
|
|
|
0.11 |
|
449
|
|
|
|
|
|
|
|
|
450
|
|
|
|
|
|
|
=head1 AUTHOR |
|
451
|
|
|
|
|
|
|
|
|
452
|
|
|
|
|
|
|
Glen Hinkle C |
|
453
|
|
|
|
|
|
|
|