remote 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 +3 -0
- data/LICENSE +20 -0
- data/README.md +78 -0
- data/Rakefile +19 -0
- data/bin/remote +25 -0
- data/lib/remote.rb +7 -0
- data/lib/remote/app.rb +154 -0
- data/lib/remote/printer.rb +14 -0
- data/lib/remote/server.rb +49 -0
- metadata +73 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Rico Sta. Cruz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,78 @@
|
|
1
|
+
Remote
|
2
|
+
======
|
3
|
+
|
4
|
+
SSH helpers.
|
5
|
+
|
6
|
+
Getting started
|
7
|
+
---------------
|
8
|
+
|
9
|
+
First, install it.
|
10
|
+
|
11
|
+
gem install remote
|
12
|
+
|
13
|
+
Then, create a sample config file.
|
14
|
+
|
15
|
+
remote --sample
|
16
|
+
|
17
|
+
This generates a file called `remotes.yml`. Edit this sample file. (Tip: you can also put this in `config/remotes.yml` -- useful for Rails/Sinatra projects.)
|
18
|
+
|
19
|
+
# remotes.yml
|
20
|
+
live:
|
21
|
+
host: foo.mysite.com
|
22
|
+
user: app
|
23
|
+
key: ~/.ssh/id_rsa
|
24
|
+
path: /home/app/myapp
|
25
|
+
|
26
|
+
Now, you may easily run commands on your given servers.
|
27
|
+
|
28
|
+
remote live rake db:migrate
|
29
|
+
# Equivalent of running `ssh -i ~/.ssh/id_rsa app@foo.mysite.com -- rake db:migrate`
|
30
|
+
|
31
|
+
Going further
|
32
|
+
-------------
|
33
|
+
|
34
|
+
You may even define aliases for complicated commands.
|
35
|
+
|
36
|
+
# remotes.yml
|
37
|
+
live:
|
38
|
+
host: foo.mysite.com
|
39
|
+
commands:
|
40
|
+
deploy: |
|
41
|
+
git pull
|
42
|
+
thin -C config/thin.yml restart
|
43
|
+
rake cdn:propogate
|
44
|
+
echo Deployed new version `rake app:version`
|
45
|
+
|
46
|
+
You may then run it easily with one command:
|
47
|
+
|
48
|
+
remote live deploy
|
49
|
+
# Runs the deploy script you've defined in the config file
|
50
|
+
|
51
|
+
You may also define aliases than take arguments.
|
52
|
+
|
53
|
+
# remotes.yml
|
54
|
+
live:
|
55
|
+
host: foo.mysite.com
|
56
|
+
commands:
|
57
|
+
thor: env RACK_ENV=production rake %s
|
58
|
+
|
59
|
+
So then you may:
|
60
|
+
|
61
|
+
remote live rake app:version
|
62
|
+
# Executes 'env RACK_ENV=production rake app:version' on the remote server
|
63
|
+
|
64
|
+
Using in Ruby
|
65
|
+
-------------
|
66
|
+
|
67
|
+
require 'remote'
|
68
|
+
|
69
|
+
r = Remote::App.new
|
70
|
+
r = Remote::App.new(:config => 'servers.yml') # Optional
|
71
|
+
|
72
|
+
r.run 'live', 'ls' # Same as 'remote live ls'
|
73
|
+
r.servers #=> [<Server 'development'>, <Server 'production'>, ...]
|
74
|
+
r.write_sample # same as 'remote --sample'
|
75
|
+
|
76
|
+
r = Remote::App.new(:console => true)
|
77
|
+
r.list # Same as 'remote --list'
|
78
|
+
r.help # Same as 'remote --help'
|
data/Rakefile
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'rake'
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'jeweler'
|
6
|
+
Jeweler::Tasks.new do |gem|
|
7
|
+
gem.name = "remote"
|
8
|
+
gem.summary = "Helpers to execute complex commands in multiple servers via SSH"
|
9
|
+
gem.description = "Remote lets you define a YAML config file with your servers and command aliases, and let you run them easily from the command line."
|
10
|
+
gem.email = "rico@sinefunc.com"
|
11
|
+
gem.homepage = "http://github.com/sinefunc/remote"
|
12
|
+
gem.authors = ["Rico Sta. Cruz"]
|
13
|
+
# gem.add_development_dependency "thoughtbot-shoulda", ">= 0"
|
14
|
+
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
15
|
+
end
|
16
|
+
Jeweler::GemcutterTasks.new
|
17
|
+
rescue LoadError
|
18
|
+
puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
|
19
|
+
end
|
data/bin/remote
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
prefix = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
4
|
+
require "#{prefix}/remote"
|
5
|
+
|
6
|
+
app = Remote::App.new :console => true
|
7
|
+
|
8
|
+
if __FILE__ == $0
|
9
|
+
if ARGV.first == '--sample'
|
10
|
+
app.write_sample
|
11
|
+
|
12
|
+
elsif ARGV.first == "--list"
|
13
|
+
app.list
|
14
|
+
|
15
|
+
elsif ARGV.empty? or ARGV == ['-h'] or ARGV == ['--help'] or ARGV == ['-?']
|
16
|
+
cmd = File.basename($0)
|
17
|
+
app.help(cmd)
|
18
|
+
|
19
|
+
else
|
20
|
+
servers = ARGV.shift.to_s.split(',')
|
21
|
+
command = ARGV.join(' ')
|
22
|
+
|
23
|
+
app.run servers, *command
|
24
|
+
end
|
25
|
+
end
|
data/lib/remote.rb
ADDED
data/lib/remote/app.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
3
|
+
module Remote
|
4
|
+
class App
|
5
|
+
def initialize(options={})
|
6
|
+
if options[:config]
|
7
|
+
@config_file = [options[:config]].flatten
|
8
|
+
end
|
9
|
+
|
10
|
+
self.extend Printer if options[:console]
|
11
|
+
end
|
12
|
+
|
13
|
+
# Returns the config file location.
|
14
|
+
def config_file
|
15
|
+
config_file_locations.detect { |f| File.exists? (f) }
|
16
|
+
end
|
17
|
+
|
18
|
+
# Returns a list of where configuration files are expected to be present.
|
19
|
+
def config_file_locations
|
20
|
+
@config_file || ['config/remotes.yml', 'remotes.yml']
|
21
|
+
end
|
22
|
+
|
23
|
+
# Returns the configuration hash.
|
24
|
+
def config
|
25
|
+
verify_config
|
26
|
+
@config ||= YAML::load_file(config_file)
|
27
|
+
end
|
28
|
+
|
29
|
+
def servers
|
30
|
+
return @servers unless @servers.nil?
|
31
|
+
@servers = Hash.new
|
32
|
+
config.each { |name, data| @servers[name] = Server.new(name, data) }
|
33
|
+
@servers
|
34
|
+
end
|
35
|
+
|
36
|
+
def list
|
37
|
+
servers.keys.each { |name| log " #{name}" }
|
38
|
+
end
|
39
|
+
|
40
|
+
def run(to, *cmd)
|
41
|
+
verify_config
|
42
|
+
[to].flatten.each do |server_name|
|
43
|
+
svr = servers[server_name]
|
44
|
+
if svr.nil?
|
45
|
+
log "Unknown server '#{server_name}'. Available servers are:"
|
46
|
+
list
|
47
|
+
log "See #{config_file} for more information."
|
48
|
+
exit
|
49
|
+
end
|
50
|
+
|
51
|
+
command = svr.to_cmd(*cmd)
|
52
|
+
what = cmd.any? ? cmd.join(' ') : 'console'
|
53
|
+
status svr.to_s, what
|
54
|
+
system command
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def write_sample
|
59
|
+
unless config_file.nil?
|
60
|
+
log "A configuration file already exists in #{config_file}."
|
61
|
+
return
|
62
|
+
end
|
63
|
+
|
64
|
+
fname = config_file_locations.last
|
65
|
+
begin
|
66
|
+
File.open(fname, 'w') { |f| f.write(sample_data) }
|
67
|
+
log "Created the file #{fname}."
|
68
|
+
log "Edit this file with your list of servers, then type 'remote <yourserver>' to try it out."
|
69
|
+
rescue => e
|
70
|
+
log "Error: unable to save to #{fname}."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def help(cmd='remote')
|
75
|
+
log "Usage: #{cmd} <server>"
|
76
|
+
log " Opens a console session in the given server."
|
77
|
+
log ""
|
78
|
+
log "Usage: #{cmd} <server> <command>"
|
79
|
+
log " Executes the given command in the given server."
|
80
|
+
log ""
|
81
|
+
log "Usage: remote <svr1>,<svr2> <command>"
|
82
|
+
log " Executes the given command in multiple servers."
|
83
|
+
log ""
|
84
|
+
log "Usage: remote --list"
|
85
|
+
log " Lists available servers."
|
86
|
+
log ""
|
87
|
+
log "Configuration"
|
88
|
+
log "-------------"
|
89
|
+
log ""
|
90
|
+
log "Servers are defined in a config file. Use `#{cmd} --sample` to"
|
91
|
+
log "create a sample config file."
|
92
|
+
log ""
|
93
|
+
|
94
|
+
if config_file.nil?
|
95
|
+
log "Config files are searched for in:"
|
96
|
+
log " " + config_file_locations.join(", ")
|
97
|
+
else
|
98
|
+
log "Your configuration file is in #{config_file}."
|
99
|
+
end
|
100
|
+
|
101
|
+
log ""
|
102
|
+
log "Examples"
|
103
|
+
log "--------"
|
104
|
+
log ""
|
105
|
+
log "1) Executes 'irb -r./init' in the server called 'live'."
|
106
|
+
log " #{cmd} live irb -r./init"
|
107
|
+
log ""
|
108
|
+
log "2) Starts a console for the 'live' server."
|
109
|
+
log " #{cmd} live"
|
110
|
+
log ""
|
111
|
+
end
|
112
|
+
|
113
|
+
protected
|
114
|
+
def status(where, what)
|
115
|
+
end
|
116
|
+
|
117
|
+
def log(what)
|
118
|
+
end
|
119
|
+
|
120
|
+
def verify_config
|
121
|
+
if config_file.nil?
|
122
|
+
log "Error: no config file is present."
|
123
|
+
exit
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def sample_data
|
128
|
+
<<-adios.gsub(/^ {6}/, '')
|
129
|
+
staging: &defaults
|
130
|
+
host: staging.myserver.com
|
131
|
+
# Everything below are optional.
|
132
|
+
user: rsc
|
133
|
+
path: /home/rsc/app/current
|
134
|
+
key: ~/.ssh/id_rsa.pub
|
135
|
+
commands:
|
136
|
+
# These are optional aliases.
|
137
|
+
# Typing 'remoter staging rake x' will execute this, with %s replaced by 'x'.
|
138
|
+
rake: 'bin/rake %s'
|
139
|
+
|
140
|
+
# Typing 'remoter staging deploy' will execute this script.
|
141
|
+
deploy: |
|
142
|
+
echo Deploying...
|
143
|
+
git pull
|
144
|
+
echo Done!
|
145
|
+
|
146
|
+
# This is an example of how to define another server via inheritance.
|
147
|
+
live:
|
148
|
+
<<: *defaults
|
149
|
+
path: /home/rsc/app
|
150
|
+
host: www.myserver.com
|
151
|
+
adios
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'ostruct'
|
2
|
+
require 'shellwords'
|
3
|
+
|
4
|
+
module Remote
|
5
|
+
class Server < OpenStruct
|
6
|
+
def initialize(name, data)
|
7
|
+
super data
|
8
|
+
self.host ||= name
|
9
|
+
self.commands ||= Hash.new
|
10
|
+
end
|
11
|
+
|
12
|
+
def to_s
|
13
|
+
host.to_s
|
14
|
+
end
|
15
|
+
|
16
|
+
# Construct an SSH command line for the given command.
|
17
|
+
def to_cmd(*what)
|
18
|
+
hostname = self.user.nil? ? self.host.to_s : "#{self.user}@#{self.host}"
|
19
|
+
|
20
|
+
cmd = [ "ssh", hostname ]
|
21
|
+
cmd << "-i #{self.key}" unless self.key.nil?
|
22
|
+
|
23
|
+
[ cmd.join(' '), translate(*what) ].compact.join(' -- ')
|
24
|
+
end
|
25
|
+
|
26
|
+
# Translates a given set of commands, taking into account aliases.
|
27
|
+
#
|
28
|
+
# translate('git pull', 'thor app:restart') #=> "cd ~/x; git pull; env RACK_ENV=production thor app:restart"
|
29
|
+
def translate(*cmds)
|
30
|
+
return nil if cmds.empty?
|
31
|
+
|
32
|
+
ret = []
|
33
|
+
ret << "cd #{self.path}" unless self.path.nil?
|
34
|
+
cmds.each { |full_cmd| ret << translate_single(full_cmd) }
|
35
|
+
ret.flatten.compact.join(';').shellescape
|
36
|
+
end
|
37
|
+
|
38
|
+
# Translates a single command by resolving aliases.
|
39
|
+
def translate_single(full_cmd)
|
40
|
+
key, *args = full_cmd.split(' ')
|
41
|
+
command_alias = self.commands[key]
|
42
|
+
unless command_alias.nil?
|
43
|
+
command_alias.gsub("\n",';').gsub('%s', args.join(' '))
|
44
|
+
else
|
45
|
+
full_cmd
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: remote
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
version: 0.0.1
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Rico Sta. Cruz
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-11-26 00:00:00 +08:00
|
18
|
+
default_executable: remote
|
19
|
+
dependencies: []
|
20
|
+
|
21
|
+
description: Remote lets you define a YAML config file with your servers and command aliases, and let you run them easily from the command line.
|
22
|
+
email: rico@sinefunc.com
|
23
|
+
executables:
|
24
|
+
- remote
|
25
|
+
extensions: []
|
26
|
+
|
27
|
+
extra_rdoc_files:
|
28
|
+
- LICENSE
|
29
|
+
- README.md
|
30
|
+
files:
|
31
|
+
- .gitignore
|
32
|
+
- LICENSE
|
33
|
+
- README.md
|
34
|
+
- Rakefile
|
35
|
+
- bin/remote
|
36
|
+
- lib/remote.rb
|
37
|
+
- lib/remote/app.rb
|
38
|
+
- lib/remote/printer.rb
|
39
|
+
- lib/remote/server.rb
|
40
|
+
has_rdoc: true
|
41
|
+
homepage: http://github.com/sinefunc/remote
|
42
|
+
licenses: []
|
43
|
+
|
44
|
+
post_install_message:
|
45
|
+
rdoc_options:
|
46
|
+
- --charset=UTF-8
|
47
|
+
require_paths:
|
48
|
+
- lib
|
49
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
segments:
|
55
|
+
- 0
|
56
|
+
version: "0"
|
57
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
58
|
+
none: false
|
59
|
+
requirements:
|
60
|
+
- - ">="
|
61
|
+
- !ruby/object:Gem::Version
|
62
|
+
segments:
|
63
|
+
- 0
|
64
|
+
version: "0"
|
65
|
+
requirements: []
|
66
|
+
|
67
|
+
rubyforge_project:
|
68
|
+
rubygems_version: 1.3.7
|
69
|
+
signing_key:
|
70
|
+
specification_version: 3
|
71
|
+
summary: Helpers to execute complex commands in multiple servers via SSH
|
72
|
+
test_files: []
|
73
|
+
|