kube_cluster 0.7.0 → 0.9.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 (29) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile.lock +27 -1
  3. data/examples/02-manifest-with-middleware/manifest.rb +1 -1
  4. data/guides/middleware/readme.md +1 -1
  5. data/kube_cluster.gemspec +1 -0
  6. data/lib/kube/cluster/manifest.rb +7 -7
  7. data/lib/kube/cluster/middleware/namespace.rb +75 -71
  8. data/lib/kube/cluster/middleware/set_namespace.rb +38 -0
  9. data/lib/kube/cluster/middleware/set_reloader_auto.rb +36 -0
  10. data/lib/kube/cluster/middleware/stack.rb +1 -1
  11. data/lib/kube/cluster/standard/cdi/data_volume.rb +35 -0
  12. data/lib/kube/cluster/standard/cloud_native_pg/cluster.rb +27 -0
  13. data/lib/kube/cluster/standard/cloud_native_pg/database_with_external_secret.rb +44 -0
  14. data/lib/kube/cluster/standard/cloud_native_pg/external_secret.rb +57 -0
  15. data/lib/kube/cluster/standard/config_map.rb +45 -0
  16. data/lib/kube/cluster/standard/custom_resource_definition.rb +82 -0
  17. data/lib/kube/cluster/standard/daemon_set.rb +70 -0
  18. data/lib/kube/cluster/standard/eso/external_secret.rb +96 -0
  19. data/lib/kube/cluster/standard/forgejo/helm.rb +64 -0
  20. data/lib/kube/cluster/standard/gateway_api/http_route.rb +33 -0
  21. data/lib/kube/cluster/standard/job.rb +50 -0
  22. data/lib/kube/cluster/standard/kube_virt/virtual_machine.rb +36 -0
  23. data/lib/kube/cluster/standard/meta_controller/composite_controller.rb +80 -0
  24. data/lib/kube/cluster/standard/meta_controller/decorator_controller.rb +79 -0
  25. data/lib/kube/cluster/standard/persistent_volume_claim.rb +37 -0
  26. data/lib/kube/cluster/version.rb +1 -1
  27. data/lib/kube/cluster.rb +1 -0
  28. metadata +32 -2
  29. data/lib/kube/cluster/standard/cloud_native_pg.rb +0 -17
@@ -0,0 +1,82 @@
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 CustomResourceDefinition < Kube::Cluster['CustomResourceDefinition']
10
+ def initialize(
11
+ kind:, group:, version: 'v1', scope: 'Namespaced',
12
+ short_names: [], categories: [], schema: nil, &block
13
+ )
14
+
15
+ plural = kind.downcase.pluralize
16
+ singular = kind.downcase
17
+
18
+ schema ||= {
19
+ type: 'object',
20
+ 'x-kubernetes-preserve-unknown-fields': true
21
+ }
22
+
23
+ super() {
24
+ metadata.name = "#{plural}.#{group}"
25
+
26
+ spec.group = group
27
+ spec.names.kind = kind
28
+ spec.names.listKind = "#{kind}List"
29
+ spec.names.plural = plural
30
+ spec.names.singular = singular
31
+ spec.names.shortNames = short_names unless short_names.empty?
32
+ spec.names.categories = categories unless categories.empty?
33
+ spec.scope = scope
34
+ spec.versions = [{
35
+ name: version,
36
+ served: true,
37
+ storage: true,
38
+ subresources: { status: {} },
39
+ schema: {
40
+ openAPIV3Schema: schema
41
+ }
42
+ }]
43
+
44
+ instance_exec(&block) if block
45
+ }
46
+
47
+ api_version = "#{group}/#{version}"
48
+ Kube::Schema.register(
49
+ kind,
50
+ schema: {
51
+ 'type' => 'object',
52
+ 'properties' => {
53
+ 'apiVersion' => { 'type' => 'string' },
54
+ 'kind' => { 'type' => 'string' },
55
+ 'metadata' => { 'type' => 'object' },
56
+ 'spec' => { 'type' => 'object', 'x-kubernetes-preserve-unknown-fields' => true },
57
+ 'status' => { 'type' => 'object', 'x-kubernetes-preserve-unknown-fields' => true }
58
+ },
59
+ 'x-kubernetes-preserve-unknown-fields' => true
60
+ },
61
+ api_version: api_version
62
+ )
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
68
+
69
+ test do
70
+ describe "CustomResourceDefinition" do
71
+ it "initializes without error" do
72
+ Kube::Cluster::Standard::CustomResourceDefinition
73
+ .new(
74
+ kind: "Widget",
75
+ group: "example.com",
76
+ )
77
+ .to_yaml
78
+ .is_a?(String)
79
+ .should == true
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'kube/cluster'
4
+ require 'kube/cluster/standard/env_processing'
5
+ require 'kube/cluster/standard/volume_processing'
6
+
7
+ module Kube
8
+ module Cluster
9
+ module Standard
10
+ class DaemonSet < Kube::Cluster['DaemonSet']
11
+ def initialize(
12
+ name:,
13
+ image:,
14
+ env: {},
15
+ volume_mounts: {},
16
+ command: nil,
17
+ service_account: nil,
18
+ node_selector: nil,
19
+ tolerations: nil,
20
+ host_pid: false,
21
+ &block
22
+ )
23
+ processed_env = EnvProcessing.process(env)
24
+ processed_volumes = VolumeProcessing.process(volume_mounts)
25
+
26
+ super() {
27
+ metadata.name = name
28
+ metadata.labels = { 'app' => name }
29
+
30
+ spec.selector.matchLabels = { 'app' => name }
31
+
32
+ spec.template.metadata.labels = { 'app' => name }
33
+ spec.template.spec.serviceAccountName = service_account || name
34
+
35
+ container = {
36
+ name: name,
37
+ image: image,
38
+ env: processed_env
39
+ }
40
+ container[:command] = command if command
41
+ container[:volumeMounts] = processed_volumes[:volume_mounts] unless processed_volumes[:volume_mounts].empty?
42
+
43
+ spec.template.spec.containers = [container]
44
+ spec.template.spec.volumes = processed_volumes[:volumes] unless processed_volumes[:volumes].empty?
45
+ spec.template.spec.hostPID = true if host_pid
46
+ spec.template.spec.nodeSelector = node_selector if node_selector
47
+ spec.template.spec.tolerations = tolerations if tolerations
48
+
49
+ instance_exec(&block) if block
50
+ }
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ test do
58
+ describe "DaemonSet" do
59
+ it "initializes without error" do
60
+ Kube::Cluster::Standard::DaemonSet
61
+ .new(
62
+ name: "my-daemon",
63
+ image: "busybox",
64
+ )
65
+ .to_yaml
66
+ .is_a?(String)
67
+ .should == true
68
+ end
69
+ end
70
+ 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
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kube
4
4
  module Cluster
5
- VERSION = "0.7.0"
5
+ VERSION = "0.9.0"
6
6
  end
7
7
  end
data/lib/kube/cluster.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "bundler/setup"
4
4
  require "scampi"
5
5
  require "kube/schema"
6
+ require "active_support/core_ext/string/inflections"
6
7
 
7
8
  require_relative "../kube/errors"
8
9
  require_relative "cluster/version"