fezzik 0.5.2 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1 @@
1
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,2 @@
1
+ source :rubygems
2
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,23 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ fezzik (0.5.2.1)
5
+ colorize (>= 0.5.8)
6
+ rake (~> 0.8.7)
7
+ rake-remote_task (~> 2.0.2)
8
+
9
+ GEM
10
+ remote: http://rubygems.org/
11
+ specs:
12
+ colorize (0.5.8)
13
+ open4 (1.3.0)
14
+ rake (0.8.7)
15
+ rake-remote_task (2.0.6)
16
+ open4 (~> 1.0)
17
+ rake (~> 0.8)
18
+
19
+ PLATFORMS
20
+ ruby
21
+
22
+ DEPENDENCIES
23
+ fezzik!
data/README.md CHANGED
@@ -1,120 +1,242 @@
1
1
  # Fezzik
2
2
 
3
- Fezzik (or fez) is a slim and snappy alternative to Capistrano.
3
+ Fezzik (or fez) is a slim and snappy way to run commands on servers.
4
+ This is useful for many things, including deployment.
4
5
 
5
- It sets up a rake-based rsync workflow with a single configuration file
6
- and gets out of your way.
6
+ It wraps a rake-based rsync workflow and tries to keep it simple.
7
7
 
8
8
  ## Install
9
9
 
10
10
  gem install fezzik
11
11
 
12
- ## Setup
12
+ ## Basic setup
13
13
 
14
- $ cd myproject
15
- $ ls
16
- server.rb
17
- $ fezify
18
- [new] bin/run_app.sh created
19
- [new] config/recipes/core.rb created
20
- [new] config/deploy.rb created
21
- [done]
14
+ Require Fezzik in your project Rakefile and define a destination:
22
15
 
23
- **config/deploy.rb**: set your app name and destination servers
16
+ ```ruby
17
+ require "fezzik"
24
18
 
25
- set :app, "fezzik"
26
- ...
27
- destination :prod do
28
- set :domain, "www.fezzik.com"
29
- end
19
+ Fezzik.destination :prod do
20
+ set :domain, "root@myapp.com"
21
+ end
22
+ ```
30
23
 
31
- **bin/run_app.sh**: write a command that will start your app
24
+ Write some rake tasks that will execute on the specified destination:
32
25
 
