server_remote 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/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