kube_cluster 0.1.1 → 0.2.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.
@@ -0,0 +1,52 @@
1
+ # to run define K3S_TOKEN, K3S_VERSION is optional, eg:
2
+ # K3S_TOKEN=${RANDOM}${RANDOM}${RANDOM} docker-compose up
3
+
4
+ services:
5
+
6
+ server:
7
+ image: "rancher/k3s:${K3S_VERSION:-latest}"
8
+ command: server
9
+ tmpfs:
10
+ - /run
11
+ - /var/run
12
+ ulimits:
13
+ nproc: 65535
14
+ nofile:
15
+ soft: 65535
16
+ hard: 65535
17
+ privileged: true
18
+ restart: always
19
+ environment:
20
+ - K3S_TOKEN=${K3S_TOKEN:?err}
21
+ - K3S_KUBECONFIG_OUTPUT=/output/kubeconfig.yaml
22
+ - K3S_KUBECONFIG_MODE=666
23
+ volumes:
24
+ #- k3s-server:/var/lib/rancher/k3s
25
+ # This is just so that we get the kubeconfig file out
26
+ - .:/output
27
+ ports:
28
+ - 6443:6443 # Kubernetes API Server
29
+ - 80:80 # Ingress controller port 80
30
+ - 443:443 # Ingress controller port 443
31
+
32
+ #agent:
33
+ # image: "rancher/k3s:${K3S_VERSION:-latest}"
34
+ # tmpfs:
35
+ # - /run
36
+ # - /var/run
37
+ # ulimits:
38
+ # nproc: 65535
39
+ # nofile:
40
+ # soft: 65535
41
+ # hard: 65535
42
+ # privileged: true
43
+ # restart: always
44
+ # environment:
45
+ # - K3S_URL=https://server:6443
46
+ # - K3S_TOKEN=${K3S_TOKEN:?err}
47
+ # volumes:
48
+ # - k3s-agent:/var/lib/rancher/k3s
49
+
50
+ #volumes:
51
+ # k3s-server: {}
52
+ # k3s-agent: {}
data/exe/kube_cluster CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env ruby
2
2
  # frozen_string_literal: true
3
3
 
4
- require_relative "../lib/kube_cluster"
4
+ require_relative "../lib/kube/cluster"
5
5
 
6
6
  puts "hello from kube_cluster"
data/kube_cluster.gemspec CHANGED
@@ -1,10 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative "lib/kube_cluster/version"
3
+ require_relative "lib/kube/cluster/version"
4
4
 
5
5
  Gem::Specification.new do |spec|
6
6
  spec.name = "kube_cluster"
7
- spec.version = KubeCluster::VERSION
7
+ spec.version = Kube::Cluster::VERSION
8
8
  spec.authors = ["Nathan K"]
9
9
  spec.email = ["nathankidd@hey.com"]
10
10
 
@@ -23,7 +23,7 @@ Gem::Specification.new do |spec|
23
23
  spec.metadata["documentation_uri"] = spec.homepage
24
24
  spec.metadata["rubygems_mfa_required"] = "true"
25
25
 
26
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|data)/}) }
26
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
27
27
  spec.bindir = "exe"
28
28
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
29
  spec.require_paths = ["lib"]
@@ -31,4 +31,6 @@ Gem::Specification.new do |spec|
31
31
  spec.add_development_dependency "minitest", "~> 5.0"
32
32
  spec.add_development_dependency "rake", "~> 13.0"
33
33
  spec.add_development_dependency "rubocop", "~> 1.21"
34
+
35
+ spec.add_dependency "kube_schema", "~> 1.0"
34
36
  end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kube
