kube_cluster 0.16.0 → 0.17.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2db1b928c5cf97c3b248b5d035bf071d719cc2387c3ceab02ab999dcf45febe2
4
- data.tar.gz: 85fc1a4be926b518f7e3720d58308fbf8e3915f628d5058dcbfef26799918406
3
+ metadata.gz: 928086733f7475611aa69193643f88719d926b70bb4aa3ed9d8a1c8f27efdec7
4
+ data.tar.gz: 3cc456aa515c6ca51fd87f26d35b2fcc740d984f8d907385a9f4b46bfc273a1c
5
5
  SHA512:
6
- metadata.gz: f5c41a08ad784585e5fc92bdb6de5107b031e04d31d8a3d0d04e8670b44496e15244704b82a6c4c6e1027cca5fdd1c0475dc2af61df40b146b23cab611224e68
7
- data.tar.gz: 68e00e0750fc326881b61cbe3a705ae7341eab8ba37b68429387e0055bc5eb26fa2bfb38f9f59d2e53ac5d0f8220e5fe2bbb5edb507c9a750e5ece721e52276f
6
+ metadata.gz: 9fb588b0b214cad46930473827384993fb0577d9997ae1a60a2a5e808eb23a792697d95e877d31e959661322b7adf1c2c31ecaf41cc7a910e132ad728f31bab4
7
+ data.tar.gz: 240eb7a87911cb284cd6fc2526608f843dadfee100f25b7b9611e6a27531d440604ae9c7b1d8586b26b5b926292c01f98ffd97e64c51659c0bad983629322e41
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- kube_cluster (0.16.0)
4
+ kube_cluster (0.17.1)
5
5
  activesupport (~> 8.0)
6
6
  kube_kubectl (~> 2.0)
7
7
  kube_schema (~> 1.7)
@@ -0,0 +1,71 @@
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
+ # A CronJob plus the ServiceAccount/Role/RoleBinding it runs as, bundled
10
+ # into one Manifest. The RBAC rules use the Role shorthand; the CronJob's
11
+ # serviceAccountName is wired to the generated ServiceAccount. Everything
12
+ # is named after +name+.
13
+ #
14
+ # CronJobWithServiceAccount.new(
15
+ # name: "glauth-config-builder",
16
+ # image: "nixery.dev/shell/ruby/kubectl/cacert",
17
+ # schedule: "*/5 * * * *",
18
+ # rules: [
19
+ # "secrets" => %w[get list],
20
+ # "batch/jobs" => %w[create],
21
+ # ],
22
+ # command: RubyScript(<<~'BUILD'),
23
+ # ...
24
+ # BUILD
25
+ # )
26
+ #
27
+ class CronJobWithServiceAccount < Kube::Cluster::Manifest
28
+ def initialize(name:, image:, schedule:, rules:, env: {}, command: nil,
29
+ backoff_limit: 3, ttl: 300, concurrency_policy: "Forbid", &block)
30
+ service_account_with_role = Kube::Cluster::Standard::ServiceAccountWithRole.new(
31
+ service_account: Kube::Cluster::Standard::ServiceAccount.new(name: name),
32
+ role: Kube::Cluster::Standard::Role.new(rules: rules),
33
+ )
34
+
35
+ cron_job = Kube::Cluster::Standard::CronJob.new(
36
+ name: name,
37
+ image: image,
38
+ schedule: schedule,
39
+ env: env,
40
+ command: command,
41
+ backoff_limit: backoff_limit,
42
+ ttl: ttl,
43
+ concurrency_policy: concurrency_policy,
44
+ ) do
45
+ spec.jobTemplate.spec.template.spec.serviceAccountName = name
46
+ end
47
+
48
+ super(*service_account_with_role.to_a, cron_job)
49
+
50
+ instance_exec(&block) if block
51
+ end
52
+ end
53
+ end
54
+ end
55
+ end
56
+
57
+ test do
58
+ describe "CronJobWithServiceAccount" do
59
+ it "emits the RBAC trio plus the cron job" do
60
+ m = Kube::Cluster::Standard::CronJobWithServiceAccount.new(
61
+ name: "builder",
62
+ image: "nixery.dev/shell/kubectl",
63
+ schedule: "*/5 * * * *",
64
+ rules: ["secrets" => %w[get list]],
65
+ command: ["true"],
66
+ )
67
+
68
+ m.map { |r| r.to_h[:kind] }.sort.should == %w[CronJob Role RoleBinding ServiceAccount]
69
+ end
70
+ end
71
+ end
@@ -73,7 +73,7 @@ module Kube
73
73
 
74
74
  instance_exec(&block) if block
75
75
 
76
- _apply_limits(deployment)
76
+ deployment = _apply_limits(deployment)
77
77
  _apply_probes(deployment)
78
78
  end
79
79
 
@@ -88,7 +88,7 @@ module Kube
88
88
  private
