antimony-ssh 0.1.4

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
+ SHA1:
3
+ metadata.gz: e13880e85fe266cd7cff4b9eeb56076c71d0b0c3
4
+ data.tar.gz: 8f63105db29c061a17f537ba3c845d669f4b514d
5
+ SHA512:
6
+ metadata.gz: 9359b2f40479e62d65c1f814cc6e0b4921d60a455582f41cc3ae2ce93cda93e895c09cbcd5c8171422f637a4383cee015ada0d10d4b997ecb43cc35f7643a1a9
7
+ data.tar.gz: 85a45f33ed26cf25fbbf97bdcedfb748694c3364d8aee6fecdac6e139e00d08119db269e760449ed806857f250760545de689412f31366a12f53bc6ea55f864a
data/bin/sb ADDED
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env ruby
2
+ # encoding: UTF-8
3
+
4
+ lib_path = File.expand_path('../../lib'.freeze, __FILE__)
5
+ ($LOAD_PATH.unshift lib_path) unless $LOAD_PATH.include? lib_path
6
+
7
+ require 'thor'
8
+ require 'logging'
9
+ require 'antimony'
10
+
11
+ class AntimonyCLI < Thor
12
+ desc 'formula FILE [--inputs my_input:hello other_input:goodbye]', 'run the specified formula'
13
+ method_option :inputs, type: :hash
14
+ def formula(file)
15
+ inputs = options[:inputs] || {}
16
+ formula = Antimony::Formula.new(file, inputs)
17
+ formula.run
18
+ end
19
+
20
+ default_task :formula
21
+ end
22
+
23
+ AntimonyCLI.start(ARGV)
@@ -0,0 +1,6 @@
1
+ # encoding: UTF-8
2
+ module Antimony
3
+ class Config
4
+ attr_accessor :show_output, :pretty
5
+ end
6
+ end
@@ -0,0 +1,63 @@
1
+ # encoding: UTF-8
2
+ module Antimony
3
+ class Formula
4
+ LOG_PARENT_PATH = Dir.pwd + '/log'
5
+ LOG_PATH = "#{LOG_PARENT_PATH}/formulas".freeze
6
+ FORMULAS_PATH = Dir.pwd + '/formulas'
7
+
8
+ attr_accessor :inputs, :outputs, :formula_log
9
+
10
+ def initialize(name, inputs = {})
11
+ ($LOAD_PATH.unshift FORMULAS_PATH) unless $LOAD_PATH.include? FORMULAS_PATH
12
+
13
+ @inputs = indifferent_hash(inputs)
14
+ @outputs = {}
15
+
16
+ init_log(name)
17
+
18
+ helper_path = "#{FORMULAS_PATH}/formula_helper.rb"
19
+
20
+ eval File.read(helper_path) if File.exist?(helper_path) # rubocop:disable Lint/Eval
21
+
22
+ @formula = File.read("#{FORMULAS_PATH}/#{name}.rb")
23
+ end
24
+
25
+ def run
26
+ eval(@formula) # rubocop:disable Lint/Eval
27
+ end
28
+
29
+ def session(host, username, password)
30
+ @connection = Antimony::Session.new(host, username, password)
31
+ yield if block_given?
32
+ @log.info @connection.session_log
33
+ @connection.close
34
+ @connection = nil
35
+ end
36
+
37
+ private
38
+
39
+ def init_log(name)
40
+ Dir.mkdir LOG_PARENT_PATH unless Dir.exist? LOG_PARENT_PATH
41
+ Dir.mkdir LOG_PATH unless Dir.exist? LOG_PATH
42
+ @log = Logging.logger[name]
43
+ layout = Logging.layouts.pattern(pattern: "%m\n")
44
+ @log.add_appenders(Logging.appenders.file("#{LOG_PATH}/#{name}.log", truncate: true,
45
+ layout: layout))
46
+ end
47
+
48
+ def method_missing(name, *args, &block)
49
+ raise 'No active connection!' unless @connection
50
+ @connection.send(name, *args, &block)
51
+ end
52
+
53
+ def indifferent_hash(hash)
54
+ {}.tap do |new_hash|
55
+ hash.each do |key, value|
56
+ value = indifferent_hash(value) if value.is_a?(Hash)
57
+ new_hash[key.to_sym] = value
58
+ new_hash[key.to_s] = value
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,132 @@
1
+ # encoding: UTF-8
2
+ module Antimony
3
+ class Session
4
+ def initialize(host, username, password)
5
+ @cursor_position = 0
6
+ @screen_text = blank_screen
7
+ @session_log = []
8
+
9
+ puts "Establishing SSH tunnel ..."
10
+ @gateway = Net::SSH::Gateway.new(host, username, :password => password)
11
+ tunnel_port = @gateway.open(host, 23)
12
+
13
+ puts "Connecting to #{host} using local port #{tunnel_port} ..."
14
+ @connection = Net::Telnet.new(
15
+ 'Host' => 'localhost',
16
+ 'Port' => tunnel_port,
17
+ 'Timeout' => 10
18
+ )
19
+
20
+ update_screen
21
+ end
22
+
23
+ def close
24
+ @connection.close
25
+ @gateway.shutdown!
26
+ end
27
+
28
+ def send_keys(keys, count = 1, text = true)
29
+ count.times { @connection.print keys }
30
+ update_screen
31
+ add_text(keys) if text
32
+ log_screen
33
+ print_screen if Antimony.configuration.show_output
34
+ end
35
+
36
+ def value_at(row, column, length)
37
+ start_index = ((row - 1) * 80) + (column - 1)
38
+ end_index = start_index + length - 1
39
+ @screen_text[start_index..end_index].join
40
+ end
41
+
42
+ def screen_text
43
+ @screen_text.join
44
+ end
45
+
46
+ def printable_screen_text
47
+ @screen_text.join.scan(LINE)
48
+ end
49
+
50
+ def print_screen
51
+ puts SCREEN_SEPARATOR
52
+ puts printable_screen_text
53
+ puts SCREEN_SEPARATOR
54
+ end
55
+
56
+ def session_log
57
+ txt = EMPTY.clone
58
+ @session_log.each_with_index do |entry, i|
59
+ txt += LOG_SEPARATOR + ENTER
60
+ txt += "#{i}: #{ENTER}"
61
+ txt += entry + ENTER
62
+ end
63
+ txt += LOG_SEPARATOR
64
+ txt
65
+ end
66
+
67
+ def log(text)
68
+ @session_log.push(text)
69
+ end
70
+
71
+ def log_screen
72
+ log(printable_screen_text.join("\n"))
73
+ end
74
+
75
+ private
76
+
77
+ def update_screen
78
+ @screen_data = receive_data
79
+ @screen_text = blank_screen if @screen_data.include?('[2J')
80
+ parse_ansi
81
+ end
82
+
83
+ def add_text(text)
84
+ text.chars.each do |char|
85
+ @screen_text[@cursor_position] = char
86
+ @cursor_position += 1
87
+ end
88
+ end
89
+
90
+ def receive_data
91
+ whole = []
92
+ whole.push(@chunk) while chunk
93
+ whole.join
94
+ end
95
+
96
+ def chunk
97
+ @chunk = @connection.waitfor RECEIVE_OPTS
98
+ rescue # rubocop:disable Lint/HandleExceptions
99
+ end
100
+
101
+ def cursor_position(esc)
102
+ esc_code = /\e\[/
103
+ pos = esc.gsub(esc_code, EMPTY).gsub(H, EMPTY).split(SEMICOLON)
104
+ row_index = pos[0].to_i - 1
105
+ col_index = pos[1].to_i - 1
106
+ (row_index * 80) + col_index
107
+ end
108
+
109
+ def parse_ansi
110
+ ansi = @screen_data.scan(ANSI_REGEX).map do |e|
111
+ {
112
+ value: e[0],
113
+ type: e[0].include?(ESCAPE) ? :esc : :chr
114
+ }
115
+ end
116
+
117
+ ansi.each do |e|
118
+ if (e[:type] == :esc) && (e[:value].end_with? H)
119
+ @cursor_position = cursor_position(e[:value])
120
+ elsif e[:type] == :chr
121
+ @screen_text[@cursor_position] = e[:value]
122
+ @cursor_position += 1
123
+ end
124
+ end
125
+ @screen_text
126
+ end
127
+
128
+ def blank_screen
129
+ (1..1920).to_a.map { |_i| SPACE }
130
+ end
131
+ end
132
+ end
data/lib/antimony.rb ADDED
@@ -0,0 +1,90 @@
1
+ # encoding: UTF-8
2
+ # encoding 'ASCII-8bit'
3
+ lib_path = File.expand_path('../antimony', __FILE__)
4
+ ($LOAD_PATH.unshift lib_path) unless $LOAD_PATH.include? lib_path
5
+
6
+ require 'bundler'
7
+ Bundler.require(:default)
8
+
9
+ # Misc
10
+ require 'net/telnet'
11
+ require 'net/ssh/gateway'
12
+ require 'logging'
13
+
14
+ # Gem
15
+ require "#{lib_path}/config.rb"
16
+ require "#{lib_path}/formula.rb"
17
+ require "#{lib_path}/session.rb"
18
+
19
+ module Antimony
20
+ class << self
21
+ attr_writer :configuration
22
+ end
23
+
24
+ def self.configuration
25
+ @configuration ||= Antimony::Config.new.tap { |config| config.show_output = false }
26
+ end
27
+
28
+ def self.configure
29
+ yield configuration if block_given?
30
+ end
31
+
32
+ # Constants
33
+ ANSI_REGEX = /(\e\[.{1,2};?.{0,2}m|\e\[.{1}J|\e\[.{1,2}A|\e\[.{1,2}B|\e\[.{1,2}C|\e\[.{1,2}D|\e\[K|\e\[.{1,2};.{1,2}H|\e\[.{3}|.)/ # rubocop:disable Metrics/LineLength
34
+ SCREEN_SEPARATOR = '################################################################################'.freeze
35
+ LOG_SEPARATOR = '================================================================================'.freeze
36
+
37
+ SPACE = ' '.freeze
38
+ EMPTY = ''.freeze
39
+ RECEIVE_OPTS = { 'Match' => /.{20}/, 'Timeout' => 1 }.freeze
40
+
41
+ ENTER = "\n".freeze
42
+ TAB = "\t".freeze
43
+ ESCAPE = "\e".freeze
44
+
45
+ H = 'H'.freeze
46
+
47
+ SEMICOLON = ';'.freeze
48
+
49
+ LINE = /.{80}/
50
+
51
+ class Session
52
+ KEYBOARD_METHODS = {
53
+ f1: "\x1B\x31".freeze,
54
+ f2: "\x1B\x32".freeze,
55
+ f3: "\x1B\x33".freeze,
56
+ f4: "\x1B\x34".freeze,
57
+ f5: "\x1B\x35".freeze,
58
+ f6: "\x1B\x36".freeze,
59
+ f7: "\x1B\x37".freeze,
60
+ f8: "\x1B\x38".freeze,
61
+ f9: "\x1B\x39".freeze,
62
+ f10: "\x1B\x30".freeze,
63
+ f11: "\x1B\x2D".freeze,
64
+ f12: "\x1B\x3D".freeze,
65
+ f13: "\x1B\x21".freeze,
66
+ f14: "\x1B\x40".freeze,
67
+ f15: "\x1B\x23".freeze,
68
+ f16: "\x1B\x24".freeze,
69
+ f17: "\x1B\x25".freeze,
70
+ f18: "\x1B\x5E".freeze,
71
+ f19: "\x1B\x26".freeze,
72
+ f20: "\x1B\x2A".freeze,
73
+ f21: "\x1B\x28".freeze,
74
+ f22: "\x1B\x29".freeze,
75
+ f23: "\x1B\x5F".freeze,
76
+ f24: "\x1B\x2B".freeze,
77
+ enter: "\n".freeze,
78
+ tab: "\t".freeze,
79
+ backspace: "\177".freeze,
80
+ arrow_up: "\e[A".freeze,
81
+ arrow_down: "\e[B".freeze,
82
+ arrow_right: "\e[C".freeze,
83
+ arrow_left: "\e[D".freeze
84
+ }.freeze
85
+
86
+ KEYBOARD_METHODS.each do |key, val|
87
+ define_method(key) { |count = 1| send_keys val, count, false }
88
+ end
89
+ end
90
+ end
metadata ADDED
@@ -0,0 +1,166 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: antimony-ssh
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.4
5
+ platform: ruby
6
+ authors:
7
+ - Andrew Terris
8
+ - Adrienne Hisbrook
9
+ - Lesley Dennison
10
+ - Ryan Rosenblum
11
+ - Vinh Luong
12
+ autorequire:
13
+ bindir: bin
14
+ cert_chain: []
15
+ date: 2020-09-29 00:00:00.000000000 Z
16
+ dependencies:
17
+ - !ruby/object:Gem::Dependency
18
+ requirement: !ruby/object:Gem::Requirement
19
+ requirements:
20
+ - - "~>"
21
+ - !ruby/object:Gem::Version
22
+ version: '1.2'
23
+ name: logging
24
+ type: :runtime
25
+ prerelease: false
26
+ version_requirements: !ruby/object:Gem::Requirement
27
+ requirements:
28
+ - - "~>"
29
+ - !ruby/object:Gem::Version
30
+ version: '1.2'
31
+ - !ruby/object:Gem::Dependency
32
+ requirement: !ruby/object:Gem::Requirement
33
+ requirements:
34
+ - - "~>"
35
+ - !ruby/object:Gem::Version
36
+ version: '0'
37
+ name: thor
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ requirements:
42
+ - - "~>"
43
+ - !ruby/object:Gem::Version
44
+ version: '0'
45
+ - !ruby/object:Gem::Dependency
46
+ requirement: !ruby/object:Gem::Requirement
47
+ requirements:
48
+ - - "~>"
49
+ - !ruby/object:Gem::Version
50
+ version: 4.2.0
51
+ name: net-ssh
52
+ type: :runtime
53
+ prerelease: false
54
+ version_requirements: !ruby/object:Gem::Requirement
55
+ requirements:
56
+ - - "~>"
57
+ - !ruby/object:Gem::Version
58
+ version: 4.2.0
59
+ - !ruby/object:Gem::Dependency
60
+ requirement: !ruby/object:Gem::Requirement
61
+ requirements:
62
+ - - "~>"
63
+ - !ruby/object:Gem::Version
64
+ version: 1.3.0
65
+ name: net-ssh-gateway
66
+ type: :runtime
67
+ prerelease: false
68
+ version_requirements: !ruby/object:Gem::Requirement
69
+ requirements:
70
+ - - "~>"
71
+ - !ruby/object:Gem::Version
72
+ version: 1.3.0
73
+ - !ruby/object:Gem::Dependency
74
+ requirement: !ruby/object:Gem::Requirement
75
+ requirements:
76
+ - - "~>"
77
+ - !ruby/object:Gem::Version
78
+ version: '10.3'
79
+ name: rake
80
+ type: :development
81
+ prerelease: false
82
+ version_requirements: !ruby/object:Gem::Requirement
83
+ requirements:
84
+ - - "~>"
85
+ - !ruby/object:Gem::Version
86
+ version: '10.3'
87
+ - !ruby/object:Gem::Dependency
88
+ requirement: !ruby/object:Gem::Requirement
89
+ requirements:
90
+ - - "~>"
91
+ - !ruby/object:Gem::Version
92
+ version: '3.0'
93
+ name: rspec
94
+ type: :development
95
+ prerelease: false
96
+ version_requirements: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - "~>"
99
+ - !ruby/object:Gem::Version
100
+ version: '3.0'
101
+ - !ruby/object:Gem::Dependency
102
+ requirement: !ruby/object:Gem::Requirement
103
+ requirements:
104
+ - - "~>"
105
+ - !ruby/object:Gem::Version
106
+ version: 0.10.1
107
+ name: pry
108
+ type: :development
109
+ prerelease: false
110
+ version_requirements: !ruby/object:Gem::Requirement
111
+ requirements:
112
+ - - "~>"
113
+ - !ruby/object:Gem::Version
114
+ version: 0.10.1
115
+ - !ruby/object:Gem::Dependency
116
+ requirement: !ruby/object:Gem::Requirement
117
+ requirements:
118
+ - - "~>"
119
+ - !ruby/object:Gem::Version
120
+ version: 0.37.0
121
+ name: rubocop
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ requirements:
126
+ - - "~>"
127
+ - !ruby/object:Gem::Version
128
+ version: 0.37.0
129
+ description: DSL for writing scripts to drive TN5250 green-screen applications using
130
+ Telnet via SSH tunneling
131
+ email: ''
132
+ executables:
133
+ - sb
134
+ extensions: []
135
+ extra_rdoc_files: []
136
+ files:
137
+ - bin/sb
138
+ - lib/antimony.rb
139
+ - lib/antimony/config.rb
140
+ - lib/antimony/formula.rb
141
+ - lib/antimony/session.rb
142
+ homepage: https://github.com/manheim/antimony
143
+ licenses:
144
+ - MIT
145
+ metadata: {}
146
+ post_install_message:
147
+ rdoc_options: []
148
+ require_paths:
149
+ - lib
150
+ required_ruby_version: !ruby/object:Gem::Requirement
151
+ requirements:
152
+ - - ">="
153
+ - !ruby/object:Gem::Version
154
+ version: '0'
155
+ required_rubygems_version: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ requirements: []
161
+ rubyforge_project:
162
+ rubygems_version: 2.6.14.1
163
+ signing_key:
164
+ specification_version: 4
165
+ summary: TN5250/AS400 automation
166
+ test_files: []