train-telnet 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: ce78fe5b33ad84efb95d26d49d28e52696dba529df1cde3873334248e7b05e25
4
+ data.tar.gz: c5d5aff8f5b333c837c01bbc22ea1b1c8ccaed7441d8685616210bfdcaf8dd5f
5
+ SHA512:
6
+ metadata.gz: a5a5093d1cb53a10dc80a307e936993dc4bf619062e7e119e8c76273b0d4e3a6ecf7a042440681723448b48c5cdae0daf08f9d5f5fc66b7957d2e2257a0b1022
7
+ data.tar.gz: b01e9efcffaf31bb8074ecd180490f7d2b6d78302aec23f647164000d46a8efdaaf7c29b49b87e3b272c75371d016c7a4f57678035520ff0dcdad55532298ddc
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec
4
+
5
+ group :development do
6
+ gem "pry"
7
+ gem "bundler"
8
+ gem "rake"
9
+ gem "chefstyle"
10
+ end
data/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # train-telnet - Train Plugin for connecting via Telnet
2
+
3
+ This plugin allows applications that rely on Train to communicate via Telnet.
4
+
5
+ Of course you already know that this is an insecure protocol - but if you look
6
+ for this plugin, you probably have some specific needs. Right? If not, please
7
+ try the default SSH transport included in Train.
8
+
9
+ ## Installation
10
+
11
+ You will have to build this gem yourself to install it as it is not yet on
12
+ Rubygems.Org. For this there is a rake task which makes this a one-liner:
13
+
14
+ ```bash
15
+ rake install:local
16
+ ```
17
+
18
+ ## Transport parameters
19
+
20
+ | Option | Explanation | Default |
21
+ | ----------------- | ---------------------------- | ---------------- |
22
+ | `host` | Hostname | (required) |
23
+ | `user` | Username to connect | (required) |
24
+ | `password` | Password to connect | (required) |
25
+ | `port` | Remote port | `23` |
26
+ | `login_prompt` | Username prompt on login | `Username: ` |
27
+ | `password_prompt` | Password prompt on login | `Password: ` |
28
+ | `setup` | Commands to issue on open | ` ` |
29
+ | `teardown` | Commands to issue on close | ` ` |
30
+ | `raw_output` | Suppress stdout processing | `false` |
31
+ | `error_pattern` | Regex to match error lines | `ERROR.*` |
32
+ | `prompt_pattern` | Regex to match device prompt | `[-a-zA-Z0-9]+(?:\((?:config\|config-[a-z]+\|vlan)\))?[#>]\s*$` |
33
+
34
+ ## Example use
35
+
36
+ This will work for a Cisco IOS XE device with Telnet enabled:
37
+ ```ruby
38
+ require "train"
39
+ train = Train.create("telnet", {
40
+ host: "10.0.0.1",
41
+ user: "username",
42
+ password: "password",
43
+ setup: "terminal length 0\n",
44
+ logger: Logger.new($stdout, level: :info)
45
+ })
46
+ conn = train.connection
47
+ result = conn.run_command("show version\n")
48
+ conn.close
@@ -0,0 +1,7 @@
1
+ libdir = File.dirname(__FILE__)
2
+ $LOAD_PATH.unshift(libdir) unless $LOAD_PATH.include?(libdir)
3
+
4
+ require "train-telnet/version"
5
+
6
+ require "train-telnet/transport"
7
+ require "train-telnet/connection"
@@ -0,0 +1,107 @@
1
+ require "net/telnet"
2
+ require "train"
3
+
4
+ module TrainPlugins
5
+ module Telnet
6
+ class Connection < Train::Plugins::Transport::BaseConnection
7
+
8
+ def initialize(options)
9
+ super(options)
10
+ end
11
+
12
+ def close
13
+ return if @session.nil?
14
+
15
+ unless @options[:teardown]&.empty?
16
+ logger.debug format("[Telnet] Sending teardown commands to %s:%d", @options[:host], @options[:port])
17
+ execute_on_channel(@options[:teardown])
18
+ end
19
+
20
+ logger.info format("[Telnet] Closed connection to %s:%d", @options[:host], @options[:port])
21
+ session.close
22
+ ensure
23
+ @session = nil
24
+ end
25
+
26
+ def uri
27
+ "telnet://#{options[:user]}@#{@options[:host]}:#{@options[:port]}/"
28
+ end
29
+
30
+ def run_command_via_connection(cmd, &data_handler)
31
+ logger.debug format("[Telnet] Sending command to %s:%d", @options[:host], @options[:port])
32
+ exit_status, stdout, stderr = execute_on_channel(cmd, &data_handler)
33
+
34
+ CommandResult.new(stdout, stderr, exit_status)
35
+ end
36
+
37
+ def execute_on_channel(cmd, &data_handler)
38
+ if @options[:debug_telnet]
39
+ logger.debug "[Telnet] => #{cmd}\n"
40
+ end
41
+
42
+ stdout = session.cmd(cmd)
43
+ stderr = ""
44
+ exit_status = 0
45
+
46
+ # Output needs to be flushed first, so we don't skip a beat (Check why)
47
+ session.waitfor("Waittime" => 0.1, "Match" => /./, "Timeout" => 0.1) rescue Net::ReadTimeout
48
+
49
+ # Remove \r in linebreaks
50
+ stdout.delete!("\r")
51
+
52
+ if @options[:debug_telnet]
53
+ logger.debug "[Telnet] <= '#{stdout}'"
54
+ end
55
+
56
+ # Extract command output only (no leading/trailing prompts)
57
+ unless @options[:raw_output]
58
+ stdout = stdout.match(/#{Regexp.quote(cmd.strip)}\n(.*?)\n#{@options[:prompt_pattern]}/m)&.captures&.first
59
+ end
60
+ stdout = "" if stdout.nil?
61
+
62
+ # Simulate exit code and stderr
63
+ errors = stdout.match(/^(#{@options[:error_pattern]})/)
64
+ if errors
65
+ exit_status = 1
66
+ stderr = errors.captures.first
67
+ stdout.gsub!(/^#{@options[:error_pattern]}/, "")
68
+ end
69
+
70
+ [exit_status, stdout, stderr]
71
+ end
72
+
73
+ def session(retry_options = {})
74
+ @session ||= create_session
75
+ end
76
+
77
+ def create_session
78
+ logger.info format("[Telnet] Opening connection to %s:%d", @options[:host], @options[:port])
79
+
80
+ @session = Net::Telnet.new(
81
+ "Host" => @options[:host],
82
+ "Port" => @options[:port],
83
+ "Prompt" => Regexp.new(@options[:prompt_pattern])
84
+ )
85
+
86
+ @session.login(
87
+ "Name" => @options[:user],
88
+ "Password" => @options[:password],
89
+ "LoginPrompt" => Regexp.new(@options[:login_prompt]),
90
+ "PasswordPrompt" => Regexp.new(@options[:password_prompt])
91
+ )
92
+
93
+ unless @options[:setup].empty?
94
+ logger.debug format("[Telnet] Sending setup commands to %s:%d", @options[:host], @options[:port])
95
+
96
+ execute_on_channel(@options[:setup])
97
+ end
98
+
99
+ @session
100
+ end
101
+
102
+ def reset_session
103
+ @session.close
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,30 @@
1
+ require "train-telnet/connection"
2
+
3
+ module TrainPlugins
4
+ module Telnet
5
+ class Transport < Train.plugin(1)
6
+ name "telnet"
7
+
8
+ option :host, required: true
9
+ option :user, required: true
10
+ option :password, required: true
11
+
12
+ option :port, default: 23
13
+ option :raw_output, default: false
14
+
15
+ option :login_prompt, default: "Username: "
16
+ option :password_prompt, default: "Password: "
17
+ option :setup, default: ""
18
+ option :teardown, default: ""
19
+ option :error_pattern, default: "ERROR: .*$"
20
+ option :prompt_pattern, default: "[-a-zA-Z0-9]+(?:\((?:config|config-[a-z]+|vlan)\))?[#>]\s*$"
21
+
22
+ # Non documented options for development
23
+ option :debug_telnet, default: false
24
+
25
+ def connection(_instance_opts = nil)
26
+ @connection ||= TrainPlugins::Telnet::Connection.new(@options)
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module TrainPlugins
2
+ module Telnet
3
+ VERSION = "0.1.0".freeze
4
+ end
5
+ end
@@ -0,0 +1,24 @@
1
+ lib = File.expand_path("../lib", __FILE__)
2
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
3
+ require "train-telnet/version"
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "train-telnet"
7
+ spec.version = TrainPlugins::Telnet::VERSION
8
+ spec.authors = ["Thomas Heinen"]
9
+ spec.email = ["theinen@tecracer.de"]
10
+ spec.summary = "Train Transport for Telnet connections"
11
+ spec.description = "Allows applications using Train connect via Telnet"
12
+ spec.homepage = "https://github.com/tecracer_theinen/train-telnet"
13
+ spec.license = "Apache-2.0"
14
+
15
+ spec.files = %w{
16
+ README.md train-telnet.gemspec Gemfile
17
+ } + Dir.glob(
18
+ "lib/**/*", File::FNM_DOTMATCH
19
+ ).reject { |f| File.directory?(f) }
20
+ spec.require_paths = ["lib"]
21
+
22
+ spec.add_dependency "train", "~> 2.0"
23
+ spec.add_dependency "net-telnet", "~> 0.2"
24
+ end
metadata ADDED
@@ -0,0 +1,78 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: train-telnet
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Thomas Heinen
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-08-28 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: train
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: net-telnet
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.2'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.2'
41
+ description: Allows applications using Train connect via Telnet
42
+ email:
43
+ - theinen@tecracer.de
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - Gemfile
49
+ - README.md
50
+ - lib/train-telnet.rb
51
+ - lib/train-telnet/connection.rb
52
+ - lib/train-telnet/transport.rb
53
+ - lib/train-telnet/version.rb
54
+ - train-telnet.gemspec
55
+ homepage: https://github.com/tecracer_theinen/train-telnet
56
+ licenses:
57
+ - Apache-2.0
58
+ metadata: {}
59
+ post_install_message:
60
+ rdoc_options: []
61
+ require_paths:
62
+ - lib
63
+ required_ruby_version: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ required_rubygems_version: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ version: '0'
73
+ requirements: []
74
+ rubygems_version: 3.0.3
75
+ signing_key:
76
+ specification_version: 4
77
+ summary: Train Transport for Telnet connections
78
+ test_files: []