mina 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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
@@ -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
@@ -0,0 +1,6 @@
1
+ # This file is invoked from Rake.
2
+ extend Mina::Helpers
3
+ extend Mina::DeployHelpers
4
+
5
+ require 'mina/default'
6
+ require 'mina/deploy' if Rake.application.have_rakefile
@@ -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
@@ -0,0 +1,14 @@
1
+ module Mina
2
+ module Tools
3
+ if IO.respond_to?(:popen4)
4
+ def self.popen4(*cmd, &blk)
5
+ IO.popen4 *cmd, &blk
6
+ end
7
+ else
8
+ def self.popen4(*cmd, &blk)
9
+ require 'open4'
10
+ Open4.popen4 *cmd, &blk
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,5 @@
1
+ module Mina
2
+ def self.version
3
+ "0.1.0"
4
+ end
5
+ end
@@ -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