kube_cluster 0.6.0 → 0.8.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 (28) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +27 -1
  3. data/kube_cluster.gemspec +1 -0
  4. data/lib/kube/cluster/standard/cdi/data_volume.rb +35 -0
  5. data/lib/kube/cluster/standard/cloud_native_pg/cluster.rb +27 -0
  6. data/lib/kube/cluster/standard/cloud_native_pg/database_with_external_secret.rb +44 -0
  7. data/lib/kube/cluster/standard/cloud_native_pg/external_secret.rb +57 -0
  8. data/lib/kube/cluster/standard/cloud_native_pg/helm.rb +27 -21
  9. data/lib/kube/cluster/standard/config_map.rb +45 -0
  10. data/lib/kube/cluster/standard/custom_resource_definition.rb +82 -0
  11. data/lib/kube/cluster/standard/daemon_set.rb +70 -0
  12. data/lib/kube/cluster/standard/deployment_with_service.rb +152 -0
  13. data/lib/kube/cluster/standard/env_processing.rb +42 -0
  14. data/lib/kube/cluster/standard/eso/external_secret.rb +96 -0
  15. data/lib/kube/cluster/standard/forgejo/helm.rb +64 -0
  16. data/lib/kube/cluster/standard/gateway_api/http_route.rb +33 -0
  17. data/lib/kube/cluster/standard/job.rb +50 -0
  18. data/lib/kube/cluster/standard/kube_virt/virtual_machine.rb +36 -0
  19. data/lib/kube/cluster/standard/meta_controller/composite_controller.rb +80 -0
  20. data/lib/kube/cluster/standard/meta_controller/decorator_controller.rb +79 -0
  21. data/lib/kube/cluster/standard/persistent_volume_claim.rb +37 -0
  22. data/lib/kube/cluster/standard/secret.rb +34 -0
  23. data/lib/kube/cluster/standard/service.rb +41 -0
  24. data/lib/kube/cluster/standard/volume_processing.rb +88 -0
  25. data/lib/kube/cluster/version.rb +1 -1
  26. data/lib/kube/cluster.rb +1 -0
  27. metadata +35 -2
  28. data/lib/kube/cluster/standard/cloud_native_pg.rb +0 -17
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ module EnvProcessing
10
+ # Convert an env hash into a Kubernetes env array.
11
+ #
12
+ # String/numeric values become plain env vars:
13
+ # "FOO" => "bar" => { name: "FOO", value: "bar" }
14
+ #
15
+ # ExternalSecret::TemplateRef values become secretKeyRef env vars
16
+ # and register the template entry on the ExternalSecret:
17
+ # "FOO" => secret.template("{{ .bar }}")
18
+ # => { name: "FOO", valueFrom: { secretKeyRef: { name: "secret-name", key: "FOO" } } }
19
+ #
20
+ def self.process(env)
21
+ return env if env.is_a?(Array)
22
+ return [] if env.nil?
23
+
24
+ env.map do |key, value|
25
+ key = key.to_s
26
+
27
+ if value.is_a?(ExternalSecret::TemplateRef)
28
+ value.secret.register_template!(key, value.template_value)
29
+ { name: key, valueFrom: { secretKeyRef: { name: value.secret.secret_name, key: key } } }
30
+ else
31
+ { name: key, value: value.to_s }
32
+ end
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+
40
+ test do
41
+ # no op
42
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ module ESO
10
+ class ExternalSecret < Kube::Cluster['ExternalSecret']
11
+ # Returned by .template — carries the secret ref and template value.
12
+ # When the env hash is processed, the key becomes both the env var name
13
+ # and the secret template data key.
14
+ TemplateRef = Struct.new(:secret, :template_value)
15
+
16
+ # Returned by .key — carries the secret ref and key name.
17
+ # When the volume_mounts hash is processed, this tells the volume
18
+ # processing layer to mount a single key from the secret as a file.
19
+ KeyRef = Struct.new(:secret, :key_name)
20
+
21
+ attr_reader :secret_name
22
+
23
+ def initialize(name:, store:, remote_key:, keys: nil, deletion_policy: nil, &block)
24
+ @secret_name = name
25
+ @remote_key = remote_key
26
+ @_template_data = {}
27
+ @_remote_properties = {}
28
+ @_keys = keys
29
+ @_deletion_policy = deletion_policy
30
+
31
+ super() do
32
+ metadata.name = name
33
+ spec.refreshInterval = '1h'
34
+ spec.secretStoreRef = { kind: 'ClusterSecretStore', name: store }
35
+
36
+ target = { name: name, creationPolicy: 'Owner' }
37
+ target[:deletionPolicy] = deletion_policy if deletion_policy
38
+ spec.target = target
39
+
40
+ if keys
41
+ spec.data = keys.map do |secret_key, property|
42
+ { secretKey: secret_key, remoteRef: { key: remote_key, property: property } }
43
+ end
44
+ end
45
+
46
+ instance_exec(&block) if block
47
+ end
48
+ end
49
+
50
+ # Returns a TemplateRef. The env hash processor calls .register!
51
+ # on the ref to wire up the template data and remote properties.
52
+ def template(template_string)
53
+ TemplateRef.new(self, template_string)
54
+ end
55
+ alias with_template template
56
+
57
+ # Returns a KeyRef for mounting a single key from this secret as a file.
58
+ # The volume processing layer uses this to generate the volume and mount.
59
+ def key(key_name)
60
+ KeyRef.new(self, key_name)
61
+ end
62
+
63
+ # Called by env processing to register a template entry.
64
+ def register_template!(env_key, template_string)
65
+ @_template_data[env_key] = template_string
66
+
67
+ template_string.scan(/\{\{\s*\.(\w+)\s*\}\}/) do |match|
68
+ @_remote_properties[match[0]] = true
69
+ end
70
+
71
+ @data.spec.target.template = { data: @_template_data }
72
+ @data.spec.data = @_remote_properties.keys.map do |prop|
73
+ { secretKey: prop, remoteRef: { key: @remote_key, property: prop } }
74
+ end
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ test do
83
+ describe "ESO::ExternalSecret" do
84
+ it "initializes without error" do
85
+ Kube::Cluster::Standard::ESO::ExternalSecret
86
+ .new(
87
+ name: "my-external-secret",
88
+ store: "my-cluster-store",
89
+ remote_key: "secret/data/my-app",
90
+ )
91
+ .to_yaml
92
+ .is_a?(String)
93
+ .should == true
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,64 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ module Forgejo
10
+ class Helm < Kube::Cluster["HelmChart"]
11
+
12
+ CHART_VERSION = "16.2.1"
13
+
14
+ def initialize(
15
+ domain:,
16
+ chart_version: CHART_VERSION,
17
+ target_namespace: "default",
18
+ storage_size: "200Gi",
19
+ storage_class: "local-path",
20
+ node_selector: nil,
21
+ &block
22
+ )
23
+ super {
24
+ metadata.name = "forgejo"
25
+ metadata.namespace = "kube-system"
26
+ spec.version = chart_version
27
+ spec.chart = "oci://codeberg.org/forgejo-contrib/forgejo"
28
+ spec.targetNamespace = target_namespace
29
+ spec.valuesContent = <<~YAML
30
+ gitea:
31
+ config:
32
+ server:
33
+ ROOT_URL: https://#{domain}/
34
+ DOMAIN: #{domain}
35
+ SSH_DOMAIN: #{domain}
36
+ persistence:
37
+ enabled: true
38
+ size: #{storage_size}
39
+ storageClass: #{storage_class}
40
+ #{node_selector ? "nodeSelector:\n kubernetes.io/hostname: #{node_selector}" : ""}
41
+ YAML
42
+
43
+ instance_exec(&block) if block
44
+ }
45
+ end
46
+ end
47
+ end
48
+ end
49
+ end
50
+ end
51
+
52
+ test do
53
+ describe "Forgejo::Helm" do
54
+ it "initializes without error" do
55
+ Kube::Cluster::Standard::Forgejo::Helm
56
+ .new(
57
+ domain: "git.facebook.com"
58
+ )
59
+ .to_yaml
60
+ .is_a?(String)
61
+ .should == true
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ module GatewayApi
10
+ #class HTTPRoute < Kube::Cluster::Manifest
11
+ #end
12
+ end
13
+ end
14
+ end
15
+ end
16
+
17
+ test do
18
+ #describe "GatewayApi::HTTPRoute" do
19
+ # it "initializes without error" do
20
+ # Kube::Cluster::Standard::GatewayApi::HTTPRoute
21
+ # .new(
22
+ # name: nil,
23
+ # hostname: nil,
24
+ # service: nil,
25
+ # namespace: nil,
26
+ # port: nil,
27
+ # )
28
+ # .to_yaml
29
+ # .is_a?(String)
30
+ # .should == true
31
+ # end
32
+ #end
33
+ end
@@ -0,0 +1,50 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ class Job < Kube::Cluster["Secret"]
10
+ def initialize(name:, image:, env: {}, command: nil, backoff_limit: 3, ttl: 300, &block)
11
+ processed_env = EnvProcessing.process(env)
12
+
13
+ super() do
14
+ metadata.name = name
15
+
16
+ spec.backoffLimit = backoff_limit
17
+ spec.ttlSecondsAfterFinished = ttl
18
+ spec.template.spec.restartPolicy = 'OnFailure'
19
+
20
+ container = {
21
+ name: name,
22
+ image: image,
23
+ env: processed_env
24
+ }
25
+ container[:command] = command if command
26
+
27
+ spec.template.spec.containers = [container]
28
+
29
+ instance_exec(&block) if block
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
35
+ end
36
+
37
+ test do
38
+ describe "Job" do
39
+ it "initializes without error" do
40
+ Kube::Cluster::Standard::Job
41
+ .new(
42
+ name: "my-job",
43
+ image: "nixery.dev/cowsay", # nixery.dev is pretty sick, mate!
44
+ )
45
+ .to_yaml
46
+ .is_a?(String)
47
+ .should == true
48
+ end
49
+ end
50
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ module KubeVirt
10
+ class VirtualMachine < Kube::Cluster['VirtualMachine']
11
+ def initialize(name:, &block)
12
+ super() {
13
+ metadata.name = name
14
+ spec.template.metadata.labels = { 'kubevirt.io/domain' => name }
15
+ instance_exec(&block) if block
16
+ }
17
+ end
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ test do
25
+ describe "KubeVirt::VirtualMachine" do
26
+ it "initializes without error" do
27
+ Kube::Cluster::Standard::KubeVirt::VirtualMachine
28
+ .new(
29
+ name: "my-virtual-machine"
30
+ )
31
+ .to_yaml
32
+ .is_a?(String)
33
+ .should == true
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,80 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TEMPORARILY DISABLED: metacontroller.k8s.io CRDs (CompositeController) are not
4
+ # registered in kube_schema, so subclassing the kind raises at load time and
5
+ # aborts `require "kube/cluster"`. Commented out until the schema is registered.
6
+ =begin
7
+ require "bundler/setup"
8
+ require "kube/cluster"
9
+
10
+ module Kube
11
+ module Cluster
12
+ module Standard
13
+ module MetaController
14
+ class CompositeController < Kube::Cluster['CompositeController']
15
+ def initialize(
16
+ name:, webhook_url:,
17
+ resync_period: 30, parent_resource:, child_resources: {}, &block
18
+ )
19
+
20
+ resolved_parent = resolve_ref(parent_resource)
21
+ resolved_children = resolve_hash(child_resources)
22
+
23
+ super() {
24
+ metadata.name = "#{name}-composite-controller"
25
+
26
+ spec.generateSelector = true
27
+ spec.resyncPeriodSeconds = resync_period
28
+ spec.hooks.sync.webhook = { url: webhook_url }
29
+ spec.parentResource = resolved_parent
30
+ spec.childResources = resolved_children
31
+
32
+ instance_exec(&block) if block
33
+ }
34
+ end
35
+
36
+ private
37
+
38
+ def resolve_ref(ref)
39
+ if ref.is_a?(Hash)
40
+ ref
41
+ else
42
+ if ref.is_a?(Class)
43
+ klass = ref
44
+ else
45
+ klass = ref.class
46
+ end
47
+
48
+ {
49
+ apiVersion: klass.defaults['apiVersion'],
50
+ resource: klass.defaults['kind'].downcase.pluralize
51
+ }
52
+ end
53
+ end
54
+
55
+ def resolve_hash(hash)
56
+ hash.map do |klass, options|
57
+ resolve_ref(klass).merge(options || {})
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ test do
67
+ describe "MetaController::CompositeController" do
68
+ it "initializes without error" do
69
+ Kube::Cluster::Standard::MetaController::CompositeController
70
+ .new(
71
+ name: nil,
72
+ webhook_url: nil,
73
+ )
74
+ .to_yaml
75
+ .is_a?(String)
76
+ .should == true
77
+ end
78
+ end
79
+ end
80
+ =end
@@ -0,0 +1,79 @@
1
+ # frozen_string_literal: true
2
+
3
+ # TEMPORARILY DISABLED: metacontroller.k8s.io CRDs (DecoratorController) are not
4
+ # registered in kube_schema, so subclassing the kind raises at load time and
5
+ # aborts `require "kube/cluster"`. Commented out until the schema is registered.
6
+ =begin
7
+ require "bundler/setup"
8
+ require "kube/cluster"
9
+
10
+ module Kube
11
+ module Cluster
12
+ module Standard
13
+ module MetaController
14
+ class DecoratorController < Kube::Cluster['DecoratorController']
15
+ def initialize(
16
+ name:, webhook_url:,
17
+ resync_period: 30, resources: {}, attachments: {}, &block
18
+ )
19
+
20
+ resolved_resources = resolve_hash(resources)
21
+ resolved_attachments = resolve_hash(attachments)
22
+
23
+ super() {
24
+ metadata.name = name
25
+
26
+ spec.resources = resolved_resources
27
+ spec.attachments = resolved_attachments
28
+ spec.resyncPeriodSeconds = resync_period
29
+ spec.hooks.sync.webhook = { url: webhook_url }
30
+
31
+ instance_exec(&block) if block
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def resolve_ref(ref)
38
+ if ref.is_a?(Hash)
39
+ ref
40
+ else
41
+ if ref.is_a?(Class)
42
+ klass = ref
43
+ else
44
+ klass = ref.class
45
+ end
46
+
47
+ {
48
+ apiVersion: klass.defaults['apiVersion'],
49
+ resource: klass.defaults['kind'].downcase.pluralize
50
+ }
51
+ end
52
+ end
53
+
54
+ def resolve_hash(hash)
55
+ hash.map do |klass, options|
56
+ resolve_ref(klass).merge(options || {})
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+
65
+ test do
66
+ describe "MetaController::DecoratorController" do
67
+ it "initializes without error" do
68
+ Kube::Cluster::Standard::MetaController::DecoratorController
69
+ .new(
70
+ name: nil,
71
+ webhook_url: nil,
72
+ )
73
+ .to_yaml
74
+ .is_a?(String)
75
+ .should == true
76
+ end
77
+ end
78
+ end
79
+ =end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ class PersistentVolumeClaim < Kube::Cluster["PersistentVolumeClaim"]
10
+ def initialize(name:, storage:, access_modes: ["ReadWriteOnce"], storage_class: nil, &block)
11
+ super() {
12
+ metadata.name = name
13
+ spec.accessModes = access_modes
14
+ spec.storageClassName = storage_class if storage_class
15
+ spec.resources = { requests: { storage: storage } }
16
+ instance_exec(&block) if block_given?
17
+ }
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+ test do
25
+ describe "PersistentVolumeClaim" do
26
+ it "initializes without error" do
27
+ Kube::Cluster::Standard::PersistentVolumeClaim
28
+ .new(
29
+ name: "my-volume",
30
+ storage: "25Gi",
31
+ )
32
+ .to_yaml
33
+ .is_a?(String)
34
+ .should == true
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ class Secret < Kube::Cluster["Secret"]
10
+ def initialize(name:, **data, &block)
11
+ super() {
12
+ metadata.name = name
13
+ data.each { |k, v| stringData[k.to_s] = v }
14
+ instance_exec(&block) if block_given?
15
+ }
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ test do
23
+ describe "Secret" do
24
+ it "initializes without error" do
25
+ Kube::Cluster::Standard::Secret
26
+ .new(
27
+ name: "my-secret"
28
+ )
29
+ .to_yaml
30
+ .is_a?(String)
31
+ .should == true
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ class Service < Kube::Cluster["Service"]
10
+ def initialize(name:, ports:, namespace: "default", **options, &block)
11
+ super() {
12
+ metadata.name = name
13
+ metadata.namespace = namespace
14
+ metadata.labels = { "app" => name }
15
+ spec.selector = { "app" => name }
16
+ spec.ports = ports.map do |port|
17
+ { name: "http-#{port}", port: port, targetPort: port, protocol: "TCP" }
18
+ end
19
+
20
+ instance_exec(&block) if block_given?
21
+ }
22
+ end
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ test do
29
+ describe "Service" do
30
+ it "initializes without error" do
31
+ Kube::Cluster::Standard::Service
32
+ .new(
33
+ name: "my-secret",
34
+ ports: [],
35
+ )
36
+ .to_yaml
37
+ .is_a?(String)
38
+ .should == true
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/setup"
4
+ require "kube/cluster"
5
+
6
+ module Kube
7
+ module Cluster
8
+ module Standard
9
+ module VolumeProcessing
10
+ # Convert a volume_mounts hash into Kubernetes volumes and volumeMounts arrays.
11
+ #
12
+ # Hash values (mount path => source ref):
13
+ # ExternalSecret::KeyRef => secret volume with items, mount with subPath + readOnly
14
+ # ExternalSecret => secret volume, directory mount
15
+ # PersistentVolumeClaim => persistentVolumeClaim volume, plain mount
16
+ #
17
+ # Legacy: if input is an Array, it is returned as-is for backwards compat.
18
+ #
19
+ def self.process(input)
20
+ return { volumes: [], volume_mounts: input } if input.is_a?(Array)
21
+ return { volumes: [], volume_mounts: [] } if input.nil? || input.empty?
22
+
23
+ volumes = []
24
+ mounts = []
25
+
26
+ input.each do |mount_path, source|
27
+ case source
28
+ when ExternalSecret::KeyRef
29
+ name = source.secret.secret_name
30
+ key = source.key_name
31
+ volumes << {
32
+ name: name,
33
+ secret: {
34
+ secretName: name,
35
+ items: [{ key: key, path: key }]
36
+ }
37
+ }
38
+ mounts << {
39
+ name: name,
40
+ mountPath: mount_path,
41
+ subPath: key,
42
+ readOnly: true
43
+ }
44
+
45
+ when ExternalSecret
46
+ name = source.secret_name
47
+ volumes << { name: name, secret: { secretName: name } }
48
+ mounts << { name: name, mountPath: mount_path }
49
+
50
+ when PersistentVolumeClaim
51
+ name = source.to_h.dig(:metadata, :name)
52
+ volumes << { name: name, persistentVolumeClaim: { claimName: name } }
53
+ mounts << { name: name, mountPath: mount_path }
54
+
55
+ when ConfigMap::KeyRef
56
+ name = source.config_map.config_map_name
57
+ key = source.key_name
58
+ volumes << {
59
+ name: name,
60
+ configMap: {
61
+ name: name,
62
+ items: [{ key: key, path: key }]
63
+ }
64
+ }
65
+ mounts << {
66
+ name: name,
67
+ mountPath: mount_path,
68
+ subPath: key,
69
+ readOnly: true
70
+ }
71
+
72
+ when ConfigMap
73
+ name = source.to_h.dig(:metadata, :name)
74
+ volumes << { name: name, configMap: { name: name } }
75
+ mounts << { name: name, mountPath: mount_path, readOnly: true }
76
+ end
77
+ end
78
+
79
+ { volumes: volumes, volume_mounts: mounts }
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+
86
+ test do
87
+ # no op
88
+ end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kube
4
4
  module Cluster
5
- VERSION = "0.6.0"
5
+ VERSION = "0.8.0"
6
6
  end
7
7
  end