fezzik 0.8.0.beta2 → 0.8.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile CHANGED
@@ -1,2 +1,2 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
2
  gemspec
data/README.md CHANGED
@@ -5,9 +5,13 @@ This is useful for many tasks, including deployment.
5
5
 
6
6
  It wraps a rake-based rsync workflow and tries to keep things simple.
7
7
 
8
+ If upgrading to 0.8 from an earlier version of Fezzik, see [Upgrading](#upgrading).
9
+
8
10
  ## Install
9
11
 
10
- gem install fezzik
12
+ ```
13
+ gem install fezzik
14
+ ```
11
15
 
12
16
  ## Basic setup
13
17
 
@@ -17,27 +21,60 @@ Require Fezzik in your project Rakefile and define a destination:
17
21
  require "fezzik"
18
22
 
19
23
  Fezzik.destination :prod do
20
- set :user, "root"
21
- set :domain, "myapp.com"
24
+ Fezzik.set :user, "root"
25
+ Fezzik.set :domain, "myapp.com"
22
26
  end
23
27
  ```
24
28
 
25
- Write some rake tasks that will execute on the specified destination:
29
+ A host task is similar to a normal Rake task, but will run once for every host defined by `:domain`. The body
30
+ of a host task exposes two methods:
31
+
32
+ ```
33
+ run <command> Run a shell command on the remote host
34
+ host The domain that the currently running host task is targeting
35
+ ```
36
+
37
+ Write some host tasks that will execute on the specified destination:
26
38
 
27
39
  ```ruby
28
40
  namespace :fezzik do
29
- remote_task :touch do
30
- run "touch /tmp/test_file"
41
+ Fezzik.host_task :echo do
42
+ run "echo 'Running on #{host}'"
31
43
  end
32
44
  end
33
45
  ```
34
46
 
35
- Run your remote_tasks with fezzik:
47
+ Run your host tasks with fezzik by passing a destination and list of tasks to run:
36
48
 
37
49
  ```
38
- $ fez prod touch
50
+ $ fez prod echo
39
51
  ```
40
52
 
53
+ ### host_task
54
+
55
+ The `host_task` method is similar to Rake's `task` in functionality, but has a slightly different API due to
56
+ its additional options. A host task is defined with a name and three (optional) options: `:args`, `:deps`,
57
+ and `:roles`. `:args` and `:deps` correspond to Rake's task arguments and task dependencies, and `:roles` is a
58
+ Fezzik-specific option explained later.
59
+
60
+ A Rake task that looks like this:
61
+
62
+ ```ruby
63
+ task :echo, [:arg1, :arg2] => [:dep1, :dep2] do |t, args|
64
+ ...
65
+ end
66
+ ```
67
+
68
+ would look like this as a host task:
69
+
70
+ ```ruby
71
+ Fezzik.host_task :echo, :args => [:arg1, :arg2],
72
+ :deps => [:dep1, :dep2] do |t, args|
73
+ ...
74
+ end
75
+ ```
76
+
77
+
41
78
  ## Deployments
42
79
 
43
80
  One of the more useful things you can use Fezzik for is handling deployments.
@@ -48,25 +85,24 @@ require "fezzik"
48
85
  # Fezzik will automatically load any .rake files it finds in this directory.
49
86
  Fezzik.init(:tasks => "config/tasks")
50
87
 
51
- # Fezzik wraps rake/remote_task, which is the same rake plugin used by Vlad the Deployer.
52
- # See http://hitsquad.rubyforge.org/vlad/doco/variables_txt.html for a full list of variables it supports.
53
-
54
- set :app, "myapp"
55
- set :deploy_to, "/opt/#{app}"
56
- set :release_path, "#{deploy_to}/releases/#{Time.now.strftime("%Y%m%d%H%M")}"
57
- set :local_path, Dir.pwd
58
- set :user, "root"
88
+ # The only special settings are `:domain` and `:user`. The rest are purely convention. All settings can be
89
+ # retrieved in your tasks with `get` (e.g., `Fezzik.get :current_path`).
90
+ Fezzik.set :app, "myapp"
91
+ Fezzik.set :user, "root"
92
+ Fezzik.set :deploy_to, "/opt/#{app}"
93
+ Fezzik.set :release_path, "#{deploy_to}/releases/#{Time.now.strftime("%Y%m%d%H%M")}"
94
+ Fezzik.set :current_path, "#{deploy_to}/current"
59
95
 
60
96
  Fezzik.destination :staging do
61
- set :domain, "myapp-staging.com"
97
+ Fezzik.set :domain, "myapp-staging.com"
62
98
  end
63
99
 
64
100
  Fezzik.destination :prod do
65
- set :domain, "myapp.com"
101
+ Fezzik.set :domain, "myapp.com"
66
102
  end
67
103
  ```
68
104
 
69
- Fezzik comes bundled with some useful rake tasks for common things like deployment.
105
+ Fezzik comes bundled with some useful tasks for common things like deployment.
70
106
  You can download the ones you need:
71
107
 
72
108
  ```
@@ -82,13 +118,13 @@ project.
82
118
  namespace :fezzik do
83
119
  ...
84
120
  desc "runs the executable in project/bin"
85
- remote_task :start do
86
- puts "starting from #{Fezzik::Util.capture_output { run "readlink #{current_path}" }}"
87
- run "cd #{current_path} && ./bin/run_app.sh"
121
+ host_task :start do
122
+ puts "starting from #{(run "readlink #{Fezzik.get :current_path}", :output => capture)[:stdout] }}"
123
+ run "cd #{Fezzik.get :current_path} && ./bin/run_app.sh"
88
124
  end
89
125
 
90
126
  desc "kills the application by searching for the specified process name"
91
- remote_task :stop do
127
+ host_task :stop do
92
128
  puts "stopping app"
93
129
  run "(kill `ps aux | grep 'myapp' | grep -v grep | awk '{print $2}'` || true)"
94
130
  end
@@ -101,7 +137,7 @@ Deploy win!
101
137
  ```
102
138
  $ fez prod deploy
103
139
  ...
104
- myapp deployed!
140
+ [out|myapp.com] myapp deployed!
105
141
  [success]
106
142
  ```
107
143
 
@@ -111,13 +147,13 @@ Configuration often changes when you deploy your project. Fezzik lets you set en
111
147
 
112
148
  ```
113
149
  $ cd config/tasks
114
- $ fez get environment
115
- [new] environment.rake
150
+ $ fez get deploy
151
+ [new] deploy.rake
116
152
  ```
117
153
 
118
154
  ```ruby
119
155
  Fezzik.destination :prod do
120
- set :domain, "myapp.com"
156
+ Fezzik.set :domain, "myapp.com"
121
157
  Fezzik.env :rack_env, "production"
122
158
  end
123
159
  ```
@@ -128,17 +164,16 @@ project directly.
128
164
 
129
165
  ```ruby
130
166
  desc "runs the executable in project/bin"
131
- remote_task :start do
132
- puts "starting from #{Fezzik::Util.capture_output { run "readlink #{current_path}" }}"
133
- run "cd #{current_path} && (source environment.sh || true) && ./bin/run_app.sh"
167
+ Fezzik.host_task :start do
168
+ run "cd #{Fezzik.get :current_path} && (source environment.sh || true) && ./bin/run_app.sh"
134
169
  end
135
170
  ```
136
171
 
137
- You can assign different environments to a subset of the hosts you deploy to.
172
+ You can assign different environments to subsets of hosts:
138
173
 
139
174
  ```ruby
140
175
  Fezzik.destination :prod do
141
- set :domain, ["myapp1.com", "myapp2.com"]
176
+ Fezzik.set :domain, ["myapp1.com", "myapp2.com"]
142
177
  Fezzik.env :rack_env, "production"
143
178
  Fezzik.env :is_canary, "true", :hosts => ["myapp1.com"]
144
179
  end
@@ -149,37 +184,45 @@ This can be useful if you have common environment variables shared across destin
149
184
 
150
185
  ```ruby
151
186
  Fezzik.destination :staging, :prod do
152
- env :unicorn_workers, 4
187
+ Fezzik.env :unicorn_workers, 4
153
188
  end
154
189
  ```
155
190
 
156
191
  You can access the environment settings in your tasks, if you like. It's a hash.
157
192
 
158
193
  ```ruby
159
- task :inspect_environment do
194
+ task :inspect_all_environments do
160
195
  puts Fezzik.environments.inspect
161
196
  end
162
197
  ```
163
198
 
199
+ To access the environment for the currently targeted host:
200
+
201
+ ```ruby
202
+ Fezzik.host_task :inspect_environment do
203
+ puts Fezzik.environemnts[host].inspect
204
+ end
205
+ ```
206
+
164
207
 
165
208
  ## Roles
166
209
 
167
- Fezzik supports role deployments. Roles allow you to assign remote_tasks different configurations according
210
+ Fezzik supports role deployments. Roles allow you to assign host tasks different configurations according
168
211
  to their purpose. For example, you might want to perform your initial package installations as root, but run
169
212
  your app as an unprivileged user.
170
213
 
171
214
  ```ruby
172
215
  Fezzik.destination :prod do
173
- set :domain, "myapp.com"
216
+ Fezzik.set :domain, "myapp.com"
174
217
  Fezzik.role :root_user, :user => "root"
175
218
  Fezzik.role :run_user, :user => "app"
176
219
  end
177
220
 
178
- remote_task :install, :roles => :root_user
221
+ Fezzik.host_task :install, :roles => :root_user
179
222
  # Install all the things.
180
223
  end
181
224
 
182
- remote_task :run, :roles => :run_user
225
+ Fezzik.host_task :run, :roles => :run_user
183
226
  # Run all the things.
184
227
  end
185
228
  ```
@@ -188,7 +231,7 @@ Or, you might have different domains for database deployment and app deployment.
188
231
 
189
232
  ```ruby
190
233
  Fezzik.destination :prod do
191
- set :user, "root"
234
+ Fezzik.set :user, "root"
192
235
  Fezzik.role :db, :domain => "db.myapp.com"
193
236
  Fezzik.role :app, :domain => "myapp.com"
194
237
  end
@@ -205,13 +248,13 @@ end
205
248
  ```
206
249
 
207
250
  The `Fezzik.role` method accepts a role name and a hash of values that you want assigned with the
208
- `set :var, value` syntax. These will override the global or destination settings when that remote_task is
251
+ `set :var, value` syntax. These will override the global or destination settings when a host task is
209
252
  run.
210
253
 
211
254
 
212
255
  ## Utilities
213
256
 
214
- Fezzik exposes some functions that can be useful when running remote tasks.
257
+ Fezzik exposes some functions that can be useful when running host tasks.
215
258
 
216
259
  ### Override hosts from command line
217
260
 
@@ -222,33 +265,30 @@ $ domain="example1.com,example2.com" fez prod deploy
222
265
  Set the "domain" environment variable to override the domains set in your destination block. Useful for running
223
266
  one-off tasks against a subset of your hosts.
224
267
 
225
- ### Capture or redirect output
268
+ ### Capture or modify output
226
269
 
227
- ```ruby
228
- Fezzik::Util.capture_output(&block)
229
- ```
270
+ The output of `run` can be captured or modified instead of printing directly with the host prefix.
230
271
 
231
- Use this function if you would like to hide or capture the normal output that the "run" command prints.
272
+ It can return a hash of `:stdout, :stderr`, or it can stream the raw output without prefixing each host.
232
273
 
233
274
  ```ruby
234
- remote_task :print_hello
235
- # Nothing is printed to stdout
236
- server_output = Fezzik::Util.capture_output { run "echo 'hello'"}
275
+ # prints "[out|myapp.com] hi"
276
+ run "echo 'hi'"
237
277
 
238
- # prints "hello"
239
- puts server_output
240
- end
278
+ # prints "hi"
279
+ run "echo 'hi'", :output => :raw
280
+
281
+ # output == { :stdout => "hi" :stderr => "" }
282
+ output = run "echo 'hi'", :output => :capture
241
283
  ```
242
284
 
243
- ### Inspect the target destination
285
+ ### A note on `puts`
244
286
 
245
- You can see which destination fezzik is operating on from within your tasks.
287
+ Ruby's `puts` is not thread-safe. In particular, running multiple `puts` in parallel can result in the
288
+ newlines being separated from the rest of the string.
246
289
 
247
- ```ruby
248
- task :print_destination
249
- puts Fezzik.target_destination
250
- end
251
- ```
290
+ As a helper, any `puts` used from within a host task will call an overridden thread-safe version of `puts`. If
291
+ `$stdout.puts` or `$stderr.puts` is used instead, the normal thread-unsafe method will be called.
252
292
 
253
293
 
254
294
  ## DSL
@@ -258,6 +298,9 @@ the following functions:
258
298
 
259
299
  ```
260
300
  destination
301
+ host_task
302
+ set
303
+ get
261
304
  env
262
305
  role
263
306
  capture_output
@@ -269,19 +312,24 @@ This lets you write your configuration more tersely:
269
312
  include Fezzik::DSL
270
313
 
271
314
  destination :prod do
315
+ set :domain "myapp.com"
272
316
  env :rack_env, "production"
273
317
  role :root_user, :user => "root"
274
318
  end
319
+
320
+ host_task :echo do
321
+ run "echo 'Running on #{host}'"
322
+ end
275
323
  ```
276
324
 
277
325
 
278
- ## Tasks
326
+ ## Included Tasks
279
327
 
280
- Fezzik has a number of useful tasks other than deploy.rake and environment.rake. These can also be downloaded
328
+ Fezzik has a number of useful tasks other than those defined in deploy.rake. These can also be downloaded
281
329
  with `$ fez get <task>` and placed in the directory you specify with `Fezzik.init(:tasks => "config/tasks")`.
282
330
 
283
331
  These tasks are meant to be starting points. For example, if you want to save your environment files in a
284
- place that's not your project root you can simply edit the task in environment.rake.
332
+ place that's not your project root you can simply edit the task in deploy.rake.
285
333
 
286
334
  If you write a recipe that would be useful to other developers, please submit a pull request!
287
335
 
@@ -318,8 +366,8 @@ $ fez get rollback
318
366
  [new] rollback.rake
319
367
  ```
320
368
 
321
- Emergency! Rollback! Every deployment you make is saved on the server by default.
322
- You can move between these deployments (to roll back, for example), with the rollback.rb recipe.
369
+ Emergency! Rollback! Every deployment you make is saved on the server if you use the default tasks defined in
370
+ deploy.rake. You can move between these deployments (to roll back, for example), with rollback.rake.
323
371
 
324
372
  ```
325
373
  $ fez prod rollback
@@ -332,3 +380,47 @@ configuring for root@myapp.com
332
380
  Rollback to release (0):
333
381
  ```
334
382
 
383
+ ### Rake passthroughs
384
+
385
+ Because Fezzik is built on Rake it passes through some options directly to Rake. You can use these with the
386
+ `fez` command as if you were running `rake` directly:
387
+
388
+ ```
389
+ --trace Turn on invoke/execute tracing, enable full backtrace.
390
+ --dry-run Do a dry run without executing actions.
391
+ ```
392
+
393
+ <a name="upgrading"></a>
394
+ ## Upgrading
395
+
396
+ ### 0.8.0
397
+
398
+ Fezzik 0.8 replaces much of its internal piping with [Weave](https://github.com/cespare/weave), an excellent
399
+ parallel SSH library. This allows for cleaner output and faster task execution due to using a shared
400
+ connection pool, but necessarily introduces a few breaking changes. These are detailed below.
401
+
402
+ ### Breaking changes
403
+
404
+ - The method `target_host` is gone and has been replaced by using `host` in a host task. The old method `host`
405
+ has been replaced with the new one defined in host tasks. There should no longer be a reason to use the old
406
+ `host` method.
407
+ - The `current_path` setting is no longer set automatically. To continue using it in your deployments, define
408
+ it manually:
409
+
410
+ ```ruby
411
+ Fezzik.set :current_path, "#{Fezzik.get :deploy_to}/current`.
412
+ ```
413
+
414
+ - The helper method `rsync` no longer exists. Instead of `rsync "..."` use `system("rsync -az ...")`
415
+
416
+ ### Deprecations
417
+
418
+ - The `remote_task` method is deprecated. Use `host_task` instead.
419
+ - Using settings defined by `Fezzik.set` as top-level method calls is deprecated. Instead of `domain`, use
420
+ `Fezzik.get :domain` instead.
421
+ - Fezzik::Util.capture_output is deprecated. Pass options directly to `run` instead:
422
+
423
+ ```ruby
424
+ run "echo 'hi'", :output => :capture
425
+ run "echo 'hi'", :output => :raw
426
+ ```
data/bin/fez CHANGED
@@ -7,15 +7,14 @@ require "fezzik"
7
7
  module Fezzik
8
8
  RAKE_AT_LEAST_090 = defined?(Rake::VERSION) &&
9
9
  Gem.loaded_specs["rake"].version >= Gem::Version.create("0.9.0")
10
- end
11
10
 
12
- # Required for using rake/remote-task with rake >= 0.9.x
13
- # TODO(caleb): Still necessary w/out RRT?
14
- include Rake::DSL if Fezzik::RAKE_AT_LEAST_090
11
+ # Required for using rake/remote-task with rake >= 0.9.x
12
+ # TODO(caleb): Still necessary w/out RRT?
13
+ include Rake::DSL if Fezzik::RAKE_AT_LEAST_090
15
14
 
16
- Fezzik.activated = true
15
+ Fezzik.activated = true
17
16
 
18
- USAGE = <<EOF
17
+ USAGE = <<EOF
19
18
  Version #{Fezzik::VERSION}
20
19
  fez <destination> <tasks> # Run deployment tasks on destination servers
21
20
  fez get <tasks> # Download tasks to use in your project
@@ -26,105 +25,146 @@ fez --task-names # Print all tasks names
26
25
  fez --destinations # Print all destinations
27
26
  EOF
28
27
 
29
- def print_usage_and_exit
30
- puts USAGE
31
- exit
32
- end
28
+ COLORS = { :red => 1, :green => 2 }
29
+ def self.color_string(string, color)
30
+ return string unless STDOUT.isatty
31
+ "\e[01;#{COLORS[color]+30}m#{string}\e[m"
32
+ end
33
+ private_class_method :color_string
33
34
 
34
- def print_version_and_exit
35
- puts "Version #{Fezzik::VERSION}"
36
- exit
37
- end
35
+ def self.capture_output(&block)
36
+ output = StringIO.new
37
+ $stdout = output
38
+ block.call
39
+ output.string
40
+ ensure
41
+ $stdout = STDOUT
42
+ end
43
+ private_class_method :capture_output
38
44
 
39
- def print_destinations_and_exit
40
- Rake.application.init
41
- Rake.application.load_rakefile
42
- puts Fezzik.destinations.to_a.join("\n")
43
- exit 0
44
- end
45
+ def self.print_usage_and_exit
46
+ puts USAGE
47
+ exit
48
+ end
49
+ private_class_method :print_usage_and_exit
45
50
 
46
- def display_tasks_and_exit(hide_descriptions = false)
47
- if Fezzik::RAKE_AT_LEAST_090
48
- Rake::TaskManager.record_task_metadata = true
49
- Rake::application.options.show_tasks = :tasks
51
+ def self.print_version_and_exit
52
+ puts "Version #{Fezzik::VERSION}"
53
+ exit
50
54
  end
51
- Rake.application.init
52
- Rake.application.load_rakefile
53
- Rake.application.options.show_task_pattern = /^fezzik:/
54
- output = Fezzik::Util.capture_output { Rake.application.display_tasks_and_comments }
55
- output.gsub!(/^rake fezzik:/, "fez <destination> ")
56
- output.gsub!(/^fez <destination> (\S+).*$/, "\\1") if hide_descriptions
57
- output.strip.empty? ? $stderr.puts("(No Fezzik tasks with descriptions are defined.)") : puts(output)
58
- exit 0
59
- end
55
+ private_class_method :print_version_and_exit
60
56
 
61
- TASKS_URL = "https://raw.github.com/dmacdougall/fezzik/master/tasks"
62
- def download_tasks_and_exit
63
- OPTIONS[1..-1].each do |task|
64
- task += ".rake" unless task =~ /\.rake$/
65
- system("curl -f #{TASKS_URL}/#{task} -o #{task} > /dev/null 2>&1")
66
- if $? == 0
67
- puts Fezzik.color_string(" [new]", :green) + " #{task}"
68
- else
69
- puts Fezzik.color_string(" [fail]", :red) + " #{task}"
70
- end
57
+ def self.print_destinations_and_exit
58
+ Rake.application.init
59
+ Rake.application.load_rakefile
60
+ puts Fezzik.destinations.to_a.join("\n")
61
+ exit 0
71
62
  end
72
- exit 0
73
- end
63
+ private_class_method :print_destinations_and_exit
74
64
 
75
- def run_fezzik_tasks
76
- ENV["fezzik_destination"] = OPTIONS[0]
77
- Fezzik.init
78
- Rake.application.init
79
- Rake.application.load_rakefile
80
- set :domain, ENV["domain"].split(",") if ENV["domain"]
81
- begin
82
- host_list = Array(domain).join("\n ")
83
- puts "Targeting hosts:"
84
- puts " #{host_list}"
85
- rescue NameError => e
86
- puts "Invalid destination: #{Fezzik.target_destination}"
87
- puts "Make sure this destination is configured and includes `set :domain, \"yourdomain.com\"`"
88
- puts Fezzik.color_string("[fail]", :red)
89
- exit 1
65
+ def self.display_tasks_and_exit(hide_descriptions = false)
66
+ if Fezzik::RAKE_AT_LEAST_090
67
+ Rake::TaskManager.record_task_metadata = true
68
+ Rake::application.options.show_tasks = :tasks
69
+ end
70
+ Rake.application.init
71
+ Rake.application.load_rakefile
72
+ Rake.application.options.show_task_pattern = /^fezzik:/
73
+ output = capture_output { Rake.application.display_tasks_and_comments }
74
+ output.gsub!(/^rake fezzik:/, "fez <destination> ")
75
+ output.gsub!(/^fez <destination> (\S+).*$/, "\\1") if hide_descriptions
76
+ output.strip.empty? ? $stderr.puts("(No Fezzik tasks with descriptions are defined.)") : puts(output)
77
+ exit 0
90
78
  end
91
- begin
92
- tasks = OPTIONS[1..-1]
93
- tasks.each do |task_with_params|
94
- task_name, params = Fezzik::Util.split_task_and_params(task_with_params)
95
- Rake::Task["fezzik:#{task_name}"].invoke(*params)
79
+ private_class_method :display_tasks_and_exit
80
+
81
+ TASKS_URL = "https://raw.github.com/dmacdougall/fezzik/master/tasks"
82
+ def self.download_tasks_and_exit
83
+ OPTIONS[1..-1].each do |task|
84
+ task += ".rake" unless task =~ /\.rake$/
85
+ system("curl -f #{TASKS_URL}/#{task} -o #{task} > /dev/null 2>&1")
86
+ if $? == 0
87
+ puts color_string(" [new]", :green) + " #{task}"
88
+ else
89
+ puts color_string(" [fail]", :red) + " #{task}"
90
+ end
96
91
  end
97
- puts Fezzik.color_string("[success]", :green)
98
- rescue SystemExit => e
99
- if e.status == 0
100
- puts Fezzik.color_string("[success]", :green)
92
+ exit 0
93
+ end
94
+ private_class_method :download_tasks_and_exit
95
+
96
+ def self.split_task_and_params(task_with_params)
97
+ params_match = /(.+)\[(.+)\]/.match(task_with_params)
98
+ if params_match
99
+ task = params_match[1]
100
+ params = params_match[2].split(",")
101
101
  else
102
- puts Fezzik.color_string("[fail]", :red)
103
- exit e.status
102
+ task = task_with_params
103
+ params = nil
104
104
  end
105
- rescue StandardError => e
106
- puts e.message
107
- puts e.backtrace
108
- puts Fezzik.color_string("[fail]", :red)
109
- fail
105
+ [task, params]
110
106
  end
111
- end
107
+ private_class_method :split_task_and_params
108
+
109
+ def self.run_fezzik_tasks
110
+ ENV["fezzik_destination"] = OPTIONS[0]
111
+ Fezzik.init
112
+ Rake.application.init
113
+ Rake.application.load_rakefile
114
+ set :domain, ENV["domain"].split(",") if ENV["domain"]
115
+ begin
116
+ host_list = Array(get :domain).join("\n ")
117
+ puts "Targeting hosts:"
118
+ puts " #{host_list}"
119
+ rescue NameError => e
120
+ puts "Invalid destination: #{Fezzik.target_destination}"
121
+ puts "Make sure this destination is configured and includes `set :domain, \"yourdomain.com\"`"
122
+ puts color_string("[fail]", :red)
123
+ exit 1
124
+ end
125
+ begin
126
+ tasks = OPTIONS[1..-1]
127
+ tasks.each do |task_with_params|
128
+ task_name, params = split_task_and_params(task_with_params)
129
+ Rake::Task["fezzik:#{task_name}"].invoke(*params)
130
+ end
131
+ puts color_string("[success]", :green)
132
+ rescue SystemExit => e
133
+ if e.status == 0
134
+ puts color_string("[success]", :green)
135
+ else
136
+ puts color_string("[fail]", :red)
137
+ exit e.status
138
+ end
139
+ rescue StandardError => e
140
+ puts e.message
141
+ puts e.backtrace
142
+ puts color_string("[fail]", :red)
143
+ exit 1
144
+ end
145
+ end
146
+ private_class_method :run_fezzik_tasks
147
+
148
+ OPTIONS = ARGV.dup
149
+ # Rake directly inspects ARGV and will crash if it doesn't recognize a flag that was passed to fez.
150
+ ARGV.clear
151
+ # Pass these options through to Rake.
152
+ # TODO: Pass full trace options through (e.g., --trace=STDOUT)
153
+ ARGV << "--trace" if OPTIONS.delete("--trace")
154
+ ARGV << "--dry-run" if OPTIONS.delete("--dry-run")
112
155
 
113
- # Rake directly inspects ARGV and will crash if it doesn't recognize a flag that was passed to fez.
114
- OPTIONS = ARGV.dup
115
- ARGV.clear
116
-
117
- # Pass these options through to Rake.
118
- ARGV << "--trace" if OPTIONS.delete("--trace")
119
- ARGV << "--dry-run" if OPTIONS.delete("--dry-run")
120
-
121
- case OPTIONS[0]
122
- when nil then abort USAGE
123
- when "-h", "--help" then print_usage_and_exit
124
- when "-v", "--version" then print_version_and_exit
125
- when "-T", "--tasks" then display_tasks_and_exit
126
- when "get" then download_tasks_and_exit
127
- when "--task-names" then display_tasks_and_exit(hide_descriptions = true)
128
- when "--destinations" then print_destinations_and_exit
129
- else run_fezzik_tasks
156
+ def self.main
157
+ case OPTIONS[0]
158
+ when nil then abort USAGE
159
+ when "-h", "--help" then print_usage_and_exit
160
+ when "-v", "--version" then print_version_and_exit
161
+ when "-T", "--tasks" then display_tasks_and_exit
162
+ when "get" then download_tasks_and_exit
163
+ when "--task-names" then display_tasks_and_exit(hide_descriptions = true)
164
+ when "--destinations" then print_destinations_and_exit
165
+ else run_fezzik_tasks
166
+ end
167
+ end
130
168
  end
169
+
170
+ Fezzik.main()
data/example/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source :rubygems
1
+ source "https://rubygems.org"
2
2
  gem "fezzik", :path => ".."
3
3
  gem "rake"
data/example/Rakefile CHANGED
@@ -1,12 +1,13 @@
1
1
  require "fezzik"
2
2
  include Fezzik::DSL
3
+ include Rake::DSL
3
4
 
4
5
  Fezzik.init(:tasks => "../tasks")
5
6
 
6
7
  set :app, "test"
7
- set :deploy_to, "/opt/#{app}"
8
- set :release_path, "#{deploy_to}/releases/#{Time.now.strftime("%Y%m%d%H%M")}"
9
- set :local_path, Dir.pwd
8
+ set :deploy_to, "/opt/#{get :app}"
9
+ set :release_path, "#{get :deploy_to}/releases/#{Time.now.strftime("%Y%m%d%H%M")}"
10
+ set :current_path, "#{get :deploy_to}/current"
10
11
  set :user, "root"
11
12
 
12
13
  destination :vagrant do
@@ -24,44 +25,44 @@ end
24
25
 
25
26
  namespace :fezzik do
26
27
  desc "Print information on the destination and environment"
27
- remote_task :info do
28
+ host_task :info do
28
29
  puts "Destination: #{Fezzik.target_destination.inspect}"
29
30
  puts "Environment: #{Fezzik.environments.inspect}"
30
31
  end
31
32
 
32
33
  desc "An example of appending..."
33
- remote_task :append do
34
+ host_task :append do
34
35
  run "echo hi"
35
36
  end
36
37
 
37
38
  desc "...new actions to an existing task"
38
- remote_task :append do
39
+ host_task :append do
39
40
  run "echo bye"
40
41
  end
41
42
 
42
43
  desc "Tasks can take arguments"
43
- remote_task :args, :arg1, :arg2 do |t, args|
44
+ host_task :argtest, :args => [:arg1, :arg2] do |t, args|
44
45
  puts "args: #{args.inspect}"
45
46
  end
46
47
 
47
48
  desc "Tasks may print out the host and user."
48
- remote_task :host do
49
+ host_task :host do
49
50
  puts "#{user}@#{host}"
50
51
  end
51
52
 
52
53
  desc "Tasks can depend on other tasks"
53
- remote_task :deps => :append do
54
+ host_task :depstest, :deps => :append do
54
55
  puts "some other task just ran"
55
56
  end
56
57
 
57
58
  desc "If a server command fails the block should exist and fezzik should indicate failure"
58
- remote_task :fails do
59
+ host_task :fails do
59
60
  run "cd i-dont-exist"
60
61
  run "echo 'should not print'"
61
62
  end
62
63
 
63
64
  desc "Use roles to override settings"
64
- remote_task :roles, :roles => [:role1, :role2] do
65
+ host_task :roles, :roles => [:role1, :role2] do
65
66
  puts "role_var: #{get :role_var}"
66
67
  end
67
68
  end
@@ -0,0 +1,4 @@
1
+ #!/bin/bash
2
+
3
+ yes &> /dev/null &
4
+ echo $! > /tmp/app.pid
data/lib/fezzik.rb CHANGED
@@ -6,7 +6,6 @@ require "set"
6
6
 
7
7
  require "fezzik/host_task"
8
8
  require "fezzik/base"
9
- require "fezzik/colors"
10
9
  require "fezzik/dsl"
11
10
  require "fezzik/environment"
12
11
  require "fezzik/role"
data/lib/fezzik/base.rb CHANGED
@@ -14,7 +14,8 @@ module Fezzik
14
14
  end
15
15
 
16
16
  Object.send :define_method, name do
17
- # TODO: Add deprecation note for setting/accessing global settings
17
+ warn "WARN [Fezzik]: accessing #{name} at the top-level is deprecated as of 0.8.0," +
18
+ " use Fezzik.get(:#{name}) instead"
18
19
  Fezzik.get name
19
20
  end
20
21
  end
@@ -24,11 +25,10 @@ module Fezzik
24
25
  @@settings[name]
25
26
  end
26
27
 
27
- # TODO(caleb): Private method?
28
28
  def self.clear(name) @@settings.delete(name) end
29
29
 
30
- # TODO: add deprecation warning for remote_task
31
30
  def self.remote_task(*args, &block)
31
+ warn "WARN [Fezzik]: remote_task is deprecated as of 0.8.0, use host_task instead"
32
32
  roles = (Hash === args.last && args.last[:roles]) ? args.pop[:roles] : []
33
33
  name, args, deps = Rake.application.resolve_args(args)
34
34
  host_task(name, { :args => Array(args), :deps => Array(deps), :roles => Array(roles) }, &block)
@@ -50,7 +50,9 @@ module Fezzik
50
50
  @target_destination = ENV["fezzik_destination"].to_sym rescue nil
51
51
  unless options[:tasks].nil?
52
52
  $stderr.puts "Loading Fezzik tasks from #{@options[:tasks]}"
53
- Dir[File.join(File.expand_path(@options[:tasks]), "**", "*.rake")].sort.each { |lib| import lib }
53
+ Dir[File.join(File.expand_path(@options[:tasks]), "**", "*.rake")].sort.each do |lib|
54
+ Rake.application.add_import(lib)
55
+ end
54
56
  end
55
57
  end
56
58
 
data/lib/fezzik/role.rb CHANGED
@@ -30,4 +30,5 @@ module Fezzik
30
30
  to_clear.each { |setting| Fezzik.clear setting }
31
31
  to_set.each { |setting, value| Fezzik.set setting, value }
32
32
  end
33
+ private_class_method :override_settings
33
34
  end
data/lib/fezzik/util.rb CHANGED
@@ -1,24 +1,14 @@
1
1
  module Fezzik
2
2
  module Util
3
3
  def self.capture_output(&block)
4
+ warn "WARN [Fezzik]: Fezzik::Util.capture_output is deprecated as of 0.8.0," +
5
+ " use `run \"...\", :output => capture` instead"
4
6
  output = StringIO.new
5
- Thread.current[:stdout] = output
7
+ $stdout = output
6
8
  block.call
7
9
  output.string
8
10
  ensure
9
- Thread.current[:stdout] = STDOUT
10
- end
11
-
12
- def self.split_task_and_params(task_with_params)
13
- params_match = /(.+)\[(.+)\]/.match(task_with_params)
14
- if params_match
15
- task = params_match[1]
16
- params = params_match[2].split(",")
17
- else
18
- task = task_with_params
19
- params = nil
20
- end
21
- [task, params]
11
+ $stdout = STDOUT
22
12
  end
23
13
  end
24
14
  end
@@ -1,3 +1,3 @@
1
1
  module Fezzik
2
- VERSION = "0.8.0.beta2"
2
+ VERSION = "0.8.0.beta3"
3
3
  end
data/tasks/command.rake CHANGED
@@ -31,6 +31,8 @@ namespace :fezzik do
31
31
  end
32
32
 
33
33
  desc "run a single command on destination servers"
34
- remote_task(:command_execute, :command) { |t, args| run args[:command], :continue_on_failure => true }
34
+ host_task(:command_execute, :args => :command) do |t, args|
35
+ run args[:command], :continue_on_failure => true
36
+ end
35
37
  end
36
38
 
data/tasks/deploy.rake CHANGED
@@ -1,60 +1,79 @@
1
1
  require "fileutils"
2
+ require "fezzik"
3
+
4
+ include Fezzik::DSL
2
5
 
3
6
  namespace :fezzik do
7
+ desc "writes environment files on the server"
8
+ host_task :write_environment do
9
+ puts "writing environment files"
10
+ environment = Fezzik.environments[host]
11
+ staging_path = "/tmp/#{get :app}/environments/#{host}"
12
+ FileUtils.mkdir_p staging_path
13
+ File.open("#{staging_path}/environment.rb", "w") do |f|
14
+ environment.each do |key, value|
15
+ quote = value.is_a?(Numeric) ? '' : '"'
16
+ f.puts "#{key.to_s.upcase} = #{quote}#{value}#{quote}"
17
+ end
18
+ end
19
+ File.open("#{staging_path}/environment.sh", "w") do |f|
20
+ environment.each { |key, value| f.puts %[export #{key.to_s.upcase}="#{value}"] }
21
+ end
22
+ system "rsync -az #{staging_path}/environment.* #{user}@#{host}:#{get :release_path}"
23
+ end
24
+
4
25
  desc "stages the project for deployment in /tmp"
5
26
  task :stage do
6
- puts "staging project in /tmp/#{app}"
7
- FileUtils.rm_rf "/tmp/#{app}"
8
- FileUtils.mkdir_p "/tmp/#{app}/staged"
27
+ puts "staging project in /tmp/#{get :app}"
28
+ FileUtils.rm_rf "/tmp/#{get :app}"
29
+ FileUtils.mkdir_p "/tmp/#{get :app}/staged"
9
30
  # Use rsync to preserve executability and follow symlinks.
10
- system("rsync -aqE #{local_path}/. /tmp/#{app}/staged")
31
+ system("rsync -aqE ./ /tmp/#{get :app}/staged")
11
32
  end
12
33
 
13
34
  desc "performs any necessary setup on the destination servers prior to deployment"
14
- remote_task :setup do
35
+ host_task :setup do
15
36
  puts "setting up servers"
16
- run "mkdir -p #{deploy_to}/releases"
37
+ run "mkdir -p #{get :deploy_to}/releases"
17
38
  end
18
39
 
19
40
  desc "rsyncs the project from its staging location to each destination server"
20
- remote_task :push => [:stage, :setup] do
21
- puts "pushing to #{target_host}:#{release_path}"
41
+ host_task :push, :deps => [:stage, :setup] do
42
+ puts "pushing to #{user}@#{host}:#{get :release_path}"
22
43
  # Copy on top of previous release to optimize rsync
23
- rsync "-q", "--copy-dest=#{current_path}", "/tmp/#{app}/staged/", "#{target_host}:#{release_path}"
44
+ system "rsync -azq --copy-dest=#{get :current_path} /tmp/#{get :app}/staged/" +
45
+ " #{user}@#{host}:#{get :release_path}"
24
46
  end
25
47
 
26
48
  desc "symlinks the latest deployment to /deploy_path/project/current"
27
- remote_task :symlink do
28
- puts "symlinking current to #{release_path}"
29
- run "cd #{deploy_to} && ln -fns #{release_path} current"
49
+ host_task :symlink do
50
+ puts "symlinking current to #{get :release_path}"
51
+ run "cd #{get :deploy_to} && ln -fns #{get :release_path} current"
30
52
  end
31
53
 
32
54
  desc "runs the executable in project/bin"
33
- remote_task :start do
34
- puts "starting from #{Fezzik::Util.capture_output { run "readlink #{current_path}" }}"
35
- run "cd #{current_path} && (source environment.sh || true) && ./bin/run_app.sh"
55
+ host_task :start do
56
+ # A very simple run_app.sh might contain the following:
57
+ # #!/bin/sh
58
+ #
59
+ # yes &> /dev/null &
60
+ # echo $! > /tmp/app.pid
61
+ puts "starting from #{(run "readlink #{get :current_path}", :output => :capture)[:stdout] }"
62
+ run "cd #{get :current_path} && source environment.sh && ./run_app.sh"
36
63
  end
37
64
 
38
65
  desc "kills the application by searching for the specified process name"
39
- remote_task :stop do
40
- # Replace YOUR_APP_NAME with whatever is run from your bin/run_app.sh file.
41
- # If you'd like to do this nicer you can save the PID of your process with `echo $! > app.pid`
42
- # in the start task and read the PID to kill here in the stop task.
43
- # puts "stopping app"
44
- # run "(kill -9 `ps aux | grep 'YOUR_APP_NAME' | grep -v grep | awk '{print $2}'` || true)"
66
+ host_task :stop do
67
+ puts "stopping app"
68
+ run "cd #{get :current_path} && touch /tmp/app.pid && kill `cat /tmp/app.pid` || true && rm /tmp/app.pid"
45
69
  end
46
70
 
47
71
  desc "restarts the application"
48
- remote_task :restart do
49
- Rake::Task["fezzik:stop"].invoke
50
- Rake::Task["fezzik:start"].invoke
72
+ host_task :restart, :deps => [:stop, :start] do
51
73
  end
52
74
 
53
75
  desc "full deployment pipeline"
54
- task :deploy do
55
- Rake::Task["fezzik:push"].invoke
56
- Rake::Task["fezzik:symlink"].invoke
57
- Rake::Task["fezzik:restart"].invoke
58
- puts "#{app} deployed!"
76
+ task :deploy => [:push, :write_environment, :symlink, :restart] do
77
+ puts "#{get :app} deployed!"
59
78
  end
60
79
  end
data/tasks/rollback.rake CHANGED
@@ -8,12 +8,14 @@
8
8
  namespace :fezzik do
9
9
  desc "interactively roll back deployment"
10
10
  task :rollback do
11
- target_domain = domain.is_a?(Array) ? domain.first : domain
12
- releases = `ssh #{target_domain} "cd #{File.dirname(release_path)} && ls"`.split(/\s+/).reverse
13
- current_release = File.basename(`ssh #{target_domain} "cd #{deploy_to} && readlink current"`).strip
11
+ target_domain = Array(get :domain).first
12
+ releases = `ssh #{target_domain} "cd #{File.dirname(get :release_path)} && ls"`.split(/\s+/).reverse
13
+ current_release = File.basename(`ssh #{target_domain} "cd #{get :deploy_to} && readlink current"`).strip
14
14
  puts "=== Releases ==="
15
15
  puts "0: Abort"
16
- releases.each_index { |i| puts "#{i+1}: #{releases[i]} #{releases[i] == current_release ? "(current)" : ""}" }
16
+ releases.each_index do |i|
17
+ puts "#{i+1}: #{releases[i]} #{releases[i] == current_release ? "(current)" : ""}"
18
+ end
17
19
  print "Rollback to release (0): "
18
20
  STDOUT.flush
19
21
  release_num = STDIN.gets.chomp.to_i
@@ -29,10 +31,10 @@ namespace :fezzik do
29
31
 
30
32
  desc "rolls back deployment to the previous release"
31
33
  task :rollback_one do
32
- target_domain = domain.is_a?(Array) ? domain.first : domain
33
- current_release = File.basename(`ssh #{target_domain} "cd #{deploy_to} && readlink current"`).strip
34
+ target_domain = Array(get :domain).first
35
+ current_release = File.basename(`ssh #{target_domain} "cd #{get :deploy_to} && readlink current"`).strip
34
36
  previous_release = %x{
35
- ssh #{target_domain} "cd #{File.dirname(release_path)} && ls | grep "#{current_release}" -B 1 | head -1"
37
+ ssh #{target_domain} "cd #{File.dirname(get :release_path)} && ls | grep "#{current_release}" -B 1 | head -1"
36
38
  }.strip
37
39
 
38
40
  if previous_release == current_release
@@ -44,10 +46,10 @@ namespace :fezzik do
44
46
  end
45
47
 
46
48
  desc "rolls back deployment to a specific release"
47
- remote_task :rollback_to_release, :selected_release do |t, args|
49
+ host_task :rollback_to_release, :args => :selected_release do |t, args|
48
50
  selected_release = args[:selected_release]
49
- puts "rolling #{target_host} back to #{selected_release}"
50
- run "cd #{deploy_to} && ln -fns #{File.dirname(release_path)}/#{selected_release} current"
51
+ puts "rolling #{host} back to #{selected_release}"
52
+ run "cd #{get :deploy_to} && ln -fns #{File.dirname(get :release_path)}/#{selected_release} current"
51
53
  Rake::Task["fezzik:restart"].invoke
52
54
  end
53
55
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: fezzik
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.0.beta2
4
+ version: 0.8.0.beta3
5
5
  prerelease: 6
6
6
  platform: ruby
7
7
  authors:
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2013-02-22 00:00:00.000000000 Z
13
+ date: 2013-02-26 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
@@ -93,10 +93,10 @@ files:
93
93
  - completions/_fez.zsh
94
94
  - example/Gemfile
95
95
  - example/Rakefile
96
+ - example/run_app.sh
96
97
  - fezzik.gemspec
97
98
  - lib/fezzik.rb
98
99
  - lib/fezzik/base.rb
99
- - lib/fezzik/colors.rb
100
100
  - lib/fezzik/dsl.rb
101
101
  - lib/fezzik/environment.rb
102
102
  - lib/fezzik/host_task.rb
@@ -105,12 +105,10 @@ files:
105
105
  - lib/fezzik/version.rb
106
106
  - tasks/command.rake
107
107
  - tasks/deploy.rake
108
- - tasks/environment.rake
109
108
  - tasks/rollback.rake
110
109
  - test/integration/base_test.rb
111
110
  - test/integration/environment_test.rb
112
111
  - test/integration_test_helper.rb
113
- - weave_todo.md
114
112
  homepage: http://github.com/dmacdougall/fezzik
115
113
  licenses: []
116
114
  post_install_message:
@@ -123,6 +121,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
123
121
  - - ! '>='
124
122
  - !ruby/object:Gem::Version
125
123
  version: '0'
124
+ segments:
125
+ - 0
126
+ hash: -2670329324762576840
126
127
  required_rubygems_version: !ruby/object:Gem::Requirement
127
128
  none: false
128
129
  requirements:
@@ -138,4 +139,3 @@ summary: Fezzik adds remote ssh capabilities to Rake. It simplifies running comm
138
139
  on remote servers and can be used for anything from deploying code to installing
139
140
  libraries remotely.
140
141
  test_files: []
141
- has_rdoc:
data/lib/fezzik/colors.rb DELETED
@@ -1,7 +0,0 @@
1
- module Fezzik
2
- COLORS = { :red => 1, :green => 2 }
3
- def self.color_string(string, color)
4
- return string unless STDOUT.isatty
5
- "\e[01;#{COLORS[color]+30}m#{string}\e[m"
6
- end
7
- end
@@ -1,45 +0,0 @@
1
- require "fileutils"
2
-
3
- # Any variables set in deploy.rb with `Fezzik.env` will be saved on the server in two files:
4
- # environment.sh and environment.rb. The first can be loaded into the shell environment before the run script
5
- # is called, and the second is made available to be required into your code. You can use your own
6
- # environment.rb file for development and it will be overwritten by this task when the code deploys.
7
- namespace :fezzik do
8
- desc "saves variables set by `Fezzik.env` into a local staging area before deployment"
9
- task :save_environment do
10
- Fezzik.environments.each do |server, environment|
11
- root_config_dir = "/tmp/#{app}/#{server}_config"
12
- FileUtils.mkdir_p root_config_dir
13
- File.open(File.join(root_config_dir, "environment.rb"), "w") do |file|
14
- environment.each do |key, value|
15
- quote = value.is_a?(Numeric) ? '' : '"'
16
- file.puts "#{key.to_s.upcase} = #{quote}#{value}#{quote}"
17
- end
18
- end
19
- File.open(File.join(root_config_dir, "environment.sh"), "w") do |file|
20
- environment.each { |key, value| file.puts %[export #{key.to_s.upcase}="#{value}"] }
21
- end
22
- end
23
- end
24
-
25
- # Append to existing actions defined in deploy.rake. This works because we import .rake files alphabetically,
26
- # so the tasks defined in deploy.rake will be executed before these defined in environment.rake.
27
- # TODO: Can these be handled through dependencies?
28
- # task :stage => :save_environment
29
- # task :push => :push_config
30
- task :stage do
31
- Rake::Task["fezzik:save_environment"].invoke
32
- end
33
-
34
- task :push do
35
- # Copy over the appropriate configs for the target
36
- server = target_host.gsub(/^.*@/, "")
37
- config_directory = "/tmp/#{app}/#{server}_config"
38
- if File.directory?(config_directory)
39
- ["environment.rb", "environment.sh"].each do |config_file|
40
- rsync "-q", "#{config_directory}/#{config_file}",
41
- "#{target_host}:#{release_path}/#{config_file}"
42
- end
43
- end
44
- end
45
- end
data/weave_todo.md DELETED
@@ -1,21 +0,0 @@
1
- ## Breaking changes in the weave branch
2
-
3
- * `target_host` is gone
4
- * `host` is now an alias for `target_host`, not a method
5
- * `rsync` no longer exists (unless we write a new helper function)
6
-
7
- ## Other docs to write
8
-
9
- * Call `warn` for deprecation notices
10
- * document --trace and --dry-run passthrough flags
11
- * puts is not thread-safe; instead, use print "" + "\n" (NOTE: Caleb -- this isn't actually true; the `puts`
12
- you get in a `remote_task` is a Weave wrapper, which is thread-safe. However, if you use `STDERR.puts` or
13
- `STDOUT.puts` or `print` or anything else, it's not going to be threadsafe. This may be worth pointing out
14
- in the docs.)
15
- * capture_output captures the host prefix on each line. Instead pass :capture => :output to `run`.
16
- You can also use capture_output and pass :capture => raw
17
- * We no longer provide `current_path`, which is a RRT-ism. However, this was kind of a convention anyway --
18
- RRT sets it to "current" and we generally leave that alone. In fact, if you look at the default deploy task
19
- (https://github.com/dmacdougall/fezzik/blob/master/tasks/deploy.rake) you'll see that we use `current_path`
20
- in some places and the string "current" in others. The new convention should just be `set :current_path,
21
- "#{get :deploy_to}/current"` and then use `current_path` as a normal setting.