train-kubernetes 0.1.7 → 0.1.12

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: 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