train-kubernetes 0.1.7 → 0.1.12

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
  SHA256:
3
- metadata.gz: 8986e6c4d497daf88599d830bcad43f9206936666311c6c0c0fab15002e41985
4
- data.tar.gz: d1c56b619bceddf1c957ae6b29dfebfa583177202c9f6412616d519ee9e2ad26
3
+ metadata.gz: fcca2f4cc7de5b099cb5a3ee64e8ec7e935fdb986234d4ca9a3659d73efa874e
4
+ data.tar.gz: 15006a25d2cc0a823e21025d0b9f02cdd75bcefcac6965a28b17735147c1a4df
5
5
  SHA512:
6
- metadata.gz: e2cc0de3cded1306ef5edd69c47b8c66ee49dfd517ec9f50223d152c08debe4e01ed95d9d2d8714bf7dd30865b45d9db86422e284ec0ae1a7ba271495de9a03a
7
- data.tar.gz: 8399e9ab6d48cbd741ff42208ca400a7e91f7b86bd76f0c4593b7e95c2c7daecc47135fead6faf0dd839e3cbe5bc2be2ed8cd4e7e5f4a998eed58be94eeb2d4d
6
+ metadata.gz: 17df60f7afdf0948ea15ce899b97b3aa33ba2173e87715c2b736783644a48aae037112188c570aab65f2cc3f40978e16c43c716c489623a7f556757b9204bfa6
7
+ data.tar.gz: 47f38cc6b1b41873f411da7a6fc3be723d2ead3cf5a0b225367a5bd6bfe518a57eb06f1805e1557d708d8e3c161881a4b4dbc6747aaaf0e760573795c5a43f38
data/README.md CHANGED
@@ -28,6 +28,13 @@ describe k8sobject(api: 'v1', type: 'pod', namespace: 'default', name: 'my-pod')
28
28
  end