89
89
 
90
90
  def _apply_limits(deployment)
91
- return if @_limits.empty?
91
+ return deployment if @_limits.empty?
92
92
 
93
93
  container = deployment.to_h[:spec][:template][:spec][:containers][0]
94
94
  resources = {}
@@ -108,12 +108,12 @@ module Kube
108
108
  container[:resources] = resources
109
109
  h = deployment.to_h
110
110
  h[:spec][:template][:spec][:containers][0] = container
111
- deployment.rebuild(h)
111
+ _replace(deployment, deployment.rebuild(h))
112
112
  end
113
113
 
114
114
  def _apply_probes(deployment)
115
- return if @_probes.empty?
116
- return unless @_probes[:url]
115
+ return deployment if @_probes.empty?
116
+ return deployment unless @_probes[:url]
117
117
 
118
118
  container = deployment.to_h[:spec][:template][:spec][:containers][0]
119
119
  url = @_probes[:url]
@@ -140,7 +140,14 @@ module Kube
140
140
 
141
141
  h = deployment.to_h
142
142
  h[:spec][:template][:spec][:containers][0] = container
143
- deployment.rebuild(h)
143
+ _replace(deployment, deployment.rebuild(h))
144
+ end
145
+
146
+ # rebuild returns a new resource; swap it into this manifest so the
147
+ # change actually lands in the rendered output.
148
+ def _replace(old, rebuilt)
149
+ @resources[@resources.index(old)] = rebuilt
150
+ rebuilt
144
151
  end
145
152
  end
146
153
  end
@@ -175,5 +182,63 @@ test do
175
182
  yaml.include?("stdin: true").should == true
176
183
  yaml.include?("tty: true").should == true
177
184
  end
185
+
186
+ it "renders limits from the block DSL" do
187
+ yaml = Kube::Cluster::Standard::DeploymentWithService
188
+ .new(
189
+ name: "limited",
190
+ image: "ruby/ruby",
191
+ port: 3000,
192
+ ) {
193
+ limits.cpu = { "500m" => Float::INFINITY }
194
+ limits.memory = { "1Gi" => "2Gi" }
195
+ }
196
+ .to_yaml
197
+
198
+ yaml.include?("cpu: 500m").should == true
199
+ yaml.include?("memory: 1Gi").should == true
200
+ yaml.include?("memory: 2Gi").should == true
201
+ # Infinity means request-only — no cpu limit is emitted.
202
+ yaml.scan(/cpu:/).length.should == 1
203
+ end
204
+
205
+ it "renders probes from the block DSL" do
206
+ yaml = Kube::Cluster::Standard::DeploymentWithService
207
+ .new(
208
+ name: "probed",
209
+ image: "ruby/ruby",
210
+ port: 3000,
211
+ ) {
212
+ probes.url = { path: "/healthz", port: "http" }
213
+ probes.liveness = { 120 => 30 }
214
+ probes.readiness = { 60 => 10 }
215
+ }
216
+ .to_yaml
217
+
218
+ yaml.include?("livenessProbe").should == true
219
+ yaml.include?("readinessProbe").should == true
220
+ yaml.include?("path: \"/healthz\"").should == true
221
+ yaml.include?("initialDelaySeconds: 120").should == true
222
+ yaml.include?("initialDelaySeconds: 60").should == true
223
+ end
224
+
225
+ it "renders limits and probes together" do
226
+ yaml = Kube::Cluster::Standard::DeploymentWithService
227
+ .new(
228
+ name: "both",
229
+ image: "ruby/ruby",
230
+ port: 3000,
231
+ ) {
232
+ limits.memory = { "1Gi" => "2Gi" }
233
+ probes.url = { path: "/healthz", port: "http" }
234
+ probes.readiness = { 5 => 5 }
235
+ }
236
+ .to_yaml
237
+
238
+ # The probe pass must not clobber the limits pass (each rebuilds the
239
+ # deployment; the second must start from the first's result).
240
+ yaml.include?("memory: 2Gi").should == true
241
+ yaml.include?("readinessProbe").should == true
242
+ end
178
243
  end
179
244
  end
