do 0.0.1
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 +17 -0
- data/Gemfile +4 -0
- data/README.md +298 -0
- data/Rakefile +28 -0
- data/bin/doit +30 -0
- data/do.gemspec +21 -0
- data/lib/do/commands.rb +156 -0
- data/lib/do/server.rb +212 -0
- data/lib/do/utils.rb +55 -0
- data/lib/do/version.rb +3 -0
- data/lib/do.rb +27 -0
- data/recipes/.servorc +14 -0
- data/spec/fixtures/sample +9 -0
- data/spec/server_spec.rb +76 -0
- data/spec/spec_helper.rb +19 -0
- metadata +145 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,298 @@
|
|
1
|
+
# DO .. it! - Framework Toolkit for Sysadmin
|
2
|
+
|
3
|
+
DO is a thin framework useful to manage remote servers through ssh.
|
4
|
+
|
5
|
+
There are many other alternative, once of them is [capistrano](https://github.com/capistrano/capistrano)
|
6
|
+
|
7
|
+
So why another one? Basically I need:
|
8
|
+
|
9
|
+
* easy creation of my recipes
|
10
|
+
* see perfectly what's happening on my remote servers
|
11
|
+
* highly focus on smart actions, upload, download, sudo, replace.
|
12
|
+
* manage more than one server each
|
13
|
+
* use same behaviour for manage local tasks
|
14
|
+
|
15
|
+
## Installation
|
16
|
+
|
17
|
+
```sh
|
18
|
+
$ sudo gem install do
|
19
|
+
$ doit do:setup
|
20
|
+
```
|
21
|
+
|
22
|
+
Now start to edit your `~/do/dorc` file and have fun!
|
23
|
+
|
24
|
+
## Files
|
25
|
+
|
26
|
+
There are some way to generate **DO** tasks, you can:
|
27
|
+
|
28
|
+
* Create a file called `Do` in your project directory
|
29
|
+
* Create a file called `Dofile` in your project directory
|
30
|
+
* Create `*.rake` files in `~/.do` directory, aka **home dir**
|
31
|
+
* Create a file called `dorc` in `~/.do` directory
|
32
|
+
|
33
|
+
You can change your **do home directory** with:
|
34
|
+
|
35
|
+
```
|
36
|
+
DO_PATH='/my/new/.do/path'
|
37
|
+
ENV['DO_PATH']='/my/new/.do/path'
|
38
|
+
export DO_PATH='/my/new/.do/path'
|
39
|
+
```
|
40
|
+
|
41
|
+
## Features
|
42
|
+
|
43
|
+
* Easily server logging
|
44
|
+
* SSH connections
|
45
|
+
* SFTP connections (upload and download)
|
46
|
+
* run cmd (we handle also with input)
|
47
|
+
* shortcuts (exist?, read, replace, append etc...)
|
48
|
+
|
49
|
+
Code is much better:
|
50
|
+
|
51
|
+
```rb
|
52
|
+
server = DO::Server.new('srv1', 'srv1.domain.local', 'root', :key => %w[srv1.pem]
|
53
|
+
|
54
|
+
server.run 'uname'
|
55
|
+
# root@srv1 ~ # uname
|
56
|
+
# Linux
|
57
|
+
|
58
|
+
server.run 'uname', '-a'
|
59
|
+
# root@srv1 ~ # uname -a
|
60
|
+
# Linux srv1.lipsiasoft.net 2.6.18-194.32.1.el5 x86_64 x86_64 x86_64 GNU/Linux
|
61
|
+
|
62
|
+
server.run 'mysqladmin -u root -p password 'oldone', 'newpassword'
|
63
|
+
# root@srv1 ~ # mysqladmin -u root -p password 'oldone'
|
64
|
+
# Enter password: oldone
|
65
|
+
# mysqladmin: connect to server at 'localhost' failed
|
66
|
+
# error: 'Access denied for user 'root'@'localhost' (using password: YES)'
|
67
|
+
|
68
|
+
server.exist?('~/.ssh')
|
69
|
+
# root@srv1 ~ # test -e ~/.ssh && echo True
|
70
|
+
# => true
|
71
|
+
|
72
|
+
server.read('/etc/redhat-release')
|
73
|
+
# root@srv1 ~ # cat /etc/redhat-release
|
74
|
+
# => "CentOS release 5.5 (Final)"
|
75
|
+
|
76
|
+
server.upload '/tmp/file', '/tmp/foo'
|
77
|
+
# root@srv1 ~ # upload from '/tmp/file' to '/tmp/foo'
|
78
|
+
|
79
|
+
server.download '/tmp/foo', '/tmp/file2'
|
80
|
+
# root@srv1 ~ # download from '/tmp/foo' to '/tmp/file2'
|
81
|
+
|
82
|
+
server.replace :all, 'new content', '/tmp/file'
|
83
|
+
# root@srv1 ~ # replace all in '/tmp/foo'
|
84
|
+
|
85
|
+
server.read('/tmp/foo')
|
86
|
+
# root@srv1 ~ # cat /tmp/foo
|
87
|
+
# => "new content"
|
88
|
+
|
89
|
+
server.replace /content$/, 'changed content', '/tmp/foo'
|
90
|
+
# root@srv1 ~ # replace /content$/ in '/tmp/foo'
|
91
|
+
|
92
|
+
server.read('/tmp/foo')
|
93
|
+
# root@srv1 ~ # cat /tmp/foo
|
94
|
+
# => "new changed content"
|
95
|
+
|
96
|
+
server.append('appended', '/tmp/foo')
|
97
|
+
# root@srv1 ~ # append to 'bottom' in '/tmp/foo'
|
98
|
+
|
99
|
+
server.read('/tmp/foo')
|
100
|
+
# root@srv1 ~ # cat /tmp/foo
|
101
|
+
# => "new changed contentappended"
|
102
|
+
|
103
|
+
server.append('---', '/tmp/foo', :top)
|
104
|
+
# root@srv1 ~ # append to 'top' in '/tmp/foo'
|
105
|
+
|
106
|
+
server.read('/tmp/foo')
|
107
|
+
# root@srv1 ~ # cat /tmp/foo
|
108
|
+
# => "---new changed contentappended"
|
109
|
+
|
110
|
+
server.ask "Please choose"
|
111
|
+
# root@srv1 ~ # Please choose: foo
|
112
|
+
# => "foo"
|
113
|
+
|
114
|
+
server.yes? "Do you want to proceed"
|
115
|
+
# root@srv1 ~ # Do you want to proceed? (y/n): y
|
116
|
+
# => 0
|
117
|
+
|
118
|
+
server.wait
|
119
|
+
# Press ENTER to continue...
|
120
|
+
```
|
121
|
+
|
122
|
+
## Scenario and examples
|
123
|
+
|
124
|
+
I'm porting my custom recipes to do, you can found my dot files
|
125
|
+
[here](https://github.com/daddye/.do)
|
126
|
+
|
127
|
+
I've server config in `~/.do/dorc`:
|
128
|
+
|
129
|
+
```rb
|
130
|
+
keys = %w(/keys/master.pem /keys/instances.pem /keys/stage.pem)
|
131
|
+
server :sho0, 'sho0.lipsiasoft.biz', 'root', :keys => keys
|
132
|
+
server :srv0, 'srv0.lipsiasoft.biz', 'root', :keys => keys
|
133
|
+
server :srv1, 'srv1.lipsiasoft.biz', 'root', :keys => keys
|
134
|
+
server :srv2, 'srv2.lipsiasoft.biz', 'root', :keys => keys
|
135
|
+
|
136
|
+
plugin "configure-server", "https://raw.github.com/gist/112..."
|
137
|
+
```
|
138
|
+
|
139
|
+
Then I've some recipes in my `~/.do` path where I do common tasks.
|
140
|
+
|
141
|
+
```rb
|
142
|
+
# ~/.do/configure.
|
143
|
+
namespace :configure
|
144
|
+
desc "upgrade rubygems and install usefull gems"
|
145
|
+
task :gems => :ree do
|
146
|
+
run "gem update --system" if yes?("Do you want to update rubygems?")
|
147
|
+
run "gem install rake"
|
148
|
+
run "gem install highline"
|
149
|
+
run "gem install bundler"
|
150
|
+
run "ln -s /opt/ruby-enterprise/bin/bundle /usr/bin/bundle" unless exist?("/usr/bin/bundle")
|
151
|
+
end
|
152
|
+
|
153
|
+
desc "create motd for each server"
|
154
|
+
task :motd do
|
155
|
+
replace :all, "Hey boss! Welcome to the \e[1m#{name}\e[0m of LipsiaSOFT s.r.l.\n", "/etc/motd"
|
156
|
+
end
|
157
|
+
|
158
|
+
desc "redirect emails to a real account"
|
159
|
+
task :root_emails do
|
160
|
+
append "\nroot: servers@lipsiasoft.com", "/etc/aliases"
|
161
|
+
run "newaliases"
|
162
|
+
end
|
163
|
+
|
164
|
+
desc "mysql basic configuration"
|
165
|
+
task :mysql => :yum do
|
166
|
+
run "yum install mysql-server mysql mysql-devel -y"
|
167
|
+
run "chkconfig --level 2345 mysqld on"
|
168
|
+
run "service mysqld restart"
|
169
|
+
pwd = ask "Tell me the password for mysql"
|
170
|
+
run "mysqladmin -u root -p password '#{pwd}'", :input => "\n"
|
171
|
+
run "service mysqld restart"
|
172
|
+
run "ln -fs /var/lib/mysql/mysql.sock /tmp/mysql.sock"
|
173
|
+
end
|
174
|
+
...
|
175
|
+
```
|
176
|
+
|
177
|
+
I call those with:
|
178
|
+
|
179
|
+
```sh
|
180
|
+
$ doit configure:gems
|
181
|
+
$ doit configure:motd
|
182
|
+
$ doit configure:mysql
|
183
|
+
```
|
184
|
+
|
185
|
+
**NOTE** that like rake tasks you are be able to add prerequisites to
|
186
|
+
any task, in this case:
|
187
|
+
|
188
|
+
```rb
|
189
|
+
task :mysql => :yum do; ...; end
|
190
|
+
```
|
191
|
+
|
192
|
+
That's are some local tasks:
|
193
|
+
|
194
|
+
```rb
|
195
|
+
namespace :l do
|
196
|
+
local :setup do
|
197
|
+
name = File.basename(File.expand_path('.'))
|
198
|
+
exit unless yes?('Do you want to setup "%s"?' % name)
|
199
|
+
srv = nil
|
200
|
+
|
201
|
+
until servers.map(&:name).include?(srv)
|
202
|
+
srv = ask("Which server do you want to use (%s)" % servers.map(&:name).join(", ")).to_sym
|
203
|
+
end
|
204
|
+
|
205
|
+
if File.exist?('.git')
|
206
|
+
exit unless yes?('Project "%s" has already a working repo, do you want remove it?' % name)
|
207
|
+
sh 'rm -rf .git'
|
208
|
+
end
|
209
|
+
|
210
|
+
sh 'git init'
|
211
|
+
sh 'git remote add origin git@lipsiasoft.biz:/%s.git' % name
|
212
|
+
Rake::Task['l:commit'].invoke if yes?("Are you ready to commit it, database, config etc is correct?")
|
213
|
+
end
|
214
|
+
|
215
|
+
local :commit do
|
216
|
+
sh 'git add .'
|
217
|
+
sh 'git commit -a'
|
218
|
+
sh 'git push origin master'
|
219
|
+
end
|
220
|
+
end
|
221
|
+
```
|
222
|
+
|
223
|
+
When I need to setup a new project I do:
|
224
|
+
|
225
|
+
``` sh
|
226
|
+
$ doit l:setup
|
227
|
+
$ doit l:commit # to make a fast commit and push
|
228
|
+
```
|
229
|
+
|
230
|
+
As you can see define remote and local task is simple like making common
|
231
|
+
rake tasks, infact this gem handle rake.
|
232
|
+
|
233
|
+
## Filtering
|
234
|
+
|
235
|
+
Sometimes you want to perform a task only on one or some servers:
|
236
|
+
|
237
|
+
```sh
|
238
|
+
$ doit configure:new --only-srv1 --only-srv2
|
239
|
+
$ doit configure:new --except-srv1
|
240
|
+
```
|
241
|
+
|
242
|
+
## Awesome output
|
243
|
+
|
244
|
+
What I really need is a great understandable, colored output that clarify
|
245
|
+
me what's happen on my remote servers.
|
246
|
+
|
247
|
+
```
|
248
|
+
root@sho0 ~ # touch /root/.bashrc
|
249
|
+
root@sho0 ~ # replace all in '/root/.bashrc'
|
250
|
+
root@sho0 ~ # replace all in '/etc/motd'
|
251
|
+
root@sho0 ~ # replace /HOSTNAME=.*/ in '/etc/sysconfig/network'
|
252
|
+
root@sho0 ~ # chmod +w /etc/sudoers
|
253
|
+
root@sho0 ~ # replace /^Defaults requiretty/ in '/etc/sudoers'
|
254
|
+
root@sho0 ~ # chkconfig --level 2345 sendmail on
|
255
|
+
root@sho0 ~ # chkconfig --level 2345 mysqld on
|
256
|
+
root@sho0 ~ # service sendmail restart
|
257
|
+
Shutting down sm-client: [ OK ]
|
258
|
+
Shutting down sendmail: [ OK ]
|
259
|
+
Starting sendmail: [ OK ]
|
260
|
+
Starting sm-client: [ OK ]
|
261
|
+
root@sho0 ~ # service mysqld restart
|
262
|
+
Stopping mysqld: [ OK ]
|
263
|
+
Starting mysqld: [ OK ]
|
264
|
+
root@sho0 ~ # Tell me the password for mysql: xx
|
265
|
+
root@sho0 ~ # mysqladmin -u root -p password xx'
|
266
|
+
Enter password: xx
|
267
|
+
mysqladmin: connect to server at 'localhost' failed
|
268
|
+
error: 'Access denied for user 'root'@'localhost' (using password: NO)'
|
269
|
+
root@sho0 ~ # service mysqld restart
|
270
|
+
Stopping mysqld: [ OK ]
|
271
|
+
Starting mysqld: [ OK ]
|
272
|
+
root@sho0 ~ # ln -fs /var/lib/mysql/mysql.sock /tmp/mysql.sock
|
273
|
+
```
|
274
|
+
|
275
|
+
## Copyright
|
276
|
+
|
277
|
+
Copyright (C) 2011 Davide D'Agostino -
|
278
|
+
[@daddye](http://twitter.com/daddye)
|
279
|
+
|
280
|
+
Permission is hereby granted, free of charge, to any person obtaining a
|
281
|
+
copy of this software and
|
282
|
+
associated documentation files (the “Software”), to deal in the Software
|
283
|
+
without restriction, including without
|
284
|
+
limitation the rights to use, copy, modify, merge, publish, distribute,
|
285
|
+
sublicense, and/or sell copies of the Software,
|
286
|
+
and to permit persons to whom the Software is furnished to do so,
|
287
|
+
subject to the following conditions:
|
288
|
+
|
289
|
+
The above copyright notice and this permission notice shall be included
|
290
|
+
in all copies or substantial portions of the Software.
|
291
|
+
|
292
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS
|
293
|
+
OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
294
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
295
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM,
|
296
|
+
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
297
|
+
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
298
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'bundler/gem_tasks'
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
|
6
|
+
%w(install release).each do |task|
|
7
|
+
Rake::Task[task].enhance do
|
8
|
+
sh "rm -rf pkg"
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
desc "Bump version on github"
|
13
|
+
task :bump do
|
14
|
+
puts "\e[31mNothing to commit (working directory clean)\e[0m" and return unless `git status -s`.chomp == ""
|
15
|
+
version = Bundler.load_gemspec(Dir[File.expand_path('../*.gemspec', __FILE__)].first).version
|
16
|
+
sh "git add .; git commit -a -m \"Bump to version #{version}\""
|
17
|
+
end
|
18
|
+
|
19
|
+
task :release => :bump
|
20
|
+
|
21
|
+
desc "Run complete application spec suite"
|
22
|
+
RSpec::Core::RakeTask.new("spec") do |t|
|
23
|
+
t.skip_bundler = true
|
24
|
+
t.pattern = './spec/**/*_spec.rb'
|
25
|
+
t.rspec_opts = %w(-fs --color --fail-fast)
|
26
|
+
end
|
27
|
+
|
28
|
+
task :default => :spec
|
data/bin/doit
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
$:.push File.expand_path("../../lib", __FILE__)
|
3
|
+
require 'rubygems' unless defined?(Gem)
|
4
|
+
require 'rake/dsl_definition'
|
5
|
+
require 'rake'
|
6
|
+
require 'do'
|
7
|
+
|
8
|
+
ARGV << "-T" if ARGV.empty?
|
9
|
+
|
10
|
+
only = ARGV.map { |a| a =~ /--only-(.*)/ && $1.to_sym }.compact
|
11
|
+
except = ARGV.map { |a| a =~ /--except-(.*)/ && $1.to_sym }.compact
|
12
|
+
|
13
|
+
ARGV.delete_if { |a| a =~ /--(only|except)/ }
|
14
|
+
|
15
|
+
puts "\e[32m"
|
16
|
+
puts "*" * 60
|
17
|
+
puts "*" + "DO".center(58) + "*"
|
18
|
+
puts "*" * 60
|
19
|
+
puts "\e[0m"
|
20
|
+
|
21
|
+
Rake.application.init
|
22
|
+
Rake.application.instance_variable_set(:@name, 'doit')
|
23
|
+
load(File.expand_path('../../lib/do/commands.rb', __FILE__))
|
24
|
+
DO.recipes.each { |recipe| Rake.application.add_import(recipe) }
|
25
|
+
Rake.application.load_imports
|
26
|
+
|
27
|
+
servers.delete_if { |server| !only.include?(server.name) } unless only.empty?
|
28
|
+
servers.delete_if { |server| except.include?(server.name) }
|
29
|
+
|
30
|
+
Rake.application.top_level
|
data/do.gemspec
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/do/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["Davide D'Agostino"]
|
6
|
+
gem.email = ["d.dagostino@lipsiasoft.com"]
|
7
|
+
gem.description = %q[DO is a thin framework useful to manage remote servers through ssh.]
|
8
|
+
gem.summary = %q[DO is a thin framework useful to manage remote servers through ssh.]
|
9
|
+
gem.homepage = 'https://github.com/daddye/do'
|
10
|
+
|
11
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
12
|
+
gem.files = `git ls-files`.split("\n")
|
13
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
14
|
+
gem.name = "do"
|
15
|
+
gem.require_paths = ['lib']
|
16
|
+
gem.version = DO::VERSION
|
17
|
+
gem.add_dependency "rake", "~>0.9.2"
|
18
|
+
gem.add_dependency "net-ssh", "~>2.1.4"
|
19
|
+
gem.add_dependency "net-sftp", "~>2.0.5"
|
20
|
+
gem.add_development_dependency "rspec"
|
21
|
+
end
|
data/lib/do/commands.rb
ADDED
@@ -0,0 +1,156 @@
|
|
1
|
+
require 'rake/dsl_definition'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
module DO
|
5
|
+
module Commands
|
6
|
+
include Rake::DSL
|
7
|
+
include DO::Utils
|
8
|
+
|
9
|
+
##
|
10
|
+
# Array of DO::Server defined in our tasks
|
11
|
+
#
|
12
|
+
def servers
|
13
|
+
@_servers ||= []
|
14
|
+
end
|
15
|
+
|
16
|
+
##
|
17
|
+
# An array of DO::Server selected by our remote task
|
18
|
+
#
|
19
|
+
def servers_selected
|
20
|
+
@_servers_selected ||=[]
|
21
|
+
end
|
22
|
+
|
23
|
+
##
|
24
|
+
# Returns the current server
|
25
|
+
#
|
26
|
+
def current_server
|
27
|
+
@_current_server
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# This method define our servers
|
32
|
+
#
|
33
|
+
# ==== Examples:
|
34
|
+
# keys = %w[key1.pem key2.pem key3.pem key4.pem]
|
35
|
+
# server :srv1, 's1.domain.local', 'user', :keys => keys
|
36
|
+
# server :srv2, 's2.domain.local', 'user', :keys => keys
|
37
|
+
# server :srv3, 's3.domain.local', 'user', :keys => keys
|
38
|
+
# server :srv4, 's4.domain.local', 'user', :keys => keys
|
39
|
+
#
|
40
|
+
def server(name, host, user, options={})
|
41
|
+
servers.push(DO::Server.new(name, host, user, options))
|
42
|
+
local(name) { servers_selected.replace(servers.select { |s| s.name == name }) }
|
43
|
+
end
|
44
|
+
|
45
|
+
##
|
46
|
+
# Install in your DO_PATH a remote task
|
47
|
+
#
|
48
|
+
# === Examples
|
49
|
+
# # You can install/update task with
|
50
|
+
# # rake plugin:configuration
|
51
|
+
# plugin "configuration", "https://gist.github.com/raw/xys.rake"
|
52
|
+
#
|
53
|
+
def plugin(name, repo)
|
54
|
+
desc "Install #{name} plugin"
|
55
|
+
local("plugin:#{name}" => "do:setup") do
|
56
|
+
Dir.mkdir(DO_PATH) unless File.exist?(DO_PATH)
|
57
|
+
sh "curl --location --progress-bar #{repo} > #{File.join(DO_PATH, name + '.rake')}"
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Execute DO::Server operations on remote defined servers.
|
63
|
+
#
|
64
|
+
# ==== Examples:
|
65
|
+
# # Define our ssh connections
|
66
|
+
# keys = %w[key1.pem key2.pem key3.pem key4.pem]
|
67
|
+
# server :srv1, 's1.domain.local', 'user', :keys => keys
|
68
|
+
# server :srv2, 's2.domain.local', 'user', :keys => keys
|
69
|
+
# server :srv3, 's3.domain.local', 'user', :keys => keys
|
70
|
+
# server :srv4, 's4.domain.local', 'user', :keys => keys
|
71
|
+
#
|
72
|
+
# # => Executes commands only to :srv1, :srv2, :srv3
|
73
|
+
# remote :name => [:srv1, :srv2, :srv3] do; ...; end
|
74
|
+
#
|
75
|
+
# # => Same as above
|
76
|
+
# task :name => [:srv1, :srv2, :srv3] do; ...; end
|
77
|
+
#
|
78
|
+
# # => Executes commands on all defined servers
|
79
|
+
# remote :name => :servers do; ...; end
|
80
|
+
# # => Same as above
|
81
|
+
# remote :name => do; ...; end
|
82
|
+
# # => Same as above
|
83
|
+
# task :name do; ...; end
|
84
|
+
# # => Same as above
|
85
|
+
# local :name => :servers do; ...; end
|
86
|
+
#
|
87
|
+
# # => Execute the task on your machine
|
88
|
+
# local :name do; sh 'uname'; end
|
89
|
+
#
|
90
|
+
# # => Execute commands both on servers side and local side
|
91
|
+
# remote :name do |t|
|
92
|
+
# t.run 'run this command on remote servers'
|
93
|
+
# sh 'run this command on my local machine'
|
94
|
+
# end
|
95
|
+
#
|
96
|
+
# # same of:
|
97
|
+
#
|
98
|
+
# task :name do |t|
|
99
|
+
# t.run 'command on remote servers'
|
100
|
+
# sh 'command on my local machine'
|
101
|
+
# end
|
102
|
+
#
|
103
|
+
# # => Execute command only on remote server srv1
|
104
|
+
# task :name => :srv1 do
|
105
|
+
# run 'command only on remote server srv1'
|
106
|
+
# end
|
107
|
+
#
|
108
|
+
def remote(args, &block)
|
109
|
+
args = { args => :servers } unless args.is_a?(Hash)
|
110
|
+
local(args) do
|
111
|
+
name = args.is_a?(Hash) ? args.keys[0] : args
|
112
|
+
servers_selected.each do |current|
|
113
|
+
begin
|
114
|
+
server_was, @_current_server = @_current_server, current
|
115
|
+
self.class.send(:define_method, name, &block)
|
116
|
+
method = self.class.instance_method(name)
|
117
|
+
self.class.send(:remove_method, name)
|
118
|
+
block.arity == 1 ? method.bind(self).call(current) : method.bind(current).call
|
119
|
+
ensure
|
120
|
+
@_current_server = server_was
|
121
|
+
end # begin
|
122
|
+
end # servers
|
123
|
+
end # local
|
124
|
+
end
|
125
|
+
|
126
|
+
alias_method :local, :task
|
127
|
+
alias_method :task, :remote
|
128
|
+
|
129
|
+
##
|
130
|
+
# Log text under current_server if available
|
131
|
+
#
|
132
|
+
def log(text, new_line=true)
|
133
|
+
if current_server
|
134
|
+
current_server.log(text, new_line)
|
135
|
+
else
|
136
|
+
text += "\n" if new_line && text[-1] != ?\n
|
137
|
+
print(text)
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end # Commands
|
141
|
+
end # DO
|
142
|
+
|
143
|
+
self.extend DO::Commands
|
144
|
+
|
145
|
+
local :servers do
|
146
|
+
servers_selected.replace(servers)
|
147
|
+
end
|
148
|
+
|
149
|
+
namespace :do do
|
150
|
+
desc "setup a working home directory"
|
151
|
+
local :setup do
|
152
|
+
File.mkdir(DO_PATH) unless File.exist?(DO_PATH)
|
153
|
+
sh 'touch %s' % File.join(DO_PATH, 'dorc')
|
154
|
+
sh 'ln -s %s %s' % [File.join(DO_PATH, 'dorc'), File.expand_path("~/.dorc")]
|
155
|
+
end
|
156
|
+
end
|
data/lib/do/server.rb
ADDED
@@ -0,0 +1,212 @@
|
|
1
|
+
require 'net/ssh'
|
2
|
+
require 'net/sftp'
|
3
|
+
|
4
|
+
module DO
|
5
|
+
class Server
|
6
|
+
include DO::Utils
|
7
|
+
|
8
|
+
LOG_FORMAT = "\e[36m%s\e[33m@\e[31m%s \e[33m~ \e[35m#\e[0m %s" unless defined?(LOG_FORMAT)
|
9
|
+
|
10
|
+
attr_reader :name, :host, :user, :options
|
11
|
+
attr_accessor :logger
|
12
|
+
|
13
|
+
##
|
14
|
+
# Initialize a new DO Server
|
15
|
+
#
|
16
|
+
# name:: is a shortcut useful in rake tasks
|
17
|
+
# host:: is the host where we connect
|
18
|
+
# user:: the user of our server
|
19
|
+
# options:: an hash of options used by ssh/sftpd, where normally we provide :keys => ['path/to/key.pem']
|
20
|
+
#
|
21
|
+
# ==== Examples:
|
22
|
+
# srv1 = DO::Server.new(:srv1, 'srv1.lipsiasoft.biz', 'root', :keys => %w[/path/to/key.pem]
|
23
|
+
# srv1.logger = StringIO.new # default is STDOUT
|
24
|
+
#
|
25
|
+
def initialize(name, host, user, options={})
|
26
|
+
@name, @host, @user, @options = name, host, user, options
|
27
|
+
@logger = STDOUT
|
28
|
+
end
|
29
|
+
|
30
|
+
##
|
31
|
+
# Method used to print a formatted version of our commands
|
32
|
+
# using DO::Server::LOG_FORMAT, by default we have a nice
|
33
|
+
# colored version like:
|
34
|
+
#
|
35
|
+
# srv1@root ~ # ls -al
|
36
|
+
#
|
37
|
+
# If you don't like colors or our format feel free to edit:
|
38
|
+
#
|
39
|
+
# ==== Examples:
|
40
|
+
# DO::Server::LOG_FORMAT = "%s@%s$ %s"
|
41
|
+
#
|
42
|
+
def log(text, new_line=true)
|
43
|
+
text += "\n" if new_line && text[-1] != ?\n
|
44
|
+
logger.print LOG_FORMAT % [user, name, text]
|
45
|
+
end
|
46
|
+
|
47
|
+
##
|
48
|
+
# This is the ssh connection
|
49
|
+
#
|
50
|
+
def ssh
|
51
|
+
@_ssh ||= Net::SSH.start(host, user, options)
|
52
|
+
end
|
53
|
+
|
54
|
+
##
|
55
|
+
# The sftp connection used to perform uploads, downloads
|
56
|
+
#
|
57
|
+
def sftp
|
58
|
+
@_sftp ||= Net::SFTP.start(host, user, options)
|
59
|
+
end
|
60
|
+
|
61
|
+
##
|
62
|
+
# Method used to close the ssh connection
|
63
|
+
#
|
64
|
+
def close
|
65
|
+
ssh.close if @_ssh
|
66
|
+
end
|
67
|
+
|
68
|
+
##
|
69
|
+
# Run commands on remote server
|
70
|
+
#
|
71
|
+
# ==== Examples:
|
72
|
+
# run 'ls -al'
|
73
|
+
# run 'ls', '-al'
|
74
|
+
# run 'mysqladmin -u root -p password 'new', :input => 'oldpassword'
|
75
|
+
#
|
76
|
+
def run(*args)
|
77
|
+
options = args.last.is_a?(Hash) ? args.pop : {}
|
78
|
+
cmd = args.join(" ")
|
79
|
+
cmd = "su #{options[:as]} -c '#{cmd.gsub(/'/, "\'")}'" if options[:as]
|
80
|
+
log cmd
|
81
|
+
result = ""
|
82
|
+
ssh.exec!(cmd) do |channel, stream, data|
|
83
|
+
result << data
|
84
|
+
logger.print(data) unless options[:silent]
|
85
|
+
if options[:input]
|
86
|
+
match = options[:match] || /^Enter password:/
|
87
|
+
if data =~ match
|
88
|
+
options[:input] += "\n" if options[:input][-1] != ?\n
|
89
|
+
channel.send_data(options[:input])
|
90
|
+
logger.puts(options[:input]) unless options[:silent]
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
result.chomp
|
95
|
+
end
|
96
|
+
|
97
|
+
##
|
98
|
+
# Returns true if a given file exist on the remote server
|
99
|
+
#
|
100
|
+
def exist?(file)
|
101
|
+
run("test -e #{file} && echo True", :silent => true) == "True"
|
102
|
+
end
|
103
|
+
|
104
|
+
##
|
105
|
+
# Return the content of a given file
|
106
|
+
#
|
107
|
+
def read(file)
|
108
|
+
run("cat #{file}", :silent => true)
|
109
|
+
end
|
110
|
+
|
111
|
+
##
|
112
|
+
# Upload a file or directory from a local location to the remote location
|
113
|
+
# When you need to upload a directory you need to provide:
|
114
|
+
#
|
115
|
+
# :recursive => true
|
116
|
+
#
|
117
|
+
# === Examples
|
118
|
+
# upload(/my/file, /tmp/file)
|
119
|
+
# up(/my/dir, /tmp, :recursive => true)
|
120
|
+
#
|
121
|
+
def upload(from, to, options={})
|
122
|
+
log "upload from '%s' to '%s'" % [from, to]
|
123
|
+
sftp.upload!(from, to)
|
124
|
+
end
|
125
|
+
alias :up :upload
|
126
|
+
|
127
|
+
##
|
128
|
+
# Download a file o a directory from a remote location to a local location
|
129
|
+
# As for +upload+ we can download an entire directory providing
|
130
|
+
#
|
131
|
+
# :recursive => true
|
132
|
+
#
|
133
|
+
# ==== Examples
|
134
|
+
# download(/tmp/file, /my/file)
|
135
|
+
# get(/tmp/dir, /my, :recursive => true)
|
136
|
+
#
|
137
|
+
def download(from, to, options={})
|
138
|
+
log "download from '%s' to '%s'" % [from, to]
|
139
|
+
sftp.download!(from, to)
|
140
|
+
end
|
141
|
+
alias :get :download
|
142
|
+
|
143
|
+
##
|
144
|
+
# Replace a pattern with text in a given file.
|
145
|
+
#
|
146
|
+
# Pattern can be:
|
147
|
+
#
|
148
|
+
# * string
|
149
|
+
# * regexp
|
150
|
+
# * symbol (:all, :any, :everything) => replace all content
|
151
|
+
#
|
152
|
+
# ==== Examples
|
153
|
+
# replace :all, my_template, "/root/.gemrc"
|
154
|
+
# replace /^motd/, "New motd", "/etc/motd"
|
155
|
+
# replace "Old motd, "New motd", "/etc/motd"
|
156
|
+
#
|
157
|
+
def replace(pattern, replacement, file)
|
158
|
+
was = sftp.file.open(file, "r") { |f| f.read }
|
159
|
+
found = case pattern
|
160
|
+
when :all, :any, :everything
|
161
|
+
log "replace \e[1m%s\e[0m in '%s'" % [pattern, file]
|
162
|
+
true
|
163
|
+
when Regexp
|
164
|
+
log "replace \e[1m%s\e[0m in '%s'" % [pattern.inspect, file]
|
165
|
+
replacement = was.gsub(pattern, replacement)
|
166
|
+
was =~ pattern
|
167
|
+
when String
|
168
|
+
log "replace \e[1m%s\e[0m in '%s'" % ["String", file]
|
169
|
+
replacement = was.gsub(pattern, replacement)
|
170
|
+
was.include?(pattern)
|
171
|
+
else raise "%s is not a valid. You can use a String, Regexp or :all, :any and :everything" % pattern.inspect
|
172
|
+
end
|
173
|
+
|
174
|
+
if found
|
175
|
+
sftp.file.open(file, "w") { |f| f.write replacement }
|
176
|
+
else
|
177
|
+
log "\e[31m '%s' does not include your \e[1mpattern\e[0m" % file unless was =~ pattern
|
178
|
+
end
|
179
|
+
end
|
180
|
+
alias :gsub :replace
|
181
|
+
|
182
|
+
##
|
183
|
+
# Append text into a given file in a specified location
|
184
|
+
#
|
185
|
+
# Locations can be:
|
186
|
+
#
|
187
|
+
# :top, :start:: Add your text before the old content
|
188
|
+
# :bottom, :end:: Append your text at bottom of your file
|
189
|
+
#
|
190
|
+
# ==== Examples:
|
191
|
+
# append "By default Im at bottom", "/tmp/file"
|
192
|
+
# append "Im on top", "/tmp/file", :top
|
193
|
+
#
|
194
|
+
def append(pattern, file, where=:bottom)
|
195
|
+
was = sftp.file.open(file, "r") { |f| f.read }
|
196
|
+
|
197
|
+
if was.include?(pattern)
|
198
|
+
log "'%s' already match your pattern" % file
|
199
|
+
return false
|
200
|
+
else
|
201
|
+
replacement = case where
|
202
|
+
when :top, :start then pattern+was
|
203
|
+
when :bottom, :end then was+pattern
|
204
|
+
else raise "%s is not a valid, available values are (:top, :start, :bottom, :end)" % where.inspect
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
log "append to '%s' in '%s'" % [where, file]
|
209
|
+
sftp.file.open(file, "w") { |f| f.write(replacement) }
|
210
|
+
end
|
211
|
+
end # Server
|
212
|
+
end # DO
|
data/lib/do/utils.rb
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
module DO
|
2
|
+
module Utils
|
3
|
+
##
|
4
|
+
# This is generally used when calling DO::Server from a CLI
|
5
|
+
#
|
6
|
+
# ==== Examples:
|
7
|
+
# [srv1, srv2, srv3].each do |server|
|
8
|
+
# server.run "long task"
|
9
|
+
# server.wait # because we want to see all outputs before start with new one
|
10
|
+
# end
|
11
|
+
#
|
12
|
+
def wait
|
13
|
+
puts "\e[36mPress ENTER to continue...\e[0m"
|
14
|
+
STDIN.gets
|
15
|
+
end
|
16
|
+
|
17
|
+
##
|
18
|
+
# Ask question to your STDIN.
|
19
|
+
# This command is useful in conjunction with a CLI
|
20
|
+
#
|
21
|
+
# ==== Example
|
22
|
+
# old_pwd = ask "Tell me the old password of mysql"
|
23
|
+
# new_pwd = ask "Tell me the new password of mysql"
|
24
|
+
# run "mysqladmin -u root -p password '#{new_pwd}', :input => old_pwd
|
25
|
+
#
|
26
|
+
def ask(question, allow_blank=false)
|
27
|
+
result = ""
|
28
|
+
loop do
|
29
|
+
log("\e[36m%s: \e[0m" % question, false)
|
30
|
+
result = STDIN.gets.chomp
|
31
|
+
break if allow_blank || result != ""
|
32
|
+
end
|
33
|
+
result
|
34
|
+
end
|
35
|
+
|
36
|
+
##
|
37
|
+
# Ask a yes/no question and return true if it is equal to y or yes
|
38
|
+
#
|
39
|
+
# ==== Examples:
|
40
|
+
# if yes?("Do you want to proceed?")
|
41
|
+
# do_some
|
42
|
+
# end
|
43
|
+
#
|
44
|
+
def yes?(question)
|
45
|
+
result = ""
|
46
|
+
question += "?" if question[-1] != ??
|
47
|
+
loop do
|
48
|
+
log("\e[36m%s (y/n): \e[0m" % question, false)
|
49
|
+
result = STDIN.gets.chomp
|
50
|
+
break if result =~ /y|yes|n|no/i
|
51
|
+
end
|
52
|
+
return result =~ /y|yes/i
|
53
|
+
end
|
54
|
+
end # Utils
|
55
|
+
end # DO
|
data/lib/do/version.rb
ADDED
data/lib/do.rb
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
DO_PATH = ENV['DO_PATH'] ||= File.expand_path("~/.do") unless defined?(DO_PATH)
|
2
|
+
|
3
|
+
module DO
|
4
|
+
|
5
|
+
autoload :Server, 'do/server.rb'
|
6
|
+
autoload :Utils, 'do/utils.rb'
|
7
|
+
autoload :VERSION, 'do/version.rb'
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
##
|
12
|
+
# DO loads rakefiles in these locations:
|
13
|
+
#
|
14
|
+
# ~/do/dorc
|
15
|
+
# ~/do/*.rake
|
16
|
+
# ./Do
|
17
|
+
# ./Dofile
|
18
|
+
#
|
19
|
+
# DO_PATH, default is ~/do.
|
20
|
+
#
|
21
|
+
def recipes
|
22
|
+
@_recipes ||= (
|
23
|
+
%w[dorc *.rake].map { |f| Dir[File.join(DO_PATH, f)] }.flatten +
|
24
|
+
%w[./Do ./Dofile].map { |f| File.expand_path(f) }
|
25
|
+
).reject { |f| !File.exist?(f) }
|
26
|
+
end
|
27
|
+
end # DO
|
data/recipes/.servorc
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
keys = %w(/Developer/src/LipsiaSoft/lipsiahosting/lib/lipsiarec/recipes/servers/resources/keys/Lipsiasoft.pem)
|
2
|
+
server :srv1, 'srv1.lipsiasoft.biz', 'root', :keys => keys
|
3
|
+
server :srv2, 'srv2.lipsiasoft.biz', 'root', :keys => keys
|
4
|
+
|
5
|
+
plugin "configure-server", "https://raw.github.com/gist/112..."
|
6
|
+
|
7
|
+
namespace :status do
|
8
|
+
|
9
|
+
desc "Show processes"
|
10
|
+
task :ps do
|
11
|
+
run "ps aux --sort -rss"
|
12
|
+
wait
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,9 @@
|
|
1
|
+
Their sighs, lamentations and loud wailings
|
2
|
+
resounded through the starless air,
|
3
|
+
so that at first it made me weep;
|
4
|
+
Strange utterances, horrible pronouncements,
|
5
|
+
words of pain, tones of anger,
|
6
|
+
voices shrill and faint, and beating hands,
|
7
|
+
all went to make a tumult that will whirl
|
8
|
+
forever through that turbid, timeless air,
|
9
|
+
like sand that eddies when a whirlwind swirls.
|
data/spec/server_spec.rb
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe DO::Server do
|
4
|
+
before do
|
5
|
+
keys = %w(/Developer/src/LipsiaSoft/lipsiahosting/lib/lipsiarec/recipes/servers/resources/keys/Lipsiasoft.pem)
|
6
|
+
@server = DO::Server.new(:sho0, 'sho0.lipsiasoft.biz', 'ec2-user', :keys => keys)
|
7
|
+
@server.logger = StringIO.new
|
8
|
+
@fixture = File.expand_path('../fixtures/sample', __FILE__)
|
9
|
+
@fixture_was = File.read(@fixture)
|
10
|
+
end
|
11
|
+
|
12
|
+
it 'should read my uname' do
|
13
|
+
release = @server.run 'uname'
|
14
|
+
release.should match(/Linux/)
|
15
|
+
end
|
16
|
+
|
17
|
+
it 'should upload a something' do
|
18
|
+
@server.upload @fixture, '/tmp/sample'
|
19
|
+
@server.exist?('/tmp/sample').should be_true
|
20
|
+
end
|
21
|
+
|
22
|
+
it 'should download something' do
|
23
|
+
tmp = File.expand_path('./sample')
|
24
|
+
@server.download '/tmp/sample', tmp
|
25
|
+
File.read(tmp).should == @fixture_was
|
26
|
+
FileUtils.rm_rf(tmp)
|
27
|
+
end
|
28
|
+
|
29
|
+
it 'should replace in file' do
|
30
|
+
@server.replace 'eddies', 'dummies', '/tmp/sample'
|
31
|
+
result = @server.read '/tmp/sample'
|
32
|
+
result.should match(/dummies/)
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'should replace everything' do
|
36
|
+
@server.replace :all, 'foo', '/tmp/sample'
|
37
|
+
result = @server.read '/tmp/sample'
|
38
|
+
result.should == 'foo'
|
39
|
+
end
|
40
|
+
|
41
|
+
it 'should not replace if pattern is not valid' do
|
42
|
+
proc {
|
43
|
+
@server.replace :xyz, 'foo', '/tmp/sample'
|
44
|
+
}.should raise_exception
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'should replace with a regex' do
|
48
|
+
@server.upload @fixture, '/tmp/sample'
|
49
|
+
@server.replace /and/, 'AND', '/tmp/sample'
|
50
|
+
result = @server.read '/tmp/sample'
|
51
|
+
matches = result.scan(/and/i)
|
52
|
+
matches.size.should == 5
|
53
|
+
matches.all? { |m| m == 'AND' }.should be_true
|
54
|
+
end
|
55
|
+
|
56
|
+
it 'should append on bottom' do
|
57
|
+
@server.append '---', '/tmp/sample'
|
58
|
+
result = @server.read '/tmp/sample'
|
59
|
+
result.should match(/---$/)
|
60
|
+
@server.append '~~~', '/tmp/sample', :bottom
|
61
|
+
result = @server.read '/tmp/sample'
|
62
|
+
result.should match(/~~~$/)
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'should append on top' do
|
66
|
+
@server.append '---', '/tmp/sample', :top
|
67
|
+
result = @server.read '/tmp/sample'
|
68
|
+
result.should match(/^---/)
|
69
|
+
end
|
70
|
+
|
71
|
+
it 'should not append if the where condition is not valid' do
|
72
|
+
proc {
|
73
|
+
@server.append 'xyz', '/tmp/sample', :xyz
|
74
|
+
}.should raise_exception
|
75
|
+
end
|
76
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems' unless defined?(Gem)
|
2
|
+
require 'bundler/setup'
|
3
|
+
require 'rspec'
|
4
|
+
require 'fileutils'
|
5
|
+
require 'do'
|
6
|
+
|
7
|
+
module Helper
|
8
|
+
def capture_stdout(&block)
|
9
|
+
stdout_was, $stdout = $stdout, StringIO.new
|
10
|
+
block.call
|
11
|
+
return $stdout
|
12
|
+
ensure
|
13
|
+
$stdout = stdout_was
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.include(Helper)
|
19
|
+
end
|
metadata
ADDED
@@ -0,0 +1,145 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: do
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Davide D'Agostino
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-08-09 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
prerelease: false
|
23
|
+
type: :runtime
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ~>
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 63
|
30
|
+
segments:
|
31
|
+
- 0
|
32
|
+
- 9
|
33
|
+
- 2
|
34
|
+
version: 0.9.2
|
35
|
+
name: rake
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
prerelease: false
|
39
|
+
type: :runtime
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ~>
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 3
|
46
|
+
segments:
|
47
|
+
- 2
|
48
|
+
- 1
|
49
|
+
- 4
|
50
|
+
version: 2.1.4
|
51
|
+
name: net-ssh
|
52
|
+
version_requirements: *id002
|
53
|
+
- !ruby/object:Gem::Dependency
|
54
|
+
prerelease: false
|
55
|
+
type: :runtime
|
56
|
+
requirement: &id003 !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ~>
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
hash: 5
|
62
|
+
segments:
|
63
|
+
- 2
|
64
|
+
- 0
|
65
|
+
- 5
|
66
|
+
version: 2.0.5
|
67
|
+
name: net-sftp
|
68
|
+
version_requirements: *id003
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
prerelease: false
|
71
|
+
type: :development
|
72
|
+
requirement: &id004 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ">="
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
hash: 3
|
78
|
+
segments:
|
79
|
+
- 0
|
80
|
+
version: "0"
|
81
|
+
name: rspec
|
82
|
+
version_requirements: *id004
|
83
|
+
description: DO is a thin framework useful to manage remote servers through ssh.
|
84
|
+
email:
|
85
|
+
- d.dagostino@lipsiasoft.com
|
86
|
+
executables:
|
87
|
+
- doit
|
88
|
+
extensions: []
|
89
|
+
|
90
|
+
extra_rdoc_files: []
|
91
|
+
|
92
|
+
files:
|
93
|
+
- .gitignore
|
94
|
+
- Gemfile
|
95
|
+
- README.md
|
96
|
+
- Rakefile
|
97
|
+
- bin/doit
|
98
|
+
- do.gemspec
|
99
|
+
- lib/do.rb
|
100
|
+
- lib/do/commands.rb
|
101
|
+
- lib/do/server.rb
|
102
|
+
- lib/do/utils.rb
|
103
|
+
- lib/do/version.rb
|
104
|
+
- recipes/.servorc
|
105
|
+
- spec/fixtures/sample
|
106
|
+
- spec/server_spec.rb
|
107
|
+
- spec/spec_helper.rb
|
108
|
+
has_rdoc: true
|
109
|
+
homepage: https://github.com/daddye/do
|
110
|
+
licenses: []
|
111
|
+
|
112
|
+
post_install_message:
|
113
|
+
rdoc_options: []
|
114
|
+
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
hash: 3
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
version: "0"
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ">="
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
hash: 3
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
version: "0"
|
135
|
+
requirements: []
|
136
|
+
|
137
|
+
rubyforge_project:
|
138
|
+
rubygems_version: 1.6.2
|
139
|
+
signing_key:
|
140
|
+
specification_version: 3
|
141
|
+
summary: DO is a thin framework useful to manage remote servers through ssh.
|
142
|
+
test_files:
|
143
|
+
- spec/fixtures/sample
|
144
|
+
- spec/server_spec.rb
|
145
|
+
- spec/spec_helper.rb
|