33
- #!/bin/sh
34
- nohup ruby server.rb > /dev/null 2>&1 &
35
-
36
- Ready to deploy!
37
-
38
- $ fez prod deploy
39
- ...
40
- fezzik deployed!
26
+ ```ruby
27
+ namespace :fezzik do
28
+ remote_task :touch do
29
+ run "touch /tmp/test_file"
30
+ end
31
+ end
32
+ ```
33
+
34
+ Run your remote_tasks with fezzik:
35
+
36
+ ```
37
+ $ fez prod touch
38
+ ```
39
+
40
+ ## Deployments
41
+
42
+ One of the more useful things you can use Fezzik for is handling deployments.
43
+
44
+ ```ruby
45
+ require "fezzik"
46
+
47
+ # Fezzik will automatically load any .rake files it finds in this directory.
48
+ Fezzik.init(:tasks => "config/tasks")
49
+
50
+ # Fezzik wraps rake/remote_task, which is the same rake plugin used by Vlad the Deployer.
51
+ # See http://hitsquad.rubyforge.org/vlad/doco/variables_txt.html for a full list of variables it supports.
52
+
53
+ set :app, "myapp"
54
+ set :deploy_to, "/opt/#{app}"
55
+ set :release_path, "#{deploy_to}/releases/#{Time.now.strftime("%Y%m%d%H%M")}"
56
+ set :local_path, Dir.pwd
57
+ set :user, "root"
58
+
59
+ Fezzik.destination :staging do
60
+ set :domain, "#{user}@myapp-staging.com"
61
+ end
62
+
63
+ Fezzik.destination :prod do
64
+ set :domain, "#{user}@myapp.com"
65
+ end
66
+ ```
67
+
68
+ Fezzik comes bundled with some useful rake tasks for common things like deployment.
69
+ You can download the ones you need:
70
+
71
+ ```
72
+ $ cd config/tasks
73
+ $ fez get deploy
74
+ [new] deploy.rake
75
+ ```
76
+
77
+ You'll need to edit the fezzik:start and fezzik:stop tasks in deploy.rake since those are specific to your
78
+ project.
79
+
80
+ ```ruby
81
+ namespace :fezzik do
82
+ ...
83
+ desc "runs the executable in project/bin"
84
+ remote_task :start do
85
+ puts "starting from #{Fezzik::Util.capture_output { run "readlink #{current_path}" }}"
86
+ run "cd #{current_path} && ./bin/run_app.sh"
87
+ end
88
+
89
+ desc "kills the application by searching for the specified process name"
90
+ remote_task :stop do
91
+ puts "stopping app"
92
+ run "(kill `ps aux | grep 'myapp' | grep -v grep | awk '{print $2}'` || true)"
93
+ end
94
+ ...
95
+ end
96
+ ```
97
+
98
+ Deploy win!
99
+
100
+ ```
101
+ $ fez prod deploy
102
+ ...
103
+ myapp deployed!
104
+ [success]
105
+ ```
106
+
107
+ ## Environments
108
+
109
+ Configuration often changes when you deploy your project. Fezzik lets you set environments for your hosts.
110
+
111
+ ```
112
+ $ cd config/tasks
113
+ $ fez get environment
114
+ [new] environment.rake
115
+ ```
116
+
117
+ ```ruby
118
+ Fezzik.destination :prod do
119
+ set :domain, "#{user}@myapp.com"
120
+ Fezzik.env :rack_env, "production"
121
+ end
122
+ ```
123
+
124
+ This will be exposed in the form of an environment.sh file and an environment.rb file in your project root
125
+ directory when you deploy. You can source the .sh file before running your app or require the .rb file in your
126
+ project directly.
127
+
128
+ ```ruby
129
+ desc "runs the executable in project/bin"
130
+ remote_task :start do
131
+ puts "starting from #{Fezzik::Util.capture_output { run "readlink #{current_path}" }}"
132
+ run "cd #{current_path} && (source environment.sh || true) && ./bin/run_app.sh"
133
+ end
134
+ ```
135
+
136
+ You can assign different environments to a subset of the hosts you deploy to.
137
+
138
+ ```ruby
139
+ Fezzik.destination :prod do
140
+ set :domain, ["#{user}@myapp1.com", "#{user}@myapp2.com"]
141
+ Fezzik.env :rack_env, "production"
142
+ Fezzik.env :is_canary, "true", :hosts => ["myapp1.com"]
143
+ end
144
+ ```
145
+
146
+ You can access the environment settings in your tasks, if you like. It's a hash.
147
+
148
+ ```ruby
149
+ task :inspect_environment do
150
+ puts Fezzik.environments.inspect
151
+ end
152
+ ```
41
153
 
42
154
  ## Utilities
43
155
 
44
- Fezzik exposes some utilities that can be useful when running remote tasks.
45
-
46
- ### Host override
47
-
48
- Sometimes you'll want to run a fezzik task on a subset of hosts, rather than the full destination fleet.
49
- You can override what hosts a fezzik task executes on from the command line.
50
-
51
- # deploy to a single host
52
- $ fez prod:domain1.com deploy
156
+ Fezzik exposes some functions that can be useful when running remote tasks.
53
157
 
54
- # deploy to a single host as root
55
- $ fez prod:root@domain1.com deploy
158
+ ### Capture or redirect output
56
159
 
57
- # deploy to multiple hosts
58
- $ fez prod:domain1.com,domain2.com deploy
160
+ ```ruby
161
+ Fezzik::Util.capture_output(&block)
162
+ ```
59
163
 
60
- The overriding hosts don't need to be a subset of the specified destination's domains.
61
- They can be any hosts you want to use with a destination's configuration.
164
+ Use this function if you would like to hide or capture the normal output that the "run" command prints.
62
165
 
