mina 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|