4
+ module Cluster
5
+ class CommandNode
6
+ include Enumerable
7
+
8
+ BLANK_DATA = { commands: [], resource: nil, args: [], flags: {} }.freeze
9
+
10
+ attr_reader :command_data
11
+
12
+ def initialize(current_node:, command_data: BLANK_DATA)
13
+ @current_node = current_node
14
+ @command_data = command_data
15
+ end
16
+
17
+ def flag(key, value = nil)
18
+ self.class.new(
19
+ current_node: @current_node,
20
+ command_data: @command_data.merge(
21
+ flags: @command_data[:flags].merge(key.to_s.tr("_", "-") => value)
22
+ )
23
+ )
24
+ end
25
+
26
+ def each(&block)
27
+ to_a.each(&block)
28
+ end
29
+
30
+ def to_a
31
+ QueryBuilder.new(@command_data).to_a
32
+ end
33
+
34
+ def to_s
35
+ QueryBuilder.new(@command_data).to_s
36
+ end
37
+
38
+ def inspect
39
+ "#<#{self.class.name} #{to_s}>"
40
+ end
41
+
42
+ def method_missing(name, *args, &block)
43
+ segment = name.to_s.tr("_", "-")
44
+ child = @current_node.children[segment]
45
+
46
+ if child&.command?
47
+ self.class.new(
48
+ current_node: child,
49
+ command_data: @command_data.merge(
50
+ commands: @command_data[:commands] + [segment],
51
+ args: @command_data[:args] + args.map(&:to_s)
52
+ )
53
+ )
54
+ elsif child&.resource?
55
+ self.class.new(
56
+ current_node: child,
57
+ command_data: @command_data.merge(
58
+ resource: (@command_data[:resource] || ResourceSelector.new) + [segment],
59
+ args: @command_data[:args] + args.map(&:to_s)
60
+ )
61
+ )
62
+ elsif @current_node.resource? || @current_node.children.empty?
63
+ # Leaf command or already in resource mode — free-form resource segment
64
+ self.class.new(
65
+ current_node: TreeNode.new(name: segment, type: :resource),
66
+ command_data: @command_data.merge(
67
+ resource: (@command_data[:resource] || ResourceSelector.new) + [segment],
68
+ args: @command_data[:args] + args.map(&:to_s)
69
+ )
70
+ )
71
+ elsif Enumerable.method_defined?(name)
72
+ to_a.public_send(name, *args, &block)
73
+ else
74
+ super
75
+ end
76
+ end
77
+
78
+ def respond_to_missing?(name, include_private = false)
79
+ segment = name.to_s.tr("_", "-")
80
+
81
+ @current_node.children.key?(segment) ||
82
+ @current_node.resource? ||
83
+ @current_node.children.empty? ||
84
+ Enumerable.method_defined?(name) ||
85
+ super
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+
5
+ module Kube
6
+ module Cluster
7
+ class Ctl
8
+ GEM_ROOT = File.expand_path("../../..", __dir__)
9
+
10
+ COMMAND_TREE = JSON.parse(
11
+ File.read(File.join(GEM_ROOT, "data", "kubectl-command-tree-v1-minimal.json"))
12
+ )
13
+
14
+ ROOT = TreeNode.new(
15
+ name: "kubectl",
16
+ type: :command,
17
+ children: TreeNode.build(COMMAND_TREE)
18
+ )
19
+
20
+ def method_missing(name, *args, &block)
21
+ CommandNode.new(current_node: ROOT).public_send(name, *args, &block)
22
+ end
23
+
24
+ def respond_to_missing?(name, include_private = false)
25
+ CommandNode.new(current_node: ROOT).respond_to?(name) || super
26
+ end
27
+
28
+ def inspect
29
+ "#<#{self.class.name}>"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "open3"
4
+
5
+ module Kube
6
+ module Cluster
7
+ class QueryBuilder
8
+ def initialize(command_data)
9
+ @commands = command_data[:commands] || []
10
+ @resource = command_data[:resource]
11
+ @args = command_data[:args] || []
12
+ @flags = command_data[:flags] || {}
13
+ end
14
+
15
+ def to_s
16
+ ["kubectl", *@commands, *resource_arg, *@args, *rendered_flags].join(" ")
17
+ end
18
+
19
+ def to_a
20
+ stdout, _status = Open3.capture2("kubectl", *@commands, *resource_arg, *@args, *rendered_flags)
21
+ stdout.lines.map(&:chomp)
22
+ end
23
+
24
+ private
25
+
26
+ def resource_arg
27
+ @resource ? [@resource.to_s] : []
28
+ end
29
+
30
+ def rendered_flags
31
+ @flags.flat_map { |k, v| v.nil? ? ["--#{k}"] : ["--#{k}", v.to_s] }
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kube
4
+ module Cluster
5
+ class ResourceSelector < Array
6
+ def +(other)
7
+ self.class.new(super)
8
+ end
9
+
10
+ def to_s
11
+ join(".")
12
+ end
13
+
14
+ def to_regex
15
+ Regexp.new(map { |s| Regexp.escape(s) }.join('.*\.'))
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,51 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kube
4
+ module Cluster
5
+ class TreeNode
6
+ # Commands whose leaf children are resource types.
7
+ # Non-leaf children (those with their own subtree) remain commands.
8
+ RESOURCE_PARENTS = %w[
9
+ create
10
+ create/secret
11
+ create/service
12
+ top
13
+ ].freeze
14
+
15
+ attr_reader :name, :type, :children
16
+
17
+ def initialize(name:, type:, children: {})
18
+ @name = name.freeze
19
+ @type = type
20
+ @children = children.freeze
21
+ end
22
+
23
+ def command? = type == :command
24
+ def resource? = type == :resource
25
+
26
+ # Recursively build a children hash of TreeNodes from the parsed JSON.
27
+ # parent_path tracks position in the tree to determine child types.
28
+ def self.build(hash, parent_path: nil)
29
+ hash.each_with_object({}) do |(key, subtree), children|
30
+ name = key.to_s
31
+ current_path = [parent_path, name].compact.join("/")
32
+ leaf = subtree.empty?
33
+
34
+ # Under a resource parent, leaf nodes are resources, non-leaves are commands.
35
+ # Everywhere else, everything is a command.
36
+ child_type = if RESOURCE_PARENTS.include?(parent_path.to_s) && leaf
37
+ :resource
38
+ else
39
+ :command
40
+ end
41
+
42
+ children[name] = new(
43
+ name: name,
44
+ type: child_type,
45
+ children: build(subtree, parent_path: current_path)
46
+ )
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kube
4
+ module Cluster
5
+ VERSION = "0.2.0"
6
+ end
7
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Kube
4
+ module Cluster
5
+ VERSION = "<%= version %>"
6
+ end
7
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "cluster/version"
4
+ require_relative "cluster/tree_node"
5
+ require_relative "cluster/resource_selector"
6
+ require_relative "cluster/query_builder"
7
+ require_relative "cluster/command_node"
8
+ require_relative "cluster/ctl"
9
+
10
+ module Kube
11
+ module Cluster
12
+ def self.ctl
13
+ Ctl.new
14
+ end
15
+ end
16
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kube_cluster
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K
@@ -51,6 +51,20 @@ dependencies:
51
51
  - - "~>"
