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