kube_cluster 0.3.6 → 0.3.8

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.
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if __FILE__ == $0
4
+ require "bundler/setup"
5
+ require "kube/cluster"
6
+ end
7
+
3
8
  module Kube
4
9
  module Cluster
5
10
  class Middleware
@@ -62,3 +67,186 @@ module Kube
62
67
  end
63
68
  end
64
69
  end
70
+
71
+ if __FILE__ == $0
72
+ require "minitest/autorun"
73
+
74
+ class ResourcePresetMiddlewareTest < Minitest::Test
75
+ Middleware = Kube::Cluster::Middleware
76
+
77
+ def test_injects_small_preset_into_deployment
78
+ m = manifest(Kube::Cluster["Deployment"].new {
79
+ metadata.name = "web"
80
+ metadata.labels = { "app.kubernetes.io/size": "small" }
81
+ spec.selector.matchLabels = { app: "web" }
82
+ spec.template.metadata.labels = { app: "web" }
83
+ spec.template.spec.containers = [
84
+ { name: "web", image: "nginx:latest" },
85
+ ]
86
+ })
87
+
88
+ Middleware::ResourcePreset.new.call(m)
89
+ container = m.resources.first.to_h.dig(:spec, :template, :spec, :containers, 0)
90
+
91
+ assert_equal "500m", container.dig(:resources, :requests, :cpu)
92
+ assert_equal "512Mi", container.dig(:resources, :requests, :memory)
93
+ assert_equal "750m", container.dig(:resources, :limits, :cpu)
94
+ assert_equal "768Mi", container.dig(:resources, :limits, :memory)
95
+ end
96
+
97
+ def test_injects_nano_preset
98
+ m = manifest(Kube::Cluster["Deployment"].new {
99
+ metadata.name = "tiny"
100
+ metadata.labels = { "app.kubernetes.io/size": "nano" }
101
+ spec.selector.matchLabels = { app: "tiny" }
102
+ spec.template.metadata.labels = { app: "tiny" }
103
+ spec.template.spec.containers = [
104
+ { name: "app", image: "app:latest" },
105
+ ]
106
+ })
107
+
108
+ Middleware::ResourcePreset.new.call(m)
109
+ container = m.resources.first.to_h.dig(:spec, :template, :spec, :containers, 0)
110
+
111
+ assert_equal "100m", container.dig(:resources, :requests, :cpu)
112
+ assert_equal "128Mi", container.dig(:resources, :requests, :memory)
113
+ end
114
+
115
+ def test_injects_xlarge_preset
116
+ m = manifest(Kube::Cluster["Deployment"].new {
117
+ metadata.name = "big"
118
+ metadata.labels = { "app.kubernetes.io/size": "xlarge" }
119
+ spec.selector.matchLabels = { app: "big" }
120
+ spec.template.metadata.labels = { app: "big" }
121
+ spec.template.spec.containers = [
122
+ { name: "app", image: "app:latest" },
123
+ ]
124
+ })
125
+
126
+ Middleware::ResourcePreset.new.call(m)
127
+ container = m.resources.first.to_h.dig(:spec, :template, :spec, :containers, 0)
128
+
129
+ assert_equal "1", container.dig(:resources, :requests, :cpu)
130
+ assert_equal "3072Mi", container.dig(:resources, :requests, :memory)
131
+ assert_equal "3", container.dig(:resources, :limits, :cpu)
132
+ assert_equal "6144Mi", container.dig(:resources, :limits, :memory)
133
+ end
134
+
135
+ def test_applies_to_all_containers
136
+ m = manifest(Kube::Cluster["Deployment"].new {
137
+ metadata.name = "multi"
138
+ metadata.labels = { "app.kubernetes.io/size": "micro" }
139
+ spec.selector.matchLabels = { app: "multi" }
140
+ spec.template.metadata.labels = { app: "multi" }
141
+ spec.template.spec.containers = [
142
+ { name: "app", image: "app:latest" },
143
+ { name: "sidecar", image: "sidecar:latest" },
144
+ ]
145
+ })
146
+
147
+ Middleware::ResourcePreset.new.call(m)
148
+ containers = m.resources.first.to_h.dig(:spec, :template, :spec, :containers)
149
+
150
+ containers.each do |c|
151
+ assert_equal "250m", c.dig(:resources, :requests, :cpu)
152
+ assert_equal "256Mi", c.dig(:resources, :requests, :memory)
153
+ end
154
+ end
155
+
156
+ def test_skips_non_pod_bearing_resources
157
+ resource = Kube::Cluster["ConfigMap"].new {
158
+ metadata.name = "config"
159
+ metadata.labels = { "app.kubernetes.io/size": "small" }
160
+ }
161
+ m = manifest(resource)
162
+
163
+ Middleware::ResourcePreset.new.call(m)
164
+
165
+ assert_equal resource.to_h, m.resources.first.to_h
166
+ end
167
+
168
+ def test_skips_resources_without_size_label
169
+ m = manifest(Kube::Cluster["Deployment"].new {
170
+ metadata.name = "web"
171
+ spec.selector.matchLabels = { app: "web" }
172
+ spec.template.metadata.labels = { app: "web" }
173
+ spec.template.spec.containers = [
174
+ { name: "web", image: "nginx:latest" },
175
+ ]
176
+ })
177
+
178
+ Middleware::ResourcePreset.new.call(m)
179
+ container = m.resources.first.to_h.dig(:spec, :template, :spec, :containers, 0)
180
+
181
+ assert_nil container[:resources]
182
+ end
183
+
184
+ def test_raises_on_unknown_size
185
+ m = manifest(Kube::Cluster["Deployment"].new {
186
+ metadata.name = "web"
187
+ metadata.labels = { "app.kubernetes.io/size": "potato" }
188
+ spec.selector.matchLabels = { app: "web" }
189
+ spec.template.metadata.labels = { app: "web" }
190
+ spec.template.spec.containers = [
191
+ { name: "web", image: "nginx:latest" },
192
+ ]
193
+ })
194
+
195
+ error = assert_raises(ArgumentError) do
196
+ Middleware::ResourcePreset.new.call(m)
197
+ end
198
+
199
+ assert_includes error.message, "potato"
200
+ assert_includes error.message, "Valid sizes"
201
+ end
202
+
203
+ def test_applies_to_statefulset
204
+ m = manifest(Kube::Cluster["StatefulSet"].new {
205
+ metadata.name = "db"
206
+ metadata.labels = { "app.kubernetes.io/size": "medium" }
207
+ spec.selector.matchLabels = { app: "db" }
208
+ spec.template.metadata.labels = { app: "db" }
209
+ spec.template.spec.containers = [
210
+ { name: "postgres", image: "postgres:16" },
211
+ ]
212
+ })
213
+
214
+ Middleware::ResourcePreset.new.call(m)
215
+ container = m.resources.first.to_h.dig(:spec, :template, :spec, :containers, 0)
216
+
217
+ assert_equal "500m", container.dig(:resources, :requests, :cpu)
218
+ assert_equal "1024Mi", container.dig(:resources, :requests, :memory)
219
+ end
220
+
221
+ def test_preserves_existing_container_resources_via_deep_merge
222
+ m = manifest(Kube::Cluster["Deployment"].new {
223
+ metadata.name = "web"
224
+ metadata.labels = { "app.kubernetes.io/size": "small" }
225
+ spec.selector.matchLabels = { app: "web" }
226
+ spec.template.metadata.labels = { app: "web" }
227
+ spec.template.spec.containers = [
228
+ {
229
+ name: "web", image: "nginx:latest",
230
+ resources: { requests: { cpu: "999m" } },
231
+ },
232
+ ]
233
+ })
234
+
235
+ Middleware::ResourcePreset.new.call(m)
236
+ container = m.resources.first.to_h.dig(:spec, :template, :spec, :containers, 0)
237
+
238
+ # The container's explicit value wins over the preset
239
+ assert_equal "999m", container.dig(:resources, :requests, :cpu)
240
+ # The preset fills in missing values
241
+ assert_equal "512Mi", container.dig(:resources, :requests, :memory)
242
+ end
243
+
244
+ private
245
+
246
+ def manifest(*resources)
247
+ m = Kube::Cluster::Manifest.new
248
+ resources.each { |r| m << r }
249
+ m
250
+ end
251
+ end
252
+ end
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if __FILE__ == $0
4
+ require "bundler/setup"
5
+ require "kube/cluster"
6
+ end
7
+
3
8
  module Kube
