trainsh 0.2.0 → 0.3.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 +4 -4
- data/CHANGELOG.md +15 -0
- data/README.md +18 -0
- data/bin/trainsh +5 -0
- data/lib/trainsh/cli.rb +68 -17
- data/lib/trainsh/detectors/target/env.rb +2 -2
- data/lib/trainsh/detectors/target/kitchen.rb +42 -32
- data/lib/trainsh/mixin/builtin_commands.rb +87 -32
- data/lib/trainsh/mixin/sessions.rb +15 -3
- data/lib/trainsh/mixin/shell_output.rb +13 -0
- data/lib/trainsh/session.rb +3 -1
- data/lib/trainsh/version.rb +1 -1
- data/lib/trainsh.rb +3 -0
- metadata +6 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e16073675a7ba8bcf1dd7940f3437d724f294bcec683995064fa7d5b6ac09cf7
|
4
|
+
data.tar.gz: cfb59bdeb766c1c8c03313dc4745c55cb5579eef1e6687a8967491eb9c65f191
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2ea26ef8e8a52d8bf457a46b0ce39156fb81d47757ad6e9a90883bbd901fae275140d46e412b19638589b613be13746f6c83ec606533d707c4c1b8ad02df359c
|
7
|
+
data.tar.gz: 33ede37016876d9295bb8e75ef57aa577c8a569413728a040850842a9aae6cdbc924236c3e12d9ee643afc46a769b9475d81313825b494f248121284269d1430
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,20 @@
|
|
1
1
|
# Changelog
|
2
2
|
|
3
|
+
## Version 0.3.0
|
4
|
+
|
5
|
+
- Add auto-detection of targets from environment (variable, test-kitchen)
|
6
|
+
- Add `!copy` for cross-session copying
|
7
|
+
- Add `!help` and `?` for usage in shell
|
8
|
+
- Add detailed help for commands
|
9
|
+
- Add output on copying files cross-session and editing/saving
|
10
|
+
- Add devcontainer for VSCode
|
11
|
+
- Change errors to be red, Shell built-in to yellow
|
12
|
+
- Fix handling of URL encoded parameters
|
13
|
+
- Fix `list-transports` to output all installed non-API transports
|
14
|
+
- Fix crashes on unexpected input in shell
|
15
|
+
- Fix exception handling in various places
|
16
|
+
- Fix missing binary in Gem
|
17
|
+
|
3
18
|
## Version 0.2.0
|
4
19
|
|
5
20
|
- Renamed to TrainSH
|
data/README.md
CHANGED
@@ -69,6 +69,9 @@ Clear your TrainSH history, for example to remove clutter or sensitive informati
|
|
69
69
|
`!connect <uri>`
|
70
70
|
Connect to another system. The URI needs to match the format of the used Train transport, which is usually `transportname://host` but varies. See the Train transport's documentation for details.
|
71
71
|
|
72
|
+
`!copy @<session>:/<path> @<session>:/<path>`
|
73
|
+
Copy a file between two established sessions.
|
74
|
+
|
72
75
|
`!detect`
|
73
76
|
Re-runs the OS detection which is running automatically on start. This will determine the OS, OS-family and general platform information via Train.
|
74
77
|
|
@@ -81,6 +84,9 @@ Downloads the remote file as temporary file and opens the system default editor
|
|
81
84
|
`!env`
|
82
85
|
Prints the environment variables of your remote shell. This will be filled on first command invocation to save IO. **Currently unsupported for Windows remote systems**
|
83
86
|
|
87
|
+
`!help`
|
88
|
+
Print out help
|
89
|
+
|
84
90
|
`!history`
|
85
91
|
Output your TrainSH command history. As this uses the popular Readline library, you can also navigate your history with the Up/Down arrows or use Ctrl-R for reverse search. You can do auto completion for built-in commands.
|
86
92
|
|
@@ -141,3 +147,15 @@ To make this easier, internal commands get attached to your input like this:
|
|
141
147
|
- Postfix: Retrieve and save new environment variables
|
142
148
|
|
143
149
|
Output of commands gets separated by outputting a highly random string between, which should not result in false positives. If a false positive occurs for some reason, TrainSH will fail and output an error.
|
150
|
+
|
151
|
+
## Target Detection
|
152
|
+
|
153
|
+
As providing target URLs to connect to can be tedious, TrainSH will detect targets to connect to via plugins. In these cases, `trainsh connect` does not need any parameters.
|
154
|
+
|
155
|
+
### Environment Variables
|
156
|
+
|
157
|
+
This will check the `TARGET` environment variable for a URL and use it to connect. Only one target is allowed.
|
158
|
+
|
159
|
+
### Test Kitchen Configuration
|
160
|
+
|
161
|
+
This will detect if the current directory has a Test Kitchen configuration and a created machine. If so, it will connect to the machine by parsing information in `.kitchen/` and the kitchen configuration file.
|
data/bin/trainsh
ADDED
data/lib/trainsh/cli.rb
CHANGED
@@ -4,14 +4,12 @@ require_relative 'session'
|
|
4
4
|
require_relative 'mixin/builtin_commands'
|
5
5
|
require_relative 'mixin/file_helpers'
|
6
6
|
require_relative 'mixin/sessions'
|
7
|
-
|
8
|
-
# TODO
|
9
|
-
# require_relative 'detectors/target/env.rb'
|
10
|
-
# require_relative 'detectors/target/kitchen.rb'
|
7
|
+
require_relative 'mixin/shell_output'
|
11
8
|
|
12
9
|
require 'colored'
|
13
10
|
require 'fileutils'
|
14
11
|
require 'readline'
|
12
|
+
require 'rubygems'
|
15
13
|
require 'train'
|
16
14
|
require 'thor'
|
17
15
|
|
@@ -19,7 +17,6 @@ module TrainSH
|
|
19
17
|
class Cli < Thor
|
20
18
|
include Thor::Actions
|
21
19
|
check_unknown_options!
|
22
|
-
# add_runtime_options!
|
23
20
|
|
24
21
|
def self.exit_on_failure?
|
25
22
|
true
|
@@ -34,10 +31,14 @@ module TrainSH
|
|
34
31
|
EXIT_COMMANDS = %w[!!! exit quit logout disconnect].freeze
|
35
32
|
INTERACTIVE_COMMANDS = %w[more less vi vim nano].freeze
|
36
33
|
|
34
|
+
NON_OS_TRANSPORTS = %w[aws core kubernetes azure pgsql vsphere vault digitalocean rest].freeze
|
35
|
+
CORE_TRANSPORTS = %w[docker ssh].freeze
|
36
|
+
|
37
37
|
no_commands do
|
38
38
|
include TrainSH::Mixin::BuiltInCommands
|
39
39
|
include TrainSH::Mixin::FileHelpers
|
40
40
|
include TrainSH::Mixin::Sessions
|
41
|
+
include TrainSH::Mixin::ShellOutput
|
41
42
|
|
42
43
|
def __disconnect
|
43
44
|
session.close
|
@@ -70,12 +71,19 @@ module TrainSH
|
|
70
71
|
end
|
71
72
|
|
72
73
|
def target_detectors
|
73
|
-
Dir[File.join(__dir__, 'lib', '*.rb')].sort.each { |file| require file }
|
74
|
-
|
75
74
|
TrainSH::Detectors::TargetDetector.descendants
|
76
75
|
end
|
77
76
|
|
77
|
+
def detect_target
|
78
|
+
target_detectors.detect(&:url).url
|
79
|
+
end
|
80
|
+
|
78
81
|
def execute(input)
|
82
|
+
if input == '?'
|
83
|
+
execute_builtin 'help'
|
84
|
+
return
|
85
|
+
end
|
86
|
+
|
79
87
|
case input[0]
|
80
88
|
when '.'
|
81
89
|
execute_locally input[1..]
|
@@ -142,7 +150,7 @@ module TrainSH
|
|
142
150
|
end
|
143
151
|
|
144
152
|
def prompt
|
145
|
-
exitcode = current_session.exitcode
|
153
|
+
exitcode = current_session.exitcode || 0
|
146
154
|
exitcode_prefix = exitcode.zero? ? 'OK '.green : format('E%02d ', exitcode).red
|
147
155
|
|
148
156
|
format(::TrainSH::PROMPT,
|
@@ -159,7 +167,7 @@ module TrainSH
|
|
159
167
|
|
160
168
|
choices.concat(builtin_commands.map { |cmd| "!#{cmd.tr('_', '-')}" })
|
161
169
|
choices.concat(sessions.map { |session_id| "@#{session_id}" })
|
162
|
-
choices.concat %w[!!!]
|
170
|
+
choices.concat %w[!!! ?]
|
163
171
|
|
164
172
|
choices.filter { |choice| choice.start_with? partial }
|
165
173
|
end
|
@@ -175,14 +183,55 @@ module TrainSH
|
|
175
183
|
#
|
176
184
|
# Logger.const_get(l.upcase)
|
177
185
|
# end
|
186
|
+
|
187
|
+
def local_gems
|
188
|
+
Gem::Specification.sort_by { |g| [g.name.downcase, g.version] }.group_by(&:name)
|
189
|
+
end
|
178
190
|
end
|
179
191
|
|
180
192
|
# class_option :log_level, desc: "Log level", aliases: "-l", default: :info
|
181
193
|
class_option :messy, desc: 'Skip deletion of temporary files for speedup', default: false, type: :boolean
|
182
194
|
|
183
195
|
desc 'connect URL', 'Connect to a destination interactively'
|
196
|
+
long_desc <<-DESC
|
197
|
+
Create an interactive shell session with the remote system. The specified URL has to match the
|
198
|
+
chosen transport plugin.
|
199
|
+
|
200
|
+
If no URL was given, possible targets are detected from the environment variable TARGET or any
|
201
|
+
existing Test Kitchen instances (max: 1).
|
202
|
+
|
203
|
+
URL Examples:
|
204
|
+
docker://d9443b195d16
|
205
|
+
local://
|
206
|
+
ssh://user@remote.example.com
|
207
|
+
winrm://Administrator:PASSWORD@10.2.42.1
|
208
|
+
|
209
|
+
URL Examples from non-standard transports:
|
210
|
+
aws-ssm://i-1234567890ab
|
211
|
+
serial://dev/ttyUSB1/9600
|
212
|
+
telnet://127.0.0.1
|
213
|
+
vsphere-gom://Administrator@vcenter.server/virtual.machine
|
214
|
+
|
215
|
+
Every transport has its own, proprietary options which can currently only be added as URL
|
216
|
+
query parameters:
|
217
|
+
ssh://user@remote.example.com?key_files=/home/ubuntu/test.pem
|
218
|
+
|
219
|
+
Passwords currently have to be part of the URL.
|
220
|
+
DESC
|
221
|
+
def connect(url = nil)
|
222
|
+
# TODO: Pass options to `use_session`
|
223
|
+
unless url
|
224
|
+
show_message 'No URL given, trying to detect ...'
|
225
|
+
url = detect_target
|
226
|
+
|
227
|
+
show_message "Detected URL to be #{url}" if url
|
228
|
+
end
|
229
|
+
|
230
|
+
unless url
|
231
|
+
show_error 'No target could be detected'
|
232
|
+
exit
|
233
|
+
end
|
184
234
|
|
185
|
-
def connect(url)
|
186
235
|
exit unless use_session(url)
|
187
236
|
|
188
237
|
say format('Connected to %<url>s', url: session.url).bold
|
@@ -227,14 +276,19 @@ module TrainSH
|
|
227
276
|
|
228
277
|
execute input
|
229
278
|
end
|
279
|
+
rescue Interrupt
|
280
|
+
show_error 'Interrupted execution'
|
230
281
|
end
|
231
282
|
|
232
283
|
# desc 'copy FILE/DIR|URL FILE/DIR|URL', 'Copy files or directories'
|
233
284
|
# def copy(url_or_file, url_or_file)
|
234
|
-
# # TODO
|
285
|
+
# # TODO
|
235
286
|
# end
|
236
287
|
|
237
288
|
desc 'detect URL', 'Retrieve remote OS and platform information'
|
289
|
+
long_desc <<~DESC
|
290
|
+
Detect remote OS via Train. Uses the same schema as URLs for `connect`.
|
291
|
+
DESC
|
238
292
|
def detect(url)
|
239
293
|
exit unless use_session(url)
|
240
294
|
__detect
|
@@ -249,13 +303,10 @@ module TrainSH
|
|
249
303
|
|
250
304
|
desc 'list-transports', 'List available transports'
|
251
305
|
def list_transports
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
# TODO: Filter for only "OS" transports as well
|
256
|
-
transports = %w[local ssh winrm docker]
|
306
|
+
installed = local_gems.select { |name| name.start_with? 'train-' }.keys.map { |name| name.delete_prefix('train-') }
|
307
|
+
transports = installed - NON_OS_TRANSPORTS + CORE_TRANSPORTS
|
257
308
|
|
258
|
-
say "
|
309
|
+
say "Installed transports: #{transports.sort.join(', ')}"
|
259
310
|
end
|
260
311
|
end
|
261
312
|
end
|
@@ -1,44 +1,54 @@
|
|
1
1
|
require_relative '../target'
|
2
2
|
|
3
|
+
require 'yaml' unless defined?(YAML)
|
4
|
+
|
3
5
|
module TrainSH
|
4
6
|
module Detectors
|
5
7
|
class KitchenTarget < TargetDetector
|
6
|
-
def url
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def self.url
|
9
|
+
return unless kitchen_directory
|
10
|
+
|
11
|
+
files = Dir.glob("#{kitchen_directory}/*.yml")
|
12
|
+
return if files.empty?
|
13
|
+
|
14
|
+
# TODO: allow connecting to multiple instances
|
15
|
+
if files.count > 1
|
16
|
+
say "Found #{files.count} active kitchen instances, while only supporting 1"
|
17
|
+
exit
|
18
|
+
end
|
19
|
+
|
20
|
+
# Can get IP only from YAML files
|
21
|
+
instance_yaml = YAML.load_file(files.first)
|
22
|
+
|
23
|
+
# Can get user + protocol only from kitchen
|
24
|
+
instance_name = File.basename(files.first, '.yml')
|
25
|
+
env_prefix = prefix_env_vars
|
26
|
+
cmd = "#{env_prefix} kitchen diagnose #{instance_name}"
|
27
|
+
instance_data = YAML.safe_load(`#{cmd}`, [Symbol, Array, String])
|
28
|
+
|
29
|
+
transport = instance_data.dig('instances', instance_name, 'transport')
|
30
|
+
|
31
|
+
# TODO: Additional parameters like keypair etc
|
32
|
+
format('%<transport>s://%<user>s%<password>s@%<host>s',
|
33
|
+
transport: transport['name'],
|
34
|
+
user: transport['username'] || transport['user'],
|
35
|
+
password: transport['password'] ? ":#{transport['password']}" : '',
|
36
|
+
host: instance_yaml['hostname'] || instance_yaml['host']
|
37
|
+
)
|
11
38
|
end
|
12
39
|
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
# true
|
18
|
-
# rescue
|
19
|
-
# false
|
20
|
-
# end
|
21
|
-
|
22
|
-
# attr_writer :kitchen_instances
|
23
|
-
# def kitchen_instances
|
24
|
-
# return if @kitchen_instances
|
25
|
-
|
26
|
-
# @kitchen_instances ||= YAML.load(`KITCHEN_LOCAL_YAML=".kitchen.ec2.yaml" kitchen list --json`)
|
27
|
-
# end
|
28
|
-
|
29
|
-
# def kitchen_config
|
30
|
-
# return if @kitchen_config
|
31
|
-
|
32
|
-
# parsed_output = YAML.load(`KITCHEN_LOCAL_YAML=".kitchen.ec2.yaml" kitchen diagnose customize-amazon2`)
|
33
|
-
# @kitchen_config = kitchen_config['instances'].keys.first
|
34
|
-
# end
|
40
|
+
def self.kitchen_directory
|
41
|
+
# TODO: Recurse up
|
42
|
+
'.kitchen' if Dir.exist?('.kitchen')
|
43
|
+
end
|
35
44
|
|
36
|
-
|
37
|
-
|
45
|
+
def self.prefix_env_vars
|
46
|
+
kitchen_vars = ENV.select { |key, _value| key.start_with? 'KITCHEN_' }
|
38
47
|
|
39
|
-
|
40
|
-
|
41
|
-
|
48
|
+
# rubocop:disable Style/StringConcatenation
|
49
|
+
kitchen_vars.map { |key, value| "#{key}=\"#{value}\"" }.join(' ') + ' '
|
50
|
+
# rubocop:enable Style/StringConcatenation
|
51
|
+
end
|
42
52
|
end
|
43
53
|
end
|
44
54
|
end
|
@@ -4,6 +4,7 @@ module TrainSH
|
|
4
4
|
module Mixin
|
5
5
|
module BuiltInCommands
|
6
6
|
BUILTIN_PREFIX = 'builtincmd_'.freeze
|
7
|
+
SESSION_PATH_REGEX = %r{(/@(\d+):(/.*)$/)}.freeze
|
7
8
|
|
8
9
|
def builtin_commands
|
9
10
|
methods.sort.filter { |method| method.to_s.start_with? BUILTIN_PREFIX }.map { |method| method.to_s.delete_prefix BUILTIN_PREFIX }
|
@@ -15,16 +16,33 @@ module TrainSH
|
|
15
16
|
|
16
17
|
def builtincmd_connect(url = nil)
|
17
18
|
if url.nil? || url.strip.empty?
|
18
|
-
|
19
|
+
show_error 'Expecting session url, e.g. `!connect docker://123456789abcdef0`'
|
19
20
|
return false
|
20
21
|
end
|
21
22
|
|
22
23
|
use_session(url)
|
23
24
|
end
|
24
25
|
|
25
|
-
|
26
|
-
|
27
|
-
|
26
|
+
def builtincmd_copy(src = nil, dst = nil)
|
27
|
+
src_id, src_path = src&.match(SESSION_PATH_REGEX)&.captures
|
28
|
+
dst_id, dst_path = dst&.match(SESSION_PATH_REGEX)&.captures
|
29
|
+
unless src && dst && src_id && dst_id && src_path && dst_path
|
30
|
+
show_error 'Expecting source and destination, e.g. `!copy @0:/etc/hosts @1:/home/ubuntu/old_hosts'
|
31
|
+
return
|
32
|
+
end
|
33
|
+
|
34
|
+
src_session = session(src_id)
|
35
|
+
dst_session = session(dst_id)
|
36
|
+
unless src_session && dst_session
|
37
|
+
show_error 'Expecting valid session identifiers. Check available sessions via !sessions'
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
content = src_session.file(src_path)
|
42
|
+
dst_session.file(dst_path).content = content
|
43
|
+
|
44
|
+
show_message "Copied #{content.size} bytes successfully"
|
45
|
+
end
|
28
46
|
|
29
47
|
def builtincmd_detect(_args = nil)
|
30
48
|
__detect
|
@@ -32,45 +50,72 @@ module TrainSH
|
|
32
50
|
|
33
51
|
def builtincmd_download(remote_path = nil, local_path = nil)
|
34
52
|
if remote_path.nil? || local_path.nil?
|
35
|
-
|
53
|
+
show_error 'Expecting remote path and local path, e.g. `!download /etc/passwd /home/ubuntu`'
|
36
54
|
return false
|
37
55
|
end
|
38
56
|
|
39
57
|
return unless train_mutable?
|
40
58
|
|
41
59
|
session.download(remote_path, local_path)
|
42
|
-
|
43
|
-
|
60
|
+
|
61
|
+
show_message "Downloaded #{remote_path} successfully"
|
62
|
+
rescue NotImplementedError
|
63
|
+
show_error 'Backend for session does not implement file operations'
|
64
|
+
rescue StandardError => e
|
65
|
+
show_error "Error occured: #{e.message}"
|
44
66
|
end
|
45
67
|
|
46
68
|
def builtincmd_edit(path = nil)
|
47
69
|
if path.nil? || path.strip.empty?
|
48
|
-
|
70
|
+
show_error 'Expecting remote path, e.g. `!less /tmp/somefile.txt`'
|
49
71
|
return false
|
50
72
|
end
|
51
73
|
|
52
74
|
tempfile = read_file(path)
|
75
|
+
old_content = File.read(tempfile.path)
|
53
76
|
|
54
77
|
localeditor = ENV['EDITOR'] || ENV['VISUAL'] || 'vi' # TODO: configuration, Windows, ...
|
55
|
-
|
78
|
+
show_message format('Using local editor `%<editor>s` for %<tempfile>s', editor: localeditor, tempfile: tempfile.path)
|
56
79
|
|
57
80
|
system("#{localeditor} #{tempfile.path}")
|
58
|
-
|
59
81
|
new_content = File.read(tempfile.path)
|
60
82
|
|
61
|
-
|
83
|
+
if new_content == old_content
|
84
|
+
show_message 'No changes detected'
|
85
|
+
else
|
86
|
+
write_file(path, new_content)
|
87
|
+
|
88
|
+
show_message "Wrote #{new_content.size} bytes successfully"
|
89
|
+
end
|
90
|
+
|
62
91
|
tempfile.unlink
|
63
|
-
rescue
|
64
|
-
|
92
|
+
rescue NotImplementedError
|
93
|
+
show_error 'Backend for session does not implement file operations'
|
94
|
+
rescue StandardError => e
|
95
|
+
show_error "Error occured: #{e.message}"
|
65
96
|
end
|
66
97
|
|
67
98
|
def builtincmd_env(_args = nil)
|
68
99
|
puts session.env
|
69
100
|
end
|
70
101
|
|
102
|
+
def builtincmd_help(_args = nil)
|
103
|
+
show_message <<~HELP
|
104
|
+
Unprefixed commands get sent to the remote host of the active session.
|
105
|
+
|
106
|
+
Commands with a prefix of `@n` with n being a number will be executed on the specified session. For a list of sessions check `!sessions`.
|
107
|
+
|
108
|
+
Commands with a prefix of `.` get executed locally.
|
109
|
+
|
110
|
+
Builtin commands are prefixed with `!`:
|
111
|
+
HELP
|
112
|
+
|
113
|
+
builtin_commands.each { |cmd| show_message " !#{cmd}" }
|
114
|
+
end
|
115
|
+
|
71
116
|
def builtincmd_read(path = nil)
|
72
117
|
if path.nil? || path.strip.empty?
|
73
|
-
|
118
|
+
show_error 'Expecting remote path, e.g. `!read /tmp/somefile.txt`'
|
74
119
|
return false
|
75
120
|
end
|
76
121
|
|
@@ -78,12 +123,14 @@ module TrainSH
|
|
78
123
|
return false unless tempfile
|
79
124
|
|
80
125
|
localpager = ENV['PAGER'] || 'less' # TODO: configuration, Windows, ...
|
81
|
-
|
126
|
+
show_message format('Using local pager `%<pager>s` for %<tempfile>s', pager: localpager, tempfile: tempfile.path)
|
82
127
|
system("#{localpager} #{tempfile.path}")
|
83
128
|
|
84
129
|
tempfile.unlink
|
85
|
-
rescue
|
86
|
-
|
130
|
+
rescue NotImplementedError
|
131
|
+
show_error 'Backend for session does not implement file operations'
|
132
|
+
rescue StandardError => e
|
133
|
+
show_error "Error occured: #{e.message}"
|
87
134
|
end
|
88
135
|
|
89
136
|
def builtincmd_history(_args = nil)
|
@@ -91,16 +138,24 @@ module TrainSH
|
|
91
138
|
end
|
92
139
|
|
93
140
|
def builtincmd_host(_args = nil)
|
94
|
-
|
141
|
+
show_message session.host
|
95
142
|
end
|
96
143
|
|
97
144
|
def builtincmd_ping(_args = nil)
|
98
145
|
session.run_idle
|
99
|
-
|
146
|
+
|
147
|
+
show_message format('Ping: %<ping>dms', ping: session.ping)
|
100
148
|
end
|
101
149
|
|
150
|
+
# rubocop:disable Lint/Debugger
|
151
|
+
def builtincmd_pry(_args = nil)
|
152
|
+
require 'pry' unless defined?(binding.pry)
|
153
|
+
binding.pry
|
154
|
+
end
|
155
|
+
# rubocop:enable Lint/Debugger
|
156
|
+
|
102
157
|
def builtincmd_pwd(_args = nil)
|
103
|
-
|
158
|
+
show_message session.pwd
|
104
159
|
end
|
105
160
|
|
106
161
|
def builtincmd_reconnect(_args = nil)
|
@@ -108,20 +163,16 @@ module TrainSH
|
|
108
163
|
end
|
109
164
|
|
110
165
|
def builtincmd_sessions(_args = nil)
|
111
|
-
|
166
|
+
show_message 'Active sessions:'
|
112
167
|
|
113
168
|
@sessions.each_with_index do |session, idx|
|
114
|
-
|
169
|
+
show_message format('[%<idx>d] %<session>s', idx: idx, session: session.url)
|
115
170
|
end
|
116
171
|
end
|
117
172
|
|
118
173
|
def builtincmd_session(session_id = nil)
|
119
174
|
session_id = validate_session_id(session_id)
|
120
|
-
|
121
|
-
if session_id.nil?
|
122
|
-
say 'Expecting valid session id, e.g. `!session 2`'.red
|
123
|
-
return false
|
124
|
-
end
|
175
|
+
return if session_id.nil?
|
125
176
|
|
126
177
|
# TODO: Make this more pretty
|
127
178
|
session_url = @sessions[session_id].url
|
@@ -131,17 +182,21 @@ module TrainSH
|
|
131
182
|
|
132
183
|
def builtincmd_upload(local_path = nil, remote_path = nil)
|
133
184
|
if remote_path.nil? || local_path.nil?
|
134
|
-
|
185
|
+
show_error 'Expecting remote path and local path, e.g. `!download /home/ubuntu/passwd /etc'
|
135
186
|
return false
|
136
187
|
end
|
137
188
|
|
138
189
|
return unless train_mutable?
|
139
190
|
|
140
191
|
session.upload(local_path, remote_path)
|
192
|
+
|
193
|
+
show_message "Uploaded to #{remote_path} successfully"
|
141
194
|
rescue ::Errno::ENOENT
|
142
|
-
|
143
|
-
rescue
|
144
|
-
|
195
|
+
show_error "Local file/directory '#{local_path}' does not exist"
|
196
|
+
rescue NotImplementedError
|
197
|
+
show_error 'Backend for session does not implement upload operation'
|
198
|
+
rescue StandardError => e
|
199
|
+
show_error "Error occured: #{e.message}"
|
145
200
|
end
|
146
201
|
|
147
202
|
private
|
@@ -149,7 +204,7 @@ module TrainSH
|
|
149
204
|
def train_mutable?
|
150
205
|
return true if session.respond_to?(:upload)
|
151
206
|
|
152
|
-
|
207
|
+
show_error "Support for remote file modification needs at least Train #{::TrainSH::TRAIN_MUTABLE_VERSION} (is: #{::Train::VERSION})"
|
153
208
|
end
|
154
209
|
end
|
155
210
|
end
|
@@ -20,7 +20,9 @@ module TrainSH
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def session(session_id = current_session_id)
|
23
|
-
|
23
|
+
id = validate_session_id(session_id)
|
24
|
+
|
25
|
+
@sessions[id] if id
|
24
26
|
end
|
25
27
|
|
26
28
|
# ?
|
@@ -37,13 +39,23 @@ module TrainSH
|
|
37
39
|
end
|
38
40
|
|
39
41
|
def validate_session_id(session_id)
|
40
|
-
unless session_id
|
42
|
+
unless session_id
|
43
|
+
say 'Expecting valid session id, e.g. `!session 2`'.red
|
44
|
+
return
|
45
|
+
end
|
46
|
+
|
47
|
+
unless session_id.to_s.match?(/^[0-9]+$/)
|
41
48
|
say 'Expected session id to be numeric'.red
|
42
49
|
return
|
43
50
|
end
|
44
51
|
|
45
52
|
if @sessions[session_id.to_i].nil?
|
46
|
-
say
|
53
|
+
say 'Expecting valid session id, e.g. `!session 2`'.red
|
54
|
+
|
55
|
+
say "\nActive sessions:"
|
56
|
+
@sessions.each_with_index { |data, idx| say "[#{idx}] #{data.url}" }
|
57
|
+
say
|
58
|
+
|
47
59
|
return
|
48
60
|
end
|
49
61
|
|
data/lib/trainsh/session.rb
CHANGED
@@ -1,10 +1,11 @@
|
|
1
1
|
require 'benchmark'
|
2
2
|
require 'forwardable'
|
3
|
-
|
3
|
+
require 'cgi'
|
4
4
|
require 'train'
|
5
5
|
|
6
6
|
module TrainSH
|
7
7
|
class Command
|
8
|
+
# Used for command separation, randomly generated
|
8
9
|
MAGIC_STRING = 'mVDK6afaqa6fb7kcMqTpR2aoUFbYsRt889G4eGoI'.freeze
|
9
10
|
|
10
11
|
attr_writer :connection
|
@@ -98,6 +99,7 @@ module TrainSH
|
|
98
99
|
@url = url
|
99
100
|
|
100
101
|
data = Train.unpack_target_from_uri(url)
|
102
|
+
data.transform_values! { |val| CGI.unescape(val) }
|
101
103
|
|
102
104
|
# TODO: Wire up with "messy" parameter
|
103
105
|
data[:cleanup] = false
|
data/lib/trainsh/version.rb
CHANGED
data/lib/trainsh.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: trainsh
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Thomas Heinen
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2021-
|
11
|
+
date: 2021-11-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bump
|
@@ -168,12 +168,14 @@ description: Based on the Train ecosystem, provide a shell to manage systems via
|
|
168
168
|
multitude of transports.
|
169
169
|
email:
|
170
170
|
- theinen@tecracer.de
|
171
|
-
executables:
|
171
|
+
executables:
|
172
|
+
- trainsh
|
172
173
|
extensions: []
|
173
174
|
extra_rdoc_files: []
|
174
175
|
files:
|
175
176
|
- CHANGELOG.md
|
176
177
|
- README.md
|
178
|
+
- bin/trainsh
|
177
179
|
- lib/trainsh.rb
|
178
180
|
- lib/trainsh/cli.rb
|
179
181
|
- lib/trainsh/config.rb
|
@@ -186,6 +188,7 @@ files:
|
|
186
188
|
- lib/trainsh/mixin/builtin_commands.rb
|
187
189
|
- lib/trainsh/mixin/file_helpers.rb
|
188
190
|
- lib/trainsh/mixin/sessions.rb
|
191
|
+
- lib/trainsh/mixin/shell_output.rb
|
189
192
|
- lib/trainsh/session.rb
|
190
193
|
- lib/trainsh/version.rb
|
191
194
|
homepage: https://github.com/tecracer-chef/trainsh
|