train 0.13.1 → 0.14.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 7604c39afcff5da8e779ef47620e390b71d83a0a
4
- data.tar.gz: 1191f7c084243fd91c9e3b1dcfc1ad4ee4758ade
3
+ metadata.gz: bef369edee3db008a24f018851fb38b4b8ee2690
4
+ data.tar.gz: 4a029b67d3631b3975ad928c1b7c7832ac7da2cf
5
5
  SHA512:
6
- metadata.gz: 94543cfac0c7dfb5ccfdcfb2a56f5e9d53680201187f5a10dad5d649eeaa1a9403eff44e56a280f877cea4cba0fa0fa1c97cb3e7d494c8fede74fef2a4296a18
7
- data.tar.gz: aa1849279ce4a0c81018d0645f6cea47b0c33d7862c5fab49445d997237371f2b0203146c1b1b93f6877106d3051c463795784c13b06e6ec1c2f5abcbc769ba4
6
+ metadata.gz: 81d4f61c914a1b3cbdfc11037c90be5652d3a7a8264d8ba351c65e614036a71bb507339ea6bfec310098b65a261b831d0ca97e517bd86dde5d286fae4cc9d466
7
+ data.tar.gz: c8d5bddc212d56e7d7f604df95008339b676c8dcf59a7dc59c7422d997d23781adce45276db983184a7c4b659282b98bd1d3b6f50b7434444fca12dfb7d8e7c0
data/CHANGELOG.md CHANGED
@@ -1,7 +1,20 @@
1
1
  # Change Log
2
2
 