29
29
  ```
30
30
 
31
+ ## File resource
32
+ ```ruby
33
+ inspec.backend.file('PATH', pod: 'POD', container: 'CONTAINER', namespace: 'NAMESPACE')
34
+ ```
35
+ Currently it supports only Linux based containers
36
+
37
+
31
38
  ## Preconditions
32
39
 
33
40
  - InSpec 3.7+ or 4.x+
@@ -1,6 +1,7 @@
1
1
  require 'train'
2
2
  require 'k8s-ruby'
3
3
  require 'train-kubernetes/platform'
4
+ require 'train-kubernetes/kubectl_client'
4
5
 
5
6
  module TrainPlugins
6
7
  module TrainKubernetes
@@ -9,8 +10,9 @@ module TrainPlugins
9
10
 
10
11
  def initialize(options)
11
12
  super(options)
12
- @pod = options[:pod]
13
+ @pod = options[:pod] || options[:path]&.gsub('/', '')
13
14
  @container = options[:container]
15
+ @namespace = options[:namespace] || options[:host]
14
16
  parse_kubeconfig
15
17
  connect
16
18
  end
@@ -34,15 +36,22 @@ module TrainPlugins
34
36
 
35
37
  def parse_kubeconfig
36
38
  kubeconfig_file = @options[:kubeconfig] if @options[:kubeconfig]
37
- @client = K8s::Client.config(K8s::Config.load_file(File.expand_path(kubeconfig_file)))
39
+ @client = K8s::Client.config(K8s::Config.load_file(::File.expand_path(kubeconfig_file)))
38
40
  end
39
41
 
40
42
  private
41
43
 
42
- attr_reader :pod, :container
44
+ attr_reader :pod, :container, :namespace
43
45
 
44
- def run_command_via_connection(cmd, &_data_handler)
45
- KubectlClient.new(pod: pod, container: container).execute(cmd)
46
+ def run_command_via_connection(cmd, opts = {}, &_data_handler)
47
+ KubectlClient.new(pod: opts[:pod] || pod,
48
+ container: opts[:container] || container,
49
+ namespace: opts[:namespace] || namespace)
50
+ .execute(cmd)
51
+ end
52
+
53
+ def file_via_connection(path, **args)
54
+ TrainPlugins::TrainKubernetes::File::Linux.new(self, path, pod: args[:pod], container: args[:container], namespace: args[:namespace])
46
55
  end
47
56
  end
48
57
  end
@@ -0,0 +1,149 @@
1
+ require 'train/file/remote/linux'
2
+ require 'train/extras/stat'
3
+
4
+ module TrainPlugins
5
+ module TrainKubernetes
6
+ module File
7
+ class Linux < ::Train::File::Remote::Linux
8
+ def initialize(backend, path, follow_symlink = true, pod:, **args)
9
+ @backend = backend
10
+ @path = path || ''
11
+ @follow_symlink = follow_symlink
12
+ @pod = pod
13
+ @container = args[:container]
14
+ @namespace = args[:namespace]
15
+
16
+ sanitize_filename(path)
17
+ super(backend, path, follow_symlink)
18
+ end
19
+
20
+ def content
21
+ return @content if defined?(@content)
22
+
23
+ @content = @backend.run_command("cat #{@spath} || echo -n",
24
+ { pod: pod, namespace: namespace, container: container })
25
+ .stdout
26
+ return @content unless @content.empty?
27
+
28
+ @content = nil if directory? || size.nil? || (size > 0)
29
+ @content
30
+ end
31
+
32
+ def content=(new_content)
33
+ execute_result = @backend.run_command('base64 --help', { pod: pod, namespace: namespace, container: container })
34
+ if execute_result.exit_status != 0
35
+ raise TransportError, "#{self.class} found no base64 binary for file writes"
36
+ end
37
+
38
+ unix_cmd = format("echo '%<base64>s' | base64 --decode > %<file>s",
39
+ base64: Base64.strict_encode64(new_content),
40
+ file: @spath)
41
+
42
+ @backend.run_command(unix_cmd, { pod: pod, namespace: namespace, container: container })
43
+
44
+ @content = new_content
45
+ end
46
+
47
+ def exist?
48
+ @exist ||= begin
49
+ f = @follow_symlink ? '' : " || test -L #{@spath}"
50
+ @backend.run_command("test -e #{@spath}" + f,
51
+ { pod: pod, namespace: namespace, container: container })
52
+ .exit_status == 0
53
+ end
54
+ end
55
+
56
+ def mounted
57
+ @mounted ||=
58
+ @backend.run_command("mount | grep -- ' on #{@path} '",
59
+ { pod: pod, namespace: namespace, container: container })
60
+ end
61
+
62
+ def path
63
+ return @path unless @follow_symlink && symlink?
64
+
65
+ @link_path ||= read_target_path
66
+ end
67
+
68
+ def shallow_link_path
69
+ return nil unless symlink?
70
+
71
+ @shallow_link_path ||=
72
+ @backend.run_command("readlink #{@spath}", { pod: pod, namespace: namespace, container: container })
73
+ .stdout
74
+ .chomp
75
+ end
76
+
77
+ def stat
78
+ @stat ||= begin
79
+ shell_escaped_path = @spath
80
+ backend = @backend
81
+ follow_symlink = @follow_symlink
82
+ lstat = follow_symlink ? ' -L' : ''
83
+ format = '--printf'
84
+ 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'",
85
+ { pod: pod, namespace: namespace, container: container })
86
+ # ignore the exit_code: it is != 0 if selinux labels are not supported
87
+ # on the system.
88
+
89
+ fields = res.stdout.split("\n")
90
+ return {} if fields.length != 9
91
+
92
+ tmask = fields[1].to_i(16)
93
+ selinux = fields[8]
94
+ ## selinux security context string not available on esxi
95
+ selinux = nil if (selinux == '?') || (selinux == '(null)') || (selinux == 'C')
96
+ {
97
+ type: Train::Extras::Stat.find_type(tmask),
98
+ mode: tmask & 07777,
99
+ owner: fields[2],
100
+ uid: fields[3].to_i,
101
+ group: fields[4],
102
+ gid: fields[5].to_i,
103
+ mtime: fields[7].to_i,
104
+ size: fields[0].to_i,
105
+ selinux_label: selinux
106
+ }
107
+ end
108
+ end
109
+
110
+ def source
111
+ if @follow_symlink
112
+ self.class.new(@backend, @path, false, pod: pod, container: container, namespace: namespace)
113
+ else
114
+ self
115
+ end
116
+ end
117
+
118
+ def user_permissions
119
+ return {} unless exist?
120
+
121
+ skip_reource '`user_permissions` is not supported on your Linux Containers yet.'
122
+ end
123
+
124
+ def inherited?
125
+ return false unless exist?
126
+
127
+ skip_resource '`inherited?` is not supported on your Linux Containers yet.'
128
+ end
129
+
130
+ private
131
+
132
+ # Returns full path of a symlink target(real dest) or '' on symlink loop
133
+ def read_target_path
134
+ full_path = @backend.run_command("readlink -n #{@spath} -f",
135
+ { pod: pod, namespace: namespace, container: container })
136
+ .stdout
137
+ # Needed for some OSes like OSX that returns relative path
138
+ # when the link and target are in the same directory
139
+ if !full_path.start_with?('/') && full_path != ''
140
+ full_path = ::File.expand_path("../#{full_path}", @spath)
141
+ end
142
+ full_path
143
+ end
144
+
145
+ attr_reader :pod, :container, :namespace
146
+ end
147
+ end
148
+ end
149
+ end
@@ -0,0 +1,50 @@
1
+ require 'inspec/resources/file'
2
+ require 'inspec/exceptions'
3
+
4
+ module TrainPlugins
5
+ module TrainKubernetes
6
+ module File
7
+ class LinuxImmutableFileCheck < Inspec::Resources::LinuxImmutableFlagCheck
8
+ def initialize(inspec, file, pod:, container: nil, namespace: nil)
9
+ @pod = pod
10
+ @container = container
11
+ @namespace = namespace
12
+ super(inspec, file)
13
+ end
14
+
15
+ def find_utility_or_error(utility_name)
16
+ %W(/usr/sbin/#{utility_name} /sbin/#{utility_name} /usr/bin/#{utility_name} /bin/#{utility_name} #{utility_name}).each do |cmd|
17
+ if inspec.backend
18
+ .run_command("sh -c 'type \"#{cmd}\"'", { pod: pod, container: container, namespace: namespace })
19
+ .exit_status.to_i == 0
20
+ return cmd
21
+ end
22
+ end
23
+
24
+ raise Inspec::Exceptions::ResourceFailed, "Could not find `#{utility_name}`"
25
+ end
26
+
27
+ def is_immutable?
28
+ # Check if lsattr is available. In general, all linux system has lsattr & chattr
29
+ # This logic check is valid for immutable flag set with chattr
30
+ utility = find_utility_or_error('lsattr')
31
+
32
+ utility_cmd = inspec.backend.run_command("#{utility} #{file_path}",
33
+ { pod: pod, container: container, namespace: namespace })
34
+
35
+ raise Inspec::Exceptions::ResourceFailed, "Executing #{utility} #{file_path} failed: #{utility_cmd.stderr}" if utility_cmd.exit_status.to_i != 0
36
+
37
+ # General output for lsattr file_name is:
38
+ # ----i---------e----- file_name
39
+ # The fifth char resembles the immutable flag. Total 20 flags are allowed.
40
+ lsattr_info = utility_cmd.stdout.strip.squeeze(' ')
41
+ lsattr_info =~ /^.{4}i.{15} .*/
42
+ end
43
+
44
+ private
45
+
46
+ attr_reader :pod, :container, :namespace
47
+ end
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,25 @@
1
+ module TrainPlugins
2
+ module TrainKubernetes
3
+ module File
4
+ class LinuxPermissions < Inspec::Resources::UnixFilePermissions
5
+ def initialize(inspec, pod:, namespace: nil, container: nil)
6
+ @pod = pod
7
+ @namespace = namespace
8
+ @container = container
9
+ super(inspec)
10
+ end
11
+
12
+ def check_file_permission_by_user(access_type, user, path)
13
+ flag = permission_flag(access_type)
14
+ perm_cmd = "su -s /bin/sh -c \"test -#{flag} #{path}\" #{user}"
15
+ cmd = inspec.backend.run_command(perm_cmd, { pod: pod, namespace: namespace, container: container })
16
+ cmd.exit_status == 0
17
+ end
18
+
19
+ private
20
+
21
+ attr_reader :pod, :container, :namespace
22
+ end
23
+ end
24
+ end
25
+ end
@@ -1,18 +1,26 @@
1
1
  require 'mixlib/shellout'
