do 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|