63
- ### Capture or redirect output
166
+ ```ruby
167
+ remote_task :print_hello
168
+ # Nothing is printed to stdout
169
+ server_output = Fezzik::Util.capture_output { run "echo 'hello'"}
64
170
 
65
- capture_output(&block)
171
+ # prints "hello"
172
+ puts server_output
173
+ end
174
+ ```
66
175
 
67
- Use this function if you would like to hide or capture the normal output that the "run" command prints.
176
+ ### Inspect the target destination
68
177
 
69
- remote_task :my_task
70
- # Nothing is printed to stdout
71
- server_output = capture_output { run "echo 'hello'"}
178
+ You can see which destination fezzik is operating on from within your tasks.
72
179
 
73
- # prints "hello"
74
- puts server_output
75
- end
180
+ ```ruby
181
+ task :print_destination
182
+ puts Fezzik.target_destination
183
+ end
184
+ ```
76
185
 
77
- ## Recipes
186
+ ## Tasks
78
187
 
79
- Fezzik uses a recipe system similar to Capistrano. Any recipe placed in your config/recipes directory will be
80
- picked up and available to the fez command. Some useful recipes that are not part of the fezzik core can be
81
- found in the fezzik project recipes directory. You can download them with fezzik:
188
+ Fezzik has a number of useful tasks other than deploy.rake and environment.rake. These can also be downloaded
189
+ with `$ fez get <task>` and placed in the directory you specify with `Fezzik.init(:tasks => "config/tasks")`.
82
190
 
83
- fez get <recipes>
191
+ These tasks are meant to be starting points. For example, if you want to save your environment files in a
192
+ place that's not your project root you can simply edit the task in environment.rake.
84
193
 
85
194
  If you write a recipe that would be useful to other developers, please submit a pull request!
86
195
 
87
196
  ### Command
88
197
 
89
- fez get command
198
+ ```
199
+ $ cd config/tasks
200
+ $ fez get command
201
+ [new] command.rake
202
+ ```
90
203
 
91
204
  Sometimes you just need to get your hands dirty and run a shell on your servers.
92
- The command.rb recipe gives you a prompt that lets you execute shell code on each of your hosts.
205
+ The command.rake tasks give you a prompt that lets you execute shell code on each of your hosts.
93
206
 
94
- $ fez prod command
95
- configuring for root@domain.com
96
- run command (or "quit"): tail www/myapp/log.txt -n 1
97
- [2011-07-01 00:01:23] GET / 200
207
+ ```
208
+ $ fez prod command
209
+ Targeting hosts:
210
+ root@myapp.com
211
+ run command (or "quit"): tail www/myapp/log.txt -n 1
212
+ [2011-07-01 00:01:23] GET / 200
213
+ ```
98
214
 
99
215
  You can also run a single command:
100
216
 
