kubetailrb 0.1.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 +7 -0
- data/.pryrc +18 -0
- data/.rubocop.yml +23 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +5 -0
- data/Guardfile +34 -0
- data/LICENSE.txt +21 -0
- data/README.md +73 -0
- data/Rakefile +85 -0
- data/exe/kubetailrb +6 -0
- data/journey_log.md +469 -0
- data/k3d/clock/Dockerfile +5 -0
- data/k3d/clock/clock.rb +8 -0
- data/k3d/clock-json/Dockerfile +5 -0
- data/k3d/clock-json/clock_json.rb +18 -0
- data/k3d/default.yml +23 -0
- data/kubetailrb.png +0 -0
- data/lib/boolean.rb +22 -0
- data/lib/kubetailrb/cli.rb +24 -0
- data/lib/kubetailrb/cmd/file.rb +96 -0
- data/lib/kubetailrb/cmd/help.rb +45 -0
- data/lib/kubetailrb/cmd/k8s.rb +127 -0
- data/lib/kubetailrb/cmd/version.rb +18 -0
- data/lib/kubetailrb/file_reader.rb +122 -0
- data/lib/kubetailrb/json_formatter.rb +66 -0
- data/lib/kubetailrb/k8s_opts.rb +38 -0
- data/lib/kubetailrb/k8s_pod_reader.rb +83 -0
- data/lib/kubetailrb/k8s_pods_reader.rb +86 -0
- data/lib/kubetailrb/no_op_formatter.rb +10 -0
- data/lib/kubetailrb/opts_parser.rb +28 -0
- data/lib/kubetailrb/validated.rb +19 -0
- data/lib/kubetailrb/version.rb +5 -0
- data/lib/kubetailrb/with_k8s_client.rb +25 -0
- data/lib/kubetailrb.rb +9 -0
- data/sig/kubetailrb.rbs +4 -0
- metadata +294 -0
data/journey_log.md
ADDED
@@ -0,0 +1,469 @@
|
|
1
|
+
# Journey log
|
2
|
+
|
3
|
+
> Here lies the chronicle of my journey learning Ruby, a collection of trials,
|
4
|
+
> tribulations, and triumphs as I navigated the world of this dynamic
|
5
|
+
> programming language.
|
6
|
+
|
7
|
+
|
8
|
+
## 🤔 Things that I'm curious about
|
9
|
+
|
10
|
+
|
11
|
+
---
|
12
|
+
|
13
|
+
## 2024-10-27
|
14
|
+
### Context
|
15
|
+
|
16
|
+
I already learned some basic Ruby by reading the
|
17
|
+
[official Ruby documentation](https://www.ruby-lang.org/en/) as well as doing
|
18
|
+
the [Ruby Koans](https://www.rubykoans.com/).
|
19
|
+
|
20
|
+
Now it's time to the practical exercise by creating a new project.
|
21
|
+
Since I'm on the team to "embrace suffering to learn efficiently", I think
|
22
|
+
coding and meeting lots of issue while I'm building this tool will heavily
|
23
|
+
benefit my Ruby skill and its ecosystem.
|
24
|
+
|
25
|
+
The subject of the exercise is to have something relevant to my day-to-day work,
|
26
|
+
something I'm sure I'll often use.
|
27
|
+
|
28
|
+
We are using Kubernetes at work (like most companies), and we are using
|
29
|
+
structured logs following the [Elastic Common Schema (ECS) specification](https://www.elastic.co/guide/en/ecs/current/ecs-reference.html),
|
30
|
+
so not quite easily readable by a human.
|
31
|
+
|
32
|
+
Tools like [stern](https://github.com/stern/stern) or [kubetail](https://github.com/johanhaleby/kubetail)
|
33
|
+
are useful to watch multiple Kubernetes pod logs directly from the terminal.
|
34
|
+
However, they do not format the logs in JSON format easily. I could pipe the
|
35
|
+
result and use other tools like [jq](https://github.com/jqlang/jq), but it's not
|
36
|
+
fun, and I wanted to really learn Ruby, hence this project was created.
|
37
|
+
|
38
|
+
Let's hope I will see it through and manage to implement the whole project 🤞.
|
39
|
+
|
40
|
+
### Project goal
|
41
|
+
|
42
|
+
The idea is to have a CLI, like [stern](https://github.com/stern/stern), to read
|
43
|
+
and follow the Kubernetes pod logs directly from the terminal, so something like
|
44
|
+
this:
|
45
|
+
|
46
|
+
```bash
|
47
|
+
kubetailrb --namespace my-namespace pod-name-regex
|
48
|
+
```
|
49
|
+
|
50
|
+
The name `kubetailrb` is copied from [kubetail](https://github.com/johanhaleby/kubetail)
|
51
|
+
with a simple prefix `rb` to indicate that it's implemented in Ruby, so quite
|
52
|
+
straightforward.
|
53
|
+
|
54
|
+
I want to have it like a library, so a Gem, that can also be used by other Ruby
|
55
|
+
project.
|
56
|
+
I also have the ambition to learn [Ruby on Rails](https://rubyonrails.org/), so
|
57
|
+
I also plan to implement a web version of `kubetailrb`.
|
58
|
+
|
59
|
+
### Project initialization
|
60
|
+
|
61
|
+
There are lots of way to create a new Ruby project.
|
62
|
+
|
63
|
+
I first went for the tutorial at [RubyGems](https://guides.rubygems.org/make-your-own-gem/).
|
64
|
+
It did work for a bit. However, the projects at my work are using [Bundler](https://bundler.io/),
|
65
|
+
which seems to better scale Ruby projects, in the sense that it can track and
|
66
|
+
manage dependencies with the idea of `Gemfile.lock`.
|
67
|
+
|
68
|
+
So I followed the [tutorial from Bundler](https://bundler.io/guides/creating_gem.html#testing-our-gem)
|
69
|
+
which is quite complete as it provides some boilerplate to quickly help me start
|
70
|
+
up a new project, like:
|
71
|
+
|
72
|
+
- the basic project structure,
|
73
|
+
- a `Gemfile` as well as the `kubetailrb.gemspec`,
|
74
|
+
- a `Rakefile` to execute some task, like running the tests.
|
75
|
+
|
76
|
+
The project generation was performed with a single command line:
|
77
|
+
|
78
|
+
```bash
|
79
|
+
bundle gem kubetailrb --bin --no-coc --no-ext --mit --test=minitest --ci=github --linter=rubocop
|
80
|
+
```
|
81
|
+
|
82
|
+
Lots of things to learn. Let's take it one by one.
|
83
|
+
|
84
|
+
### Project structure
|
85
|
+
|
86
|
+
It seems the convention is:
|
87
|
+
|
88
|
+
- `lib/` contains the source code.
|
89
|
+
- `test/` (or `spec/` depending on the test framework) contains the test code.
|
90
|
+
- `features` contains the cucumber scenarios, i.e. integration tests.
|
91
|
+
- `bin/` contains some scripts that can help the developer experience,
|
92
|
+
- Rails projects also have scripts in this `bin/` directory.
|
93
|
+
- `exec/` contains the executables that will be installed to the user system if
|
94
|
+
the latter is installing the gem
|
95
|
+
- It seems to be a convention from Bundler, but that is configurable in the
|
96
|
+
`gemspec` file.
|
97
|
+
|
98
|
+
I'm still not sure about the other directories, but I'll find out sooner or
|
99
|
+
later.
|
100
|
+
|
101
|
+
### Gemfile vs gemspec
|
102
|
+
|
103
|
+
The `Gemfile` is used to manage gem dependencies for our library’s development.
|
104
|
+
This file contains a `gemspec` line meaning that Bundler will include
|
105
|
+
dependencies specified in `kubetailrb.gemspec` too. It’s best practice to
|
106
|
+
specify all the gems that our library depends on in the `gemspec`.
|
107
|
+
|
108
|
+
The `gemspec` is the Gem Specification file. This is where we provide
|
109
|
+
information for Rubygems' consumption such as the name, description and homepage
|
110
|
+
of our gem. This is also where we specify the dependencies our gem needs to run.
|
111
|
+
|
112
|
+
> The benefit of putting this dependency specification inside of
|
113
|
+
> `foodie.gemspec` rather than the `Gemfile` is that anybody who runs gem
|
114
|
+
> install `foodie --dev` will get these development dependencies installed too.
|
115
|
+
> This command is used for when people wish to test a gem without having to fork
|
116
|
+
> it or clone it from GitHub.
|
117
|
+
|
118
|
+
src: https://bundler.io/guides/creating_gem.html#testing-our-gem
|
119
|
+
|
120
|
+
So, I'll put the dependencies to the `gemspec` by default. Looking at some
|
121
|
+
project, like [cucumber-ruby](https://github.com/cucumber/cucumber-ruby/tree/main), they are also putting everything in their `gemspec`.
|
122
|
+
|
123
|
+
### Rake
|
124
|
+
|
125
|
+
[Rake](https://github.com/ruby/rake) is a popular task runner in Ruby.
|
126
|
+
|
127
|
+
In a newly created project, it only runs the tests and the linter (Rubocop).
|
128
|
+
|
129
|
+
A good tutorial on Rake: https://www.rubyguides.com/2019/02/ruby-rake/
|
130
|
+
|
131
|
+
I wanted to add [cucumber](https://github.com/cucumber/cucumber-ruby/tree/main)
|
132
|
+
in the `:default` task so that it execute all the tests (minitest + cucumber).
|
133
|
+
|
134
|
+
To know how to add this step, I directly looked at the source code of
|
135
|
+
[cucumber-ruby](https://github.com/cucumber/cucumber-ruby/blob/main/lib/cucumber/rake/task.rb) and add it to my [Rakefile](./Rakefile):
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
# ... previous code
|
139
|
+
|
140
|
+
require "cucumber/rake"
|
141
|
+
Cucumber::Rake::Task.new
|
142
|
+
|
143
|
+
task default: %i[test cucumber rubocop]
|
144
|
+
```
|
145
|
+
|
146
|
+
### `require` vs `require_relative`
|
147
|
+
|
148
|
+
Difference between `require` and `require_relative`:
|
149
|
+
- `require` is global.
|
150
|
+
- `require_relative` is relative to this current directory of this file.
|
151
|
+
- `require "./some_file"` is relative to your current working directory.
|
152
|
+
|
153
|
+
src: https://stackoverflow.com/a/3672600/3612053
|
154
|
+
|
155
|
+
### Create a Ruby CLI application
|
156
|
+
|
157
|
+
We can parse CLI options using only stdlib.
|
158
|
+
No need to use some fancy library, like Thor or cli-ui.
|
159
|
+
The goal is to learn Ruby, not to learn to use 3rd party libraries.
|
160
|
+
|
161
|
+
src: https://www.rubyguides.com/2018/12/ruby-argv/
|
162
|
+
|
163
|
+
Some tools if I ever decide to change mind:
|
164
|
+
|
165
|
+
- [rails/thor: Thor is a toolkit for building powerful command-line interfaces](https://github.com/rails/thor)
|
166
|
+
- [TTY: The Ruby terminal apps toolkit](https://ttytoolkit.org/)
|
167
|
+
- [Shopify/cli-ui: CLI tooling framework with simple interactive widgets](https://github.com/Shopify/cli-ui?tab=readme-ov-file)
|
168
|
+
|
169
|
+
### Bundle exec everything?
|
170
|
+
|
171
|
+
The documentation is always prefixing all the command with `bundle exec`, e.g.
|
172
|
+
`bundle exec rake`. But I already have `rake` in my `$PATH`, so why do they
|
173
|
+
suggest adding this `bundle exec` which seems to provide more typing.
|
174
|
+
|
175
|
+
> In some cases, running executables without bundle exec may work, if the
|
176
|
+
> executable happens to be installed in your system and does not pull in any
|
177
|
+
> gems that conflict with your bundle.
|
178
|
+
>
|
179
|
+
> However, this is unreliable and is the source of considerable pain. Even if it
|
180
|
+
> looks like it works, it may not work in the future or on another machine.
|
181
|
+
|
182
|
+
src: https://stackoverflow.com/a/6588708/3612053
|
183
|
+
|
184
|
+
## 2024-10-29
|
185
|
+
### RUBYGEMS_GEMDEPS env variable
|
186
|
+
|
187
|
+
Previously, to ensure we are using the right gems, we needed to prefix all our
|
188
|
+
ruby/gem commands with `bundle exec`. But it seems there's a better way: set the
|
189
|
+
`RUBYGEMS_GEMDEPS=-` environment variable. This will autodetect the `Gemfile` in
|
190
|
+
the current or parent directories or set it to the path of your `Gemfile`.
|
191
|
+
|
192
|
+
> `use_gemdeps(path = nil)`
|
193
|
+
>
|
194
|
+
> Looks for a gem dependency file at path and
|
195
|
+
> activates the gems in the file if found. If the file is not found an
|
196
|
+
> ArgumentError is raised.
|
197
|
+
>
|
198
|
+
> If path is not given the RUBYGEMS_GEMDEPS environment variable is used, but if
|
199
|
+
> no file is found no exception is raised.
|
200
|
+
>
|
201
|
+
> If ‘-’ is given for path RubyGems searches up from the current working
|
202
|
+
> directory for gem dependency files (gem.deps.rb, Gemfile, Isolate) and
|
203
|
+
> activates the gems in the first one found.
|
204
|
+
>
|
205
|
+
> You can run this automatically when rubygems starts. To enable, set the
|
206
|
+
> RUBYGEMS_GEMDEPS environment variable to either the path of your gem
|
207
|
+
> dependencies file or “-” to auto-discover in parent directories.
|
208
|
+
>
|
209
|
+
> NOTE: Enabling automatic discovery on multiuser systems can lead to execution
|
210
|
+
> of arbitrary code when used from directories outside your control.
|
211
|
+
|
212
|
+
src: https://ruby-doc.org/3.3.5/stdlibs/rubygems/Gem.html
|
213
|
+
|
214
|
+
### Guard to execute tests automatically
|
215
|
+
|
216
|
+
I want to have fast feedback loop, and to get this developer experience, I need
|
217
|
+
something that will run automatically the tests every time I update a file.
|
218
|
+
|
219
|
+
I could have use [entr](https://github.com/clibs/entr) as usual, but since I'm
|
220
|
+
learning Ruby, let's try to keep on Ruby's ecosystem.
|
221
|
+
|
222
|
+
And it appears there's a tool for that: [Guard](https://github.com/guard/guard).
|
223
|
+
|
224
|
+
It's quite powerful, especially because it's also offering several plugins to
|
225
|
+
support multiple use cases, such as
|
226
|
+
[guard-minitest](https://rubygems.org/gems/guard-minitest) to run Minitest and
|
227
|
+
Test/Unit tests, and [guard-cucumber](https://github.com/guard/guard-cucumber)
|
228
|
+
to re-run changed/affected Cucumber features.
|
229
|
+
|
230
|
+
## 2024-10-30
|
231
|
+
### Debugging
|
232
|
+
|
233
|
+
I thought the keyword `pry` was native to Ruby. It appears I need to add some
|
234
|
+
gems to enable debugging:
|
235
|
+
|
236
|
+
```ruby
|
237
|
+
spec.add_development_dependency "pry"
|
238
|
+
spec.add_development_dependency "pry-byebug"
|
239
|
+
```
|
240
|
+
|
241
|
+
The latter is needed to go step-by-step. I also have to add a `.pryrc` at the
|
242
|
+
root of the project in order to have some nice shortcuts, like `n` for `next`.
|
243
|
+
|
244
|
+
To add a breaking point, I had to add:
|
245
|
+
|
246
|
+
```ruby
|
247
|
+
require "pry"
|
248
|
+
require "pry-byebug"
|
249
|
+
|
250
|
+
binding.pry
|
251
|
+
```
|
252
|
+
|
253
|
+
## 2024-10-31
|
254
|
+
### Require in tests
|
255
|
+
|
256
|
+
I tried to test my [`OptsParser`](./lib/kubetailrb/opts_parser.rb) with
|
257
|
+
[`OptsParserTest`](./test/kubetailrb/opts_parser_test.rb), but I got an error
|
258
|
+
while creating a new instance:
|
259
|
+
|
260
|
+
```
|
261
|
+
1) Error:
|
262
|
+
no argument provided#test_0001_should return help command:
|
263
|
+
NameError: uninitialized constant Kubetailrb::OptsParser
|
264
|
+
test/kubetailrb/opts_parser_test.rb:7:in `block (2 levels) in <class:OptsParserTest>'
|
265
|
+
```
|
266
|
+
|
267
|
+
So I had to add the following to my test file:
|
268
|
+
|
269
|
+
```ruby
|
270
|
+
require "kubetailrb/opts_parser"
|
271
|
+
```
|
272
|
+
|
273
|
+
But do I not need to add this line for [`VersionTest`](./test/kubetailrb/cmd/version_test.rb) and [`HelpTest`](./test/kubetailrb/cmd/help_test.rb)?
|
274
|
+
|
275
|
+
It was because I had the following in my
|
276
|
+
[`CLI`](./lib/kubetailrb/cli.rb):
|
277
|
+
|
278
|
+
```ruby
|
279
|
+
require "cmd/help"
|
280
|
+
require "cmd/version"
|
281
|
+
```
|
282
|
+
|
283
|
+
which include my classes.
|
284
|
+
|
285
|
+
That raises the question of when should we add those `require` /
|
286
|
+
`require_relative`? How can we know if one is already provided by an upstream
|
287
|
+
class?
|
288
|
+
|
289
|
+
### Method verb conjugation
|
290
|
+
|
291
|
+
I wanted to check if a `String` started with some prefix `-`, so I figured there
|
292
|
+
was a method to do it, like most programming language:
|
293
|
+
|
294
|
+
```ruby
|
295
|
+
'some string'.starts_with?("some")
|
296
|
+
```
|
297
|
+
|
298
|
+
But then, I got an error:
|
299
|
+
|
300
|
+
```
|
301
|
+
NoMethodError: undefined method `starts_with?' for an instance of String
|
302
|
+
```
|
303
|
+
|
304
|
+
It appears it was a typo and maybe it's a convention to have the verb be in
|
305
|
+
infinitive form, so in this case `start_with`, but not for all methods, e.g. `is_a?`.
|
306
|
+
|
307
|
+
### Executing shell commands directly from Ruby code
|
308
|
+
|
309
|
+
It's quite easy to execute some shell commands directly from Ruby code! I was
|
310
|
+
quite impressed by the simplicity:
|
311
|
+
|
312
|
+
```ruby
|
313
|
+
# using Kernel#`
|
314
|
+
with_backtick = `ls`
|
315
|
+
# or with %x
|
316
|
+
another_way = %x|ls|
|
317
|
+
```
|
318
|
+
|
319
|
+
src: https://www.rubyguides.com/2018/12/ruby-system/
|
320
|
+
|
321
|
+
### On default argument
|
322
|
+
|
323
|
+
There's a particular behavior that I did not expect for default argument.
|
324
|
+
|
325
|
+
Let's say, we have the following method:
|
326
|
+
|
327
|
+
```ruby
|
328
|
+
def foobar(var = 1)
|
329
|
+
puts var
|
330
|
+
end
|
331
|
+
|
332
|
+
foobar # will print: 1
|
333
|
+
|
334
|
+
foobar(nil) # will print nothing
|
335
|
+
```
|
336
|
+
|
337
|
+
So `nil` will not make the method use the default argument. It's by design, so
|
338
|
+
be careful when using those default argument.
|
339
|
+
|
340
|
+
src: https://stackoverflow.com/a/10506137/3612053
|
341
|
+
|
342
|
+
### Double colons
|
343
|
+
|
344
|
+
```ruby
|
345
|
+
# Sometime, we are prefixing the class with ::
|
346
|
+
some_var = ::SomePackage::SomeClass
|
347
|
+
|
348
|
+
# Some other time, we do not
|
349
|
+
some_var = SomePackage::SomeClass
|
350
|
+
```
|
351
|
+
|
352
|
+
I believe the first one is to avoid collision. So then, why not always writing
|
353
|
+
the first one? Why bother writing the second form, if there's a risk of
|
354
|
+
collision?
|
355
|
+
|
356
|
+
It's more verbose to add the `::` prefix, because we have to put the absolute
|
357
|
+
path to the class.
|
358
|
+
|
359
|
+
It's all about scopes and readability.
|
360
|
+
|
361
|
+
In case of ambiguity, go for `::` prefix.
|
362
|
+
|
363
|
+
Check https://cirw.in/blog/constant-lookup.html for more in-depth information.
|
364
|
+
|
365
|
+
### On duck typing
|
366
|
+
|
367
|
+
Duck typing is really powerful and can make one program flexible and decouple
|
368
|
+
code.
|
369
|
+
|
370
|
+
However, how does one can keep easily keep track of which classes are
|
371
|
+
implementing a particular behavior? If we want to rename a method, how can we
|
372
|
+
ensure all the implementations are also updated?
|
373
|
+
|
374
|
+
With static typed programming language, we have the compiler to help us track
|
375
|
+
and update the method name.
|
376
|
+
For small projects, it is manageable and can be updated with careful `grep`, but
|
377
|
+
for really large projects (hundred thousands to millions of LoC), how can one
|
378
|
+
keep ~~their sanity~~track of the class methods?
|
379
|
+
|
380
|
+
Some strategies to track the implementations:
|
381
|
+
|
382
|
+
- Use [`caller_location`](https://devdocs.io/ruby~3/kernel#method-i-caller_locations)
|
383
|
+
to log which classes are calling the monitored method, and monitor for a few
|
384
|
+
days/weeks/months, then refactor.
|
385
|
+
- Really slow refactor...
|
386
|
+
- Rely on failed tests to check the impact radius of the update.
|
387
|
+
|
388
|
+
An approach is to create an interface-like class:
|
389
|
+
|
390
|
+
```ruby
|
391
|
+
class Foobar
|
392
|
+
def a_method
|
393
|
+
raise NotImplementedError
|
394
|
+
end
|
395
|
+
|
396
|
+
def another_method
|
397
|
+
raise NotImplementedError
|
398
|
+
end
|
399
|
+
end
|
400
|
+
|
401
|
+
class SomeFoobarImplementation
|
402
|
+
def a_method
|
403
|
+
puts 'Implementation of Foobar#a_method'
|
404
|
+
end
|
405
|
+
|
406
|
+
def another_method
|
407
|
+
puts 'Implementation of Foobar#another_method'
|
408
|
+
end
|
409
|
+
end
|
410
|
+
```
|
411
|
+
|
412
|
+
Another approach with tests instead of interfaces:
|
413
|
+
https://morningcoffee.io/interfaces-in-ruby.html
|
414
|
+
|
415
|
+
### Getter on boolean
|
416
|
+
|
417
|
+
We can easily add getters with the keyword `attr_reader`. However, by
|
418
|
+
convention, methods that return a boolean should have the suffix `?`.
|
419
|
+
|
420
|
+
But `attr_reader` does not seem to add this suffix `?` to the boolean variable
|
421
|
+
(which is logical, since Ruby is a dynamic programming language, so it cannot
|
422
|
+
know in advance if the variable is boolean or not).
|
423
|
+
|
424
|
+
Do we have to manually implement this getter?
|
425
|
+
|
426
|
+
```ruby
|
427
|
+
class Foobar
|
428
|
+
def initialize
|
429
|
+
@foo = true
|
430
|
+
end
|
431
|
+
|
432
|
+
def foo?
|
433
|
+
@foo
|
434
|
+
end
|
435
|
+
end
|
436
|
+
```
|
437
|
+
|
438
|
+
There is no default accessors for `boolean` because Ruby does not know if the
|
439
|
+
variable is a `boolean` or not.
|
440
|
+
|
441
|
+
### On ensure
|
442
|
+
|
443
|
+
While I was implementing the `FileReader`, I must close the file regardless of
|
444
|
+
the result (otherwise, bad things may happen).
|
445
|
+
|
446
|
+
So, I used the `rescue` keyword:
|
447
|
+
|
448
|
+
```ruby
|
449
|
+
def foobar
|
450
|
+
file = File.open('/path/to/file')
|
451
|
+
# so some stuff with file
|
452
|
+
ensure
|
453
|
+
file&.close
|
454
|
+
end
|
455
|
+
```
|
456
|
+
|
457
|
+
Is it the right way to do it? Is there a better way?
|
458
|
+
|
459
|
+
In general, if the class provides a block in their methods, it will take care of
|
460
|
+
cleaning up. Example:
|
461
|
+
|
462
|
+
```ruby
|
463
|
+
File.open(@filepath, 'r').each_line do |line|
|
464
|
+
# Do something with file.
|
465
|
+
end
|
466
|
+
# File is now closed here.
|
467
|
+
```
|
468
|
+
|
469
|
+
Otherwise, using `ensure` is also an idiomatic way of closing resources.
|
data/k3d/clock/clock.rb
ADDED
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require('json')
|
4
|
+
|
5
|
+
def print_log
|
6
|
+
now = "#{Time.now.utc.strftime("%Y-%m-%dT%H:%M:%S.%3N")}Z"
|
7
|
+
{
|
8
|
+
'@timestamp' => now,
|
9
|
+
'log.level' => 'INFO',
|
10
|
+
'message' => "Time is #{now}"
|
11
|
+
}
|
12
|
+
end
|
13
|
+
|
14
|
+
loop do
|
15
|
+
puts JSON[print_log]
|
16
|
+
sleep 1
|
17
|
+
$stdout.flush
|
18
|
+
end
|
data/k3d/default.yml
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
---
|
2
|
+
#
|
3
|
+
# Local Kubernetes cluster.
|
4
|
+
# src: https://k3d.io/v5.6.3/usage/configfile/
|
5
|
+
#
|
6
|
+
|
7
|
+
apiVersion: k3d.io/v1alpha5
|
8
|
+
kind: Simple
|
9
|
+
servers: 1
|
10
|
+
agents: 0
|
11
|
+
image: docker.io/rancher/k3s:v1.30.1-k3s1
|
12
|
+
# ingress
|
13
|
+
ports:
|
14
|
+
- port: 80:80
|
15
|
+
nodeFilters:
|
16
|
+
- server:0
|
17
|
+
# will use host docker registry
|
18
|
+
registries:
|
19
|
+
create:
|
20
|
+
name: registry.localhost
|
21
|
+
host: "0.0.0.0"
|
22
|
+
hostPort: "5000"
|
23
|
+
|
data/kubetailrb.png
ADDED
Binary file
|
data/lib/boolean.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
#
|
4
|
+
# Just a simple module to represent a boolean in Ruby, as it appears there's
|
5
|
+
# not much to know if an object is a boolean or not...
|
6
|
+
# So I'll use this occasion to override some native classes like suggested.
|
7
|
+
# src: https://stackoverflow.com/a/3028378/3612053
|
8
|
+
#
|
9
|
+
module Boolean
|
10
|
+
end
|
11
|
+
|
12
|
+
# Include the `Boolean` module to native `TrueClass` so I can do something liks
|
13
|
+
# this: true.is_a?(Boolean).
|
14
|
+
class TrueClass
|
15
|
+
include Boolean
|
16
|
+
end
|
17
|
+
|
18
|
+
# Include the `Boolean` module to native `TrueClass` so I can do something liks
|
19
|
+
# this: false.is_a?(Boolean).
|
20
|
+
class FalseClass
|
21
|
+
include Boolean
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'opts_parser'
|
4
|
+
|
5
|
+
module Kubetailrb
|
6
|
+
# CLI application to run kubetailrb.
|
7
|
+
class CLI
|
8
|
+
def execute(*args)
|
9
|
+
cmd = OptsParser.new(*args).parse
|
10
|
+
|
11
|
+
# NOTE: Is it better to use this approach by checking the method existence
|
12
|
+
# or is it better to use a raise/rescue approach? Or another approach?
|
13
|
+
raise 'Invalid cmd' unless cmd.respond_to?(:execute)
|
14
|
+
|
15
|
+
begin
|
16
|
+
cmd.execute
|
17
|
+
# Capture Ctrl+c so the program will not display an error in the
|
18
|
+
# terminal.
|
19
|
+
rescue SignalException
|
20
|
+
puts '' # No need to display anything.
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'kubetailrb/file_reader'
|
4
|
+
|
5
|
+
module Kubetailrb
|
6
|
+
module Cmd
|
7
|
+
# Command to read a file.
|
8
|
+
class File
|
9
|
+
DEFAULT_NB_LINES = 10
|
10
|
+
DEFAULT_FOLLOW = false
|
11
|
+
FILE_FLAG = '--file'
|
12
|
+
TAIL_FLAG = '--tail'
|
13
|
+
attr_reader :reader
|
14
|
+
|
15
|
+
def initialize(filepath:, last_nb_lines: DEFAULT_NB_LINES, follow: DEFAULT_FOLLOW)
|
16
|
+
@reader = Kubetailrb::FileReader.new(filepath: filepath, last_nb_lines: last_nb_lines, follow: follow)
|
17
|
+
end
|
18
|
+
|
19
|
+
def execute
|
20
|
+
@reader.read
|
21
|
+
end
|
22
|
+
|
23
|
+
class << self
|
24
|
+
def create(*args)
|
25
|
+
new(filepath: parse_filepath(*args), last_nb_lines: parse_nb_lines(*args), follow: parse_follow(*args))
|
26
|
+
end
|
27
|
+
|
28
|
+
def applicable?(*args)
|
29
|
+
args.include?(FILE_FLAG)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
#
|
35
|
+
# Parse the file path from arguments provided in the CLI, e.g.
|
36
|
+
#
|
37
|
+
# kubetailrb --file /path/to/file
|
38
|
+
#
|
39
|
+
def parse_filepath(*args)
|
40
|
+
index = args.find_index { |arg| arg == FILE_FLAG }.to_i
|
41
|
+
|
42
|
+
raise MissingFileError, "Missing #{FILE_FLAG} value." if args[index + 1].nil?
|
43
|
+
|
44
|
+
args[index + 1]
|
45
|
+
end
|
46
|
+
|
47
|
+
#
|
48
|
+
# Parse nb lines from arguments provided in the CLI, e.g.
|
49
|
+
#
|
50
|
+
# kubetailrb --file /path/to/file --tail 3
|
51
|
+
#
|
52
|
+
# will return 3.
|
53
|
+
#
|
54
|
+
# Will raise `MissingNbLinesValueError` if the value is not provided:
|
55
|
+
#
|
56
|
+
# kubetailrb --file /path/to/file --tail
|
57
|
+
#
|
58
|
+
# Will raise `InvalidNbLinesValueError` if the provided value is not a
|
59
|
+
# number:
|
60
|
+
#
|
61
|
+
# kubetailrb --file /path/to/file --tail some-string
|
62
|
+
#
|
63
|
+
def parse_nb_lines(*args)
|
64
|
+
return DEFAULT_NB_LINES unless args.include?(TAIL_FLAG)
|
65
|
+
|
66
|
+
index = args.find_index { |arg| arg == TAIL_FLAG }.to_i
|
67
|
+
|
68
|
+
raise MissingNbLinesValueError, "Missing #{TAIL_FLAG} value." if args[index + 1].nil?
|
69
|
+
|
70
|
+
last_nb_lines = args[index + 1].to_i
|
71
|
+
|
72
|
+
raise InvalidNbLinesValueError, "Invalid #{TAIL_FLAG} value: #{args[index + 1]}." if last_nb_lines.zero?
|
73
|
+
|
74
|
+
last_nb_lines
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_follow(*args)
|
78
|
+
flags = %w[-f --follow]
|
79
|
+
|
80
|
+
return DEFAULT_FOLLOW unless args.any? { |arg| flags.include?(arg) }
|
81
|
+
|
82
|
+
true
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
class MissingNbLinesValueError < RuntimeError
|
88
|
+
end
|
89
|
+
|
90
|
+
class MissingFileError < RuntimeError
|
91
|
+
end
|
92
|
+
|
93
|
+
class InvalidNbLinesValueError < RuntimeError
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|