kube_cluster 0.2.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/release.yml +43 -0
  3. data/.github/workflows/tag-gem-version-bump.yml +47 -0
  4. data/.gitignore +2 -0
  5. data/Gemfile.lock +48 -52
  6. data/bin/console +3 -0
  7. data/bin/dev +4 -0
  8. data/docker-compose.yml +26 -0
  9. data/examples/01-basic-redis-pod/manifest.rb +60 -0
  10. data/examples/02-manifest-with-middleware/manifest.rb +37 -0
  11. data/examples/02-manifest-with-middleware/middleware/labels.rb +4 -0
  12. data/examples/02-manifest-with-middleware/middleware/namespace.rb +4 -0
  13. data/examples/02-manifest-with-middleware/templates/config_map.rb +13 -0
  14. data/examples/02-manifest-with-middleware/templates/deployment.rb +59 -0
  15. data/examples/02-manifest-with-middleware/templates/horizontal_pod_autoscaler.rb +30 -0
  16. data/examples/02-manifest-with-middleware/templates/ingress.rb +38 -0
  17. data/examples/02-manifest-with-middleware/templates/service.rb +12 -0
  18. data/examples/03-app-with-database/demo.rb +87 -0
  19. data/examples/03-app-with-database/helpers.rb +18 -0
  20. data/examples/03-app-with-database/my_app.rb +45 -0
  21. data/examples/03-app-with-database/postgresql.rb +81 -0
  22. data/examples/03-app-with-database/ruby_on_rails.rb +31 -0
  23. data/flake.lock +3 -3
  24. data/flake.nix +6 -0
  25. data/kube_cluster.gemspec +3 -1
  26. data/lib/kube/cli/cluster.rb +41 -0
  27. data/lib/kube/cluster/connection.rb +18 -0
  28. data/lib/kube/cluster/instance.rb +21 -0
  29. data/lib/kube/cluster/manifest.rb +25 -0
  30. data/lib/kube/cluster/middleware/annotations.rb +32 -0
  31. data/lib/kube/cluster/middleware/hpa_for_deployment.rb +111 -0
  32. data/lib/kube/cluster/middleware/ingress_for_service.rb +91 -0
  33. data/lib/kube/cluster/middleware/labels.rb +59 -0
  34. data/lib/kube/cluster/middleware/namespace.rb +31 -0
  35. data/lib/kube/cluster/middleware/pod_anti_affinity.rb +61 -0
  36. data/lib/kube/cluster/middleware/resource_preset.rb +64 -0
  37. data/lib/kube/cluster/middleware/security_context.rb +84 -0
  38. data/lib/kube/cluster/middleware/service_for_deployment.rb +71 -0
  39. data/lib/kube/cluster/middleware/stack.rb +43 -0
  40. data/lib/kube/cluster/middleware.rb +69 -0
  41. data/lib/kube/cluster/resource/dirty_tracking.rb +113 -0
  42. data/lib/kube/cluster/resource/persistence.rb +67 -0
  43. data/lib/kube/cluster/resource.rb +99 -0
  44. data/lib/kube/cluster/version.rb +1 -1
  45. data/lib/kube/cluster.rb +34 -7
  46. data/lib/kube/errors.rb +57 -0
  47. metadata +69 -17
  48. data/Rakefile +0 -11
  49. data/TREE_PLAN.md +0 -513
  50. data/bin/generate-command-schema-v1 +0 -44
  51. data/data/kubectl-command-tree-v1-minimal.json +0 -125
  52. data/data/kubectl-command-tree-v1.json +0 -1469
  53. data/examples/quick-repl/docker-compose.yml +0 -52
  54. data/exe/kube_cluster +0 -6
  55. data/lib/kube/cluster/command_node.rb +0 -89
  56. data/lib/kube/cluster/ctl.rb +0 -33
  57. data/lib/kube/cluster/query_builder.rb +0 -35
  58. data/lib/kube/cluster/resource_selector.rb +0 -19
  59. data/lib/kube/cluster/tree_node.rb +0 -51