52
52
  - !ruby/object:Gem::Version
53
53
  version: '1.21'
54
+ - !ruby/object:Gem::Dependency
55
+ name: kube_schema
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ type: :runtime
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: '1.0'
54
68
  description: 'OOP abstraction that allows you to deploy and manage kubernetes resources
55
69
  using Ruby.
56
70
 
@@ -64,24 +78,35 @@ extra_rdoc_files: []
64
78
  files:
65
79
  - ".envrc"
66
80
  - ".gitignore"
81
+ - ".rubocop.yml"
67
82
  - Gemfile
83
+ - Gemfile.lock
68
84
  - LICENSE
69
85
  - README.md
70
86
  - Rakefile
87
+ - TREE_PLAN.md
71
88
  - bin/console
89
+ - bin/generate-command-schema-v1
72
90
  - bin/increment-version
73
91
  - bin/release-gem
74
92
  - bin/setup
75
93
  - bin/tag-version
76
94
  - bin/test
95
+ - data/kubectl-command-tree-v1-minimal.json
96
+ - data/kubectl-command-tree-v1.json
97
+ - examples/quick-repl/docker-compose.yml
77
98
  - exe/kube_cluster
78
99
  - flake.lock
79
100
  - flake.nix
80
101
  - kube_cluster.gemspec
81
- - kube_cluster.gemspec.erb
82
- - lib/kube_cluster.rb
83
- - lib/kube_cluster/version.rb
84
- - lib/kube_cluster/version.rb.erb
102
+ - lib/kube/cluster.rb
103
+ - lib/kube/cluster/command_node.rb
104
+ - lib/kube/cluster/ctl.rb
105
+ - lib/kube/cluster/query_builder.rb
106
+ - lib/kube/cluster/resource_selector.rb
107
+ - lib/kube/cluster/tree_node.rb
108
+ - lib/kube/cluster/version.rb
109
+ - lib/kube/cluster/version.rb.erb
85
110
  homepage: https://github.com/general-intelligence-systems/kube_cluster
86
111
  licenses:
87
112
  - Apache-2.0
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "lib/kube_cluster/version"
4
-
5
- Gem::Specification.new do |spec|
6
- spec.name = "<%= gem_name %>"
7
- spec.version = KubeCluster::VERSION
8
- spec.authors = [<%= authors.map { |a| %("#{a}") }.join(", ") %>]
9
- spec.email = [<%= emails.map { |e| %("#{e}") }.join(", ") %>]
10
-
11
- spec.summary = "<%= summary %>"
12
-
13
- spec.description = <<~DESC
14
- <%= description %>
15
- DESC
16
-
17
- spec.homepage = "<%= homepage %>"
18
- spec.license = "<%= license %>"
19
- spec.required_ruby_version = ">= <%= ruby_version %>"
20
-
21
- spec.metadata["homepage_uri"] = spec.homepage
22
- spec.metadata["source_code_uri"] = spec.homepage
23
- spec.metadata["documentation_uri"] = spec.homepage
24
- spec.metadata["rubygems_mfa_required"] = "true"
25
-
26
- spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features|data)/}) }
27
- spec.bindir = "exe"
28
- spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
29
- spec.require_paths = ["lib"]
30
-
31
- spec.add_development_dependency "minitest", "~> 5.0"
32
- spec.add_development_dependency "rake", "~> 13.0"
33
- spec.add_development_dependency "rubocop", "~> 1.21"
34
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module KubeCluster
4
- VERSION = "0.1.1"
5
- end
@@ -1,5 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module KubeCluster
4
- VERSION = "<%= version %>"
5
- end
data/lib/kube_cluster.rb DELETED
@@ -1,6 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "kube_cluster/version"
4
-
5
- module KubeCluster
6
- end