train-core 3.4.8 → 3.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 54dee42a0ad37ef8b4308a184f5bd63f869565db241c1c1fa910626d6c9a323f
4
- data.tar.gz: afbeb697f1d0613f9d86af612e5ce6745b27b4ac597221b5e07db0e11b9d90d8
3
+ metadata.gz: 7a7d91d3461558b977d37ebc8b71a904995e096cd42776b4d63df73314985c5d
4
+ data.tar.gz: b6b8ccaec9143f42ed4a2a6f03d49335a199f3ba76f31a63a4d26ded880cf397
5
5
  SHA512:
6
- metadata.gz: d2029c737655fc5f2eea92e290d73bbd0b6b7eac73ecc7e2e5526870e5318f6beceba8bc64c555ac451007cd1083870bba35985fa8353560a6040a5c6b997186
7
- data.tar.gz: c57536c1bdddbde99d4955276d5fe14c20dbe84b7f109059bb8d8ee56a02f49f484811784aacf8384b1d6e5229f92f46f03fe005008677531eaef2abea55fe62
6
+ metadata.gz: 7d034aa2c18e2d875969a1bae10d81a3afae603d6ed7112344caccbcedcf4dd0c267b60bc29bf0213998add5aa4e9b190274980a8ed17804034824c1753c3158
7
+ data.tar.gz: f0c2a005d0dd5c27cd3ccb6128f49ed3ea748460cfd32deb1742549ac4eef7f09298a8532ea791fb76cf13ab0eb02d0b023dbebad9bb3ccfe0e74fbb5eb2fdc0
@@ -15,6 +15,12 @@ module Train
15
15
  nil
16
16
  end
17
17
 
18
+ def content=(new_content)
19
+ ::File.open(@path, "w", encoding: "UTF-8") { |fp| fp.write(new_content) }
20
+
21
+ @content = new_content
22
+ end
23
+
18
24
  def link_path
19
25
  return nil unless symlink?
20
26
 
@@ -1,3 +1,4 @@
1
+ require "base64" unless defined?(Base64)
1
2
  require "shellwords" unless defined?(Shellwords)
2
3
 
3
4
  module Train
@@ -19,6 +20,21 @@ module Train
19
20
  end
20
21
  end
21
22
 
23
+ def content=(new_content)
24
+ execute_result = @backend.run_command("base64 --help")
25
+ if execute_result.exit_status != 0
26
+ raise TransportError, "#{self.class} found no base64 binary for file writes"
27
+ end
28
+
29
+ unix_cmd = format("echo '%<base64>s' | base64 --decode > %<file>s",
30
+ base64: Base64.strict_encode64(new_content),
31
+ file: @spath)
32
+
33
+ @backend.run_command(unix_cmd)
34
+
35
+ @content = new_content
36
+ end
37
+
22
38
  def exist?
23
39
  @exist ||= begin
24
40
  f = @follow_symlink ? "" : " || test -L #{@spath}"
@@ -26,6 +26,16 @@ module Train
26
26
  @content
27
27
  end
28
28
 
29
+ def content=(new_content)
30
+ win_cmd = format('[IO.File]::WriteAllBytes("%<file>s", [Convert]::FromBase64String("%<base64>s"))',
31
+ base64: Base64.strict_encode64(new_content),
32
+ file: @spath)
33
+
34
+ @backend.run_command(win_cmd)
35
+
36
+ @content = new_content
37
+ end
38
+
29
39
  def exist?
30
40
  return @exist if defined?(@exist)
31
41
 
@@ -1,6 +1,7 @@
1
1
  require_relative "../errors"
2
2
  require_relative "../extras"
3
3
  require_relative "../file"
4
+ require "fileutils" unless defined?(FileUtils)
4
5
  require "logger"
5
6
 
6
7
  class Train::Plugins::Transport
@@ -161,6 +162,48 @@ class Train::Plugins::Transport
161
162
  @cache[:file][path] ||= file_via_connection(path, *args)
162
163
  end
163
164
 