2
+ require 'train/extras'
2
3
 
3
4
  module TrainPlugins
4
5
  module TrainKubernetes
5
6
  class KubectlClient
6
- attr_reader :pod, :container
7
- def initialize(pod:, container: nil)
7
+ attr_reader :pod, :container, :namespace
8
+
9
+ DEFAULT_NAMESPACE = 'default'.freeze
10
+
11
+ def initialize(pod:, namespace: nil, container: nil)
8
12
  @pod = pod
9
13
  @container = container
14
+ @namespace = namespace || DEFAULT_NAMESPACE
10
15
  end
11
16
 
12
17
  def execute(command, stdin: true, tty: true)
13
18
  instruction = build_instruction(command, stdin, tty)
14
19
  shell = Mixlib::ShellOut.new(instruction)
15
- shell.run_command
20
+ res = shell.run_command
21
+ Train::Extras::CommandResult.new(res.stdout, res.stderr, res.exitstatus)
22
+ rescue Errno::ENOENT => _e
23
+ Train::Extras::CommandResult.new('', '', 1)
16
24
  end
17
25
 
18
26
  private
@@ -21,14 +29,16 @@ module TrainPlugins
21
29
  @shell ||= Mixlib::ShellOut.new(instruction)
22
30
  end
23
31
 