3
- ## [0.13.1](https://github.com/chef/train/tree/0.13.1) (2016-06-16)
4
- [Full Changelog](https://github.com/chef/train/compare/v0.12.1...0.13.1)
3
+ ## [0.14.0](https://github.com/chef/train/tree/0.14.0) (2016-06-27)
4
+ [Full Changelog](https://github.com/chef/train/compare/v0.13.1...0.14.0)
5
+
6
+ **Implemented enhancements:**
7
+
8
+ - json in and out for base connection [\#118](https://github.com/chef/train/pull/118) ([arlimus](https://github.com/arlimus))
9
+ - ESX support [\#116](https://github.com/chef/train/pull/116) ([Anirudh-Gupta](https://github.com/Anirudh-Gupta))
10
+
11
+ **Fixed bugs:**
12
+
13
+ - sporadic appveyor failure on `winrm delete ...` [\#105](https://github.com/chef/train/issues/105)
14
+ - bugfix: run frozen string commands via ssh [\#117](https://github.com/chef/train/pull/117) ([arlimus](https://github.com/arlimus))
15
+
16
+ ## [v0.13.1](https://github.com/chef/train/tree/v0.13.1) (2016-06-16)
17
+ [Full Changelog](https://github.com/chef/train/compare/v0.12.1...v0.13.1)
5
18
 
6
19
  **Implemented enhancements:**
7
20
 
@@ -9,10 +9,12 @@ module Train::Extras
9
9
  class FileCommon # rubocop:disable Metrics/ClassLength
10
10
  # interface methods: these fields should be implemented by every
11
11
  # backend File
12
- %w{
12
+ DATA_FIELDS = %w{
13
13
  exist? mode owner group uid gid content mtime size selinux_label path
14
14
  product_version file_version
15
- }.each do |m|
15
+ }.freeze
16
+
17
+ DATA_FIELDS.each do |m|
16
18
  define_method m.to_sym do
17
19
  fail NotImplementedError, "File must implement the #{m}() method."
18
20
  end
@@ -24,6 +26,14 @@ module Train::Extras
24
26
  @follow_symlink = follow_symlink
25
27
  end
26
28
 
29
+ def to_json
30
+ res = Hash[DATA_FIELDS.map { |x| [x, method(x).call] }]
31
+ # additional fields provided as input
32
+ res['type'] = type
33
+ res['follow_symlink'] = @follow_symlink
34
+ res
35
+ end
36
+
27
37
  def type
28
38
  :unknown
29
39
  end
@@ -50,27 +60,27 @@ module Train::Extras
50
60
  # Additional methods for convenience
51
61
 
52
62
  def file?
53
- type == :file
63
+ type.to_s == 'file'
54
64
  end
55
65
 
56
66
  def block_device?
57
- type == :block_device
67
+ type.to_s == 'block_device'
58
68
  end
59
69
 
60
70
  def character_device?
61
- type == :character_device
71
+ type.to_s == 'character_device'
62
72
  end
63
73
 
64
74
  def socket?
65
- type == :socket
75
+ type.to_s == 'socket'
66
76
  end
67
77
 
68
78
  def directory?
69
- type == :directory
79
+ type.to_s == 'directory'
70
80
  end
71
81
 
72
82
  def symlink?
73
- source.type == :symlink
83
+ source.type.to_s == 'symlink'
74
84
  end
75
85
 
76
86
  def source_path
@@ -12,6 +12,7 @@ require 'train/extras/os_detect_darwin'
12
12
  require 'train/extras/os_detect_linux'
13
13
  require 'train/extras/os_detect_unix'
14
14
  require 'train/extras/os_detect_windows'
15
+ require 'train/extras/os_detect_esx'
15
16
 
16
17
  module Train::Extras
17
18
  class OSCommon
@@ -19,6 +20,7 @@ module Train::Extras
19
20
  include Train::Extras::DetectLinux
20
21
  include Train::Extras::DetectUnix
21
22
  include Train::Extras::DetectWindows
23
+ include Train::Extras::DetectEsx
22
24
 
23
25
  def initialize(backend, platform = nil)
24
26
  @backend = backend
@@ -107,7 +109,7 @@ module Train::Extras
107
109
  detect_family_type
108
110
  end
109
111
 
110
- def detect_family_type
112
+ def detect_family_type # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
111
113
  pf = @platform[:family]
112
114
 
113
115
  return detect_windows if pf == 'windows'
@@ -120,6 +122,7 @@ module Train::Extras
120
122
 
121
123
  # unix based systems combine the above
122
124
  return true if pf == 'unix' and detect_darwin
125
+ return true if pf == 'unix' and detect_esx
123
126
  return true if pf == 'unix' and detect_via_uname
124
127
 
125
128
  # if we arrive here, we most likey have a regular linux
@@ -11,10 +11,12 @@
11
11
  module Train::Extras
12
12
  module DetectEsx
13
13
  def detect_esx
14
- @platform[:family] = 'esx'
15
- @platform[:name] = uname_s.lines[0].chomp
16
- @platform[:release] = uname_r.lines[0].chomp
17
- true
14
+ if uname_s.downcase.chomp == 'vmkernel'
15
+ @platform[:family] = 'esx'
16
+ @platform[:name] = uname_s.lines[0].chomp
17
+ @platform[:release] = uname_r.lines[0].chomp
18
+ true
19
+ end
18
20
  end
19
21
  end
20
22
  end
@@ -1,7 +1,6 @@
1
1
  # encoding: utf-8
2
2
  # author: Dominik Richter
3
3
  # author: Christoph Hartmann
4
-
5
4
  module Train::Extras
6
5
  class Stat
7
6
  TYPES = {
@@ -20,13 +19,13 @@ module Train::Extras
20
19
  end
21
20
 
22
21
  def self.stat(shell_escaped_path, backend, follow_symlink)
23
- # use perl scripts for aix and solaris 10
22
+ # use perl scripts for aix, solaris 10 and hpux
24
23
  if backend.os.aix? || (backend.os.solaris? && backend.os[:release].to_i < 11) || backend.os.hpux?
25
24
  return aix_stat(shell_escaped_path, backend, follow_symlink)
26
25
  end
27
26
  return bsd_stat(shell_escaped_path, backend, follow_symlink) if backend.os.bsd?
28
- # linux and solaris 11 will use standard linux stats
29
- return linux_stat(shell_escaped_path, backend, follow_symlink) if backend.os.unix?
27
+ # linux,solaris 11 and esx will use standard linux stats
28
+ return linux_stat(shell_escaped_path, backend, follow_symlink) if backend.os.unix? || backend.os.esx?
30
29
  # all other cases we don't handle
31
30
  # TODO: print an error if we get here, as it shouldn't be invoked
32
31
  # on non-unix
@@ -35,7 +34,8 @@ module Train::Extras
35
34
 
36
35
  def self.linux_stat(shell_escaped_path, backend, follow_symlink)
37
36
  lstat = follow_symlink ? ' -L' : ''
38
- res = backend.run_command("stat#{lstat} #{shell_escaped_path} 2>/dev/null --printf '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'")
37
+ format = backend.os.esx? ? '-c' : '--printf'
38
+ res = backend.run_command("stat#{lstat} #{shell_escaped_path} 2>/dev/null #{format} '%s\n%f\n%U\n%u\n%G\n%g\n%X\n%Y\n%C'")
39
39
 
40
40
  # ignore the exit_code: it is != 0 if selinux labels are not supported
41
41
  # on the system.
@@ -45,8 +45,8 @@ module Train::Extras
45
45
 
46
46
  tmask = fields[1].to_i(16)
47
47
  selinux = fields[8]
48
- selinux = nil if selinux == '?' or selinux == '(null)'
49
-
48
+ ## selinux security context string not available on esxi
49
+ selinux = nil if selinux == '?' or selinux == '(null)' or selinux == 'C'
50
50
  {
51
51
  type: find_type(tmask),
52
52
  mode: tmask & 07777,
@@ -18,6 +18,9 @@ class Train::Plugins::Transport
18
18
  class BaseConnection
19
19
  include Train::Extras
20
20
 
21
+ # Provide access to the files cache.
22
+ attr_reader :files
23
+
21
24
  # Create a new Connection instance.
22
25
  #
23
26
  # @param options [Hash] connection options
@@ -25,6 +28,7 @@ class Train::Plugins::Transport
25
28
  def initialize(options = nil)
26
29
  @options = options || {}
27
30
  @logger = @options.delete(:logger) || Logger.new(STDOUT)
31
+ @files = {}
28
32
  end
29
33
 
30
34
  # Closes the session connection, if it is still active.
@@ -32,6 +36,19 @@ class Train::Plugins::Transport
32
36
  # this method may be left unimplemented if that is applicable
33
37
  end
34
38
 
39
+ def to_json
40
+ {
41
+ 'files' => Hash[@files.map { |x, y| [x, y.to_json] }],
42
+ }
43
+ end
44
+
45
+ def load_json(j)
46
+ require 'train/transports/mock'
47
+ j['files'].each do |path, jf|
48
+ @files[path] = Train::Transports::Mock::Connection::File.from_json(jf)
49
+ end
50
+ end
51
+
35
52
  # Execute a command using this connection.
36
53
  #
37
54
  # @param command [String] command string to execute
@@ -60,7 +60,6 @@ class Train::Transports::Docker
60
60
  @id = options[:host]
61
61
  @container = ::Docker::Container.get(@id) ||
62
62
  fail("Can't find Docker container #{@id}")
63
- @files = {}
64
63
  @cmd_wrapper = nil
65
64
  @cmd_wrapper = CommandWrapper.load(self, @options)
66
65
  self
@@ -22,7 +22,6 @@ module Train::Transports
22
22
  class Connection < BaseConnection
23
23
  def initialize(options)
24
24
  super(options)
25
- @files = {}
26
25
  @cmd_wrapper = nil
27
26
  @cmd_wrapper = CommandWrapper.load(self, options)
28
27
  end
@@ -65,7 +65,6 @@ class Train::Transports::Mock
65
65
 
66
66
  def initialize(conf = nil)
67
67
  @conf = conf || {}
68
- @files = {}
69
68
  @os = OS.new(self, family: 'unknown')
70
69
  @commands = {}
71
70
  end
@@ -130,12 +129,27 @@ end
130
129
 
131
130
  class Train::Transports::Mock::Connection
132
131
  class File < FileCommon
133
- %w{
134
- exist? mode owner group link_path content mtime size
135
- selinux_label product_version file_version path type
136
- }.each do |m|
132
+ def self.from_json(json)
133
+ res = new(json['backend'],
134
+ json['path'],
135
+ json['follow_symlink'])
136
+ res.type = json['type']
137
+ Train::Extras::FileCommon::DATA_FIELDS.each do |f|
138
+ m = (f.tr('?', '') + '=').to_sym
139
+ res.method(m).call(json[f])
140
+ end
141
+ res
142
+ end
143
+
144
+ Train::Extras::FileCommon::DATA_FIELDS.each do |m|
137
145
  attr_accessor m.tr('?', '').to_sym
146
+ next unless m.include?('?')
147
+
148
+ define_method m.to_sym do
149
+ method(m.tr('?', '').to_sym).call
150
+ end
138
151
  end
152
+ attr_accessor :type
139
153
 
140
154
  def initialize(backend, path, follow_symlink = true)
141
155
  super(backend, path, follow_symlink)
@@ -38,7 +38,6 @@ class Train::Transports::SSH
38
38
  @connection_retries = @options.delete(:connection_retries)
39
39
  @connection_retry_sleep = @options.delete(:connection_retry_sleep)
40
40
  @max_wait_until_ready = @options.delete(:max_wait_until_ready)
41
- @files = {}
42
41
  @session = nil
43
42
  @transport_options = @options.delete(:transport_options)
44
43
  @cmd_wrapper = nil
@@ -73,7 +72,7 @@ class Train::Transports::SSH
73
72
  def run_command(cmd)
74
73
  stdout = stderr = ''
75
74
  exit_status = nil
76
- cmd.force_encoding('binary') if cmd.respond_to?(:force_encoding)
75
+ cmd.dup.force_encoding('binary') if cmd.respond_to?(:force_encoding)
77
76
  logger.debug("[SSH] #{self} (#{cmd})")
78
77
 
79
78
  session.open_channel do |channel|
@@ -27,7 +27,7 @@ class Train::Transports::WinRM
27
27
  # host such as executing commands, transferring files, etc.
28
28
  #
29
29
  # @author Fletcher Nichol <fnichol@nichol.ca>
30
- class Connection < BaseConnection # rubocop:disable Metrics/ClassLength
30
+ class Connection < BaseConnection
31
31
  def initialize(options)
32
32
  super(options)
33
33
  @endpoint = @options.delete(:endpoint)
@@ -36,7 +36,6 @@ class Train::Transports::WinRM
36
36
  @connection_retries = @options.delete(:connection_retries)
37
37
  @connection_retry_sleep = @options.delete(:connection_retry_sleep)
38
38
  @max_wait_until_ready = @options.delete(:max_wait_until_ready)
39
- @files = {}
40
39
  end
41
40
 
42
41
  # (see Base::Connection#close)
data/lib/train/version.rb CHANGED
@@ -3,5 +3,5 @@
3
3
  # Author:: Dominik Richter (<dominik.richter@gmail.com>)
4
4
 
5
5
  module Train
6
- VERSION = '0.13.1'.freeze
6
+ VERSION = '0.14.0'.freeze
7
7
  end
@@ -0,0 +1,4 @@
1
+ images:
2
+ - alpine:edge
3
+ provision:
4
+ - script: bootstrap.sh
@@ -0,0 +1,4 @@
1
+ images:
2
+ - ubuntu:16.04
3
+ provision:
4
+ - script: bootstrap.sh
@@ -75,5 +75,11 @@ describe 'file interface' do
75
75
  it 'has no file_version' do
76
76
  file.file_version.must_equal(nil)
77
77
  end
78
+
79
+ it 'provides a json representation' do
80
+ j = file.to_json
81
+ j.must_be_kind_of Hash
82
+ j['type'].must_equal :file
83
+ end
78
84
  end
79
85
  end
@@ -10,6 +10,13 @@ describe 'run_command' do
10
10
  res.exit_status.must_equal(0)
11
11
  end
12
12
 
13
+ it 'can run frozen commands' do
14
+ res = backend.run_command('echo hello world'.freeze)
15
+ res.stdout.must_equal("hello world\n")
16
+ res.stderr.must_equal('')
17
+ res.exit_status.must_equal(0)
18
+ end
19
+
13
20
  it 'can echo commands to stderr' do
14
21
  # TODO: Specinfra often fails on this test.
15
22
  # Fix and re-enable it.
@@ -270,6 +270,7 @@ describe 'os common plugin' do
270
270
  let(:os) { mock_platform('esx') }
271
271
  it { os.solaris?.must_equal(false) }
272
272
  it { os.linux?.must_equal(false) }
273
+ it {os[:family].must_equal('esx')}
273
274
  it { os.unix?.must_equal(false) }
274
275
  it { os.esx?.must_equal(true) }
275
276
  end
@@ -46,15 +46,56 @@ describe 'stat' do
46
46
 
47
47
  it 'ignores wrong stat results' do
48
48
  res = Minitest::Mock.new
49
+ os = Minitest::Mock.new
49
50
  res.expect :stdout, ''
51
+ os.expect :esx?, false
52
+ backend.expect :os, os
50
53
  backend.expect :run_command, res, [String]
51
54
  cls.linux_stat('/path', backend, false).must_equal({})
52
55
  end
53
56
 
54
57
  it 'reads correct stat results' do
55
58
  res = Minitest::Mock.new
59
+ os = Minitest::Mock.new
56
60
  # 43ff is 41777; linux_stat strips the 4
57
61
  res.expect :stdout, "360\n43ff\nroot\n0\nrootz\n1\n1444520846\n1444522445\n?"
62
+ os.expect :esx?, false
63
+ backend.expect :os, os
64
+ backend.expect :run_command, res, [String]
65
+ cls.linux_stat('/path', backend, false).must_equal({
66
+ type: :directory,
67
+ mode: 01777,
68
+ owner: 'root',
69
+ uid: 0,
70
+ group: 'rootz',
71
+ gid: 1,
72
+ mtime: 1444522445,
73
+ size: 360,
74
+ selinux_label: nil,
75
+ })
76
+ end
77
+ end
78
+
79
+ describe 'esx stat' do
80
+ let(:backend) { Minitest::Mock.new }
81
+
82
+ it 'ignores wrong stat results' do
83
+ res = Minitest::Mock.new
84
+ os = Minitest::Mock.new
85
+ res.expect :stdout, ''
86
+ os.expect :esx?, true
87
+ backend.expect :os, os
88
+ backend.expect :run_command, res, [String]
89
+ cls.linux_stat('/path', backend, false).must_equal({})
90
+ end
91
+
92
+ it 'reads correct stat results' do
93
+ res = Minitest::Mock.new
94
+ os = Minitest::Mock.new
95
+ # 43ff is 41777; linux_stat strips the 4
96
+ res.expect :stdout, "360\n43ff\nroot\n0\nrootz\n1\n1444520846\n1444522445\nC"
97
+ os.expect :esx?, true
98
+ backend.expect :os, os
58
99
  backend.expect :run_command, res, [String]
59
100
  cls.linux_stat('/path', backend, false).must_equal({
60
101
  type: :directory,
@@ -84,8 +84,15 @@ describe 'mock transport' do
84
84
  end
85
85
 
86
86
  describe 'when accessing a mocked file' do
87
- it 'gets results for content' do
88
-
87
+ JSON = Train.create('local').connection.file(__FILE__).to_json
88
+ RES = Train::Transports::Mock::Connection::File.from_json(JSON)
89
+
90
+ # tests if all fields between the local json and resulting mock file
91
+ # are equal
92
+ %w{ content mode owner group }.each do |f|
93
+ it "can be initialized from json (field #{f})" do
94
+ RES.method(f).call.must_equal JSON[f]
95
+ end
89
96
  end
90
97
  end
91
98
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: train
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.1
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dominik Richter
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-06-16 00:00:00.000000000 Z
11
+ date: 2016-06-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -188,8 +188,10 @@ files:
188
188
  - test/integration/sudo/passwd.rb
189
189
  - test/integration/sudo/reqtty.rb
190
190
  - test/integration/sudo/run_as.rb
191
+ - test/integration/test-one.yaml
191
192
  - test/integration/test-travis-1.yaml
192
193
  - test/integration/test-travis-2.yaml
194
+ - test/integration/test-two.yaml
193
195
  - test/integration/test_local.rb
194
196
  - test/integration/test_ssh.rb
195
197
  - test/integration/tests/path_block_device_test.rb
@@ -241,7 +243,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
241
243
  version: '0'
242
244
  requirements: []
243
245
  rubyforge_project:
244
- rubygems_version: 2.4.6
246
+ rubygems_version: 2.5.1
245
247
  signing_key:
246
248
  specification_version: 4
247
249
  summary: Transport interface to talk to different backends.
@@ -262,8 +264,10 @@ test_files:
262
264
  - test/integration/sudo/passwd.rb
263
265
  - test/integration/sudo/reqtty.rb
264
266
  - test/integration/sudo/run_as.rb
267
+ - test/integration/test-one.yaml
265
268
  - test/integration/test-travis-1.yaml
266
269
  - test/integration/test-travis-2.yaml
270
+ - test/integration/test-two.yaml
267
271
  - test/integration/test_local.rb
268
272
  - test/integration/test_ssh.rb
269
273
  - test/integration/tests/path_block_device_test.rb