4
9
  module Cluster
5
10
  class Middleware
@@ -82,3 +87,168 @@ module Kube
82
87
  end
83
88
  end
84
89
  end
90
+
91
+ if __FILE__ == $0
92
+ require "minitest/autorun"
93
+
94
+ class SecurityContextMiddlewareTest < Minitest::Test
95
+ Middleware = Kube::Cluster::Middleware
96
+
97
+ def test_applies_restricted_profile_by_default
98
+ m = manifest(Kube::Cluster["Deployment"].new {
99
+ metadata.name = "web"
100
+ spec.selector.matchLabels = { app: "web" }
101
+ spec.template.metadata.labels = { app: "web" }
102
+ spec.template.spec.containers = [
103
+ { name: "web", image: "nginx:latest" },
104
+ ]
105
+ })
106
+
107
+ Middleware::SecurityContext.new.call(m)
108
+ h = m.resources.first.to_h
109
+ pod_sc = h.dig(:spec, :template, :spec, :securityContext)
110
+ container_sc = h.dig(:spec, :template, :spec, :containers, 0, :securityContext)
111
+
112
+ assert_equal true, pod_sc[:runAsNonRoot]
113
+ assert_equal 1000, pod_sc[:runAsUser]
114
+ assert_equal 1000, pod_sc[:fsGroup]
115
+ assert_equal({ type: "RuntimeDefault" }, pod_sc[:seccompProfile])
116
+
117
+ assert_equal false, container_sc[:allowPrivilegeEscalation]
118
+ assert_equal true, container_sc[:readOnlyRootFilesystem]
119
+ assert_equal({ drop: ["ALL"] }, container_sc[:capabilities])
120
+ end
121
+
122
+ def test_applies_baseline_profile_via_label
123
+ m = manifest(Kube::Cluster["Deployment"].new {
124
+ metadata.name = "web"
125
+ metadata.labels = { "app.kubernetes.io/security": "baseline" }
126
+ spec.selector.matchLabels = { app: "web" }
127
+ spec.template.metadata.labels = { app: "web" }
128
+ spec.template.spec.containers = [
129
+ { name: "web", image: "nginx:latest" },
130
+ ]
131
+ })
132
+
133
+ Middleware::SecurityContext.new.call(m)
134
+ h = m.resources.first.to_h
135
+ pod_sc = h.dig(:spec, :template, :spec, :securityContext)
136
+ container_sc = h.dig(:spec, :template, :spec, :containers, 0, :securityContext)
137
+
138
+ assert_equal true, pod_sc[:runAsNonRoot]
139
+ assert_nil pod_sc[:seccompProfile]
140
+
141
+ assert_equal false, container_sc[:allowPrivilegeEscalation]
142
+ assert_nil container_sc[:readOnlyRootFilesystem]
143
+ end
144
+
145
+ def test_applies_baseline_profile_via_constructor_default
146
+ m = manifest(Kube::Cluster["Deployment"].new {
147
+ metadata.name = "web"
148
+ spec.selector.matchLabels = { app: "web" }
149
+ spec.template.metadata.labels = { app: "web" }
150
+ spec.template.spec.containers = [
151
+ { name: "web", image: "nginx:latest" },
152
+ ]
153
+ })
154
+
155
+ Middleware::SecurityContext.new(default: :baseline).call(m)
156
+ h = m.resources.first.to_h
157
+ pod_sc = h.dig(:spec, :template, :spec, :securityContext)
158
+
159
+ assert_nil pod_sc[:seccompProfile]
160
+ end
161
+
162
+ def test_label_overrides_constructor_default
163
+ m = manifest(Kube::Cluster["Deployment"].new {
164
+ metadata.name = "web"
165
+ metadata.labels = { "app.kubernetes.io/security": "restricted" }
166
+ spec.selector.matchLabels = { app: "web" }
167
+ spec.template.metadata.labels = { app: "web" }
168
+ spec.template.spec.containers = [
169
+ { name: "web", image: "nginx:latest" },
170
+ ]
171
+ })
172
+
173
+ Middleware::SecurityContext.new(default: :baseline).call(m)
174
+ h = m.resources.first.to_h
175
+ pod_sc = h.dig(:spec, :template, :spec, :securityContext)
176
+
177
+ assert_equal({ type: "RuntimeDefault" }, pod_sc[:seccompProfile])
178
+ end
179
+
180
+ def test_applies_to_all_containers
181
+ m = manifest(Kube::Cluster["Deployment"].new {
182
+ metadata.name = "web"
183
+ spec.selector.matchLabels = { app: "web" }
184
+ spec.template.metadata.labels = { app: "web" }
185
+ spec.template.spec.containers = [
186
+ { name: "app", image: "app:latest" },
187
+ { name: "sidecar", image: "sidecar:latest" },
188
+ ]
189
+ })
190
+
191
+ Middleware::SecurityContext.new.call(m)
192
+ containers = m.resources.first.to_h.dig(:spec, :template, :spec, :containers)
193
+
194
+ containers.each do |c|
195
+ assert_equal false, c.dig(:securityContext, :allowPrivilegeEscalation)
196
+ end
197
+ end
198
+
199
+ def test_skips_non_pod_bearing_resources
200
+ resource = Kube::Cluster["ConfigMap"].new { metadata.name = "config" }
201
+ m = manifest(resource)
202
+
203
+ Middleware::SecurityContext.new.call(m)
204
+
205
+ assert_equal resource.to_h, m.resources.first.to_h
206
+ end
207
+
208
+ def test_raises_on_unknown_profile
209
+ m = manifest(Kube::Cluster["Deployment"].new {
210
+ metadata.name = "web"
211
+ metadata.labels = { "app.kubernetes.io/security": "yolo" }
212
+ spec.selector.matchLabels = { app: "web" }
213
+ spec.template.metadata.labels = { app: "web" }
214
+ spec.template.spec.containers = [
215
+ { name: "web", image: "nginx:latest" },
216
+ ]
217
+ })
218
+
219
+ error = assert_raises(ArgumentError) do
220
+ Middleware::SecurityContext.new.call(m)
221
+ end
222
+
223
+ assert_includes error.message, "yolo"
224
+ end
225
+
226
+ def test_preserves_existing_pod_security_context
227
+ m = manifest(Kube::Cluster["Deployment"].new {
228
+ metadata.name = "web"
229
+ spec.selector.matchLabels = { app: "web" }
230
+ spec.template.metadata.labels = { app: "web" }
231
+ spec.template.spec.securityContext = { runAsUser: 9999 }
232
+ spec.template.spec.containers = [
233
+ { name: "web", image: "nginx:latest" },
234
+ ]
235
+ })
236
+
237
+ Middleware::SecurityContext.new.call(m)
238
+ pod_sc = m.resources.first.to_h.dig(:spec, :template, :spec, :securityContext)
239
+
240
+ # Existing value wins
241
+ assert_equal 9999, pod_sc[:runAsUser]
242
+ # Middleware fills in missing values
243
+ assert_equal true, pod_sc[:runAsNonRoot]
244
+ end
245
+
246
+ private
247
+
248
+ def manifest(*resources)
249
+ m = Kube::Cluster::Manifest.new
250
+ resources.each { |r| m << r }
251
+ m
252
+ end
253
+ end
254
+ end
@@ -1,5 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ if __FILE__ == $0
4
+ require "bundler/setup"
5
+ require "kube/cluster"
6
+ end
7
+
3
8
  module Kube