165
+ # Uploads local files or directories to remote host.
166
+ #
167
+ # @param locals [Array<String>] paths to local files or directories
168
+ # @param remote [String] path to remote destination
169
+ # @raise [TransportFailed] if the files could not all be uploaded
170
+ # successfully, which may vary by implementation
171
+ def upload(locals, remote)
172
+ unless file(remote).directory?
173
+ raise TransportError, "#{self.class} expects remote directory as second upload parameter"
174
+ end
175
+
176
+ Array(locals).each do |local|
177
+ new_content = File.read(local)
178
+ remote_file = File.join(remote, File.basename(local))
179
+
180
+ logger.debug("Attempting to upload '#{local}' as file #{remote_file}")
181
+
182
+ file(remote_file).content = new_content
183
+ end
184
+ end
185
+
186
+ # Download remote files or directories to local host.
187
+ #
188
+ # @param remotes [Array<String>] paths to remote files or directories
189
+ # @param local [String] path to local destination. If `local` is an
190
+ # existing directory, `remote` will be downloaded into the directory
191
+ # using its original name
192
+ # @raise [TransportFailed] if the files could not all be downloaded
193
+ # successfully, which may vary by implementation
194
+ def download(remotes, local)
195
+ FileUtils.mkdir_p(File.dirname(local))
196
+
197
+ Array(remotes).each do |remote|
198
+ new_content = file(remote).content
199
+ local_file = File.join(local, File.basename(remote))
200
+
201
+ logger.debug("Attempting to download '#{remote}' as file #{local_file}")
202
+
203
+ File.open(local_file, "w") { |fp| fp.write(new_content) }
204
+ end
205
+ end
206
+
164
207
  # Builds a LoginCommand which can be used to open an interactive
165
208
  # session on the remote host.
166
209
  #
@@ -29,6 +29,14 @@ class Train::Transports::SSH
29
29
  result.stdout.split(" ")[-1]
30
30
  end
31
31
 
32
+ def upload(locals, remote)
33
+ raise NotImplementedError, "#{self.class} does not implement #upload()"
34
+ end
35
+
36
+ def download(remotes, local)
37
+ raise NotImplementedError, "#{self.class} does not implement #download()"
38
+ end
39
+
32
40
  private
33
41
 
34
42
  def establish_connection
@@ -31,10 +31,26 @@ module Train::Transports
31
31
  nil # none, open your shell
32
32
  end
33
33
 
34
+ def close
35
+ @runner.close
36
+ end
37
+
34
38
  def uri
35
39
  "local://"
36
40
  end
37
41
 
42
+ def upload(locals, remote)
43
+ FileUtils.mkdir_p(remote)
44
+
45
+ Array(locals).each do |local|
46
+ FileUtils.cp_r(local, remote)
47
+ end
48
+ end
49
+
50
+ def download(remotes, local)
51
+ upload(remotes, local)
52
+ end
53
+
38
54
  private
39
55
 
40
56
  def select_runner(options)
@@ -70,9 +86,9 @@ module Train::Transports
70
86
  end
71
87
  end
72
88
 
73
- def run_command_via_connection(cmd, &_data_handler)
89
+ def run_command_via_connection(cmd, opts, &_data_handler)
74
90
  # Use the runner if it is available
75
- return @runner.run_command(cmd) if defined?(@runner)
91
+ return @runner.run_command(cmd, opts) if defined?(@runner)
76
92
 
77
93
  # If we don't have a runner, such as at the beginning of setting up the
78
94
  # transport and performing the first few steps of OS detection, fall
@@ -99,15 +115,24 @@ module Train::Transports
99
115
  @cmd_wrapper = Local::CommandWrapper.load(connection, options)
100
116
  end
101
117
 
102
- def run_command(cmd)
118
+ def run_command(cmd, opts = {})
103
119
  if defined?(@cmd_wrapper) && !@cmd_wrapper.nil?
104
120
  cmd = @cmd_wrapper.run(cmd)
105
121
  end
106
122
 
107
123
  res = Mixlib::ShellOut.new(cmd)
108
- res.run_command
124
+ res.timeout = opts[:timeout]
125
+ begin
126
+ res.run_command
127
+ rescue Mixlib::ShellOut::CommandTimeout
128
+ raise Train::CommandTimeoutReached
129
+ end
109
130
  Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
