line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package Cinnamon; |
2
|
3
|
|
|
3
|
|
69574
|
use strict; |
|
3
|
|
|
|
|
5
|
|
|
3
|
|
|
|
|
87
|
|
3
|
3
|
|
|
3
|
|
14
|
use warnings; |
|
3
|
|
|
|
|
6
|
|
|
3
|
|
|
|
|
85
|
|
4
|
3
|
|
|
3
|
|
57
|
use 5.010_001; |
|
3
|
|
|
|
|
14
|
|
5
|
|
|
|
|
|
|
|
6
|
|
|
|
|
|
|
our $VERSION = '0.13_02'; |
7
|
|
|
|
|
|
|
|
8
|
3
|
|
|
3
|
|
1317
|
use YAML (); |
|
3
|
|
|
|
|
18529
|
|
|
3
|
|
|
|
|
92
|
|
9
|
3
|
|
|
3
|
|
1412
|
use Class::Load (); |
|
3
|
|
|
|
|
41616
|
|
|
3
|
|
|
|
|
98
|
|
10
|
|
|
|
|
|
|
|
11
|
3
|
|
|
3
|
|
1280
|
use Cinnamon::Config; |
|
3
|
|
|
|
|
14
|
|
|
3
|
|
|
|
|
86
|
|
12
|
3
|
|
|
3
|
|
1519
|
use Cinnamon::Runner; |
|
3
|
|
|
|
|
10
|
|
|
3
|
|
|
|
|
89
|
|
13
|
3
|
|
|
3
|
|
17
|
use Cinnamon::Logger; |
|
3
|
|
|
|
|
25
|
|
|
3
|
|
|
|
|
880
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
sub new { |
16
|
9
|
|
|
9
|
0
|
18
|
my $class = shift; |
17
|
9
|
|
|
|
|
72
|
bless { }, $class; |
18
|
|
|
|
|
|
|
} |
19
|
|
|
|
|
|
|
|
20
|
|
|
|
|
|
|
sub run { |
21
|
9
|
|
|
9
|
1
|
50
|
my ($self, $role, $task, %opts) = @_; |
22
|
9
|
|
|
|
|
51
|
my @args = Cinnamon::Config::load $role, $task, %opts; |
23
|
|
|
|
|
|
|
|
24
|
9
|
100
|
|
|
|
37
|
if ($opts{info}) { |
25
|
1
|
|
|
|
|
8
|
log 'info', YAML::Dump(Cinnamon::Config::info); |
26
|
1
|
|
|
|
|
25
|
return; |
27
|
|
|
|
|
|
|
} |
28
|
|
|
|
|
|
|
|
29
|
8
|
|
|
|
|
30
|
my $hosts = Cinnamon::Config::get_role; |
30
|
8
|
|
|
|
|
31
|
my $task_def = Cinnamon::Config::get_task; |
31
|
8
|
|
50
|
|
|
26
|
my $runner = Cinnamon::Config::get('runner_class') || 'Cinnamon::Runner::Concurrent'; |
32
|
|
|
|
|
|
|
|
33
|
8
|
50
|
|
|
|
24
|
unless (defined $hosts) { |
34
|
0
|
|
|
|
|
0
|
log 'error', "undefined role : '$role'"; |
35
|
0
|
|
|
|
|
0
|
return; |
36
|
|
|
|
|
|
|
} |
37
|
8
|
50
|
|
|
|
30
|
unless (defined $task_def) { |
38
|
0
|
|
|
|
|
0
|
log 'error', "undefined task : '$task'"; |
39
|
0
|
|
|
|
|
0
|
return; |
40
|
|
|
|
|
|
|
} |
41
|
|
|
|
|
|
|
|
42
|
8
|
|
|
|
|
52
|
Class::Load::load_class $runner; |
43
|
|
|
|
|
|
|
|
44
|
6
|
|
|
|
|
692
|
my $result = $runner->start($hosts, $task_def); |
45
|
0
|
|
|
|
|
|
my (@success, @error); |
46
|
|
|
|
|
|
|
|
47
|
0
|
0
|
|
|
|
|
for my $key (keys %{$result || {}}) { |
|
0
|
|
|
|
|
|
|
48
|
0
|
0
|
|
|
|
|
if ($result->{$key}->{error}) { |
49
|
0
|
|
|
|
|
|
push @error, $key; |
50
|
|
|
|
|
|
|
} |
51
|
|
|
|
|
|
|
else { |
52
|
0
|
|
|
|
|
|
push @success, $key; |
53
|
|
|
|
|
|
|
} |
54
|
|
|
|
|
|
|
} |
55
|
|
|
|
|
|
|
|
56
|
0
|
|
0
|
|
|
|
log success => sprintf( |
57
|
|
|
|
|
|
|
"\n========================\n[success]: %s", |
58
|
|
|
|
|
|
|
(join(', ', @success) || ''), |
59
|
|
|
|
|
|
|
); |
60
|
|
|
|
|
|
|
|
61
|
0
|
|
0
|
|
|
|
log error => sprintf( |
62
|
|
|
|
|
|
|
"[error]: %s", |
63
|
|
|
|
|
|
|
(join(', ', @error) || ''), |
64
|
|
|
|
|
|
|
); |
65
|
|
|
|
|
|
|
|
66
|
0
|
|
|
|
|
|
return (\@success, \@error); |
67
|
|
|
|
|
|
|
} |
68
|
|
|
|
|
|
|
|
69
|
|
|
|
|
|
|
!!1; |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
__END__ |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=encoding utf8 |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
=head1 NAME |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
Cinnamon - A minimalistic deploy tool |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
=head1 SYNOPSIS |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
use strict; |
82
|
|
|
|
|
|
|
use warnings; |
83
|
|
|
|
|
|
|
|
84
|
|
|
|
|
|
|
# Exports some commands |
85
|
|
|
|
|
|
|
use Cinnamon::DSL; |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
my $application = 'My::App'; |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
# It's required if you want to login to remote host |
90
|
|
|
|
|
|
|
set user => 'johndoe'; |
91
|
|
|
|
|
|
|
|
92
|
|
|
|
|
|
|
# User defined params to use later |
93
|
|
|
|
|
|
|
set application => $application; |
94
|
|
|
|
|
|
|
set repository => "git://git.example.com/projects/$application"; |
95
|
|
|
|
|
|
|
|
96
|
|
|
|
|
|
|
# Lazily evaluated if passed as a code |
97
|
|
|
|
|
|
|
set lazy_value => sub { |
98
|
|
|
|
|
|
|
#... |
99
|
|
|
|
|
|
|
}; |
100
|
|
|
|
|
|
|
|
101
|
|
|
|
|
|
|
# Roles |
102
|
|
|
|
|
|
|
role development => 'development.example.com', { |
103
|
|
|
|
|
|
|
deploy_to => "/home/app/www/$application-devel", |
104
|
|
|
|
|
|
|
branch => "develop", |
105
|
|
|
|
|
|
|
}; |
106
|
|
|
|
|
|
|
|
107
|
|
|
|
|
|
|
# Lazily evaluated if passed as a code |
108
|
|
|
|
|
|
|
role production => sub { |
109
|
|
|
|
|
|
|
my $res = LWP::UserAgent->get('http://servers.example.com/api/hosts'); |
110
|
|
|
|
|
|
|
my $hosts = decode_json $res->content; |
111
|
|
|
|
|
|
|
$hosts; |
112
|
|
|
|
|
|
|
}, { |
113
|
|
|
|
|
|
|
deploy_to => "/home/app/www/$application", |
114
|
|
|
|
|
|
|
branch => "master", |
115
|
|
|
|
|
|
|
}; |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
# Tasks |
118
|
|
|
|
|
|
|
task update => sub { |
119
|
|
|
|
|
|
|
my ($host, @args) = @_; |
120
|
|
|
|
|
|
|
my $deploy_to = get('deploy_to'); |
121
|
|
|
|
|
|
|
my $branch = 'origin/' . get('branch'); |
122
|
|
|
|
|
|
|
|
123
|
|
|
|
|
|
|
# Executed on localhost |
124
|
|
|
|
|
|
|
run 'some', 'command'; |
125
|
|
|
|
|
|
|
|
126
|
|
|
|
|
|
|
# Executed on remote host |
127
|
|
|
|
|
|
|
remote { |
128
|
|
|
|
|
|
|
run "cd $deploy_to && git fetch origin && git checkout -q $branch && git submodule update --init"; |
129
|
|
|
|
|
|
|
} $host; |
130
|
|
|
|
|
|
|
}; |
131
|
|
|
|
|
|
|
task restart => sub { |
132
|
|
|
|
|
|
|
my ($host, @args) = @_; |
133
|
|
|
|
|
|
|
# ... |
134
|
|
|
|
|
|
|
}; |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
# Nest tasks |
137
|
|
|
|
|
|
|
task server => { |
138
|
|
|
|
|
|
|
setup => sub { |
139
|
|
|
|
|
|
|
my ($host, @args) = @_; |
140
|
|
|
|
|
|
|
# ... |
141
|
|
|
|
|
|
|
}, |
142
|
|
|
|
|
|
|
}; |
143
|
|
|
|
|
|
|
|
144
|
|
|
|
|
|
|
=head1 WARNINGS |
145
|
|
|
|
|
|
|
|
146
|
|
|
|
|
|
|
This software is under the heavy development and considered ALPHA quality. Things might be broken, not all features have been implemented, and APIs will be likely to change. |
147
|
|
|
|
|
|
|
|
148
|
|
|
|
|
|
|
=head1 DESCRIPTION |
149
|
|
|
|
|
|
|
|
150
|
|
|
|
|
|
|
Cinnamon is a minimalistic deploy tool aiming to provide |
151
|
|
|
|
|
|
|
structurization of issues about deployment. It only introduces the |
152
|
|
|
|
|
|
|
most essential feature for deployment and a few utilities. |
153
|
|
|
|
|
|
|
|
154
|
|
|
|
|
|
|
=head1 DSLs |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
This module provides some DSLs for use. I designed them to be kept as |
157
|
|
|
|
|
|
|
simple as possible, and I don't want to add too many commands: |
158
|
|
|
|
|
|
|
|
159
|
|
|
|
|
|
|
=head2 Structural Commands |
160
|
|
|
|
|
|
|
|
161
|
|
|
|
|
|
|
=head3 role ( I<$role: Str> => (I<$host: String> | I<$hosts: Array[String]> | I<$sub: CODE>), I<$param: HASHREF> ) |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=over 4 |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
role production => 'production.example.com'; |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
# or |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
role production => [ qw(production1.example.com production2.exampl.ecom) ]; |
170
|
|
|
|
|
|
|
|
171
|
|
|
|
|
|
|
# or |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
role production => sub { |
174
|
|
|
|
|
|
|
my $res = LWP::UserAgent->get('http://servers.example.com/api/hosts'); |
175
|
|
|
|
|
|
|
my $hosts = decode_json $res->content; |
176
|
|
|
|
|
|
|
$hosts; |
177
|
|
|
|
|
|
|
}; |
178
|
|
|
|
|
|
|
|
179
|
|
|
|
|
|
|
# or |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
role production => 'production.example.com', { |
182
|
|
|
|
|
|
|
hoge => 'fuga', |
183
|
|
|
|
|
|
|
}; |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Relates names (eg. production) to hosts to be deployed. |
186
|
|
|
|
|
|
|
|
187
|
|
|
|
|
|
|
If you pass a CODE as the second argument, this method delays the |
188
|
|
|
|
|
|
|
value to be evaluated till the value is needed at the first time. This |
189
|
|
|
|
|
|
|
is useful, for instance, when you want to retrieve hosts information |
190
|
|
|
|
|
|
|
from some external APIs or so. |
191
|
|
|
|
|
|
|
|
192
|
|
|
|
|
|
|
If you pass a HASHREF as the third argument, you can get specified |
193
|
|
|
|
|
|
|
parameters by get DSL. |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=back |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
=head3 task ( I<$taskname: Str> => (I<\%tasks: Hash[String => CODE]> | I<$sub: CODE>) ) |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
=over 4 |
200
|
|
|
|
|
|
|
|
201
|
|
|
|
|
|
|
task update => sub { |
202
|
|
|
|
|
|
|
my ($host, @args) = @_; |
203
|
|
|
|
|
|
|
my $hoge = get 'hoge'; # parameter set in global or role parameter |
204
|
|
|
|
|
|
|
# ... |
205
|
|
|
|
|
|
|
}; |
206
|
|
|
|
|
|
|
|
207
|
|
|
|
|
|
|
# you can nest tasks |
208
|
|
|
|
|
|
|
task server => { |
209
|
|
|
|
|
|
|
start => sub { |
210
|
|
|
|
|
|
|
my ($host, @args) = @_; |
211
|
|
|
|
|
|
|
# ... |
212
|
|
|
|
|
|
|
}, |
213
|
|
|
|
|
|
|
stop => sub { |
214
|
|
|
|
|
|
|
my ($host, @args) = @_; |
215
|
|
|
|
|
|
|
# ... |
216
|
|
|
|
|
|
|
}, |
217
|
|
|
|
|
|
|
}; |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
Defines some named tasks by CODEs. |
220
|
|
|
|
|
|
|
|
221
|
|
|
|
|
|
|
The arguments which are passed into the CODEs are: |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
=over 4 |
224
|
|
|
|
|
|
|
|
225
|
|
|
|
|
|
|
=item * I<$host> |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
The host name where the task is executed. Which is one of the hosts |
228
|
|
|
|
|
|
|
you set by C<role> command. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=item * I<@args> |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
Command line argument which is passed by user. |
233
|
|
|
|
|
|
|
|
234
|
|
|
|
|
|
|
$ cinammon production update foo bar baz |
235
|
|
|
|
|
|
|
|
236
|
|
|
|
|
|
|
In case above, C<@args> contains C<('foo', 'bar', 'baz')>. |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=back |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=back |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
=head2 Utilities |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
=head3 set ( I<$key: String> => (I<$value: Any> | I<$sub: CODE>) ) |
245
|
|
|
|
|
|
|
|
246
|
|
|
|
|
|
|
=over 4 |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
set key => 'value'; |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
# or |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
set key => sub { |
253
|
|
|
|
|
|
|
# values to be lazily evaluated |
254
|
|
|
|
|
|
|
}; |
255
|
|
|
|
|
|
|
|
256
|
|
|
|
|
|
|
# or |
257
|
|
|
|
|
|
|
|
258
|
|
|
|
|
|
|
set key => sub { |
259
|
|
|
|
|
|
|
my (@args) = @_; |
260
|
|
|
|
|
|
|
# value to be lazily evaluated with @args |
261
|
|
|
|
|
|
|
}; |
262
|
|
|
|
|
|
|
|
263
|
|
|
|
|
|
|
Sets a value which is related to a key. |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
If you pass a CODE as the second argument, this method delays the |
266
|
|
|
|
|
|
|
value to be evaluated till C<get> is called. This is useful when you |
267
|
|
|
|
|
|
|
want to retrieve hosts information from some external APIs or so. |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
=back |
270
|
|
|
|
|
|
|
|
271
|
|
|
|
|
|
|
=head3 get ( I<$key: String> [, I<@args: Array[Any]> ] ): Any |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=over 4 |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
my $value = get 'key'; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
# or |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
my $value = get key => qw(foo bar baz); |
280
|
|
|
|
|
|
|
|
281
|
|
|
|
|
|
|
Gets a value related to the key. |
282
|
|
|
|
|
|
|
|
283
|
|
|
|
|
|
|
If the value is a CODE, you can pass some arguments which can be used |
284
|
|
|
|
|
|
|
while evaluating. |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
=back |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
=head3 run ( I<@command: Array> ): ( I<$stdout: String>, I<$stderr: String> ) |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
=over 4 |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
my ($stdout, $stdout) = run 'git', 'pull'; |
293
|
|
|
|
|
|
|
|
294
|
|
|
|
|
|
|
Executes a command. It returns the result of execution, C<$stdout> and |
295
|
|
|
|
|
|
|
C<$stderr>, as strings. |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
=back |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
=head3 sudo ( I<@command: Array> ): ( I<$stdout: String>, I<$stderr: String> ) |
300
|
|
|
|
|
|
|
|
301
|
|
|
|
|
|
|
=over 4 |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
my ($stdout, $stdout) = sudo '/path/to/httpd', 'restart'; |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
Executes a command as well, but under I<sudo> environment. |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
=back |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
=head3 remote ( I<$sub: CODE> I<$host: String> ): Any |
310
|
|
|
|
|
|
|
|
311
|
|
|
|
|
|
|
=over 4 |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
my ($stdout, $stdout) = remote { |
314
|
|
|
|
|
|
|
run 'git', 'pull'; |
315
|
|
|
|
|
|
|
sudo '/path/to/httpd', 'restart'; |
316
|
|
|
|
|
|
|
} $host; |
317
|
|
|
|
|
|
|
|
318
|
|
|
|
|
|
|
Connects to the remote C<$host> and executes the C<$code> there. |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
Where C<run> and C<sudo> commands to be executed depends on that |
321
|
|
|
|
|
|
|
context. They are done on the remote host when set in C<remote> block, |
322
|
|
|
|
|
|
|
whereas done on localhost without it. |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
Remote login username is retrieved by C<get 'user'> or C<`whoami`> |
325
|
|
|
|
|
|
|
command. Set appropriate username in advance if needed. |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
=back |
328
|
|
|
|
|
|
|
|
329
|
|
|
|
|
|
|
=head1 REPOSITORY |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
https://github.com/kentaro/cinnamon |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
=head1 AUTHOR |
334
|
|
|
|
|
|
|
|
335
|
|
|
|
|
|
|
=over 4 |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
=item * Kentaro Kuribayashi E<lt>kentarok@gmail.comE<gt> |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
=item * Yuki Shibazaki E<lt>shibayu36 at gmail.comE<gt> |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
=back |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
=head1 SEE ALSO |
344
|
|
|
|
|
|
|
|
345
|
|
|
|
|
|
|
=over 4 |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
=item * Tutorial (Japanese) |
348
|
|
|
|
|
|
|
|
349
|
|
|
|
|
|
|
L<http://d.hatena.ne.jp/naoya/20130118/1358477523> |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
=item * L<capistrano> |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=item * L<Archer> |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
=back |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=head1 LICENSE |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
Copyright (C) Kentaro Kuribayashi |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or modify |
362
|
|
|
|
|
|
|
it under the same terms as Perl itself. |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
=cut |