Narnach-simple_gate 0.5.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Wes Oldenbeuving
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.rdoc ADDED
@@ -0,0 +1,107 @@
1
+ = SimpleGate
2
+
3
+ SimpleGate makes it possible to use net/ssh/gateway's capabilities in a simple
4
+ to use way. This makes it possible to write simple scripts that access servers that are located several hops inside a network.
5
+
6
+ The core gateway chaining logic was taken from Capistrano, but it has been
7
+ rewritten to be useful in a more generic way than Capistrano allows.
8
+
9
+ SimpleGate's Router class makes it possible to automatically find a route
10
+ through a maze of servers instead of having to manually define gateway chains.
11
+ An added benefit is that a change to one server's connections no longer
12
+ requires you to update all other servers if they relied on it as a gateway.
13
+
14
+ SimpleGate is simple, meaning it does not (yet) do fancy things such as using
15
+ ~/.ssh. Because of this, you'll have to define ~/.servers.yml and add entries
16
+ to configure your servers. To use the routing functionality used by the
17
+ gate_cp and simple_gate executables, you have to define routes in
18
+ ~/.servers.connections.yml
19
+
20
+ An example configuration for the servers 'foobar' and 'barfoo' would look be:
21
+
22
+ ---
23
+ foobar:
24
+ address: "127.0.0.1"
25
+ username: "foo"
26
+ password: "bar
27
+ port: 22
28
+ barfoo:
29
+ address: "192.168.0.1"
30
+ username: "bar"
31
+ password: "foo
32
+ port: 22
33
+
34
+ Example of a ~/.servers.connections.yml file:
35
+
36
+ ---
37
+ local:
38
+ - foo
39
+ foo:
40
+ - bar
41
+ - baz
42
+ bar:
43
+ - foobar
44
+ - foobaz
45
+ - barbaz
46
+
47
+ All keys are names of servers defined in ~/.servers.yml. The only special node
48
+ is "local", which is the starting point for all connections and not a real
49
+ connection.
50
+
51
+ == Recent changes
52
+
53
+ === Version 0.5.1
54
+
55
+ * Updated readme and simple_gate documentation
56
+ * Implemented infinite loop prevention code in Router#find
57
+ * Cleaned up Router#find to be more readable
58
+ * Added spec to show stack overflow occurs when a cyclical graph is used to
59
+ find a route
60
+ * Added specs for Router#find
61
+ * Added a -V (verbose) flag option to simple_gate
62
+ * Removed empty method definition
63
+
64
+ === Version 0.5.0
65
+
66
+ * Added syntax explanation to gate_cp
67
+ * Added gate_cp, which copies one local file to a remote destination
68
+
69
+ === Version 0.4.1
70
+
71
+ * simple_gate is now executable
72
+
73
+ === Version 0.4.0
74
+
75
+ * Updated readme with version history for previous gem versions
76
+ * Added simple_gate executable to use SimpleGate's new Router class to find a path to a server and execute a command there.
77
+
78
+ === Version 0.3.0
79
+
80
+ * Readme now has a project description and updated credits for a bit of Capistrano code
81
+ * Added mutator and documentation for ServerDefinition.servers
82
+ * Updated comment to make more sense
83
+
84
+ === Version 0.2.0
85
+
86
+ * Implemented SimpleGate#through_to, which establishes a real ssh session through a number of gateways
87
+
88
+ === Version 0.1.0
89
+
90
+ * Removed simple_gate executable
91
+ * Imported SimpleGate and ServerDefinition
92
+
93
+ === Version 0.0.1
94
+ * Created project
95
+
96
+ == Installation
97
+ === From git
98
+ From the project root, use rake to install.
99
+ git clone http://github.com/Narnach/simple_gate
100
+ cd simple_gate
101
+ rake install
102
+ This will build the gem and install it for you.
103
+
104
+ == About
105
+ simple_gate was created by Wes Oldenbeuving. It is licensed under the MIT license.
106
+
107
+ SimpleGate#through is based on GatewayConnectionFactory#initialize, which is part of Jamis Buck's Capistrano and is also licensed under the MIT license.
data/Rakefile ADDED
@@ -0,0 +1,29 @@
1
+ require "rake"
2
+ require "rake/clean"
3
+ require "rake/gempackagetask"
4
+ require 'rubygems'
5
+
6
+ ################################################################################
7
+ ### Gem
8
+ ################################################################################
9
+
10
+ begin
11
+ # Parse gemspec using the github safety level.
12
+ data = File.read('simple_gate.gemspec')
13
+ spec = nil
14
+ Thread.new { spec = eval("$SAFE = 3
15
+ %s" % data)}.join
16
+
17
+ # Create the gem tasks
18
+ Rake::GemPackageTask.new(spec) do |package|
19
+ package.gem_spec = spec
20
+ end
21
+ rescue Exception => e
22
+ printf "WARNING: Error caught (%s): %s
23
+ ", e.class.name, e.message
24
+ end
25
+
26
+ desc 'Package and install the gem for the current version'
27
+ task :install => :gem do
28
+ system "sudo gem install -l pkg/%s-%s.gem" % [spec.name, spec.version]
29
+ end
data/bin/gate_cp ADDED
@@ -0,0 +1,52 @@
1
+ #!/usr/bin/env ruby
2
+ # Copy a single local file to a remote server.
3
+ # Syntax: gate_cp <server> <source_file> <target_file>
4
+ # server: a server listed in ~/.servers.yml and reachable by a
5
+ # series of connections described in ~/.servers.connections.yml
6
+ # source_file: filename or full path+filename of local source file
7
+ # target_file: filename or full path+filename of target file
8
+ #
9
+ # Example: gate_cp foobar ~/foo.txt foo.txt
10
+ # This copies ~/foo.txt too the server foobar as foo.txt in the home dir
11
+ #
12
+ # Example: gate_cp foobar foo.txt /tmp/bar.txt
13
+ # This copies the local file foo.txt too the server foobar as /tmp/foo.txt
14
+ require 'simple_gate'
15
+ require 'simple_gate/router'
16
+ require 'net/sftp'
17
+
18
+ connections = YAML.load_file(File.expand_path('~/.servers.connections.yml'))
19
+ from = 'local'
20
+ target = ARGV.shift.to_s.strip
21
+
22
+ cmd = ARGV.join(" ")
23
+ if cmd.strip.size == 0
24
+ puts "No command was given"
25
+ exit 1
26
+ end
27
+
28
+ router = Router.new(connections)
29
+ route = router.find(from, target)
30
+ if route.nil?
31
+ puts "No route to #{target}"
32
+ exit 1
33
+ end
34
+
35
+ route.shift if route.first == 'local'
36
+
37
+ if route.size==1
38
+ puts "No gateway needed to reach #{route.last}"
39
+ exit 2
40
+ end
41
+
42
+ source = File.expand_path(ARGV.shift.to_s.strip)
43
+ target = ARGV.shift.to_s.strip
44
+
45
+ puts "Connecting to #{route.last}, using #{route.size - 1} gateway(s)"
46
+
47
+ gate = SimpleGate.new
48
+ gate.through_to(route) do |ssh|
49
+ puts "Transferring: #{source} => #{target}"
50
+ ssh.sftp.upload!(source, target)
51
+ end
52
+
data/bin/simple_gate ADDED
@@ -0,0 +1,61 @@
1
+ #!/usr/bin/env ruby
2
+ # Syntax: simple_gate [-V] <server> <command>
3
+ # Finds a route to <server>, uses the neccesary gates to connect to it and
4
+ # executes <command> on that server. Set -V for verbose output.
5
+ #
6
+ # Uses ~/.servers.connections.yml and ~/.servers.yml to find the fastest path
7
+ # from the local server to a target server, establish a series of gateways
8
+ # and then execute a command on the remote server.
9
+ # Example of a ~/.servers.connections.yml file:
10
+ #
11
+ # ---
12
+ # local:
13
+ # - foo
14
+ # foo:
15
+ # - bar
16
+ # - baz
17
+ # bar:
18
+ # - foobar
19
+ # - foobaz
20
+ # - barbaz
21
+ #
22
+ # All keys are names of servers defined in ~/.servers.yml. The only special
23
+ # node is "local", which is the starting point for all connections and not a
24
+ # real connection.
25
+
26
+ require 'simple_gate'
27
+ require 'simple_gate/router'
28
+
29
+ verbose = !ARGV.delete('-V').nil?
30
+
31
+ connections = YAML.load_file(File.expand_path('~/.servers.connections.yml'))
32
+ from = 'local'
33
+ target = ARGV.shift.to_s.strip
34
+
35
+ cmd = ARGV.join(" ")
36
+ if cmd.strip.size == 0
37
+ puts "No command was given"
38
+ exit 1
39
+ end
40
+
41
+ router = Router.new(connections)
42
+ route = router.find(from, target)
43
+ if route.nil?
44
+ puts "No route to #{target}"
45
+ exit 1
46
+ end
47
+
48
+ route.shift if route.first == 'local'
49
+
50
+ if route.size==1
51
+ puts "No gateway needed to reach #{route.last}"
52
+ exit 2
53
+ end
54
+
55
+ puts "Executing '#{cmd}' in #{route.last}, using #{route.size - 1} gateway(s)" if verbose
56
+
57
+ gate = SimpleGate.new(:verbose=>verbose)
58
+ gate.through_to(route) do |ssh|
59
+ puts ssh.exec!(cmd)
60
+ end
61
+
@@ -0,0 +1,42 @@
1
+ class Router
2
+ # Graph of server connections.
3
+ # It's a Hash of Arrays, which contains strings. Keys are server names with
4
+ # a connection to other servers. Each of these servers is a string in the
5
+ # associated Array.
6
+ # An example graph with three servers 'foo', 'bar' and 'baz'.
7
+ #
8
+ # router = Router.new
9
+ # router.paths = {
10
+ # 'foo' => %w[bar],
11
+ # 'bar' => ['baz']
12
+ # }
13
+ # router.find('foo', 'baz') #=> ['foo', 'bar', 'baz']
14
+ attr_accessor :paths
15
+
16
+ def initialize(paths={})
17
+ @paths = paths
18
+ end
19
+
20
+ # A simple pathfinder. Returns a route as Array or nil.
21
+ # Uses a very naieve depth-first recursive full-graph search to
22
+ # find the shortest route.
23
+ def find(start, target, current_route = [])
24
+ return [target] if start == target
25
+ return nil unless paths.has_key?(start)
26
+
27
+ # Map all possible paths to the target.
28
+ # Skip nodes we have already visited
29
+ next_nodes = paths[start] - current_route
30
+ routes = next_nodes.map do |next_node|
31
+ find(next_node, target, current_route + [start])
32
+ end
33
+
34
+ # Reduce the collection to the shortest path
35
+ shortest_route = routes.compact.inject(nil) {|shortest,possibility|
36
+ next possibility if shortest.nil?
37
+ possibility.size < shortest.size ? possibility : shortest
38
+ }
39
+ return [start] + shortest_route if shortest_route
40
+ nil
41
+ end
42
+ end
@@ -0,0 +1,83 @@
1
+ require 'yaml'
2
+ class ServerDefinition
3
+ attr_accessor :host, :user, :options, :port, :password
4
+
5
+ def initialize(host, options = {})
6
+ @options = {:port => 22}.merge(options)
7
+ self.host = host
8
+ self.user = @options[:user]
9
+ self.password = @options[:password]
10
+ self.port = @options[:port]
11
+ end
12
+
13
+ def user=(user)
14
+ options[:user] = user
15
+ @user=user
16
+ end
17
+
18
+ def port=(port)
19
+ options[:port] = port
20
+ @port=port
21
+ end
22
+
23
+ def password=(password)
24
+ options[:password] = password
25
+ @password=password
26
+ end
27
+
28
+ def connection_info(&block)
29
+ block.call(host, user, options.merge(ssh_options))
30
+ end
31
+
32
+ def ssh_options
33
+ {
34
+ :auth_methods => %w[password keyboard-interactive]
35
+ }
36
+ end
37
+
38
+ def to_s
39
+ "#{user}:#{'*' * password.to_s.size}@#{host}:#{port}"
40
+ end
41
+
42
+ def self.lookup(server)
43
+ server = servers[server]
44
+ new(server['address'],{
45
+ :user => server['username'],
46
+ :password => server['password'],
47
+ :port => server['port']
48
+ })
49
+ end
50
+
51
+ def self.find(server)
52
+ servers.has_key?(server) ? lookup(server) : parse(server)
53
+ end
54
+
55
+ def self.parse(ssh_string)
56
+ user, password, host, port = ssh_string.match /\A(.*?):(.*?)@(.*?):(\d*?)\Z/
57
+ new(host, :user => user, :password => password, :port => port)
58
+ end
59
+
60
+ class << self
61
+ attr_accessor :servers
62
+
63
+ # Access the pre-configured servers. ~/.servers.yml is parsed for this.
64
+ # An example entry for the servers 'foobar' and 'barfoo' would look like:
65
+ # ---
66
+ # foobar:
67
+ # address: "127.0.0.1"
68
+ # username: "foo"
69
+ # password: "bar
70
+ # port: 22
71
+ # barfoo:
72
+ # address: "192.168.0.1"
73
+ # username: "bar"
74
+ # password: "foo
75
+ # port: 22
76
+ #
77
+ # Since the parsed Hash of servers is cached, a value can be stored and
78
+ # the configuration file ignored if desired.
79
+ def servers
80
+ @servers ||= YAML.load_file(File.expand_path('~/.servers.yml'))
81
+ end
82
+ end
83
+ end
@@ -0,0 +1,64 @@
1
+ require 'rubygems'
2
+ require 'net/ssh'
3
+ require 'net/ssh/gateway'
4
+ require 'activesupport'
5
+ require 'simple_gate/server_definition'
6
+
7
+ class SimpleGate
8
+ def initialize(options={})
9
+ @options = {
10
+ :verbose => false
11
+ }.merge(options)
12
+ end
13
+
14
+ def verbose?
15
+ @options[:verbose]
16
+ end
17
+
18
+ # Connect through a list of gateways to the real server.
19
+ # Treats the last 'gateway' as the real server and the others as gateways.
20
+ # Needs at least one real gateway and a real server.
21
+ def through_to(*gateways) # :yields: ssh session
22
+ gateways = gateways.flatten
23
+ raise ArgumentError.new("Need at least 2 servers") if gateways.size < 2
24
+ target = ServerDefinition.find(gateways.pop)
25
+ through(gateways) do |gate|
26
+ target.connection_info do |host, user, options|
27
+ gate.ssh(host, user, options) do |session|
28
+ yield(session)
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ # Most of the code was taken from Capistrano and then changed to work
35
+ # outside of Capistrano.
36
+ def through(*gateways)
37
+ Thread.abort_on_exception = true
38
+ @open_connections ||= []
39
+ @gateways = gateways.flatten.collect { |g| ServerDefinition.find(g) }
40
+ tunnel = @gateways[0].connection_info do |host, user, connect_options|
41
+ puts "Setting up tunnel #{@gateways[0]}" if verbose?
42
+ gw = Net::SSH::Gateway.new(host, user, connect_options)
43
+ @open_connections << gw
44
+ gw
45
+ end
46
+ @gateway = (@gateways[1..-1]).inject(tunnel) do |tunnel, destination|
47
+ puts "Connecting to #{destination}" if verbose?
48
+ tunnel_port = tunnel.open(destination.host, (destination.port || 22))
49
+ localhost_options = {:user => destination.user, :port => tunnel_port, :password => destination.password}
50
+ local_host = ServerDefinition.new("127.0.0.1", localhost_options)
51
+ local_host.connection_info do |host, user, connect_options|
52
+ puts "Connecting using local info #{local_host}" if verbose?
53
+ gw = Net::SSH::Gateway.new(host, user, connect_options)
54
+ @open_connections << gw
55
+ gw
56
+ end
57
+ end
58
+ yield(@gateway)
59
+ ensure
60
+ while g = @open_connections.pop
61
+ g.shutdown!
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,34 @@
1
+ Gem::Specification.new do |s|
2
+ # Project
3
+ s.name = 'simple_gate'
4
+ s.summary = "SimpleGate makes it possible to use net/ssh/gateway's capabilities in a simple to use way."
5
+ s.description = s.summary
6
+ s.version = '0.5.1'
7
+ s.date = '2009-04-18'
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Wes Oldenbeuving"]
10
+ s.email = "narnach@gmail.com"
11
+ s.homepage = "http://www.github.com/Narnach/simple_gate"
12
+
13
+ # Files
14
+ root_files = %w[MIT-LICENSE README.rdoc Rakefile simple_gate.gemspec]
15
+ bin_files = %w[simple_gate gate_cp]
16
+ lib_files = %w[simple_gate
17
+ simple_gate/server_definition
18
+ simple_gate/router]
19
+ test_files = %w[]
20
+ spec_files = %w[simple_gate]
21
+ s.bindir = "bin"
22
+ s.require_path = "lib"
23
+ s.executables = bin_files
24
+ s.test_files = test_files.map {|f| 'test/%s_test.rb' % f} + spec_files.map {|f| 'spec/%s_spec.rb' % f}
25
+ s.files = root_files + bin_files.map {|f| 'bin/%s' % f} + lib_files.map {|f| 'lib/%s.rb' % f} + s.test_files
26
+
27
+ # rdoc
28
+ s.has_rdoc = true
29
+ s.extra_rdoc_files = %w[ README.rdoc MIT-LICENSE]
30
+ s.rdoc_options << '--inline-source' << '--line-numbers' << '--main' << 'README.rdoc'
31
+
32
+ # Requirements
33
+ s.required_ruby_version = ">= 1.8.0"
34
+ end
@@ -0,0 +1,6 @@
1
+ require File.join(File.dirname(__FILE__), 'spec_helper')
2
+ require 'simple_gate'
3
+
4
+ describe SimpleGate do
5
+ it 'should be tested'
6
+ end
metadata ADDED
@@ -0,0 +1,67 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: Narnach-simple_gate
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.5.1
5
+ platform: ruby
6
+ authors:
7
+ - Wes Oldenbeuving
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-04-18 00:00:00 -07:00
13
+ default_executable:
14
+ dependencies: []
15
+
16
+ description: SimpleGate makes it possible to use net/ssh/gateway's capabilities in a simple to use way.
17
+ email: narnach@gmail.com
18
+ executables:
19
+ - simple_gate
20
+ - gate_cp
21
+ extensions: []
22
+
23
+ extra_rdoc_files:
24
+ - README.rdoc
25
+ - MIT-LICENSE
26
+ files:
27
+ - MIT-LICENSE
28
+ - README.rdoc
29
+ - Rakefile
30
+ - simple_gate.gemspec
31
+ - bin/simple_gate
32
+ - bin/gate_cp
33
+ - lib/simple_gate.rb
34
+ - lib/simple_gate/server_definition.rb
35
+ - lib/simple_gate/router.rb
36
+ - spec/simple_gate_spec.rb
37
+ has_rdoc: true
38
+ homepage: http://www.github.com/Narnach/simple_gate
39
+ post_install_message:
40
+ rdoc_options:
41
+ - --inline-source
42
+ - --line-numbers
43
+ - --main
44
+ - README.rdoc
45
+ require_paths:
46
+ - lib
47
+ required_ruby_version: !ruby/object:Gem::Requirement
48
+ requirements:
49
+ - - ">="
50
+ - !ruby/object:Gem::Version
51
+ version: 1.8.0
52
+ version:
53
+ required_rubygems_version: !ruby/object:Gem::Requirement
54
+ requirements:
55
+ - - ">="
56
+ - !ruby/object:Gem::Version
57
+ version: "0"
58
+ version:
59
+ requirements: []
60
+
61
+ rubyforge_project:
62
+ rubygems_version: 1.2.0
63
+ signing_key:
64
+ specification_version: 2
65
+ summary: SimpleGate makes it possible to use net/ssh/gateway's capabilities in a simple to use way.
66
+ test_files:
67
+ - spec/simple_gate_spec.rb