server_remote 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Tobias Crawley
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.textile ADDED
@@ -0,0 +1,37 @@
1
+ h2. Server Remote
2
+
3
+ Server Remote is a gem that provides common application server commands over ssh via a script in @script/@ called @remote@.
4
+
5
+ Currently, it provides the following commands:
6
+
7
+ * @remote shell@ - same as ssh'ing to the server (this is the default command, so it can be called with just @remote@)
8
+ * @remote console@ - executes a @script/console@ on the server
9
+ * @remote logtail@ - executes @tail -f log/<environment>.log@ on the server
10
+ * @remote cmd <some command>@ executes command on the server, displaying the result. It @cd@'s to the remote app root first.
11
+ * @remote scp <local_file> :<remote_file>@ provides scp. Prefix remote files with ':'
12
+
13
+ h3. Configuration
14
+
15
+ Configuration is stored in @config/server_remote.yml@. On installation, a sample file is copied to @APP_ROOT/config/@ (along with @APP_ROOT/script/remote@).
16
+
17
+ The configuration file groups configurations into _profiles_. A profile defines the info needed to connect to a server, along with the path to the app and the environment it is running under.
18
+
19
+ The default profile is _app_. This can be changed with the @default_profile:@ setting in the config file, and overridden on any call with the @-p profile@ switch. This switch must be the first argument. Example:
20
+
21
+ @script/remote -p admin console@
22
+
23
+ h3. Installation
24
+
25
+ First, install the gem:
26
+
27
+ @gem install tobias-server_remote --source http://gems.github.com@
28
+
29
+ Second, use @server_remotify@ command to create the config and script file in your project:
30
+
31
+ @server_remotify path_to_app@
32
+
33
+ If either of the @config/@ or @script/@ dirs do not exist, they will be created for you.
34
+
35
+ *Note:* this plugin uses the ssh and scp binaries, which must be in your path. I have absolutely no idea if it will work on Windows.
36
+
37
+ Copyright (c) 2009 Tobias Crawley, released under the MIT license
data/VERSION.yml ADDED
@@ -0,0 +1,4 @@
1
+ ---
2
+ :major: 0
3
+ :minor: 2
4
+ :patch: 0
@@ -0,0 +1,9 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'server_remote'
4
+
5
+ if ARGV[0]
6
+ ServerRemote::InstallTools.install(ARGV[0])
7
+ else
8
+ $stderr.puts "Usage: #{$0} path_to_app"
9
+ end
@@ -0,0 +1,4 @@
1
+ #default settings
2
+ environment: production
3
+ app_path: /mnt/app/current
4
+ tail_initial_lines: 500
@@ -0,0 +1,39 @@
1
+ # Config file for server_remote plugin (http://github.com/tobias/server_remote)
2
+ # Options are grouped into named profiles.
3
+
4
+ # The default profile is 'app'. This can be changed here (this is the only
5
+ # top-level configuration option - the rest are within profiles).
6
+ #default_profile: app
7
+
8
+ # The only required option in a profile is host:. The rest can be pulled from
9
+ # the defaults or inferred by ssh.
10
+
11
+ # user: Specifies the user to log in as. No default; ssh will assume the
12
+ # current user.
13
+
14
+ # keyfile: specifies the path (relative to RAILS_ROOT) to the ssh keyfile. No
15
+ # default; ssh will ask for a password.
16
+
17
+ # environment: specifies the rails environment for the app on the server. Applies
18
+ # to the 'console' and 'logtail' commands. Default is 'production'.
19
+
20
+ # app_path: specifies the path to the app on the server relative to user:'s home
21
+ # or absolute. Default is '/mnt/app/current/'.
22
+
23
+ # tail_initial_lines: specifies how many lines the 'logtail' command should show
24
+ # initially. Default is 500.
25
+
26
+ common: &common
27
+ keyfile: config/server-key.pem
28
+ host: server.example.com
29
+
30
+ # Profiles:
31
+
32
+ app:
33
+ <<: *common
34
+ user: app
35
+
36
+ admin:
37
+ <<: *common
38
+ user: admin
39
+
data/lib/hash_ext.rb ADDED
@@ -0,0 +1,8 @@
1
+ class Hash
2
+ def symbolize_keys
3
+ inject({}) do |options, (key, value)|
4
+ options[(key.to_sym rescue key) || key] = value
5
+ options
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,28 @@
1
+ module ServerRemote
2
+ class InstallTools
3
+
4
+ def self.install(app_root)
5
+ app_cfg_dir = File.join(app_root, 'config')
6
+
7
+ FileUtils.mkdir(app_cfg_dir) unless File.exists?(app_cfg_dir)
8
+
9
+ cp File.join(GEM_ROOT, 'config', 'server_remote.yml.sample'), File.join(app_cfg_dir, 'server_remote.yml')
10
+
11
+ app_script_dir = File.join(app_root, 'script')
12
+
13
+ FileUtils.mkdir(app_script_dir) unless File.exists?(app_script_dir)
14
+
15
+ cp File.join(GEM_ROOT, 'script', 'remote'), File.join(app_script_dir, 'remote')
16
+ end
17
+
18
+ private
19
+ def self.cp(src, dest)
20
+ if File.exists?(dest)
21
+ puts "File '#{dest}' exists; skipping\n"
22
+ else
23
+ FileUtils.cp src, dest
24
+ end
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,230 @@
1
+ require 'rubygems'
2
+ require 'yaml'
3
+ require 'simplecli'
4
+ require 'hash_ext'
5
+
6
+ module ServerRemote
7
+ GEM_ROOT = File.join(File.dirname(__FILE__), '..', '..')
8
+
9
+ module Util
10
+
11
+ DEFAULT_PROFILE = 'app'
12
+
13
+
14
+ def default_options_path
15
+ File.join(*([GEM_ROOT] + %w{config defaults.yml}))
16
+ end
17
+
18
+ def execute(cmd)
19
+ print "--> calling '#{cmd}'\n"
20
+ Kernel.system(cmd)
21
+ end
22
+
23
+ def user_and_host
24
+ user = "#{config[:user]}@" if config[:user]
25
+ "#{user}#{config[:host]}"
26
+ end
27
+
28
+ def keyfile_option
29
+ "-i #{File.join(app_root, config[:keyfile])} " if config[:keyfile]
30
+ end
31
+
32
+ def ssh_command
33
+ "ssh -t #{keyfile_option}#{user_and_host}"
34
+ end
35
+
36
+ def scp_command
37
+ "scp #{keyfile_option}"
38
+ end
39
+
40
+ def scp_file_argument(arg)
41
+ if arg and arg[0..0] == ':'
42
+ user_and_host + arg
43
+ else
44
+ arg
45
+ end
46
+ end
47
+
48
+ def console_action
49
+ "./script/console #{config[:environment]}"
50
+ end
51
+
52
+ def cd_to_app_action
53
+ "cd #{config[:app_path]}"
54
+ end
55
+
56
+ def tail_action
57
+ "tail -n #{config[:tail_initial_lines]} -f log/#{config[:environment]}.log"
58
+ end
59
+
60
+ def remote_command(args)
61
+ args = args.join(' ') if args.respond_to?(:join)
62
+ "#{ssh_command} '#{args}'"
63
+ end
64
+
65
+ def remote_command_in_app(args)
66
+ args = args.join(' ') if args.respond_to?(:join)
67
+ remote_command("#{cd_to_app_action};#{args}")
68
+ end
69
+
70
+ def app_root=(app_root)
71
+ @app_root = app_root
72
+ end
73
+
74
+ def app_root
75
+ raise 'app_root not set!' unless @app_root
76
+ @app_root
77
+ end
78
+
79
+ def config=(cfg)
80
+ @config = cfg
81
+ end
82
+
83
+ def config
84
+ @config ||= {}
85
+ end
86
+
87
+ def config_path
88
+ options[:config_path] ? options[:config_path] : File.join(app_root, 'config', 'server_remote.yml')
89
+ end
90
+
91
+ def load_config
92
+ load_default_config
93
+ load_app_config(config_path)
94
+ end
95
+
96
+ def parse_common_args
97
+ if args.first == '-p'
98
+ args.shift
99
+ p = args.shift
100
+ if p
101
+ config[:profile] = p
102
+ else
103
+ raise "Missing profile argument for -p"
104
+ end
105
+ end
106
+
107
+ # get around simplecli's usage call when there are no arguments
108
+ # instead of calling the default action
109
+ args << '--nullarg' if args.empty?
110
+ end
111
+
112
+ protected
113
+
114
+ def load_app_config(config_path)
115
+ cfg = YAML.load_file(config_path)
116
+ self.config[:profile] ||= cfg['default_profile'] || DEFAULT_PROFILE
117
+ if cfg[config[:profile]]
118
+ self.config.merge!(cfg[config[:profile]].symbolize_keys)
119
+ else
120
+ raise "No profile '#{config[:profile]}' exists in #{config_path}"
121
+ end
122
+ end
123
+
124
+ def load_default_config
125
+ self.config.merge!(YAML.load_file(default_options_path).symbolize_keys)
126
+ end
127
+
128
+
129
+ end
130
+
131
+ class Command
132
+ include SimpleCLI
133
+ include Util
134
+
135
+ def usage_help
136
+ "Summary: prints usage message"
137
+ end
138
+
139
+ def usage
140
+ puts <<EOH
141
+ Executes commands on a remote server over ssh. Configuration is in:
142
+ #{File.join(app_root, 'config', 'server_remote.yml')}
143
+
144
+ You can override the profile used with -p profile. The default profile is: #{config[:profile]}
145
+
146
+ Learn more in the readme:
147
+ #{File.join(GEM_ROOT, 'README.textile')}
148
+
149
+ EOH
150
+
151
+ commands
152
+ end
153
+
154
+ def shell_help
155
+ "Summary: executes remote shell"
156
+ end
157
+
158
+ def shell(*args)
159
+ execute ssh_command
160
+ end
161
+
162
+ def console_help
163
+ 'Summary: executes remote console'
164
+ end
165
+
166
+ def console(*args)
167
+ execute remote_command_in_app(console_action)
168
+ end
169
+
170
+ def logtail_help
171
+ 'Summary: executes remote tail -f on the log'
172
+ end
173
+
174
+ def logtail(*args)
175
+ execute remote_command_in_app(tail_action)
176
+ end
177
+
178
+ def cmd_help
179
+ %{
180
+ Summary: executes an arbitrary command on the server after a cd to the app path
181
+
182
+ usage: #{script_name} cmd <command>
183
+ }
184
+ end
185
+
186
+ def cmd(*args)
187
+ if args.empty?
188
+ cmd_help
189
+ else
190
+ execute remote_command_in_app(args)
191
+ end
192
+ end
193
+
194
+ def scp_help
195
+ %{
196
+ Summary: copies files over scp (prefix remote files with ':')
197
+
198
+ usage: #{script_name} scp <file1> <file2>
199
+
200
+ Example:
201
+
202
+ #{script_name} scp /local/file :/remote/file executes:
203
+ scp /local/file user@host:/remote/file
204
+
205
+ Any non colon prefixed arguments will be passed to scp.
206
+ }
207
+ end
208
+
209
+ def scp(*args)
210
+ if args.empty?
211
+ scp_help
212
+ else
213
+ execute scp_command + args.collect { |f| scp_file_argument(f) }.join(' ')
214
+ end
215
+ end
216
+
217
+
218
+ def self.start(app_root, args, options = {})
219
+ remote = new(args, options.merge(:default => 'shell'))
220
+ remote.app_root = app_root
221
+ remote.parse_common_args
222
+ remote.load_config
223
+ remote.parse!
224
+ remote.run
225
+ end
226
+
227
+ end
228
+
229
+ end
230
+
@@ -0,0 +1,7 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require File.join(File.dirname(__FILE__), 'server_remote', 'server_remote')
5
+ require File.join(File.dirname(__FILE__), 'server_remote', 'install_tools')
6
+
7
+
data/script/remote ADDED
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ #-*-ruby-*-
3
+ require 'rubygems'
4
+ require 'server_remote'
5
+
6
+ ServerRemote::Command.start(File.join(File.dirname(__FILE__), '..'), ARGV)
7
+
@@ -0,0 +1,2 @@
1
+ app:
2
+ host: test
@@ -0,0 +1,3 @@
1
+ app:
2
+ host: test
3
+ environment: test
@@ -0,0 +1,3 @@
1
+ #default settings
2
+ environment: production
3
+ app_path: current
@@ -0,0 +1,43 @@
1
+ require 'test_helper'
2
+
3
+ class ServerRemoteTest < Test::Unit::TestCase
4
+ module ServerRemote::Util
5
+ def execute(cmd)
6
+ cmd
7
+ end
8
+
9
+ end
10
+
11
+
12
+ def run_cmd(args = [])
13
+ ServerRemote::Command.start(TEST_ROOT, args, :config_path => TEST_ROOT + '/config/config_no_override.yml')
14
+ end
15
+
16
+
17
+ def test_shell_action
18
+ assert_equal 'ssh -t test', run_cmd
19
+ assert_equal 'ssh -t test', run_cmd(%w{-p app})
20
+ end
21
+
22
+ def test_console_action
23
+ assert_equal "ssh -t test 'cd /mnt/app/current;./script/console production'", run_cmd(%w{console})
24
+ end
25
+
26
+ def test_logtail_action
27
+ assert_equal "ssh -t test 'cd /mnt/app/current;tail -n 500 -f log/production.log'", run_cmd(%w{logtail})
28
+ end
29
+
30
+ def test_cmd_action
31
+ assert_match /^Summary:/, run_cmd(%w{cmd})
32
+ assert_equal "ssh -t test 'cd /mnt/app/current;ls'", run_cmd(%w{cmd ls})
33
+ assert_equal "ssh -t test 'cd /mnt/app/current;ls -p'", run_cmd(%w{cmd ls -p})
34
+ assert_equal "ssh -t test 'cd /mnt/app/current;ls'", run_cmd(%w{-p app cmd ls})
35
+ end
36
+
37
+ def test_scp_action
38
+ assert_match /^Summary:/, run_cmd(%w{scp})
39
+ assert_equal "scp test:/remote/file /local/file", run_cmd(%w{scp :/remote/file /local/file})
40
+ assert_equal "scp test:/remote/file test:/local/file", run_cmd(%w{scp :/remote/file :/local/file})
41
+ end
42
+
43
+ end
@@ -0,0 +1,75 @@
1
+ require 'test_helper'
2
+
3
+ require 'pp'
4
+
5
+ class ServerRemoteUtilTest < Test::Unit::TestCase
6
+ include ServerRemote::Util
7
+ attr_accessor :args
8
+ attr_accessor :options
9
+
10
+ def setup
11
+ self.options = {}
12
+ self.app_root = TEST_ROOT
13
+ end
14
+
15
+ def test_load_config_should_load_defaults
16
+ options[:config_path] = TEST_ROOT + '/config/config_no_override.yml'
17
+ load_config
18
+ assert_equal 'app', config[:profile]
19
+ assert_equal 'production', config[:environment]
20
+ end
21
+
22
+ def test_load_config_should_override_defaults
23
+ options[:config_path] = TEST_ROOT + '/config/config_override.yml'
24
+ load_config
25
+ assert_equal 'test', config[:environment]
26
+ end
27
+
28
+ def test_parse_common_args_should_set_profile
29
+ self.config = {}
30
+ self.args = %w{-p profile}
31
+ parse_common_args
32
+ assert_equal 'profile', config[:profile]
33
+ end
34
+
35
+
36
+ def test_user_and_host
37
+ self.config = {:host => 'host'}
38
+ assert_equal 'host', user_and_host
39
+
40
+ self.config[:user] = 'user'
41
+ assert_equal 'user@host', user_and_host
42
+ end
43
+
44
+ def test_keyfile_option
45
+ self.config = {}
46
+ assert_nil keyfile_option
47
+
48
+ self.config[:keyfile] = 'kf'
49
+ assert_equal "-i #{File.join(TEST_ROOT, 'kf')} ", keyfile_option
50
+ end
51
+
52
+ def test_remote_command
53
+ self.config = {:host => 'host'}
54
+ assert_equal "ssh -t host 'cmd'", remote_command(%w{cmd})
55
+ end
56
+
57
+ def test_remote_command_in_app
58
+ self.config = {:host => 'host', :app_path => 'path'}
59
+ assert_equal "ssh -t host 'cd path;cmd'", remote_command_in_app(%w{cmd})
60
+ end
61
+
62
+ def test_scp_command
63
+ self.config = {}
64
+ assert_equal 'scp ', scp_command
65
+ self.config[:keyfile] = 'kf'
66
+ assert_equal "scp -i #{File.join(TEST_ROOT, 'kf')} ", scp_command
67
+ end
68
+
69
+ def test_scp_file_argument
70
+ self.config = {:host => 'host'}
71
+ assert_equal 'test', scp_file_argument('test')
72
+ assert_equal 'host:test', scp_file_argument(':test')
73
+ end
74
+
75
+ end
@@ -0,0 +1,13 @@
1
+ require 'rubygems'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+
5
+ TEST_ROOT = File.dirname(__FILE__)
6
+
7
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
8
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
9
+ require 'server_remote'
10
+
11
+ class Test::Unit::TestCase
12
+
13
+ end
metadata ADDED
@@ -0,0 +1,83 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: server_remote
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Tobias Crawley
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-01 00:00:00 -04:00
13
+ default_executable: server_remotify
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: remi-simplecli
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.1.5
24
+ version:
25
+ description:
26
+ email: tcrawley@gmail.com
27
+ executables:
28
+ - server_remotify
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - LICENSE
33
+ - README.textile
34
+ files:
35
+ - README.textile
36
+ - VERSION.yml
37
+ - bin/server_remotify
38
+ - config/defaults.yml
39
+ - config/server_remote.yml.sample
40
+ - lib/hash_ext.rb
41
+ - lib/server_remote.rb
42
+ - lib/server_remote/install_tools.rb
43
+ - lib/server_remote/server_remote.rb
44
+ - script/remote
45
+ - test/config/config_no_override.yml
46
+ - test/config/config_override.yml
47
+ - test/config/defaults.yml
48
+ - test/server_remote_test.rb
49
+ - test/server_remote_util_test.rb
50
+ - test/test_helper.rb
51
+ - LICENSE
52
+ has_rdoc: true
53
+ homepage: http://github.com/tobias/server_remote
54
+ licenses: []
55
+
56
+ post_install_message:
57
+ rdoc_options:
58
+ - --charset=UTF-8
59
+ require_paths:
60
+ - lib
61
+ required_ruby_version: !ruby/object:Gem::Requirement
62
+ requirements:
63
+ - - ">="
64
+ - !ruby/object:Gem::Version
65
+ version: "0"
66
+ version:
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: "0"
72
+ version:
73
+ requirements: []
74
+
75
+ rubyforge_project:
76
+ rubygems_version: 1.3.5
77
+ signing_key:
78
+ specification_version: 3
79
+ summary: A gem that provides script/remote to a project for executing remote commands.
80
+ test_files:
81
+ - test/server_remote_test.rb
82
+ - test/server_remote_util_test.rb
83
+ - test/test_helper.rb