4
9
  module Cluster
5
10
  class Middleware
@@ -69,3 +74,165 @@ module Kube
69
74
  end
70
75
  end
71
76
  end
77
+
78
+ if __FILE__ == $0
79
+ require "minitest/autorun"
80
+
81
+ class ServiceForDeploymentMiddlewareTest < Minitest::Test
82
+ Middleware = Kube::Cluster::Middleware
83
+
84
+ def test_generates_service_from_deployment
85
+ m = manifest(Kube::Cluster["Deployment"].new {
86
+ metadata.name = "web"
87
+ metadata.namespace = "production"
88
+ metadata.labels = { app: "web" }
89
+ spec.selector.matchLabels = { app: "web" }
90
+ spec.template.metadata.labels = { app: "web" }
91
+ spec.template.spec.containers = [
92
+ { name: "web", image: "nginx", ports: [{ name: "http", containerPort: 8080, protocol: "TCP" }] },
93
+ ]
94
+ })
95
+
96
+ Middleware::ServiceForDeployment.new.call(m)
97
+
98
+ assert_equal 2, m.resources.size
99
+
100
+ deploy, service = m.resources
101
+ assert_equal "Deployment", deploy.to_h[:kind]
102
+ assert_equal "Service", service.to_h[:kind]
103
+
104
+ sh = service.to_h
105
+ assert_equal "web", sh.dig(:metadata, :name)
106
+ assert_equal "production", sh.dig(:metadata, :namespace)
107
+ assert_equal({ app: "web" }, sh.dig(:spec, :selector))
108
+
109
+ port = sh.dig(:spec, :ports, 0)
110
+ assert_equal "http", port[:name]
111
+ assert_equal 8080, port[:port]
112
+ assert_equal "http", port[:targetPort]
113
+ assert_equal "TCP", port[:protocol]
114
+ end
115
+
116
+ def test_maps_multiple_ports
117
+ m = manifest(Kube::Cluster["Deployment"].new {
118
+ metadata.name = "web"
119
+ spec.selector.matchLabels = { app: "web" }
120
+ spec.template.metadata.labels = { app: "web" }
121
+ spec.template.spec.containers = [
122
+ {
123
+ name: "web", image: "nginx",
124
+ ports: [
125
+ { name: "http", containerPort: 8080 },
126
+ { name: "metrics", containerPort: 9090 },
127
+ ],
128
+ },
129
+ ]
130
+ })
131
+
132
+ Middleware::ServiceForDeployment.new.call(m)
133
+ service = m.resources.last
134
+ ports = service.to_h.dig(:spec, :ports)
135
+
136
+ assert_equal 2, ports.size
137
+ assert_equal "http", ports[0][:name]
138
+ assert_equal "metrics", ports[1][:name]
139
+ end
140
+
141
+ def test_copies_labels_from_source
142
+ m = manifest(Kube::Cluster["Deployment"].new {
143
+ metadata.name = "web"
144
+ metadata.labels = { "app.kubernetes.io/name": "web", "app.kubernetes.io/size": "small" }
145
+ spec.selector.matchLabels = { app: "web" }
146
+ spec.template.metadata.labels = { app: "web" }
147
+ spec.template.spec.containers = [
148
+ { name: "web", image: "nginx", ports: [{ name: "http", containerPort: 8080 }] },
149
+ ]
150
+ })
151
+
152
+ Middleware::ServiceForDeployment.new.call(m)
153
+ service_labels = m.resources.last.to_h.dig(:metadata, :labels)
154
+
155
+ assert_equal "web", service_labels[:"app.kubernetes.io/name"]
156
+ assert_equal "small", service_labels[:"app.kubernetes.io/size"]
157
+ end
158
+
159
+ def test_skips_deployment_without_named_ports
160
+ m = manifest(Kube::Cluster["Deployment"].new {
161
+ metadata.name = "web"
162
+ spec.selector.matchLabels = { app: "web" }
163
+ spec.template.metadata.labels = { app: "web" }
164
+ spec.template.spec.containers = [
165
+ { name: "web", image: "nginx", ports: [{ containerPort: 8080 }] },
166
+ ]
167
+ })
168
+
169
+ Middleware::ServiceForDeployment.new.call(m)
170
+
171
+ assert_equal 1, m.resources.size
172
+ end
173
+
174
+ def test_skips_deployment_without_ports
175
+ m = manifest(Kube::Cluster["Deployment"].new {
176
+ metadata.name = "worker"
177
+ spec.selector.matchLabels = { app: "worker" }
178
+ spec.template.metadata.labels = { app: "worker" }
179
+ spec.template.spec.containers = [
180
+ { name: "worker", image: "worker:latest" },
181
+ ]
182
+ })
183
+
184
+ Middleware::ServiceForDeployment.new.call(m)
185
+
186
+ assert_equal 1, m.resources.size
187
+ end
188
+
189
+ def test_skips_non_pod_bearing_resources
190
+ m = manifest(Kube::Cluster["ConfigMap"].new { metadata.name = "config" })
191
+
192
+ Middleware::ServiceForDeployment.new.call(m)
193
+
194
+ assert_equal 1, m.resources.size
195
+ end
196
+
197
+ def test_skips_deployment_without_match_labels
198
+ m = manifest(Kube::Cluster["Deployment"].new {
199
+ metadata.name = "web"
200
+ spec.template.metadata.labels = { app: "web" }
201
+ spec.template.spec.containers = [
202
+ { name: "web", image: "nginx", ports: [{ name: "http", containerPort: 8080 }] },
203
+ ]
204
+ })
205
+
206
+ Middleware::ServiceForDeployment.new.call(m)
207
+
208
+ assert_equal 1, m.resources.size
209
+ end
210
+
211
+ def test_works_with_statefulset
212
+ m = manifest(Kube::Cluster["StatefulSet"].new {
213
+ metadata.name = "db"
214
+ metadata.namespace = "database"
215
+ spec.selector.matchLabels = { app: "db" }
216
+ spec.template.metadata.labels = { app: "db" }
217
+ spec.template.spec.containers = [
218
+ { name: "postgres", image: "postgres:16", ports: [{ name: "tcp-pg", containerPort: 5432 }] },
219
+ ]
220
+ })
221
+
222
+ Middleware::ServiceForDeployment.new.call(m)
223
+
224
+ assert_equal 2, m.resources.size
225
+ assert_equal "Service", m.resources.last.to_h[:kind]
226
+ assert_equal "db", m.resources.last.to_h.dig(:metadata, :name)
227
+ assert_equal "database", m.resources.last.to_h.dig(:metadata, :namespace)
228
+ end
229
+
230
+ private
231
+
232
+ def manifest(*resources)
233
+ m = Kube::Cluster::Manifest.new
234
+ resources.each { |r| m << r }
235
+ m
236
+ end
237
+ end
238
+ end