@@ -0,0 +1,67 @@
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
+ # A one-shot Job plus the ServiceAccount/Role/RoleBinding it runs as,
10
+ # bundled into one Manifest. The RBAC rules use the Role shorthand; the
11
+ # Job's serviceAccountName is wired to the generated ServiceAccount.
12
+ # Everything is named after +name+.
13
+ #
14
+ # JobWithServiceAccount.new(
15
+ # name: "glauth-config-seed",
16
+ # image: "nixery.dev/shell/kubectl",
17
+ # rules: [
18
+ # "batch/cronjobs" => %w[get],
19
+ # "batch/jobs" => %w[create],
20
+ # ],
21
+ # command: BashScript(<<~'SEED'),
22
+ # ...
23
+ # SEED
24
+ # )
25
+ #
26
+ class JobWithServiceAccount < Kube::Cluster::Manifest
27
+ def initialize(name:, image:, rules:, env: {}, command: nil,
28
+ backoff_limit: 3, ttl: 300, &block)
29
+ service_account_with_role = Kube::Cluster::Standard::ServiceAccountWithRole.new(
30
+ service_account: Kube::Cluster::Standard::ServiceAccount.new(name: name),
31
+ role: Kube::Cluster::Standard::Role.new(rules: rules),
32
+ )
33
+
34
+ job = Kube::Cluster::Standard::Job.new(
35
+ name: name,
36
+ image: image,
37
+ env: env,
38
+ command: command,
39
+ backoff_limit: backoff_limit,
40
+ ttl: ttl,
41
+ ) do
42
+ spec.template.spec.serviceAccountName = name
43
+ end
44
+
45
+ super(*service_account_with_role.to_a, job)
46
+
47
+ instance_exec(&block) if block
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ test do
55
+ describe "JobWithServiceAccount" do
56
+ it "emits the RBAC trio plus the job" do
57
+ m = Kube::Cluster::Standard::JobWithServiceAccount.new(
58
+ name: "seed",
59
+ image: "nixery.dev/shell/kubectl",
60
+ rules: ["batch/jobs" => %w[create]],
61
+ command: ["true"],
62
+ )
63
+
64
+ m.map { |r| r.to_h[:kind] }.sort.should == %w[Job Role RoleBinding ServiceAccount]
65
+ end
66
+ end
67
+ end
@@ -16,12 +16,16 @@ module Kube
16
16
  # ])
17
17
  #
18
18
  class Role < Kube::Cluster["Role"]
19
+ # The original rules shorthand, so a named copy can be rebuilt from it
20
+ # (rebuild downgrades the class; reconstructing keeps it a Standard::Role).
21
+ attr_reader :rules_input
22
+
19
23
  def initialize(rules:, name: nil, &block)
20
- built = self.class.build_rules(rules)
24
+ @rules_input = rules
21
25
 
22
26
  super() do
23
27
  metadata.name = name if name
24
- self.rules = built
28
+ self.rules = Role.build_rules(rules)
25
29
  instance_exec(&block) if block
26
30
  end
27
31
  end
@@ -30,10 +34,6 @@ module Kube
30
34
  to_h.dig(:metadata, :name)
31
35
  end
32
36
 
33
- def name=(value)
34
- metadata.name = value
35
- end
36
-
37
37
  def self.build_rules(rules)
38
38
  entries = rules.is_a?(Hash) ? [rules] : Array(rules)
39
39
 
@@ -21,15 +21,18 @@ module Kube
21
21
  class ServiceAccountWithRole < Kube::Cluster::Manifest
22
22
  def initialize(service_account:, role:, name: nil, &block)
23
23
  name ||= service_account.name
24
- role.name = name
24
+
25
+ # Rebuild the Role with the derived name (a Standard::Role, so the
26
+ # binding's role.name reader works).
27
+ named_role = Kube::Cluster::Standard::Role.new(name: name, rules: role.rules_input)
25
28
 
26
29
  role_binding = Kube::Cluster::Standard::RoleBinding.new(
27
- role: role,
30
+ role: named_role,
28
31
  service_account: service_account,
29
32
  name: name,
30
33
  )
31
34
 
32
- super(service_account, role, role_binding)
35
+ super(service_account, named_role, role_binding)
33
36
 
34
37
  instance_exec(&block) if block
35
38
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Kube
4
4
  module Cluster
5
- VERSION = "0.16.0"
5
+ VERSION = "0.17.1"
6
6
  end
7
7
  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.16.0
4
+ version: 0.17.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nathan K
@@ -218,6 +218,7 @@ files:
218
218
  - lib/kube/cluster/standard/cloud_native_pg/helm.rb
219
219
  - lib/kube/cluster/standard/config_map.rb
220
220
  - lib/kube/cluster/standard/cron_job.rb
221
+ - lib/kube/cluster/standard/cron_job_with_service_account.rb
221
222
  - lib/kube/cluster/standard/custom_resource_definition.rb
222
223
  - lib/kube/cluster/standard/daemon_set.rb
223
224
  - lib/kube/cluster/standard/deployment_with_service.rb
@@ -226,6 +227,7 @@ files:
226
227
  - lib/kube/cluster/standard/forgejo/helm.rb
227
228
  - lib/kube/cluster/standard/gateway_api/http_route.rb
228
229
  - lib/kube/cluster/standard/job.rb
230
+ - lib/kube/cluster/standard/job_with_service_account.rb
229
231
  - lib/kube/cluster/standard/kube_virt/virtual_machine.rb
230
232
  - lib/kube/cluster/standard/meta_controller/composite_controller.rb
231
233
  - lib/kube/cluster/standard/meta_controller/decorator_controller.rb