101
- $ fez prod command_execute\['ls'\]
102
-
103
- (You'll probably need to escape the `[]` in your shell as well.)
217
+ ```
218
+ $ fez prod "command_execute[ls]"
219
+ ```
104
220
 
105
221
  ### Rollback
106
222
 
107
- fez get rollback
223
+ ```
224
+ $ cd config/tasks
225
+ $ fez get rollback
226
+ [new] rollback.rake
227
+ ```
108
228
 
109
229
  Emergency! Rollback! Every deployment you make is saved on the server by default.
110
230
  You can move between these deployments (to roll back, for example), with the rollback.rb recipe.
111
231
 
112
- $ fez prod rollback
113
- configuring for root@domain.com
114
- === Releases ===
115
- 0: Abort
116
- 1: 201107051328 (current)
117
- 2: 201106231408
118
- 3: 201106231352
119
- Rollback to release (0):
232
+ ```
233
+ $ fez prod rollback
234
+ configuring for root@myapp.com
235
+ === Releases ===
236
+ 0: Abort
237
+ 1: 201107051328 (current)
238
+ 2: 201106231408
239
+ 3: 201106231352
240
+ Rollback to release (0):
241
+ ```
120
242
 
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ desc "remove built gems"
2
+ task :clean do
3
+ sh "rm fezzik-*" rescue true
4
+ end
5
+
6
+ desc "build gem"
7
+ task :build do
8
+ sh "gem build fezzik.gemspec"
9
+ end
10
+
11
+ desc "install gem"
12
+ task :install => [:clean, :build] do
13
+ sh "gem install `ls fezzik-*`"
14
+ end
15
+
data/bin/fez CHANGED
@@ -2,51 +2,87 @@
2
2
 
3
3
  require "rubygems"
4
4
  require "rake"
5
- require "rake/remote_task"
6
- require "fezzik"
7
5
  require "colorize"
6
+ require "fezzik"
8
7
 
9
- # Include everything in config/recipes
10
- Dir[File.join(Dir.pwd, 'config/recipes/**/*.rb')].sort.each { |lib| require lib }
8
+ # TODO: Add a handy "fez init" command to set up a basic deployable directory structure
9
+ # TODO: Think about how to do domain overrides better
10
+ # TODO: Add Fezzik::DSL so you can say env or destination instead of Fezzik.env, Fezzik.destination
11
11
 
12
12
  def usage
13
13
  <<-EOF
14
+ Version #{Fezzik::VERSION}
14
15
  fez <destination> <tasks> # Run deployment tasks on destination servers
15
- fez get <recipes> # Download recipes to use in your project
16
+ fez get <tasks> # Download tasks to use in your project
16
17
  fez -T # Display all tasks
17
18
  EOF
18
19
  end
19
20
 
21
+ def print_usage_and_exit
22
+ puts usage
23
+ exit 1
24
+ end
25
+
20
26
  def display_tasks_and_exit
21
- Rake.application.options.show_task_pattern = //
22
- output = capture_output { Rake.application.display_tasks_and_comments }
27
+ Rake.application.init
28
+ Rake.application.load_rakefile
29
+ Rake.application.options.show_task_pattern = /^fezzik:/
30
+ output = Fezzik::Util.capture_output { Rake.application.display_tasks_and_comments }
23
31
  output.gsub!(/^rake fezzik:/, "fez <destination> ")
24
32
  puts output
25
33
  exit 0
26
34
  end
27
35
 
28
- RECIPE_URL = "https://raw.github.com/dmacdougall/fezzik/master/recipes"
29
- def download_recipes_and_exit
30
- system("mkdir -p config/recipes")
31
- ARGV[1..-1].each do |recipe|
32
- recipe += ".rb" unless recipe[-3..-1] == ".rb"
33
- system("curl -f #{RECIPE_URL}/#{recipe} -o config/recipes/#{recipe} > /dev/null 2>&1")
36
+ TASKS_URL = "https://raw.github.com/dmacdougall/fezzik/master/tasks"
37
+ def download_tasks_and_exit
38
+ ARGV[1..-1].each do |task|
39
+ task += ".rake" unless task =~ /\.rake$/
40
+ system("curl -f #{TASKS_URL}/#{task} -o #{task} > /dev/null 2>&1")
34
41
  if $? == 0
35
- puts " [new]".green + " config/recipes/#{recipe}"
42
+ puts " [new]".green + " #{task}"
36
43
  else
37
- puts " [fail]".red + " config/recipes/#{recipe}"
44
+ puts " [fail]".red + " #{task}"
38
45
  end
39
46
  end
40
47
  exit 0
41
48
  end
42
49
 
43
- if ARGV.size == 0
44
- puts usage
45
- exit 1
46
- elsif ARGV[0] == "-T"
47
- display_tasks_and_exit
48
- elsif ARGV[0] == "get"
49
- download_recipes_and_exit
50
+ def run_fezzik_tasks
51
+ ENV["fezzik_destination"] = ARGV[0]
52
+ Fezzik.init
53
+ Rake.application.init
54
+ Rake.application.load_rakefile
55
+ begin
56
+ host_list = Array(domain).join("\n ")
57
+ puts "Targeting hosts:"
58
+ puts " #{host_list}"
59
+ rescue Rake::ConfigurationError => e
60
+ puts "Invalid destination: #{Fezzik.target_destination}"
61
+ puts "Make sure this destination is configured and includes `set :domain, \"yourdomain.com\"`"
62
+ puts "[fail]".red
63
+ exit 1
64
+ end
65
+ begin
66
+ tasks = ARGV[1..-1]
67
+ tasks.each do |task_with_params|
68
+ task, params = Fezzik::Util.split_task_and_params(task_with_params)
69
+ Rake::Task["fezzik:#{task}"].invoke(params)
70
+ end
71
+ puts "[success]".green
72
+ rescue SystemExit, Rake::CommandFailedError => e
73
+ puts "[fail]".red
74
+ exit 1
75
+ rescue StandardError => e
76
+ puts e.message
77
+ puts e.backtrace
78
+ puts "[fail]".red
79
+ fail
80
+ end
50
81
  end
51
82
 
52
- Rake.application["fezzik:run"].invoke
83
+ case ARGV[0]
84
+ when nil then print_usage_and_exit
85
+ when "-T" then display_tasks_and_exit
86
+ when "get" then download_tasks_and_exit
87
+ else run_fezzik_tasks
88
+ end
data/fezzik.gemspec CHANGED
@@ -13,19 +13,14 @@ Gem::Specification.new do |s|
13
13
  s.email = "dmacdougall@gmail.com"
14
14
 
15
15
  s.description = "A light deployment system that gets out of your way"
16
- s.summary = "A light deployment system that gets out of your way"
16
+ s.summary = "Fezzik is a small wrapper around rake/remote_task. It simplifies running commands on" +
17
+ "remote servers and can be used for anything from deploying code to installing libraries remotely."
17
18
  s.homepage = "http://github.com/dmacdougall/fezzik"
18
19
  s.rubyforge_project = "fezzik"
19
20
 
20
21
  s.executables = %w(fez fezify)
21
- s.files = %w(
22
- README.md
23
- TODO.md
24
- fezzik.gemspec
25
- lib/fezzik.rb
26
- bin/fez
27
- bin/fezify
28
- )
22
+ s.files = `git ls-files`.split("\n")
23
+
29
24
  s.add_dependency("rake", "~>0.8.7")
30
25
  s.add_dependency("rake-remote_task", "~>2.0.2")
31
26
  s.add_dependency("colorize", ">=0.5.8")
data/lib/fezzik.rb CHANGED
@@ -1,100 +1,10 @@
1
1
  require "stringio"
2
2
  require "thread"
3
-
4
- # Synchronize 'puts' because it's annoying to have interleaved output when rake remote task is running
5
- # several things at once in multiple threads (for instance: commands on multiple servers or commands as
6
- # multiple users).
7
- class IO
8
- @@print_mutex = Mutex.new
9
- alias :old_puts :puts
10
- def puts(*args) @@print_mutex.synchronize { old_puts(*args) } end
11
- end
12
-
13
- namespace :fezzik do
14
- task :run do
15
- destination = ARGV[0]
16
- destination = $1 if destination.match(/^to_(.+)/)
17
- destination, @domain_override = destination.split(":", 2)
18
- @domain_override = @domain_override.split(",") if @domain_override
19
- tasks = ARGV[1..-1]
20
- Rake::Task["fezzik:load_config"].invoke destination
21
- begin
22
- tasks.each do |task|
23
- task, params = split_task_and_params(task)
24
- Rake::Task["fezzik:#{task}"].invoke(*params)
25
- end
26
- puts "[success]".green
27
- rescue SystemExit, Rake::CommandFailedError => e
28
- puts "[fail]".red
29
- exit 1
30
- rescue Exception => e
31
- puts e.message
32
- puts e.backtrace
33
- puts "[fail]".red
34
- fail
35
- end
36
- end
37
-
38
- task :load_config, :destination do |t, args|
39
- @destination = args[:destination].to_sym
40
- @environment = {}
41
- require "./config/deploy.rb"
42
- @servers = domain.is_a?(Array) ? domain : domain.split(",").map(&:strip)
43
- compute_environment_per_server
44
- puts "configuring for #{@servers.join(", ")}"
45
- end
46
-
47
- def destination(target, &block)
48
- if target == @destination
49
- block.call
50
- if @domain_override
51
- @domain_override.map! { |domain| domain.include?("@") ? domain : "#{user}@#{domain}" }
52
- set :domain, @domain_override
53
- end
54
- end
55
- end
56
-
57
- # If servers is given, then this environment variable will only apply to that server (or array of servers)
58
- # (these should match names given in :domain). If servers is not given, then this environment variable
59
- # applies to all servers.
60
- def env(key, value, servers = nil)
61
- servers = Array(servers) if servers
62
- @environment[[key, servers]] = value
63
- end
64
-
65
- def compute_environment_per_server
66
- @per_server_environments = Hash.new { |h, k| h[k] = {} }
67
- @environment.each do |k, value|
68
- key, servers = k
69
- if servers
70
- # Allow the user to provide "user@host.com" or "host.com" when identifying servers for per-server
71
- # environment variables.
72
- applicable_servers = @servers.select { |s1| servers.any? { |s2| s1 == s2 || s1.end_with?(s2) } }
73
- else
74
- applicable_servers = @servers
75
- end
76
- applicable_servers.each { |s| @per_server_environments[s][key] = value }
77
- end
78
- end
79
-
80
- def capture_output(&block)
81
- output = StringIO.new
82
- $stdout = output
83
- block.call
84
- return output.string
85
- ensure
86
- $stdout = STDOUT
87
- end
88
-
89
- def split_task_and_params(task_with_params)
90
- params_match = /(.+)\[(.+)\]/.match(task_with_params)
91
- if params_match
92
- task = params_match[1]
93
- params = params_match[2].split(",")
94
- else
95
- task = task_with_params
96
- params = nil
97
- end
98
- [task, params]
99
- end
100
- end
3
+ require "rake"
4
+ require "rake/remote_task"
5
+ require "colorize"
6
+ require "fezzik/base.rb"
7
+ require "fezzik/environment.rb"
8
+ require "fezzik/io.rb"
9
+ require "fezzik/util.rb"
10
+ require "fezzik/version.rb"
@@ -0,0 +1,19 @@
1
+ module Fezzik
2
+ def self.init(options={})
3
+ @options = options
4
+ @target_destination = ENV["fezzik_destination"].to_sym rescue nil
5
+ unless options[:tasks].nil?
6
+ puts "Loading Fezzik tasks from #{@options[:tasks]}"
7
+ Dir[File.join(Dir.pwd, "#{@options[:tasks]}/**/*.rake")].sort.each { |lib| import lib }
8
+ end
9
+ end
10
+
11
+ # TODO: add domain override (through environment variable?)
12
+ def self.destination(name, &block)
13
+ block.call if name == @target_destination
14
+ end
15
+
16
+ def self.target_destination
17
+ @target_destination ||= nil
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+ module Fezzik
2
+ def self.env(key, value, options={})
3
+ options = {
4
+ :hosts => domain
5
+ }.merge(options)
6
+ options[:hosts] = Array(options[:hosts])
7
+ @environments ||= Hash.new { |h, k| h[k] = {} }
8
+ options[:hosts].each { |host| @environments[host][key] = value }
9
+ end
10
+
11
+ def self.environments
12
+ @environments ||= Hash.new { |h, k| h[k] = {} }
13
+ end
14
+ end
data/lib/fezzik/io.rb ADDED
@@ -0,0 +1,9 @@
1
+ # Synchronize 'puts' because it's annoying to have interleaved output when rake remote task is running
2
+ # several things at once in multiple threads (for instance: commands on multiple servers or commands as
3
+ # multiple users).
4
+ class IO
5
+ @@print_mutex = Mutex.new
6
+ alias :old_puts :puts
7
+ def puts(*args) @@print_mutex.synchronize { old_puts(*args) } end
8
+ end
9
+
@@ -0,0 +1,24 @@
1
+ module Fezzik
2
+ module Util
3
+ def self.capture_output(&block)
4
+ output = StringIO.new
5
+ $stdout = output
6
+ block.call
7
+ return output.string
8
+ ensure
9
+ $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]
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,3 @@
1
+ module Fezzik
2
+ VERSION = "0.6.0"
3
+ end
@@ -0,0 +1,37 @@
1
+ # Tasks for running commands on destination hosts.
2
+ #
3
+ # Tasks:
4
+ # * command: interactively run commands on destination servers
5
+ # * command_execute: run a single command on destination servers
6
+ #
7
+ namespace :fezzik do
8
+ desc "interactively run commands on destination servers"
9
+ task :command do
10
+ loop do
11
+ print "run command (or \"quit\"): "
12
+ STDOUT.flush
13
+ input = STDIN.gets
14
+ # Exit gracefully on <C-D>
15
+ if input.nil?
16
+ puts
17
+ break
18
+ end
19
+ command = input.chomp
20
+ next if command.empty?
21
+ if ["quit", "q", "exit"].include? command.downcase
22
+ break
23
+ else
24
+ begin
25
+ Rake::Task["fezzik:command_execute"].invoke command
26
+ rescue Rake::CommandFailedError
27
+ ensure
28
+ Rake::Task["fezzik:command_execute"].reenable
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ desc "run a single command on destination servers"
35
+ remote_task(:command_execute, :command) { |t, args| run args[:command] }
36
+ end
37
+
data/tasks/deploy.rake ADDED
@@ -0,0 +1,60 @@
1
+ require "fileutils"
2
+
3
+ namespace :fezzik do
4
+ desc "stages the project for deployment in /tmp"
5
+ task :stage do
6
+ puts "staging project in /tmp/#{app}"
7
+ FileUtils.rm_rf "/tmp/#{app}"
8
+ FileUtils.mkdir_p "/tmp/#{app}/staged"
9
+ # Use rsync to preserve executability and follow symlinks.
10
+ system("rsync -aqE #{local_path}/. /tmp/#{app}/staged")
11
+ end
12
+
13
+ desc "performs any necessary setup on the destination servers prior to deployment"
14
+ remote_task :setup do
15
+ puts "setting up servers"
16
+ run "mkdir -p #{deploy_to}/releases"
17
+ end
18
+
19
+ 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}"
22
+ # Copy on top of previous release to optimize rsync
23
+ rsync "-q", "--copy-dest=#{current_path}", "/tmp/#{app}/staged/", "#{target_host}:#{release_path}"
24
+ end
25
+
26
+ 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"
30
+ end
31
+
32
+ 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 config/environment.sh || true) && ./bin/run_app.sh"
36
+ end
37
+
38
+ 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)"
45
+ end
46
+
47
+ desc "restarts the application"
48
+ remote_task :restart do
49
+ Rake::Task["fezzik:stop"].invoke
50
+ Rake::Task["fezzik:start"].invoke
51
+ end
52
+
53
+ 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!"
59
+ end
60
+ end
@@ -0,0 +1,42 @@
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
+ ["environment.rb", "environment.sh"].each do |config_file|
38
+ rsync "-q", "/tmp/#{app}/#{server}_config/#{config_file}",
39
+ "#{target_host}:#{release_path}/#{config_file}"
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,54 @@
1
+ # Tasks for handling deployment rollbacks.
2
+ #
3
+ # Tasks:
4
+ # * rollback: interactively rollback (or forward) your live deployment
5
+ # * rollback_one: rollback to previous deployment
6
+ # * rollback_to_release: rollback to a specific deployment
7
+ #
8
+ namespace :fezzik do
9
+ desc "interactively roll back deployment"
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
14
+ puts "=== Releases ==="
15
+ puts "0: Abort"
16
+ releases.each_index { |i| puts "#{i+1}: #{releases[i]} #{releases[i] == current_release ? "(current)" : ""}" }
17
+ print "Rollback to release (0): "
18
+ STDOUT.flush
19
+ release_num = STDIN.gets.chomp.to_i
20
+
21
+ unless release_num > 0 && release_num <= releases.size
22
+ puts "rollback aborted"
23
+ exit 1
24
+ end
25
+
26
+ selected_release = releases[release_num-1]
27
+ Rake::Task["fezzik:rollback_to_release"].invoke(selected_release)
28
+ end
29
+
30
+ desc "rolls back deployment to the previous release"
31
+ 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
+ previous_release = %x{
35
+ ssh #{target_domain} "cd #{File.dirname(release_path)} && ls | grep "#{current_release}" -B 1 | head -1"
36
+ }.strip
37
+
38
+ if previous_release == current_release
39
+ puts "already at oldest deploy, unable to rollback"
40
+ exit 1
41
+ end
42
+
43
+ Rake::Task["fezzik:rollback_to_release"].invoke(previous_release)
44
+ end
45
+
46
+ desc "rolls back deployment to a specific release"
47
+ remote_task :rollback_to_release, :selected_release do |t, args|
48
+ 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
+ Rake::Task["fezzik:restart"].invoke
52
+ end
53
+ end
54
+
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.5.2
4
+ version: 0.6.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -10,11 +10,11 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2012-01-05 00:00:00.000000000Z
13
+ date: 2012-02-09 00:00:00.000000000Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rake
17
- requirement: &2152263900 !ruby/object:Gem::Requirement
17
+ requirement: &2152662480 !ruby/object:Gem::Requirement
18
18
  none: false
