mina 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/.rspec +1 -0
- data/.travis.yml +13 -0
- data/Gemfile +10 -0
- data/HISTORY.md +148 -0
- data/LICENSE +23 -0
- data/README.md +620 -0
- data/Rakefile +33 -0
- data/bin/mina +49 -0
- data/data/deploy.rb +30 -0
- data/data/deploy.sh.erb +98 -0
- data/lib/mina.rb +20 -0
- data/lib/mina/bundler.rb +23 -0
- data/lib/mina/default.rb +97 -0
- data/lib/mina/deploy.rb +57 -0
- data/lib/mina/deploy_helpers.rb +25 -0
- data/lib/mina/git.rb +14 -0
- data/lib/mina/helpers.rb +278 -0
- data/lib/mina/rails.rb +69 -0
- data/lib/mina/rake.rb +6 -0
- data/lib/mina/settings.rb +32 -0
- data/lib/mina/tools.rb +14 -0
- data/lib/mina/version.rb +5 -0
- data/mina.gemspec +17 -0
- data/spec/command_helper.rb +52 -0
- data/spec/commands/command_spec.rb +65 -0
- data/spec/commands/deploy_spec.rb +36 -0
- data/spec/commands/outside_project_spec.rb +35 -0
- data/spec/commands/real_deploy_spec.rb +53 -0
- data/spec/commands/verbose_spec.rb +20 -0
- data/spec/dsl/queue_spec.rb +49 -0
- data/spec/dsl/settings_in_rake_spec.rb +42 -0
- data/spec/dsl/settings_spec.rb +60 -0
- data/spec/dsl/to_spec.rb +20 -0
- data/spec/spec_helper.rb +20 -0
- data/test_env/config/deploy.rb +54 -0
- metadata +117 -0
data/lib/mina/helpers.rb
ADDED
@@ -0,0 +1,278 @@
|
|
1
|
+
module Mina
|
2
|
+
module Helpers
|
3
|
+
# Invokes another Rake task.
|
4
|
+
def invoke(task)
|
5
|
+
Rake.application.invoke_task task
|
6
|
+
end
|
7
|
+
|
8
|
+
# Evaluates an ERB block and returns a string.
|
9
|
+
def erb(file, b=binding)
|
10
|
+
require 'erb'
|
11
|
+
erb = ERB.new(File.read(file))
|
12
|
+
erb.result b
|
13
|
+
end
|
14
|
+
|
15
|
+
# SSHs into the host and runs the code that has been queued.
|
16
|
+
#
|
17
|
+
# This is already automatically invoked before Rake exits to run all
|
18
|
+
# commands that have been queued up.
|
19
|
+
#
|
20
|
+
# queue "sudo restart"
|
21
|
+
# run!
|
22
|
+
#
|
23
|
+
def run!
|
24
|
+
ssh commands(:default)
|
25
|
+
end
|
26
|
+
|
27
|
+
# Executes a command via SSH.
|
28
|
+
#
|
29
|
+
# Options:
|
30
|
+
#
|
31
|
+
# pretty: Prettify the output.
|
32
|
+
#
|
33
|
+
def ssh(cmd, options={})
|
34
|
+
cmd = cmd.join("\n") if cmd.is_a?(Array)
|
35
|
+
|
36
|
+
require 'shellwords'
|
37
|
+
|
38
|
+
result = 0
|
39
|
+
if simulate_mode
|
40
|
+
puts cmd
|
41
|
+
elsif settings.term_mode == :pretty
|
42
|
+
code = "#{ssh_command} -- bash -c %s" % [ Shellwords.escape("true;"+cmd) ]
|
43
|
+
result = pretty_system("#{code} 2>&1")
|
44
|
+
elsif settings.term_mode == :exec
|
45
|
+
code = "#{ssh_command} -t -- bash -c %s" % [ Shellwords.escape("true;"+cmd) ]
|
46
|
+
exec code
|
47
|
+
else
|
48
|
+
code = "#{ssh_command} -t -- bash -c %s" % [ Shellwords.escape("true;"+cmd) ]
|
49
|
+
system code
|
50
|
+
result = $?
|
51
|
+
end
|
52
|
+
|
53
|
+
unless result == 0
|
54
|
+
err = Failed.new("Failed with status #{result}")
|
55
|
+
err.exitstatus = result
|
56
|
+
raise err
|
57
|
+
end
|
58
|
+
|
59
|
+
result
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns the SSH command to be executed.
|
63
|
+
#
|
64
|
+
# set :domain, 'foo.com'
|
65
|
+
# set :user, 'diggity'
|
66
|
+
# puts ssh_command
|
67
|
+
# #=> 'ssh diggity@foo.com'
|
68
|
+
#
|
69
|
+
def ssh_command
|
70
|
+
args = domain!
|
71
|
+
args = "#{user}@#{args}" if user?
|
72
|
+
args << " -i #{identity_file}" if identity_file?
|
73
|
+
"ssh #{args}"
|
74
|
+
end
|
75
|
+
|
76
|
+
# Works like 'system', but indents and puts color.
|
77
|
+
# Returns the exit code in integer form.
|
78
|
+
def pretty_system(code)
|
79
|
+
status =
|
80
|
+
Tools.popen4('bash', '-') do |pid, i, o, e|
|
81
|
+
i.write "( #{code} ) 2>&1\n"
|
82
|
+
i.close
|
83
|
+
|
84
|
+
last = nil
|
85
|
+
clear_on_nl = false
|
86
|
+
while c = o.getc
|
87
|
+
# Because Ruby 1.8.x returns a number on #getc
|
88
|
+
c = "%c" % [c] if c.is_a?(Fixnum)
|
89
|
+
|
90
|
+
break if o.closed?
|
91
|
+
if last == "\n"
|
92
|
+
if clear_on_nl
|
93
|
+
clear_on_nl = false
|
94
|
+
print "\033[0m"
|
95
|
+
end
|
96
|
+
|
97
|
+
# Color the verbose echo commands
|
98
|
+
if c == "$" && ((c += o.read(1)) == "$ ")
|
99
|
+
clear_on_nl = true
|
100
|
+
print " "*7 + "\033[32m#{c}\033[34m"
|
101
|
+
|
102
|
+
# (Don't) color the status messages
|
103
|
+
elsif c == "-" && ((c += o.read(5)) == "----->")
|
104
|
+
print c
|
105
|
+
|
106
|
+
# Color errors
|
107
|
+
elsif c == "=" && ((c += o.read(5)) == "=====>")
|
108
|
+
print "\033[31m=====>\033[0m"
|
109
|
+
|
110
|
+
else
|
111
|
+
print " "*7 + c
|
112
|
+
end
|
113
|
+
else
|
114
|
+
print c
|
115
|
+
end
|
116
|
+
|
117
|
+
last = c
|
118
|
+
end
|
119
|
+
end
|
120
|
+
status.exitstatus
|
121
|
+
end
|
122
|
+
|
123
|
+
# Queues code to be ran.
|
124
|
+
# This queues code to be ran to the current code bucket (defaults to `:default`).
|
125
|
+
# To get the things that have been queued, use commands[:default]
|
126
|
+
#
|
127
|
+
# queue "sudo restart"
|
128
|
+
# queue "true"
|
129
|
+
#
|
130
|
+
# commands == ['sudo restart', 'true']
|
131
|
+
#
|
132
|
+
def queue(code)
|
133
|
+
commands
|
134
|
+
commands(@to) << unindent(code)
|
135
|
+
end
|
136
|
+
|
137
|
+
def unindent(code)
|
138
|
+
if code =~ /^\n([ \t]+)/
|
139
|
+
code = code.gsub(/^#{$1}/, '')
|
140
|
+
end
|
141
|
+
|
142
|
+
code.strip
|
143
|
+
end
|
144
|
+
|
145
|
+
# Returns a hash of the code blocks where commands have been queued.
|
146
|
+
#
|
147
|
+
# queue "sudo restart"
|
148
|
+
# queue "true"
|
149
|
+
#
|
150
|
+
# to :clean do
|
151
|
+
# queue "rm"
|
152
|
+
# end
|
153
|
+
#
|
154
|
+
# commands == ["sudo restart", "true"]
|
155
|
+
# commands(:clean) == ["rm"]
|
156
|
+
#
|
157
|
+
def commands(aspect=:default)
|
158
|
+
(@commands ||= begin
|
159
|
+
@to = :default
|
160
|
+
Hash.new { |h, k| h[k] = Array.new }
|
161
|
+
end)[aspect]
|
162
|
+
end
|
163
|
+
|
164
|
+
# Starts a new block where new #commands are collected.
|
165
|
+
#
|
166
|
+
# queue "sudo restart"
|
167
|
+
# queue "true"
|
168
|
+
# commands.should == ['sudo restart', 'true']
|
169
|
+
#
|
170
|
+
# isolate do
|
171
|
+
# queue "reload"
|
172
|
+
# commands.should == ['reload']
|
173
|
+
# end
|
174
|
+
#
|
175
|
+
# commands.should == ['sudo restart', 'true']
|
176
|
+
#
|
177
|
+
def isolate(&blk)
|
178
|
+
old, @commands = @commands, nil
|
179
|
+
result = yield
|
180
|
+
new_code, @commands = @commands, old
|
181
|
+
result
|
182
|
+
end
|
183
|
+
|
184
|
+
# Defines instructions on how to do a certain thing.
|
185
|
+
# This makes the commands that are `queue`d go into a different bucket in commands.
|
186
|
+
#
|
187
|
+
# to :prepare do
|
188
|
+
# run "bundle install"
|
189
|
+
# end
|
190
|
+
# to :launch do
|
191
|
+
# run "nginx -s restart"
|
192
|
+
# end
|
193
|
+
#
|
194
|
+
# commands(:prepare) == ["bundle install"]
|
195
|
+
# commands(:restart) == ["nginx -s restart"]
|
196
|
+
#
|
197
|
+
def to(name, &blk)
|
198
|
+
old, @to = @to, name
|
199
|
+
result = yield
|
200
|
+
@to = old
|
201
|
+
result
|
202
|
+
end
|
203
|
+
|
204
|
+
# Sets settings.
|
205
|
+
#
|
206
|
+
# set :domain, 'kickflip.me'
|
207
|
+
#
|
208
|
+
def set(key, value)
|
209
|
+
settings.send :"#{key}=", value
|
210
|
+
end
|
211
|
+
|
212
|
+
# Sets default settings.
|
213
|
+
#
|
214
|
+
# set_default :term_mode, :pretty
|
215
|
+
# set :term_mode, :system
|
216
|
+
# settings.term_mode.should == :system
|
217
|
+
#
|
218
|
+
# set :term_mode, :system
|
219
|
+
# set_default :term_mode, :pretty
|
220
|
+
# settings.term_mode.should == :system
|
221
|
+
#
|
222
|
+
def set_default(key, value)
|
223
|
+
settings.send :"#{key}=", value unless settings.send(:"#{key}?")
|
224
|
+
end
|
225
|
+
|
226
|
+
# Accesses the settings hash.
|
227
|
+
#
|
228
|
+
# set :domain, 'kickflip.me'
|
229
|
+
# settings.domain #=> 'kickflip.me'
|
230
|
+
# domain #=> 'kickflip.me'
|
231
|
+
#
|
232
|
+
def settings
|
233
|
+
@settings ||= Settings.new
|
234
|
+
end
|
235
|
+
|
236
|
+
# Hook to get settings.
|
237
|
+
# See #settings for an explanation.
|
238
|
+
def method_missing(meth, *args, &blk)
|
239
|
+
settings.send meth, *args
|
240
|
+
end
|
241
|
+
|
242
|
+
def indent(count, str)
|
243
|
+
str.gsub(/^/, " "*count)
|
244
|
+
end
|
245
|
+
|
246
|
+
def error(str)
|
247
|
+
$stderr.write "#{str}\n"
|
248
|
+
end
|
249
|
+
|
250
|
+
def echo_cmd(str)
|
251
|
+
if verbose_mode
|
252
|
+
"echo #{("$ " + str).inspect} &&\n#{str}"
|
253
|
+
else
|
254
|
+
str
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
# Invoked when Rake exits.
|
259
|
+
def mina_cleanup!
|
260
|
+
run! if commands.any?
|
261
|
+
end
|
262
|
+
|
263
|
+
# Checks if Rake was invoked with --verbose.
|
264
|
+
def verbose_mode
|
265
|
+
if Rake.respond_to?(:verbose)
|
266
|
+
# Rake 0.9.x
|
267
|
+
Rake.verbose == true
|
268
|
+
else
|
269
|
+
# Rake 0.8.x
|
270
|
+
RakeFileUtils.verbose_flag != :default
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
def simulate_mode
|
275
|
+
!! ENV['simulate']
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
data/lib/mina/rails.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
settings.rails_env ||= 'production'
|
2
|
+
# TODO: This should be lambda
|
3
|
+
settings.bundle_prefix ||= lambda { %{RAILS_ENV="#{rails_env}" bundle exec} }
|
4
|
+
settings.rake ||= lambda { %{#{bundle_prefix} rake} }
|
5
|
+
settings.rails ||= lambda { %{#{bundle_prefix} rails} }
|
6
|
+
|
7
|
+
# Macro used later by :rails, :rake, etc
|
8
|
+
make_run_task = lambda { |name, sample_args|
|
9
|
+
task name, :arguments do |t, args|
|
10
|
+
command = args[:arguments]
|
11
|
+
unless command
|
12
|
+
puts %{You need to provide arguments. Try: mina "#{name}[#{sample_args}]"}
|
13
|
+
exit 1
|
14
|
+
end
|
15
|
+
queue %[cd "#{deploy_to!}/#{current_path!}" && #{rails} #{command}]
|
16
|
+
end
|
17
|
+
}
|
18
|
+
|
19
|
+
desc "Execute a Rails command in the current deploy."
|
20
|
+
make_run_task[:rails, 'console']
|
21
|
+
|
22
|
+
desc "Execute a Rake command in the current deploy."
|
23
|
+
make_run_task[:rake, 'db:migrate']
|
24
|
+
|
25
|
+
namespace :rails do
|
26
|
+
desc "Migrates the Rails database."
|
27
|
+
task :db_migrate do
|
28
|
+
queue %{
|
29
|
+
echo "-----> Migrating database"
|
30
|
+
#{echo_cmd %[#{rake} db:migrate]}
|
31
|
+
}
|
32
|
+
end
|
33
|
+
|
34
|
+
desc "Precompiles assets."
|
35
|
+
task :'assets_precompile:force' do
|
36
|
+
queue %{
|
37
|
+
echo "-----> Precompiling asset files"
|
38
|
+
#{echo_cmd %[#{rake} assets:precompile]}
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
desc "Precompiles assets (skips if nothing has changed since the last release)."
|
43
|
+
task :'assets_precompile' do
|
44
|
+
if ENV['force_assets']
|
45
|
+
invoke :'rails:assets_precompile:force'
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
queue %{
|
50
|
+
# Check if the last deploy has assets built, and if it can be re-used.
|
51
|
+
if [ -d "#{current_path}/public/assets" ]; then
|
52
|
+
count=`(
|
53
|
+
diff -r "#{current_path}/vendor/assets/" "./vendor/assets/" 2>/dev/null;
|
54
|
+
diff -r "#{current_path}/app/assets/" "./app/assets/" 2>/dev/null
|
55
|
+
) | wc -l`
|
56
|
+
|
57
|
+
if [ "$((count))" = "0" ]; then
|
58
|
+
echo "-----> Skipping asset precompilation"
|
59
|
+
#{echo_cmd %[cp -R "#{current_path}/public/assets" "./public/assets"]} &&
|
60
|
+
exit
|
61
|
+
fi
|
62
|
+
fi
|
63
|
+
|
64
|
+
echo "-----> Precompiling asset files"
|
65
|
+
#{echo_cmd %[#{rake} assets:precompile]}
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
data/lib/mina/rake.rb
ADDED
@@ -0,0 +1,32 @@
|
|
1
|
+
module Mina
|
2
|
+
class Settings < Hash
|
3
|
+
def method_missing(meth, *args, &blk)
|
4
|
+
name = meth.to_s
|
5
|
+
|
6
|
+
return evaluate(self[meth]) if name.size == 1
|
7
|
+
|
8
|
+
# Ruby 1.8.7 doesn't let you do string[-1]
|
9
|
+
key, suffix = name[0..-2].to_sym, name[-1..-1]
|
10
|
+
|
11
|
+
case suffix
|
12
|
+
when '='
|
13
|
+
self[key] = args.first
|
14
|
+
when '?'
|
15
|
+
include? key
|
16
|
+
when '!'
|
17
|
+
raise Error, "Setting :#{key} is not set" unless include?(key)
|
18
|
+
evaluate self[key]
|
19
|
+
else
|
20
|
+
evaluate self[meth]
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
def evaluate(value)
|
25
|
+
if value.is_a?(Proc)
|
26
|
+
value.call
|
27
|
+
else
|
28
|
+
value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
data/lib/mina/tools.rb
ADDED
data/lib/mina/version.rb
ADDED
data/mina.gemspec
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require './lib/mina/version'
|
2
|
+
|
3
|
+
Gem::Specification.new do |s|
|
4
|
+
s.name = "mina"
|
5
|
+
s.version = Mina.version
|
6
|
+
s.summary = %{Really fast deployer and server automation tool.}
|
7
|
+
s.description = %Q{Builds scripts."}
|
8
|
+
s.authors = ["Rico Sta. Cruz", "Michael Galero"]
|
9
|
+
s.email = ["rico@nadarei.co", "mikong@nadarei.co"]
|
10
|
+
s.homepage = "http://github.com/nadarei/mina"
|
11
|
+
s.files = `git ls-files`.strip.split("\n")
|
12
|
+
s.executables = Dir["bin/*"].map { |f| File.basename(f) }
|
13
|
+
|
14
|
+
s.add_dependency "rake"
|
15
|
+
s.add_dependency "open4"
|
16
|
+
s.add_development_dependency "rspec"
|
17
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# Invokes the main 'mina' command.
|
2
|
+
def run_command(*args)
|
3
|
+
out = ''
|
4
|
+
err = ''
|
5
|
+
@result = nil
|
6
|
+
|
7
|
+
if ENV['rake']
|
8
|
+
rake_version = "~> #{ENV['rake'] || '0.9'}.0"
|
9
|
+
script = %[require 'rubygems' unless Object.const_defined?(:Gem);]
|
10
|
+
script += %[gem 'rake', '#{rake_version}';]
|
11
|
+
script += %[load '#{root('bin/mina')}']
|
12
|
+
cmd = ['ruby', '-e', "#{script}", "--", *args]
|
13
|
+
else
|
14
|
+
cmd = [root('bin/mina'), *args]
|
15
|
+
end
|
16
|
+
|
17
|
+
status =
|
18
|
+
Mina::Tools.popen4(*cmd) do |pid, i, o, e|
|
19
|
+
out = o.read
|
20
|
+
err = e.read
|
21
|
+
end
|
22
|
+
|
23
|
+
@result = status.exitstatus
|
24
|
+
|
25
|
+
@out = out
|
26
|
+
@err = err
|
27
|
+
|
28
|
+
@result
|
29
|
+
end
|
30
|
+
|
31
|
+
# Invokes the main 'mina' command and ensures the exit status is success.
|
32
|
+
def mina(*args)
|
33
|
+
run_command *args
|
34
|
+
if exitstatus != 0 && ENV['verbose']
|
35
|
+
puts stdout
|
36
|
+
puts stderr
|
37
|
+
end
|
38
|
+
|
39
|
+
exitstatus.should == 0
|
40
|
+
end
|
41
|
+
|
42
|
+
def stdout
|
43
|
+
@out
|
44
|
+
end
|
45
|
+
|
46
|
+
def stderr
|
47
|
+
@err
|
48
|
+
end
|
49
|
+
|
50
|
+
def exitstatus
|
51
|
+
@result
|
52
|
+
end
|