110
131
  end
132
+
133
+ def close
134
+ # nothing to do at the moment
135
+ end
111
136
  end
112
137
 
113
138
  class WindowsShellRunner
@@ -118,7 +143,7 @@ module Train::Transports
118
143
  @powershell_cmd = powershell_cmd
119
144
  end
120
145
 
121
- def run_command(script)
146
+ def run_command(script, opts)
122
147
  # Prevent progress stream from leaking into stderr
123
148
  script = "$ProgressPreference='SilentlyContinue';" + script
124
149
 
@@ -129,9 +154,18 @@ module Train::Transports
129
154
  cmd = "#{@powershell_cmd} -NoProfile -EncodedCommand #{base64_script}"
130
155
 
131
156
  res = Mixlib::ShellOut.new(cmd)
132
- res.run_command
157
+ res.timeout = opts[:timeout]
158
+ begin
159
+ res.run_command
160
+ rescue Mixlib::ShellOut::CommandTimeout
161
+ raise Train::CommandTimeoutReached
162
+ end
133
163
  Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
134
164
  end
165
+
166
+ def close
167
+ # nothing to do at the moment
168
+ end
135
169
  end
136
170
 
137
171
  class WindowsPipeRunner
@@ -141,6 +175,7 @@ module Train::Transports
141
175
 
142
176
  def initialize(powershell_cmd = "powershell")
143
177
  @powershell_cmd = powershell_cmd
178
+ @server_pid = nil
144
179
  @pipe = acquire_pipe
145
180
  raise PipeError if @pipe.nil?
146
181
  end
@@ -151,21 +186,31 @@ module Train::Transports
151
186
  # A command that succeeds without setting an exit code will have exitstatus 0
152
187
  # A command that exits with an exit code will have that value as exitstatus
153
188
  # A command that fails (e.g. throws exception) before setting an exit code will have exitstatus 1
154
- def run_command(cmd)
189
+ def run_command(cmd, _opts)
155
190
  script = "$ProgressPreference='SilentlyContinue';" + cmd
156
191
  encoded_script = Base64.strict_encode64(script)
192
+ # TODO: no way to safely implement timeouts here.
157
193
  @pipe.puts(encoded_script)
158
194
  @pipe.flush
159
195
  res = OpenStruct.new(JSON.parse(Base64.decode64(@pipe.readline)))
160
196
  Local::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
161
197
  end
162
198
 
199
+ def close
200
+ Process.kill("KILL", @server_pid) unless @server_pid.nil?
201
+ @server_pid = nil
202
+ end
203
+
163
204
  private
164
205
 
165
206
  def acquire_pipe
207
+ require "win32/process"
166
208
  pipe_name = "inspec_#{SecureRandom.hex}"
167
209
 
168
- start_pipe_server(pipe_name)
210
+ @server_pid = start_pipe_server(pipe_name)
211
+
212
+ # Ensure process is killed when the Train process exits
213
+ at_exit { close rescue Errno::EIO }
169
214
 
170
215
  pipe = nil
171
216
 
@@ -227,11 +272,7 @@ module Train::Transports
227
272
  utf8_script = script.encode("UTF-16LE", "UTF-8")
228
273
  base64_script = Base64.strict_encode64(utf8_script)
229
274
  cmd = "#{@powershell_cmd} -NoProfile -ExecutionPolicy bypass -NonInteractive -EncodedCommand #{base64_script}"
230
-
231
- server_pid = Process.create(command_line: cmd).process_id
232
-
233
- # Ensure process is killed when the Train process exits
234
- at_exit { Process.kill("KILL", server_pid) }
275
+ Process.create(command_line: cmd).process_id
235
276
  end
236
277
  end
237
278
  end
data/lib/train/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
  # Author:: Dominik Richter (<dominik.richter@gmail.com>)
3
3
 
4
4
  module Train
5
- VERSION = "3.4.8".freeze
5
+ VERSION = "3.6.0".freeze
6
6
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: train-core
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.4.8
4
+ version: 3.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chef InSpec Team
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-01-26 00:00:00.000000000 Z
11
+ date: 2021-04-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: addressable