19
19
  requirements:
20
20
  - - ~>
@@ -22,10 +22,10 @@ dependencies:
22
22
  version: 0.8.7
23
23
  type: :runtime
24
24
  prerelease: false
25
- version_requirements: *2152263900
25
+ version_requirements: *2152662480
26
26
  - !ruby/object:Gem::Dependency
27
27
  name: rake-remote_task
28
- requirement: &2152263440 !ruby/object:Gem::Requirement
28
+ requirement: &2152662020 !ruby/object:Gem::Requirement
29
29
  none: false
30
30
  requirements:
31
31
  - - ~>
@@ -33,10 +33,10 @@ dependencies:
33
33
  version: 2.0.2
34
34
  type: :runtime
35
35
  prerelease: false
36
- version_requirements: *2152263440
36
+ version_requirements: *2152662020
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: colorize
39
- requirement: &2152262980 !ruby/object:Gem::Requirement
39
+ requirement: &2152661560 !ruby/object:Gem::Requirement
40
40
  none: false
41
41
  requirements:
42
42
  - - ! '>='
@@ -44,7 +44,7 @@ dependencies:
44
44
  version: 0.5.8
45
45
  type: :runtime
46
46
  prerelease: false
47
- version_requirements: *2152262980
47
+ version_requirements: *2152661560
48
48
  description: A light deployment system that gets out of your way
