do 0.0.1

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