fezzik 0.5.2 → 0.6.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +23 -0
- data/README.md +196 -74
- data/Rakefile +15 -0
- data/bin/fez +59 -23
- data/fezzik.gemspec +4 -9
- data/lib/fezzik.rb +8 -98
- data/lib/fezzik/base.rb +19 -0
- data/lib/fezzik/environment.rb +14 -0
- data/lib/fezzik/io.rb +9 -0
- data/lib/fezzik/util.rb +24 -0
- data/lib/fezzik/version.rb +3 -0
- data/tasks/command.rake +37 -0
- data/tasks/deploy.rake +60 -0
- data/tasks/environment.rake +42 -0
- data/tasks/rollback.rake +54 -0
- metadata +26 -11
data/.gitignore
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
*.gem
|
data/Gemfile
ADDED
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
|
|
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
|
|
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
|
-
##
|
|
12
|
+
## Basic setup
|
|
13
13
|
|
|
14
|
-
|
|
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
|
-
|
|
16
|
+
```ruby
|
|
17
|
+
require "fezzik"
|
|
24
18
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
end
|
|
19
|
+
Fezzik.destination :prod do
|
|
20
|
+
set :domain, "root@myapp.com"
|
|
21
|
+
end
|
|
22
|
+
```
|
|
30
23
|
|
|
31
|
-
|
|
24
|
+
Write some rake tasks that will execute on the specified destination:
|
|
32
25
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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
|
-
|
|
55
|
-
$ fez prod:root@domain1.com deploy
|
|
158
|
+
### Capture or redirect output
|
|
56
159
|
|
|
57
|
-
|
|
58
|
-
|
|
160
|
+
```ruby
|
|
161
|
+
Fezzik::Util.capture_output(&block)
|
|
162
|
+
```
|
|
59
163
|
|
|
60
|
-
|
|
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
|
-
|
|
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
|
-
|
|
171
|
+
# prints "hello"
|
|
172
|
+
puts server_output
|
|
173
|
+
end
|
|
174
|
+
```
|
|
66
175
|
|
|
67
|
-
|
|
176
|
+
### Inspect the target destination
|
|
68
177
|
|
|
69
|
-
|
|
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
|
-
|
|
74
|
-
|
|
75
|
-
|
|
180
|
+
```ruby
|
|
181
|
+
task :print_destination
|
|
182
|
+
puts Fezzik.target_destination
|
|
183
|
+
end
|
|
184
|
+
```
|
|
76
185
|
|
|
77
|
-
##
|
|
186
|
+
## Tasks
|
|
78
187
|
|
|
79
|
-
Fezzik
|
|
80
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
205
|
+
The command.rake tasks give you a prompt that lets you execute shell code on each of your hosts.
|
|
93
206
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
102
|
-
|
|
103
|
-
|
|
217
|
+
```
|
|
218
|
+
$ fez prod "command_execute[ls]"
|
|
219
|
+
```
|
|
104
220
|
|
|
105
221
|
### Rollback
|
|
106
222
|
|
|
107
|
-
|
|
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
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
#
|
|
10
|
-
|
|
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 <
|
|
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.
|
|
22
|
-
|
|
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
|
-
|
|
29
|
-
def
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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 + "
|
|
42
|
+
puts " [new]".green + " #{task}"
|
|
36
43
|
else
|
|
37
|
-
puts " [fail]".red + "
|
|
44
|
+
puts " [fail]".red + " #{task}"
|
|
38
45
|
end
|
|
39
46
|
end
|
|
40
47
|
exit 0
|
|
41
48
|
end
|
|
42
49
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
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 = "
|
|
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 =
|
|
22
|
-
|
|
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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
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"
|
data/lib/fezzik/base.rb
ADDED
|
@@ -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
|
+
|
data/lib/fezzik/util.rb
ADDED
|
@@ -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
|
data/tasks/command.rake
ADDED
|
@@ -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
|
data/tasks/rollback.rake
ADDED
|
@@ -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.
|
|
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-
|
|
13
|
+
date: 2012-02-09 00:00:00.000000000Z
|
|
14
14
|
dependencies:
|
|
15
15
|
- !ruby/object:Gem::Dependency
|
|
16
16
|
name: rake
|
|
17
|
-
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: *
|
|
25
|
+
version_requirements: *2152662480
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: rake-remote_task
|
|
28
|
-
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: *
|
|
36
|
+
version_requirements: *2152662020
|
|
37
37
|
- !ruby/object:Gem::Dependency
|
|
38
38
|
name: colorize
|
|
39
|
-
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: *
|
|
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:
|
|
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: []
|