24
- def build_instruction(command, stdin, tty)
25
- ["kubectl exec"].tap do |arr|
32
+ def build_instruction(command, stdin, _tty)
33
+ ['kubectl exec'].tap do |arr|
26
34
  arr << '--stdin' if stdin
27
- arr << "--tty" if tty
28
35
  arr << pod if pod
36
+ arr << '-n'
37
+ arr << namespace
38
+ arr << '--'
29
39
  arr << command
30
40
  end.join("\s")
31
41
  end
32
42
  end
33
43
  end
34
- end
44
+ end
@@ -5,6 +5,9 @@ module TrainPlugins
5
5
  class Transport < Train.plugin(1)
6
6
  name 'k8s'
7
7
  option :kubeconfig, default: ENV['KUBECONFIG'] || '~/.kube/config'
8
+ option :pod, default: nil
9
+ option :container, default: nil
10
+ option :namespace, default: nil
8
11
  def connection(_instance_opts = nil)
9
12
  @connection ||= TrainPlugins::TrainKubernetes::Connection.new(@options)
10
13
  end
@@ -5,6 +5,6 @@
5
5
 
6
6
  module TrainPlugins
7
7
  module TrainKubernetes
8
- VERSION = '0.1.7'.freeze
8
+ VERSION = '0.1.12'.freeze
9
9
  end
10
10
  end
@@ -18,3 +18,4 @@ require 'train-kubernetes/version'
18
18
  # Transport acts as the glue.
19
19
  require 'train-kubernetes/transport'
20
20
  require 'train-kubernetes/platform'
21
+ require 'train-kubernetes/file/linux'
@@ -44,6 +44,7 @@ Gem::Specification.new do |spec|
44
44
  # Do not list inspec as a dependency of the train plugin.
45
45
 
46
46
  # All plugins should mention train, > 1.4
47
- spec.add_dependency 'k8s-ruby', '~> 0.10'
47
+ # pinning k8s-ruby to 0.10.5 to avoid broken dry-type gem upgrades from k8s-ruby
48
+ spec.add_dependency 'k8s-ruby', '~> 0.14.0'
48
49
  spec.add_dependency 'train', '~> 3.0'
49
50
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: train-kubernetes
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.7
4
+ version: 0.1.12
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brad Geesaman
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-09-08 00:00:00.000000000 Z
11
+ date: 2023-04-14 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: k8s-ruby
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - "~>"
18
18
  - !ruby/object:Gem::Version
19
- version: '0.10'
19
+ version: 0.14.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - "~>"
25
25
  - !ruby/object:Gem::Version
26
- version: '0.10'
26
+ version: 0.14.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: train
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -50,6 +50,9 @@ files:
50
50
  - README.md
51
51
  - lib/train-kubernetes.rb
52
52
  - lib/train-kubernetes/connection.rb
53
+ - lib/train-kubernetes/file/linux.rb
54
+ - lib/train-kubernetes/file/linux_immutable_file_check.rb
55
+ - lib/train-kubernetes/file/linux_permissions.rb
53
56
  - lib/train-kubernetes/kubectl_client.rb
54
57
  - lib/train-kubernetes/platform.rb
55
58
  - lib/train-kubernetes/transport.rb
@@ -74,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
74
77
  - !ruby/object:Gem::Version
75
78
  version: '0'
76
79
  requirements: []
77
- rubygems_version: 3.0.8
80
+ rubygems_version: 3.3.7
78
81
  signing_key:
79
82
  specification_version: 4
80
83
  summary: Train Kubernetes