necktie 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/bin/necktie +1 -1
- data/lib/necktie/rush.rb +11 -0
- data/lib/necktie/services.rb +31 -11
- data/lib/necktie.rb +10 -6
- data/necktie.gemspec +2 -2
- data/rush/README.rdoc +87 -0
- data/rush/Rakefile +60 -0
- data/rush/VERSION +1 -0
- data/rush/bin/rush +13 -0
- data/rush/bin/rushd +7 -0
- data/rush/lib/rush/access.rb +130 -0
- data/rush/lib/rush/array_ext.rb +19 -0
- data/rush/lib/rush/box.rb +112 -0
- data/rush/lib/rush/commands.rb +55 -0
- data/rush/lib/rush/config.rb +154 -0
- data/rush/lib/rush/dir.rb +160 -0
- data/rush/lib/rush/embeddable_shell.rb +26 -0
- data/rush/lib/rush/entry.rb +185 -0
- data/rush/lib/rush/exceptions.rb +31 -0
- data/rush/lib/rush/file.rb +85 -0
- data/rush/lib/rush/find_by.rb +39 -0
- data/rush/lib/rush/fixnum_ext.rb +18 -0
- data/rush/lib/rush/head_tail.rb +11 -0
- data/rush/lib/rush/local.rb +402 -0
- data/rush/lib/rush/process.rb +59 -0
- data/rush/lib/rush/process_set.rb +62 -0
- data/rush/lib/rush/remote.rb +156 -0
- data/rush/lib/rush/search_results.rb +58 -0
- data/rush/lib/rush/server.rb +117 -0
- data/rush/lib/rush/shell.rb +187 -0
- data/rush/lib/rush/ssh_tunnel.rb +122 -0
- data/rush/lib/rush/string_ext.rb +3 -0
- data/rush/lib/rush.rb +87 -0
- data/rush/rush.gemspec +113 -0
- data/rush/spec/access_spec.rb +134 -0
- data/rush/spec/array_ext_spec.rb +15 -0
- data/rush/spec/base.rb +24 -0
- data/rush/spec/box_spec.rb +64 -0
- data/rush/spec/commands_spec.rb +47 -0
- data/rush/spec/config_spec.rb +108 -0
- data/rush/spec/dir_spec.rb +164 -0
- data/rush/spec/embeddable_shell_spec.rb +17 -0
- data/rush/spec/entry_spec.rb +133 -0
- data/rush/spec/file_spec.rb +83 -0
- data/rush/spec/find_by_spec.rb +58 -0
- data/rush/spec/fixnum_ext_spec.rb +19 -0
- data/rush/spec/local_spec.rb +352 -0
- data/rush/spec/process_set_spec.rb +50 -0
- data/rush/spec/process_spec.rb +73 -0
- data/rush/spec/remote_spec.rb +140 -0
- data/rush/spec/rush_spec.rb +28 -0
- data/rush/spec/search_results_spec.rb +44 -0
- data/rush/spec/shell_spec.rb +23 -0
- data/rush/spec/ssh_tunnel_spec.rb +122 -0
- data/rush/spec/string_ext_spec.rb +23 -0
- metadata +53 -3
- data/lib/necktie/files.rb +0 -14
data/bin/necktie
CHANGED
data/lib/necktie/rush.rb
ADDED
data/lib/necktie/services.rb
CHANGED
@@ -1,13 +1,33 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
system "
|
5
|
-
|
6
|
-
|
1
|
+
module Services
|
2
|
+
def self.start(name)
|
3
|
+
puts " ** Starting service #{name}"
|
4
|
+
system "update-rc.d #{name} defaults" and
|
5
|
+
system "service #{name} start" or
|
6
|
+
fail "failed to start #{name}"
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.enable(name)
|
10
|
+
system "update-rc.d #{name} defaults" or "cannot enable #{name}"
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.stop(name)
|
14
|
+
puts " ** Stopping service #{name}"
|
15
|
+
system "service #{name} stop" and
|
16
|
+
system "update-rc.d -f #{name} remove" or
|
17
|
+
fail "failed to stop #{name}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def self.disable(name)
|
21
|
+
system "update-rc.d -f #{name} remove" or fail "cannot disable #{name}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def self.restart(name)
|
25
|
+
puts " ** Stopping service #{name}"
|
26
|
+
system "service #{name} restart" or fail "failed to restart #{name}"
|
27
|
+
end
|
7
28
|
|
8
|
-
def
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
fail "failed to shutdown #{name}"
|
29
|
+
def self.status(name)
|
30
|
+
status = File.read("|sudo service --status-all 2>&1")[/^ \[ (.) \] #{Regexp.escape name}$/,1]
|
31
|
+
stauts == "+" ? true : status == "-" ? false : nil
|
32
|
+
end
|
13
33
|
end
|
data/lib/necktie.rb
CHANGED
@@ -2,7 +2,10 @@ require "fileutils"
|
|
2
2
|
extend FileUtils
|
3
3
|
require "necktie/gems"
|
4
4
|
require "necktie/services"
|
5
|
-
require "necktie/
|
5
|
+
require "necktie/rush"
|
6
|
+
|
7
|
+
puts launch_dir
|
8
|
+
exit!
|
6
9
|
|
7
10
|
def necktie(args)
|
8
11
|
etc = "/etc/necktie"
|
@@ -35,8 +38,9 @@ def necktie(args)
|
|
35
38
|
Dir.chdir repo do
|
36
39
|
executed = File.exist?(etc) ? File.read(etc).split("\n") : []
|
37
40
|
roles.each do |role|
|
38
|
-
|
39
|
-
|
41
|
+
skip = executed.map { |t| t[/^#{Regexp.escape role}\/(.*)$/, 1] }.reject(&:nil?)
|
42
|
+
tasks = Dir["tasks/#{role}/*.rb"].sort.map { |name| File.basename(name).gsub(/\.rb$/, "") }
|
43
|
+
todo = tasks - skip
|
40
44
|
if tasks.empty?
|
41
45
|
puts " * No tasks for role #{role}"
|
42
46
|
elsif todo.empty?
|
@@ -45,9 +49,9 @@ def necktie(args)
|
|
45
49
|
puts " * Now in role: #{role}"
|
46
50
|
File.open etc, "a" do |f|
|
47
51
|
todo.each do |task|
|
48
|
-
puts " ** Executing #{task
|
49
|
-
load "tasks/#{task}.rb"
|
50
|
-
f.puts task
|
52
|
+
puts " ** Executing #{task}"
|
53
|
+
load "tasks/#{role}/#{task}.rb"
|
54
|
+
f.puts task if task[/^\d+_/]
|
51
55
|
end
|
52
56
|
end
|
53
57
|
end
|
data/necktie.gemspec
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
Gem::Specification.new do |spec|
|
2
2
|
spec.name = "necktie"
|
3
|
-
spec.version = "0.
|
3
|
+
spec.version = "0.2.0"
|
4
4
|
spec.author = "Assaf Arkin"
|
5
5
|
spec.email = "assaf@labnotes.org"
|
6
6
|
spec.homepage = ""
|
7
7
|
spec.summary = "Dress to impress"
|
8
8
|
spec.description = ""
|
9
9
|
|
10
|
-
spec.files = Dir["{bin,lib}/**/*", "*.{gemspec,rdoc}"]
|
10
|
+
spec.files = Dir["{bin,lib,rush}/**/*", "*.{gemspec,rdoc}"]
|
11
11
|
spec.executable = "necktie"
|
12
12
|
end
|
data/rush/README.rdoc
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
= rush -- manage your unix systems with pure Ruby
|
2
|
+
|
3
|
+
rush is a unix integration library and an interactive shell which uses pure Ruby syntax. Walk directory trees; create, copy, search, and destroy files; find and kill processes - everything you'd normally do with shell commands, now in the strict and elegant world of Ruby.
|
4
|
+
|
5
|
+
== Usage
|
6
|
+
|
7
|
+
Count the number of classes in your project using bash:
|
8
|
+
|
9
|
+
find myproj -name \*.rb | xargs grep '^\s*class' | wc -l
|
10
|
+
|
11
|
+
In rush, this is:
|
12
|
+
|
13
|
+
myproj['**/*.rb'].search(/^\s*class/).lines.size
|
14
|
+
|
15
|
+
Pesky stray mongrels? In bash:
|
16
|
+
|
17
|
+
kill `ps aux | grep mongrel_rails | grep -v grep | cut -c 10-20`
|
18
|
+
|
19
|
+
In rush:
|
20
|
+
|
21
|
+
processes.filter(:cmdline => /mongrel_rails/).kill
|
22
|
+
|
23
|
+
== As a library
|
24
|
+
|
25
|
+
require 'rubygems'
|
26
|
+
require 'rush'
|
27
|
+
|
28
|
+
file = Rush['/tmp/myfile']
|
29
|
+
file.write "hello"
|
30
|
+
puts file.contents
|
31
|
+
file.destroy
|
32
|
+
|
33
|
+
puts Rush.my_process.pid
|
34
|
+
puts Rush.processes.size
|
35
|
+
puts Rush.bash("echo SHELL COMMAND | tr A-Z a-z")
|
36
|
+
puts Rush.launch_dir['*.rb'].search(/Rush/).entries.inspect
|
37
|
+
|
38
|
+
== Invoking the shell
|
39
|
+
|
40
|
+
Run the "rush" binary to enter the interactive shell.
|
41
|
+
|
42
|
+
== Remote access and clustering
|
43
|
+
|
44
|
+
rush can control any number of remote machines from a single location. Copy files or directories between servers as seamlessly as if it was all local.
|
45
|
+
|
46
|
+
Example of remote access:
|
47
|
+
|
48
|
+
local = Rush::Box.new('localhost')
|
49
|
+
remote = Rush::Box.new('my.remote.server.com')
|
50
|
+
local_dir = local['/Users/adam/myproj/']
|
51
|
+
remote_dir = remote['/home/myproj/app/']
|
52
|
+
|
53
|
+
local_dir.copy_to remote_dir
|
54
|
+
remote_dir['**/.svn/'].each { |d| d.destroy }
|
55
|
+
|
56
|
+
Clustering:
|
57
|
+
|
58
|
+
local_dir = Rush::Box.new('localhost')['/Users/adam/server_logs/'].create
|
59
|
+
servers = %w(www1 www2 www3).map { |n| Rush::Box.new(n) }
|
60
|
+
servers.each { |s| s['/var/log/nginx/access.log'].copy_to local_dir["#{s.host}_access.log"] }
|
61
|
+
|
62
|
+
== Reference
|
63
|
+
|
64
|
+
For more details on syntax and commands, see:
|
65
|
+
|
66
|
+
* Rush
|
67
|
+
* Rush::Entry
|
68
|
+
* Rush::File
|
69
|
+
* Rush::Dir
|
70
|
+
* Rush::Commands
|
71
|
+
* Rush::Box
|
72
|
+
* Rush::Process
|
73
|
+
|
74
|
+
== Meta
|
75
|
+
|
76
|
+
Created by Adam Wiggins
|
77
|
+
|
78
|
+
Patches contributed by Chihiro Ito, Gabriel Ware, Michael Schutte, Ricardo Chimal Jr., and Nicholas Schlueter, Pedro Belo, and Martin Kuehl
|
79
|
+
|
80
|
+
Logo by James Lindenbaum
|
81
|
+
|
82
|
+
Released under the MIT License: http://www.opensource.org/licenses/mit-license.php
|
83
|
+
|
84
|
+
http://rush.heroku.com
|
85
|
+
|
86
|
+
http://groups.google.com/group/ruby-shell
|
87
|
+
|
data/rush/Rakefile
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
require 'jeweler'
|
4
|
+
|
5
|
+
Jeweler::Tasks.new do |s|
|
6
|
+
s.name = "rush"
|
7
|
+
s.summary = "A Ruby replacement for bash+ssh."
|
8
|
+
s.description = "A Ruby replacement for bash+ssh, providing both an interactive shell and a library. Manage both local and remote unix systems from a single client."
|
9
|
+
s.author = "Adam Wiggins"
|
10
|
+
s.email = "adam@heroku.com"
|
11
|
+
s.homepage = "http://rush.heroku.com/"
|
12
|
+
s.executables = [ "rush", "rushd" ]
|
13
|
+
s.rubyforge_project = "ruby-shell"
|
14
|
+
s.has_rdoc = true
|
15
|
+
|
16
|
+
s.add_dependency 'session'
|
17
|
+
|
18
|
+
s.files = FileList["[A-Z]*", "{bin,lib,spec}/**/*"]
|
19
|
+
end
|
20
|
+
|
21
|
+
Jeweler::RubyforgeTasks.new
|
22
|
+
|
23
|
+
######################################################
|
24
|
+
|
25
|
+
require 'spec/rake/spectask'
|
26
|
+
|
27
|
+
desc "Run all specs"
|
28
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
29
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
30
|
+
end
|
31
|
+
|
32
|
+
desc "Print specdocs"
|
33
|
+
Spec::Rake::SpecTask.new(:doc) do |t|
|
34
|
+
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
35
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
36
|
+
end
|
37
|
+
|
38
|
+
desc "Run all examples with RCov"
|
39
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
40
|
+
t.spec_files = FileList['spec/*_spec.rb']
|
41
|
+
t.rcov = true
|
42
|
+
t.rcov_opts = ['--exclude', 'examples']
|
43
|
+
end
|
44
|
+
|
45
|
+
task :default => :spec
|
46
|
+
|
47
|
+
######################################################
|
48
|
+
|
49
|
+
require 'rake/rdoctask'
|
50
|
+
|
51
|
+
Rake::RDocTask.new do |t|
|
52
|
+
t.rdoc_dir = 'rdoc'
|
53
|
+
t.title = "rush, a Ruby replacement for bash+ssh"
|
54
|
+
t.options << '--line-numbers' << '--inline-source' << '-A cattr_accessor=object'
|
55
|
+
t.options << '--charset' << 'utf-8'
|
56
|
+
t.rdoc_files.include('README.rdoc')
|
57
|
+
t.rdoc_files.include('lib/rush.rb')
|
58
|
+
t.rdoc_files.include('lib/rush/*.rb')
|
59
|
+
end
|
60
|
+
|
data/rush/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.6.2
|
data/rush/bin/rush
ADDED
data/rush/bin/rushd
ADDED
@@ -0,0 +1,130 @@
|
|
1
|
+
# A class to hold permissions (read, write, execute) for files and dirs.
|
2
|
+
# See Rush::Entry#access= for information on the public-facing interface.
|
3
|
+
class Rush::Access
|
4
|
+
attr_accessor :user_can_read, :user_can_write, :user_can_execute
|
5
|
+
attr_accessor :group_can_read, :group_can_write, :group_can_execute
|
6
|
+
attr_accessor :other_can_read, :other_can_write, :other_can_execute
|
7
|
+
|
8
|
+
def self.roles
|
9
|
+
%w(user group other)
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.permissions
|
13
|
+
%w(read write execute)
|
14
|
+
end
|
15
|
+
|
16
|
+
def parse(options)
|
17
|
+
options.each do |key, value|
|
18
|
+
next unless m = key.to_s.match(/(.*)_can$/)
|
19
|
+
key = m[1].to_sym
|
20
|
+
roles = extract_list('role', key, self.class.roles)
|
21
|
+
perms = extract_list('permission', value, self.class.permissions)
|
22
|
+
set_matrix(perms, roles)
|
23
|
+
end
|
24
|
+
self
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.parse(options)
|
28
|
+
new.parse(options)
|
29
|
+
end
|
30
|
+
|
31
|
+
def apply(full_path)
|
32
|
+
FileUtils.chmod(octal_permissions, full_path)
|
33
|
+
rescue Errno::ENOENT
|
34
|
+
raise Rush::DoesNotExist, full_path
|
35
|
+
end
|
36
|
+
|
37
|
+
def to_hash
|
38
|
+
hash = {}
|
39
|
+
self.class.roles.each do |role|
|
40
|
+
self.class.permissions.each do |perm|
|
41
|
+
key = "#{role}_can_#{perm}".to_sym
|
42
|
+
hash[key] = send(key) ? 1 : 0
|
43
|
+
end
|
44
|
+
end
|
45
|
+
hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def display_hash
|
49
|
+
hash = {}
|
50
|
+
to_hash.each do |key, value|
|
51
|
+
hash[key] = true if value == 1
|
52
|
+
end
|
53
|
+
hash
|
54
|
+
end
|
55
|
+
|
56
|
+
def from_hash(hash)
|
57
|
+
self.class.roles.each do |role|
|
58
|
+
self.class.permissions.each do |perm|
|
59
|
+
key = "#{role}_can_#{perm}"
|
60
|
+
send("#{key}=".to_sym, hash[key.to_sym].to_i == 1 ? true : false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
self
|
64
|
+
end
|
65
|
+
|
66
|
+
def self.from_hash(hash)
|
67
|
+
new.from_hash(hash)
|
68
|
+
end
|
69
|
+
|
70
|
+
def octal_permissions
|
71
|
+
perms = [ 0, 0, 0 ]
|
72
|
+
perms[0] += 4 if user_can_read
|
73
|
+
perms[0] += 2 if user_can_write
|
74
|
+
perms[0] += 1 if user_can_execute
|
75
|
+
|
76
|
+
perms[1] += 4 if group_can_read
|
77
|
+
perms[1] += 2 if group_can_write
|
78
|
+
perms[1] += 1 if group_can_execute
|
79
|
+
|
80
|
+
perms[2] += 4 if other_can_read
|
81
|
+
perms[2] += 2 if other_can_write
|
82
|
+
perms[2] += 1 if other_can_execute
|
83
|
+
|
84
|
+
eval("0" + perms.join)
|
85
|
+
end
|
86
|
+
|
87
|
+
def from_octal(mode)
|
88
|
+
perms = octal_integer_array(mode)
|
89
|
+
|
90
|
+
self.user_can_read = (perms[0] & 4) > 0 ? true : false
|
91
|
+
self.user_can_write = (perms[0] & 2) > 0 ? true : false
|
92
|
+
self.user_can_execute = (perms[0] & 1) > 0 ? true : false
|
93
|
+
|
94
|
+
self.group_can_read = (perms[1] & 4) > 0 ? true : false
|
95
|
+
self.group_can_write = (perms[1] & 2) > 0 ? true : false
|
96
|
+
self.group_can_execute = (perms[1] & 1) > 0 ? true : false
|
97
|
+
|
98
|
+
self.other_can_read = (perms[2] & 4) > 0 ? true : false
|
99
|
+
self.other_can_write = (perms[2] & 2) > 0 ? true : false
|
100
|
+
self.other_can_execute = (perms[2] & 1) > 0 ? true : false
|
101
|
+
|
102
|
+
self
|
103
|
+
end
|
104
|
+
|
105
|
+
def octal_integer_array(mode)
|
106
|
+
mode %= 01000 # filter out everything but the bottom three digits
|
107
|
+
mode = sprintf("%o", mode) # convert to string
|
108
|
+
mode.split("").map { |p| p.to_i } # and finally, array of integers
|
109
|
+
end
|
110
|
+
|
111
|
+
def set_matrix(perms, roles)
|
112
|
+
perms.each do |perm|
|
113
|
+
roles.each do |role|
|
114
|
+
meth = "#{role}_can_#{perm}=".to_sym
|
115
|
+
send(meth, true)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def extract_list(type, value, choices)
|
121
|
+
list = parts_from(value)
|
122
|
+
list.each do |value|
|
123
|
+
raise(Rush::BadAccessSpecifier, "Unrecognized #{type}: #{value}") unless choices.include? value
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def parts_from(value)
|
128
|
+
value.to_s.split('_').reject { |r| r == 'and' }
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# Rush mixes in Rush::Commands in order to allow operations on groups of
|
2
|
+
# Rush::Entry items. For example, dir['**/*.rb'] returns an array of files, so
|
3
|
+
# dir['**/*.rb'].destroy would destroy all the files specified.
|
4
|
+
#
|
5
|
+
# One cool tidbit: the array can contain entries anywhere, so you can create
|
6
|
+
# collections of entries from different servers and then operate across them:
|
7
|
+
#
|
8
|
+
# [ box1['/var/log/access.log'] + box2['/var/log/access.log'] ].search /#{url}/
|
9
|
+
class Array
|
10
|
+
include Rush::Commands
|
11
|
+
|
12
|
+
def entries
|
13
|
+
self
|
14
|
+
end
|
15
|
+
|
16
|
+
include Rush::FindBy
|
17
|
+
|
18
|
+
include Rush::HeadTail
|
19
|
+
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# A rush box is a single unix machine - a server, workstation, or VPS instance.
|
2
|
+
#
|
3
|
+
# Specify a box by hostname (default = 'localhost'). If the box is remote, the
|
4
|
+
# first action performed will attempt to open an ssh tunnel. Use square
|
5
|
+
# brackets to access the filesystem, or processes to access the process list.
|
6
|
+
#
|
7
|
+
# Example:
|
8
|
+
#
|
9
|
+
# local = Rush::Box.new
|
10
|
+
# local['/etc/hosts'].contents
|
11
|
+
# local.processes
|
12
|
+
#
|
13
|
+
class Rush::Box
|
14
|
+
attr_reader :host
|
15
|
+
|
16
|
+
# Instantiate a box. No action is taken to make a connection until you try
|
17
|
+
# to perform an action. If the box is remote, an ssh tunnel will be opened.
|
18
|
+
# Specify a username with the host if the remote ssh user is different from
|
19
|
+
# the local one (e.g. Rush::Box.new('user@host')).
|
20
|
+
def initialize(host='localhost')
|
21
|
+
@host = host
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s # :nodoc:
|
25
|
+
host
|
26
|
+
end
|
27
|
+
|
28
|
+
def inspect # :nodoc:
|
29
|
+
host
|
30
|
+
end
|
31
|
+
|
32
|
+
# Access / on the box.
|
33
|
+
def filesystem
|
34
|
+
Rush::Entry.factory('/', self)
|
35
|
+
end
|
36
|
+
|
37
|
+
# Look up an entry on the filesystem, e.g. box['/path/to/some/file'].
|
38
|
+
# Returns a subclass of Rush::Entry - either Rush::Dir if you specifiy
|
39
|
+
# trailing slash, or Rush::File otherwise.
|
40
|
+
def [](key)
|
41
|
+
filesystem[key]
|
42
|
+
end
|
43
|
+
|
44
|
+
# Get the list of processes running on the box, not unlike "ps aux" in bash.
|
45
|
+
# Returns a Rush::ProcessSet.
|
46
|
+
def processes
|
47
|
+
Rush::ProcessSet.new(
|
48
|
+
connection.processes.map do |ps|
|
49
|
+
Rush::Process.new(ps, self)
|
50
|
+
end
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Execute a command in the standard unix shell. Returns the contents of
|
55
|
+
# stdout if successful, or raises Rush::BashFailed with the output of stderr
|
56
|
+
# if the shell returned a non-zero value. Options:
|
57
|
+
#
|
58
|
+
# :user => unix username to become via sudo
|
59
|
+
# :env => hash of environment variables
|
60
|
+
# :background => run in the background (returns Rush::Process instead of stdout)
|
61
|
+
#
|
62
|
+
# Examples:
|
63
|
+
#
|
64
|
+
# box.bash '/etc/init.d/mysql restart', :user => 'root'
|
65
|
+
# box.bash 'rake db:migrate', :user => 'www', :env => { :RAILS_ENV => 'production' }
|
66
|
+
# box.bash 'mongrel_rails start', :background => true
|
67
|
+
#
|
68
|
+
def bash(command, options={})
|
69
|
+
cmd_with_env = command_with_environment(command, options[:env])
|
70
|
+
|
71
|
+
if options[:background]
|
72
|
+
pid = connection.bash(cmd_with_env, options[:user], true)
|
73
|
+
processes.find_by_pid(pid)
|
74
|
+
else
|
75
|
+
connection.bash(cmd_with_env, options[:user], false)
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def command_with_environment(command, env) # :nodoc:
|
80
|
+
return command unless env
|
81
|
+
|
82
|
+
vars = env.map do |key, value|
|
83
|
+
"export #{key}='#{value}'"
|
84
|
+
end
|
85
|
+
vars.push(command).join("\n")
|
86
|
+
end
|
87
|
+
|
88
|
+
# Returns true if the box is responding to commands.
|
89
|
+
def alive?
|
90
|
+
connection.alive?
|
91
|
+
end
|
92
|
+
|
93
|
+
# This is called automatically the first time an action is invoked, but you
|
94
|
+
# may wish to call it manually ahead of time in order to have the tunnel
|
95
|
+
# already set up and running. You can also use this to pass a timeout option,
|
96
|
+
# either :timeout => (seconds) or :timeout => :infinite.
|
97
|
+
def establish_connection(options={})
|
98
|
+
connection.ensure_tunnel(options)
|
99
|
+
end
|
100
|
+
|
101
|
+
def connection # :nodoc:
|
102
|
+
@connection ||= make_connection
|
103
|
+
end
|
104
|
+
|
105
|
+
def make_connection # :nodoc:
|
106
|
+
host == 'localhost' ? Rush::Connection::Local.new : Rush::Connection::Remote.new(host)
|
107
|
+
end
|
108
|
+
|
109
|
+
def ==(other) # :nodoc:
|
110
|
+
host == other.host
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
# The commands module contains operations against Rush::File entries, and is
|
2
|
+
# mixed in to Rush::Entry and Array. This means you can run these commands against a single
|
3
|
+
# file, a dir full of files, or an arbitrary list of files.
|
4
|
+
#
|
5
|
+
# Examples:
|
6
|
+
#
|
7
|
+
# box['/etc/hosts'].search /localhost/ # single file
|
8
|
+
# box['/etc/'].search /localhost/ # entire directory
|
9
|
+
# box['/etc/**/*.conf'].search /localhost/ # arbitrary list
|
10
|
+
module Rush::Commands
|
11
|
+
# The entries command must return an array of Rush::Entry items. This
|
12
|
+
# varies by class that it is mixed in to.
|
13
|
+
def entries
|
14
|
+
raise "must define me in class mixed in to for command use"
|
15
|
+
end
|
16
|
+
|
17
|
+
# Search file contents for a regular expression. A Rush::SearchResults
|
18
|
+
# object is returned.
|
19
|
+
def search(pattern)
|
20
|
+
results = Rush::SearchResults.new(pattern)
|
21
|
+
entries.each do |entry|
|
22
|
+
if !entry.dir? and matches = entry.search(pattern)
|
23
|
+
results.add(entry, matches)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
results
|
27
|
+
end
|
28
|
+
|
29
|
+
# Search and replace file contents.
|
30
|
+
def replace_contents!(pattern, with_text)
|
31
|
+
entries.each do |entry|
|
32
|
+
entry.replace_contents!(pattern, with_text) unless entry.dir?
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
# Count the number of lines in the contained files.
|
37
|
+
def line_count
|
38
|
+
entries.inject(0) do |count, entry|
|
39
|
+
count += entry.lines.size if !entry.dir?
|
40
|
+
count
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Invoke vi on one or more files - only works locally.
|
45
|
+
def vi(*args)
|
46
|
+
names = entries.map { |f| f.quoted_path }.join(' ')
|
47
|
+
system "vim #{names} #{args.join(' ')}"
|
48
|
+
end
|
49
|
+
|
50
|
+
# Invoke TextMate on one or more files - only works locally.
|
51
|
+
def mate(*args)
|
52
|
+
names = entries.map { |f| f.quoted_path }.join(' ')
|
53
|
+
system "mate #{names} #{args.join(' ')}"
|
54
|
+
end
|
55
|
+
end
|