49
49
  email: dmacdougall@gmail.com
50
50
  executables:
@@ -53,12 +53,25 @@ executables:
53
53
  extensions: []
54
54
  extra_rdoc_files: []
55
55
  files:
56
+ - .gitignore
57
+ - Gemfile
58
+ - Gemfile.lock
56
59
  - README.md
60
+ - Rakefile
57
61
  - TODO.md
58
- - fezzik.gemspec
59
- - lib/fezzik.rb
60
62
  - bin/fez
61
63
  - bin/fezify
64
+ - fezzik.gemspec
65
+ - lib/fezzik.rb
66
+ - lib/fezzik/base.rb
67
+ - lib/fezzik/environment.rb
68
+ - lib/fezzik/io.rb
69
+ - lib/fezzik/util.rb
70
+ - lib/fezzik/version.rb
71
+ - tasks/command.rake
72
+ - tasks/deploy.rake
73
+ - tasks/environment.rake
74
+ - tasks/rollback.rake
62
75
  homepage: http://github.com/dmacdougall/fezzik
63
76
  licenses: []
64
77
  post_install_message:
@@ -82,5 +95,7 @@ rubyforge_project: fezzik
82
95
  rubygems_version: 1.8.7
83
96
  signing_key:
84
97
  specification_version: 2
85
- summary: A light deployment system that gets out of your way
98
+ summary: Fezzik is a small wrapper around rake/remote_task. It simplifies running
99
+ commands onremote servers and can be used for anything from deploying code to installing
100
+ libraries remotely.
86
101
  test_files: []