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 ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'http://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in do.gemspec
4
+ gemspec
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
@@ -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
@@ -0,0 +1,3 @@
1
+ module DO
2
+ VERSION = "0.0.1" unless defined?(DO::VERSION)
3
+ end
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.
@@ -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
@@ -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