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
|
|
|
|
|
|
|
|