data/TREE_PLAN.md DELETED
@@ -1,513 +0,0 @@
1
- How will be now create ActiveRecord::Relatino style command objects?
2
-
3
- 10:25
4
-
5
- Architected chainable, lazy-evaluated kubectl command builder pattern
6
- Architected chainable, lazy-evaluated kubectl command builder pattern
7
- Great use case for the tree! The idea is an immutable, chainable builder that only shells out when forced — exactly like ActiveRecord::Relation.
8
-
9
- ruby
10
- class Kubectl
11
- TREE = JSON.parse(
12
- File.read(File.join(GEM_DIR, 'data', 'kubectl-command-tree-v1-minimal.json')),
13
- symbolize_names: true
14
- )
15
-
16
- def initialize(commands: [], flags: {}, tree: TREE)
17
- @commands = commands
18
- @flags = flags
19
- @tree = tree
20
- end
21
-
22
- def method_missing(name, *args)
23
- cmd = name.to_s.tr('_', '-')
24
- @tree.key?(cmd.to_sym).then do |valid|
25
- raise ArgumentError, "Unknown kubectl command: #{cmd}" unless valid
26
- self.class.new(
27
- commands: @commands + [cmd] + args.map(&:to_s),
28
- flags: @flags,
29
- tree: @tree.dig(cmd.to_sym) || {}
30
- )
31
- end
32
- end
33
-
34
- def respond_to_missing?(name, include_private = false)
35
- @tree.key?(name.to_s.tr('_', '-').to_sym) || super
36
- end
37
-
38
- def flag(key, value = nil)
39
- self.class.new(
40
- commands: @commands,
41
- flags: @flags.merge(key.to_s.tr('_', '-') => value),
42
- tree: @tree
43
- )
44
- end
45
-
46
- def to_a
47
- stdout, = Open3.capture2('kubectl', *@commands, *rendered_flags)
48
- stdout.lines.map(&:chomp)
49
- end
50
-
51
- def to_s
52
- ['kubectl', *@commands, *rendered_flags].join(' ')
53
- end
54
-
55
- private
56
-
57
- def rendered_flags
58
- @flags.flat_map do |k, v|
59
- v ? ["--#{k}", v.to_s] : ["--#{k}"]
60
- end
61
- end
62
- end
63
- Which gives you a nice chainable API:
64
-
65
- ruby
66
- Kubectl.new.get('pods')
67
- .flag(:namespace, 'production')
68
- .flag(:output, 'json')
69
- .to_a
70
-
71
- Kubectl.new.rollout.status('deployment/my-app')
72
- .flag(:namespace, 'staging')
73
- .to_a
74
-
75
- # Preview without running
76
- Kubectl.new.get('nodes').flag(:output, 'wide').to_s
77
- # => "kubectl get nodes --output wide"
78
- Key design decisions:
79
-
80
- Immutable — every chain returns a new instance with merged state, nothing mutates
81
- method_missing walks the tree — invalid commands raise immediately rather than failing at shell time
82
- to_a is the forcing function — lazy until you call it, just like ActiveRecord::Relation
83
- to_s for free previewing — useful for debugging or logging the command before firing it
84
- If Enumerable would respond to the method missing then make the api call..
85
-
86
- 10:28
87
-
88
- Architected automatic API invocation through Enumerable inclusion
89
- Architected automatic API invocation through Enumerable inclusion
90
- Include Enumerable and make each the single forcing function — then all enumerable methods trigger the shell call automatically:
91
-
92
- ruby
93
- include Enumerable
94
-
95
- def each(&block)
96
- to_a.each(&block)
97
- end
98
-
99
- def method_missing(name, *args, &block)
100
- cmd = name.to_s.tr('_', '-')
101
-
102
- if @tree.key?(cmd.to_sym)
103
- self.class.new(
104
- commands: @commands + [cmd] + args.map(&:to_s),
105
- flags: @flags,
106
- tree: @tree.dig(cmd.to_sym) || {}
107
- )
108
- elsif Enumerable.method_defined?(name)
109
- to_a.public_send(name, *args, &block)
110
- else
111
- super
112
- end
113
- end
114
- Now the API call happens naturally:
115
-
116
- ruby
117
- Kubectl.new.get('pods').flag(:namespace, 'production').map { |line| line.split.first }
118
- Kubectl.new.get('nodes').flag(:output, 'wide').select { |line| line.include?('Ready') }
119
- Kubectl.new.get('pods').first(3)
120
- The chain stays lazy until an Enumerable method is hit — at which point each forces to_a which fires the shell command. No explicit .to_a needed.
121
-
122
- Okay call this object CommandNode and create an additional object called ResourceNode which handles the deployment.v1.apps pattern... assume we have an array of all supported type.version.group sets.
123
-
124
- 10:32
125
-
126
- Architected nested tree structure for resource type parsing and integration
127
- Architected nested tree structure for resource type parsing and integration
128
- ruby
129
- class ResourceNode
130
- # e.g. ["deployments.v1.apps", "pods.v1", "replicasets.v1.apps", ...]
131
- TREE = SUPPORTED_TYPES
132
- .each_with_object({}) do |type_string, tree|
133
- type_string
134
- .split('.')
135
- .reduce(tree) { |node, segment| node[segment.to_sym] ||= {} }
136
- end
137
-
138
- def initialize(path: [], tree: TREE)
139
- @path = path
140
- @tree = tree
141
- end
142
-
143
- def method_missing(name, *args, &block)
144
- segment = name.to_s.tr('_', '-')
145
-
146
- @tree.key?(segment.to_sym).then do |valid|
147
- raise ArgumentError, "Unknown resource segment: #{segment}" unless valid
148
- self.class.new(
149
- path: @path + [segment],
150
- tree: @tree[segment.to_sym]
151
- )
152
- end
153
- end
154
-
155
- def respond_to_missing?(name, include_private = false)
156
- @tree.key?(name.to_s.tr('_', '-').to_sym) || super
157
- end
158
-
159
- def to_s
160
- @path.join('.')
161
- end
162
- end
163
- Which then composes naturally with CommandNode:
164
-
165
- ruby
166
- resource = ResourceNode.new.deployments.v1.apps
167
-
168
- CommandNode.new
169
- .get(resource.to_s)
170
- .flag(:namespace, 'production')
171
- .map { |line| line.split.first }
172
- Or you could make CommandNode accept ResourceNode directly by checking in rendered_args:
173
-
174
- ruby
175
- # in CommandNode
176
- def method_missing(name, *args, &block)
177
- cmd = name.to_s.tr('_', '-')
178
-
179
- if @tree.key?(cmd.to_sym)
180
- self.class.new(
181
- commands: @commands + [cmd] + args.map(&:to_s), # to_s coerces ResourceNode naturally
182
- flags: @flags,
183
- tree: @tree.dig(cmd.to_sym) || {}
184
- )
185
- elsif Enumerable.method_defined?(name)
186
- to_a.public_send(name, *args, &block)
187
- else
188
- super
189
- end
190
- end
191
- So the full idiomatic API becomes:
192
-
193
- ruby
194
- CommandNode.new
195
- .get(ResourceNode.new.deployments.v1.apps)
196
- .flag(:namespace, 'production')
197
- .select { |line| line.include?('my-app') }
198
- The ResourceNode tree is built once at load time from the flat array — segments become nested keys, so method_missing can walk it exactly like CommandNode walks the kubectl command tree.
199
-
200
- hmm... this won't work... we need to be able to do Kubectl.get.deployment.v1 etc...
201
-
202
- What we need to do it iterate over the schema and create some TreeInstances for each key... so each key turns from a string into a TreeInstance.... then when we're going throuhg our commands we know what the next node should be....
203
-
204
- this is not a simple task... there are a lot of moving parts...
205
-
206
- **make a plan** to get this right
207
-
208
- 10:36
209
-
210
- Architected tree-node system grafting commands and resources together
211
- Architected tree-node system grafting commands and resources together
212
- The Plan
213
- The core insight: every step in the chain — whether a kubectl command or a resource segment — is the same thing: a node that knows what its valid children are. So we need one universal building block.
214
-
215
- 1. TreeNode — the universal building block
216
- TreeNode
217
- name: string
218
- children: { name => TreeNode }
219
- Built at load time. Every key in every tree becomes a TreeNode instance pointing to its children. No method_missing string parsing at runtime — the graph is pre-resolved.
220
-
221
- 2. Build two trees into TreeNode graphs
222
- Command tree — from the JSON:
223
-
224
- get -> TreeNode(name: "get", children: {})
225
- rollout -> TreeNode(name: "rollout", children: {
226
- status -> TreeNode(...),
227
- history -> TreeNode(...)
228
- })
229
- Resource tree — from the flat array:
230
-
231
- ["deployments.v1.apps", "pods.v1", ...]
232
- -> deployments -> TreeNode(name: "deployments", children: {
233
- v1 -> TreeNode(name: "v1", children: {
234
- apps -> TreeNode(name: "apps", children: {})
235
- })
236
- })
237
- 3. Graft the resource tree onto resource-accepting commands
238
- Commands like get, delete, describe, edit accept a resource as their next argument. Their children should include the resource tree nodes:
239
-
240
- get.children = resource_tree.children # deployments, pods, nodes...
241
- This is the key step — it's what lets Kubectl.get.deployments.v1.apps resolve correctly through a single unified method_missing walk.
242
-
243
- 4. CommandContext — the chainable, lazy evaluator
244
- CommandContext
245
- current_node: TreeNode # where we are in the graph
246
- args: [] # accumulated path ["get", "deployments.v1.apps"]
247
- flags: {} # --namespace production etc
248
- method_missing — looks up name in current_node.children, returns new CommandContext with updated node + args
249
- flag(key, value) — returns new CommandContext with merged flags
250
- include Enumerable + each — forces the shell call, the single lazy boundary
251
- to_s — renders the command without running it
252
- 5. Entry point
253
- ruby
254
- module Kubectl
255
- def self.method_missing(name, ...)
256
- CommandContext.new(current_node: ROOT_COMMAND_NODE, args: [], flags: {})
257
- .public_send(name, ...)
258
- end
259
- end
260
- Load-time sequence
261
- 1. Parse kubectl-command-tree-v1-minimal.json -> build command TreeNodes
262
- 2. Parse SUPPORTED_TYPES array -> build resource TreeNodes
263
- 3. Graft resource tree onto resource commands -> stitch the two graphs
264
- 4. Assign root -> ROOT_COMMAND_NODE
265
- Everything after that is just graph traversal. Sound right before we build it?
266
-
267
- Not quite... the tree node has a type... deployment.v1.app is not 3 tree nodes...
268
-
269
- 10:39
270
-
271
- Reconsidered tree structure distinguishing resource nodes from command nodes
272
- Reconsidered tree structure distinguishing resource nodes from command nodes
273
- You're right. deployments.v1.apps is one thing — a resource type. The dots are qualifiers on that resource, not tree levels. So:
274
-
275
- Revised Plan
276
- TreeNode has a type:
277
-
278
- TreeNode
279
- name: string
280
- type: :command | :resource
281
- children: { name => TreeNode }
282
- The resource tree is flat — one node per resource name:
283
-
284
- deployments -> TreeNode(name: "deployments", type: :resource)
285
- pods -> TreeNode(name: "pods", type: :resource)
286
- Each ResourceNode holds all its possible version.group qualifiers internally:
287
-
288
- deployments knows about: ["v1.apps", "v1"]
289
- pods knows about: ["v1"]
290
- So .v1.apps is qualification within the resource node, not tree traversal. The ResourceNode accumulates those segments until it resolves to a known type string.
291
-
292
- The chain then looks like:
293
-
294
- Kubectl.get # CommandNode -> type: :command
295
- .deployments # ResourceNode -> type: :resource, resolved: false
296
- .v1 # still ResourceNode, qualifier accumulated
297
- .apps # ResourceNode -> resolved: "deployments.v1.apps"
298
- .flag(...) # back to CommandContext
299
- .select { ... } # forces shell call
300
- Does that capture what you're thinking?
301
-
302
- the Resource nodes don't know anything about any other resource nodes... they only know the command nodes that will come in future... that's how they know they've exited teh resoruce node phase...
303
-
304
- what the Kubectl object shuold be ding here at everystep is building a hash datastructure...
305
- which we end up passing like this...
306
-
307
- def to_a
308
- QueryBuilder.new(self.command_data).query.to_a
309
- end
310
- This means that the primary focus of each stage on method missing is to iterate over the options and return the appropriate node.
311
-
312
- Show more
313
- 10:42
314
-
315
- Reconciled node architecture with hash-building data flow pattern
316
- Reconciled node architecture with hash-building data flow pattern
317
- Ah, now it's much clearer. Each node's only job is:
318
-
319
- "given what I know about my valid next steps, what node do I hand back?"
320
-
321
- And the whole chain is just accumulating a hash.
322
-
323
- Revised Plan
324
- The data structure being built:
325
-
326
- ruby
327
- {
328
- commands: ["get"],
329
- resource: "deployments.v1.apps",
330
- flags: { namespace: "production" }
331
- }
332
- CommandNode — knows its valid subcommands AND the resource names:
333
-
334
- method_missing:
335
- name in subcommands? -> return CommandNode (append to commands)
336
- name in resources? -> return ResourceNode (start accumulating resource)
337
- Enumerable? -> force shell call
338
- ResourceNode — knows the supported type strings AND the command nodes:
339
-
340
- method_missing:
341
- current_accumulation + segment completes a known type? -> stay ResourceNode
342
- name in command nodes? -> exit, return CommandNode
343
- Enumerable? -> force shell call
344
- The ResourceNode doesn't know other resources — it only knows commands, which is exactly how it detects it's done.
345
-
346
- Entry/exit at every step is just:
347
-
348
- ruby
349
- def method_missing(name, ...)
350
- next_node(name).new(command_data.merge(...))
351
- end
352
- to_a at any point:
353
-
354
- ruby
355
- def to_a
356
- QueryBuilder.new(command_data).query.to_a
357
- end
358
- Shall we build it?
359
-
360
- yes
361
-
362
- 10:43
363
-
364
- Architected Ruby DSL for kubectl command chaining
365
- Architected Ruby DSL for kubectl command chaining
366
- ruby
367
- require 'json'
368
- require 'open3'
369
-
370
- GEM_DIR = File.expand_path('..', __dir__)
371
-
372
- COMMAND_TREE = JSON.parse(
373
- File.read(File.join(GEM_DIR, 'data', 'kubectl-command-tree-v1-minimal.json')),
374
- symbolize_names: true
375
- )
376
-
377
- SUPPORTED_TYPES = %w[
378
- deployments.v1.apps
379
- pods.v1
380
- replicasets.v1.apps
381
- ].freeze
382
-
383
- RESOURCE_NAMES = SUPPORTED_TYPES.map { |t| t.split('.').first }.uniq.freeze
384
-
385
- class QueryBuilder
386
- def initialize(command_data)
387
- @commands = command_data[:commands]
388
- @resource = command_data[:resource]
389
- @flags = command_data[:flags]
390
- end
391
-
392
- def query
393
- stdout, = Open3.capture2('kubectl', *@commands, *[@resource].compact, *rendered_flags)
394
- stdout.lines.map(&:chomp)
395
- end
396
-
397
- private
398
-
399
- def rendered_flags
400
- @flags.flat_map { |k, v| v ? ["--#{k}", v.to_s] : ["--#{k}"] }
401
- end
402
- end
403
-
404
- class ResourceNode
405
- include Enumerable
406
-
407
- COMMAND_NAMES = COMMAND_TREE.keys.map(&:to_s).freeze
408
-
409
- def initialize(command_data:, accumulated:)
410
- @command_data = command_data
411
- @accumulated = accumulated
412
- end
413
-
414
- def flag(key, value = nil)
415
- self.class.new(
416
- command_data: @command_data.merge(flags: @command_data[:flags].merge(key => value)),
417
- accumulated: @accumulated
418
- )
419
- end
420
-
421
- def each(&block)
422
- QueryBuilder.new(@command_data.merge(resource: @accumulated)).query.each(&block)
423
- end
424
-
425
- def method_missing(name, *args, &block)
426
- segment = name.to_s.tr('_', '-')
427
- candidate = [@accumulated, segment].join('.')
428
-
429
- if COMMAND_NAMES.include?(segment)
430
- CommandNode.new(
431
- command_data: @command_data.merge(resource: @accumulated, commands: @command_data[:commands] + [segment]),
432
- tree: COMMAND_TREE[name]
433
- )
434
- elsif SUPPORTED_TYPES.any? { |t| t == candidate || t.start_with?("#{candidate}.") }
435
- self.class.new(command_data: @command_data, accumulated: candidate)
436
- else
437
- super
438
- end
439
- end
440
-
441
- def respond_to_missing?(name, include_private = false)
442
- segment = name.to_s.tr('_', '-')
443
- candidate = [@accumulated, segment].join('.')
444
-
445
- COMMAND_NAMES.include?(segment) ||
446
- SUPPORTED_TYPES.any? { |t| t == candidate || t.start_with?("#{candidate}.") } ||
447
- super
448
- end
449
- end
450
-
451
- class CommandNode
452
- include Enumerable
453
-
454
- def initialize(command_data: { commands: [], resource: nil, flags: {} }, tree: COMMAND_TREE)
455
- @command_data = command_data
456
- @tree = tree
457
- end
458
-
459
- def flag(key, value = nil)
460
- self.class.new(
461
- command_data: @command_data.merge(flags: @command_data[:flags].merge(key => value)),
462
- tree: @tree
463
- )
464
- end
465
-
466
- def each(&block)
467
- QueryBuilder.new(@command_data).query.each(&block)
468
- end
469
-
470
- def method_missing(name, *args, &block)
471
- segment = name.to_s.tr('_', '-')
472
-
473
- if @tree.key?(name)
474
- self.class.new(
475
- command_data: @command_data.merge(commands: @command_data[:commands] + [segment]),
476
- tree: @tree[name]
477
- )
478
- elsif RESOURCE_NAMES.include?(segment)
479
- ResourceNode.new(command_data: @command_data, accumulated: segment)
480
- else
481
- super
482
- end
483
- end
484
-
485
- def respond_to_missing?(name, include_private = false)
486
- segment = name.to_s.tr('_', '-')
487
- @tree.key?(name) || RESOURCE_NAMES.include?(segment) || super
488
- end
489
- end
490
-
491
- module Kubectl
492
- def self.method_missing(name, *args, &block)
493
- CommandNode.new.public_send(name, *args, &block)
494
- end
495
-
496
- def self.respond_to_missing?(name, include_private = false)
497
- CommandNode.new.respond_to?(name) || super
498
- end
499
- end
500
- The chain then flows like:
501
-
502
- ruby
503
- Kubectl.get.deployments.v1.apps.flag(:namespace, 'production').select { |l| l.include?('my-app') }
504
- # ^ ^ ^^^ ResourceNode accumulating
505
- # | | until Enumerable forces QueryBuilder
506
- # | RESOURCE_NAMES detected -> ResourceNode
507
- # CommandNode
508
- Key things to note:
509
-
510
- ResourceNode only exits back to CommandNode if it sees a known command name — that's the only thing it knows about the outside world
511
- Both nodes accumulate into command_data hash, which is the only thing QueryBuilder ever sees
512
- each is the single forcing function on both nodes — Enumerable does the rest for free
513
-
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env ruby
2
-
3
- require 'json'
4
- require 'open3'
5
-
6
- def extract(args = [])
7
- stdout, = Open3.capture2('kubectl', *args, '--help')
8
- lines = stdout.lines
9
-
10
- subcommands = lines
11
- .lazy
12
- .select { |l| l.match?(/^ \w[\w-]+\s{2,}/) }
13
- .map { |l| l.strip.split(/\s{2,}/).first }
14
- .reject { |cmd| cmd == 'help' }
15
- .map { |cmd| [cmd, extract(args + [cmd])] }
16
- .to_h
17
-
18
- flags = lines
19
- .lazy
20
- .select { |l| l.match?(/^\s+--?[\w]/) }
21
- .map { |l| l.strip }
22
- .to_a
23
-
24
- { flags:, subcommands: }
25
- end
26
-
27
- def minimize(tree)
28
- tree[:subcommands]
29
- .transform_values { |v| minimize(v) }
30
- end
31
-
32
- GEM_DIR = File.expand_path('..', __dir__)
33
-
34
- extract.then do |tree|
35
- File.write(
36
- File.join(GEM_DIR, "data", "kubectl-command-tree-v1.json"),
37
- JSON.pretty_generate(tree)
38
- )
39
-
40
- File.write(
41
- File.join(GEM_DIR, "data", "kubectl-command-tree-v1-minimal.json"),
42
- JSON.pretty_generate(minimize(tree))
43
- )
44
- end
@@ -1,125 +0,0 @@
1
- {
2
- "create": {
3
- "clusterrole": {},
4
- "clusterrolebinding": {},
5
- "configmap": {},
6
- "cronjob": {},
7
- "deployment": {},
8
- "ingress": {},
9
- "job": {},
10
- "namespace": {},
11
- "poddisruptionbudget": {},
12
- "priorityclass": {},
13
- "quota": {},
14
- "role": {},
15
- "rolebinding": {},
16
- "secret": {
17
- "docker-registry": {},
18
- "generic": {},
19
- "tls": {}
20
- },
21
- "service": {
22
- "clusterip": {},
23
- "externalname": {},
24
- "loadbalancer": {},
25
- "nodeport": {}
26
- },
27
- "serviceaccount": {},
28
- "token": {}
29
- },
30
- "expose": {},
31
- "run": {},
32
- "set": {
33
- "env": {},
34
- "image": {},
35
- "resources": {},
36
- "selector": {},
37
- "serviceaccount": {},
38
- "subject": {}
39
- },
40
- "explain": {},
41
- "get": {},
42
- "edit": {},
43
- "delete": {},
44
- "rollout": {
45
- "history": {},
46
- "pause": {},
47
- "restart": {},
48
- "resume": {},
49
- "status": {},
50
- "undo": {}
51
- },
52
- "scale": {},
53
- "autoscale": {},
54
- "certificate": {
55
- "approve": {},
56
- "deny": {}
57
- },
58
- "cluster-info": {
59
- "dump": {}
60
- },
61
- "top": {
62
- "node": {},
63
- "pod": {}
64
- },
65
- "cordon": {},
66
- "uncordon": {},
67
- "drain": {},
68
- "taint": {},
69
- "describe": {},
70
- "logs": {},
71
- "attach": {},
72
- "exec": {},
73
- "port-forward": {},
74
- "proxy": {},
75
- "cp": {},
76
- "auth": {
77
- "can-i": {},
78
- "reconcile": {},
79
- "whoami": {}
80
- },
81
- "debug": {},
82
- "events": {},
83
- "diff": {},
84
- "apply": {
85
- "edit-last-applied": {},
86
- "set-last-applied": {},
87
- "view-last-applied": {}
88
- },
89
- "patch": {},
90
- "replace": {},
91
- "wait": {},
92
- "kustomize": {},
93
- "label": {},
94
- "annotate": {},
95
- "completion": {},
96
- "alpha": {
97
- "kuberc": {
98
- "set": {},
99
- "view": {}
100
- }
101
- },
102
- "api-resources": {},
103
- "api-versions": {},
104
- "config": {
105
- "current-context": {},
106
- "delete-cluster": {},
107
- "delete-context": {},
108
- "delete-user": {},
109
- "get-clusters": {},
110
- "get-contexts": {},
111
- "get-users": {},
112
- "rename-context": {},
113
- "set": {},
114
- "set-cluster": {},
115
- "set-context": {},
116
- "set-credentials": {},
117
- "unset": {},
118
- "use-context": {},
119
- "view": {}
120
- },
121
- "plugin": {
122
- "list": {}
123
- },
124
- "version": {}
125
- }