kubetailrb 0.1.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.
@@ -1,83 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'with_k8s_client'
4
- require_relative 'validated'
5
- require_relative 'json_formatter'
6
-
7
- module Kubetailrb
8
- # Read Kubernetes pod logs.
9
- class K8sPodReader
10
- include Validated
11
- include WithK8sClient
12
-
13
- attr_reader :pod_name, :opts
14
-
15
- def initialize(pod_name:, formatter:, opts:, k8s_client: nil)
16
- validate(pod_name, formatter, opts)
17
-
18
- @k8s_client = k8s_client
19
- @pod_name = pod_name
20
- @formatter = formatter
21
- @opts = opts
22
- end
23
-
24
- def read
25
- pod_logs = read_pod_logs
26
- unless @opts.follow?
27
- print_logs pod_logs
28
- return
29
- end
30
-
31
- # NOTE: The watch method from kubeclient does not accept `tail_lines`
32
- # argument, so I had to resort to some hack... by using the first log to
33
- # print out. Not ideal, since it's not really the N last nb lines, and
34
- # assume every logs are different, which may not be true.
35
- # But it does the job for most cases.
36
- first_log_to_display = pod_logs.to_s.split("\n").first
37
- should_print_logs = false
38
-
39
- k8s_client.watch_pod_log(@pod_name, @opts.namespace) do |line|
40
- # NOTE: Is it good practice to update a variable that is outside of a
41
- # block? Can we do better?
42
- should_print_logs = true if line == first_log_to_display
43
-
44
- print_logs(line) if should_print_logs
45
- end
46
- end
47
-
48
- private
49
-
50
- def validate(pod_name, formatter, opts)
51
- raise_if_blank pod_name, 'Pod name not set.'
52
-
53
- raise ArgumentError, 'Formatter not set.' if formatter.nil?
54
-
55
- raise ArgumentError, 'Opts not set.' if opts.nil?
56
- end
57
-
58
- def print_logs(logs)
59
- if logs.to_s.include?("\n")
60
- logs.to_s.split("\n").each { |log| print_logs(log) }
61
- return
62
- end
63
-
64
- if @opts.raw?
65
- puts @formatter.format(logs)
66
- else
67
- puts "#{@pod_name} - #{@formatter.format logs}"
68
- end
69
- $stdout.flush
70
- end
71
-
72
- def read_pod_logs
73
- # The pod may still not up/ready, so small hack to retry 120 times (number
74
- # taken randomly) until the pod returns its logs.
75
- 120.times do
76
- return k8s_client.get_pod_log(@pod_name, @opts.namespace, tail_lines: @opts.last_nb_lines)
77
- rescue Kubeclient::HttpError => e
78
- puts e.message
79
- sleep 1
80
- end
81
- end
82
- end
83
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'k8s_opts'
4
- require_relative 'k8s_pod_reader'
5
- require_relative 'with_k8s_client'
6
-
7
- module Kubetailrb
8
- # Read multiple pod logs.
9
- class K8sPodsReader
10
- include Validated
11
- include WithK8sClient
12
-
13
- attr_reader :pod_query, :opts
14
-
15
- def initialize(pod_query:, formatter:, opts:, k8s_client: nil)
16
- validate(pod_query, formatter, opts)
17
-
18
- @k8s_client = k8s_client
19
- @pod_query = Regexp.new(pod_query)
20
- @formatter = formatter
21
- @opts = opts
22
- end
23
-
24
- def read
25
- pods = find_pods
26
- watch_for_new_pod_events if @opts.follow?
27
-
28
- threads = pods.map do |pod|
29
- # NOTE: How much memory does a Ruby Thread takes? Can we spawn hundreds
30
- # to thoudsands of Threads without issue?
31
- Thread.new { create_reader(pod.metadata.name).read }
32
- end
33
-
34
- # NOTE: '&:' is a shorthand way of calling 'join' method on each thread.
35
- # It's equivalent to: threads.each { |thread| thread.join }
36
- threads.each(&:join)
37
- end
38
-
39
- private
40
-
41
- def validate(pod_query, formatter, opts)
42
- raise_if_blank pod_query, 'Pod query not set.'
43
-
44
- raise ArgumentError, 'Formatter not set.' if formatter.nil?
45
-
46
- raise ArgumentError, 'Opts not set.' if opts.nil?
47
- end
48
-
49
- def find_pods
50
- k8s_client
51
- .get_pods(namespace: @opts.namespace)
52
- .select { |pod| applicable?(pod) }
53
- end
54
-
55
- def create_reader(pod_name)
56
- K8sPodReader.new(
57
- k8s_client: k8s_client,
58
- pod_name: pod_name,
59
- formatter: @formatter,
60
- opts: @opts
61
- )
62
- end
63
-
64
- #
65
- # Watch any pod events, and if there's another pod that validates the pod
66
- # query, then let's read the pod logs!
67
- #
68
- def watch_for_new_pod_events
69
- k8s_client.watch_pods(namespace: @opts.namespace) do |notice|
70
- if new_pod_event?(notice) && applicable?(notice.object)
71
- # NOTE: We are in another thread (are we?), so no sense to use
72
- # 'Thread.join' here.
73
- Thread.new { create_reader(notice.object.metadata.name).read }
74
- end
75
- end
76
- end
77
-
78
- def applicable?(pod)
79
- pod.metadata.name.match?(@pod_query)
80
- end
81
-
82
- def new_pod_event?(notice)
83
- notice.type == 'ADDED' && notice.object.kind == 'Pod'
84
- end
85
- end
86
- end
@@ -1,10 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Kubetailrb
4
- # Formatter that does nothing except return what's given to it.
5
- class NoOpFormatter
6
- def format(log)
7
- log
8
- end
9
- end
10
- end
@@ -1,25 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'kubeclient'
4
-
5
- module Kubetailrb
6
- # Add behavior to get a k8s client by using composition.
7
- # NOTE: Is it the idiomatic way? Or shall I use a factory? Or is there a
8
- # better way?
9
- module WithK8sClient
10
- def k8s_client
11
- @k8s_client ||= create_k8s_client
12
- end
13
-
14
- def create_k8s_client
15
- config = Kubeclient::Config.read(ENV['KUBECONFIG'] || "#{ENV["HOME"]}/.kube/config")
16
- context = config.context
17
- Kubeclient::Client.new(
18
- context.api_endpoint,
19
- 'v1',
20
- ssl_options: context.ssl_options,
21
- auth_options: context.auth_options
22
- )
23
- end
24
- end
25
- end