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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +18 -1
- data/README.md +49 -14
- data/Rakefile +11 -0
- data/journey_log.md +1 -7
- data/kubetailrb.png +0 -0
- data/lib/kubetailrb/cmd/file.rb +2 -2
- data/lib/kubetailrb/cmd/help.rb +11 -8
- data/lib/kubetailrb/cmd/k8s.rb +77 -23
- data/lib/kubetailrb/filter/log_filter.rb +62 -0
- data/lib/kubetailrb/formatter/json_formatter.rb +93 -0
- data/lib/kubetailrb/formatter/no_op_formatter.rb +12 -0
- data/lib/kubetailrb/formatter/pod_metadata_formatter.rb +32 -0
- data/lib/kubetailrb/k8s_opts.rb +24 -3
- data/lib/kubetailrb/painter.rb +36 -0
- data/lib/kubetailrb/reader/file_reader.rb +124 -0
- data/lib/kubetailrb/reader/k8s_pod_reader.rb +115 -0
- data/lib/kubetailrb/reader/k8s_pods_reader.rb +123 -0
- data/lib/kubetailrb/reader/with_k8s_client.rb +27 -0
- data/lib/kubetailrb/validated.rb +4 -0
- data/lib/kubetailrb/version.rb +1 -1
- metadata +19 -16
- data/lib/kubetailrb/file_reader.rb +0 -122
- data/lib/kubetailrb/json_formatter.rb +0 -66
- data/lib/kubetailrb/k8s_pod_reader.rb +0 -83
- data/lib/kubetailrb/k8s_pods_reader.rb +0 -86
- data/lib/kubetailrb/no_op_formatter.rb +0 -10
- data/lib/kubetailrb/with_k8s_client.rb +0 -25
@@ -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,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
|