metatron 0.1.1 → 0.1.3

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 951c4e0952e04293a1d0fa3b8a611e7ec3a25e7412f5911469f36dde04a0fb0c
4
- data.tar.gz: 39dc42274c086fb1222665f5f193196895ca3d9b40cf4a1a68eef306ddd474c2
3
+ metadata.gz: 7dbb4ce75ed3919fbbb31853a4e7d72a3a93ce85262daa8eb060d762223a8599
4
+ data.tar.gz: c94db73669dbe3e00d68a6d883e9c62072a3007545ce8a69e2df26743a4633a8
5
5
  SHA512:
6
- metadata.gz: 48939b1f746236e6479057104453c22e3424b4d26e1d45a52d3ced12e776e7474d541981800e3aff954e66dc92671873366923db1f35f51d6aeb4926d97366fd
7
- data.tar.gz: 540084e9e5edb46935a29d6cad77ad9ba7383b5c683768024977c1946feb234cfd0ef2ca4d262d989949ed52b1cbd81082918f13b2b99ebe846a5a3c07889012
6
+ metadata.gz: d8dfc8c8a4863f9bca41f098e546fbd342dfe008c34f92362fcda293d920c9cc686f63532b969a37a364ada8f4bde86f346efd5c37c1223be0c2f305e58f7c99
7
+ data.tar.gz: d63951090a4e9729e7ba30249d55b0743a670d4cc6103e3177cc5085f970af010ac72bab9f30a7af29fe668de1c57529ed0225d500b995314693352735ca95e5
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- metatron (0.1.1)
4
+ metatron (0.1.3)
5
5
  json (~> 2.6)
6
6
  puma (~> 5.6)
7
7
  sinatra (~> 2.2)
@@ -12,74 +12,77 @@ GEM
12
12
  specs:
13
13
  ast (2.4.2)
14
14
  backport (1.2.0)
15
- benchmark (0.2.0)
15
+ benchmark (0.2.1)
16
16
  byebug (11.1.3)
17
17
  diff-lcs (1.5.0)
18
18
  docile (1.4.0)
19
19
  e2mmap (0.1.0)
20
20
  jaro_winkler (1.5.4)
21
- json (2.6.2)
21
+ json (2.6.3)
22
22
  kramdown (2.4.0)
23
23
  rexml
24
24
  kramdown-parser-gfm (1.1.0)
25
25
  kramdown (~> 2.0)
26
26
  multi_json (1.15.0)
27
- mustermann (1.1.2)
27
+ mustermann (2.0.2)
28
28
  ruby2_keywords (~> 0.0.1)
29
29
  nio4r (2.5.8)
30
- nokogiri (1.13.8-arm64-darwin)
30
+ nokogiri (1.14.2-arm64-darwin)
31
31
  racc (~> 1.4)
32
- nokogiri (1.13.8-x86_64-linux)
32
+ nokogiri (1.14.2-x86_64-linux)
33
33
  racc (~> 1.4)
34
34
  parallel (1.22.1)
35
- parser (3.1.2.1)
35
+ parser (3.2.1.0)
36
36
  ast (~> 2.4.1)
37
37
  puma (5.6.5)
38
38
  nio4r (~> 2.0)
39
- racc (1.6.0)
40
- rack (2.2.4)
41
- rack-protection (2.2.1)
39
+ racc (1.6.2)
40
+ rack (2.2.6.2)
41
+ rack-protection (2.2.4)
42
42
  rack
43
43
  rack-test (2.0.2)
44
44
  rack (>= 1.3)
45
45
  rainbow (3.1.1)
46
46
  rake (12.3.3)
47
- regexp_parser (2.5.0)
47
+ regexp_parser (2.7.0)
48
48
  reverse_markdown (2.1.1)
49
49
  nokogiri
50
50
  rexml (3.2.5)
51
- rspec (3.11.0)
52
- rspec-core (~> 3.11.0)
53
- rspec-expectations (~> 3.11.0)
54
- rspec-mocks (~> 3.11.0)
55
- rspec-core (3.11.0)
56
- rspec-support (~> 3.11.0)
57
- rspec-expectations (3.11.0)
51
+ rspec (3.12.0)
52
+ rspec-core (~> 3.12.0)
53
+ rspec-expectations (~> 3.12.0)
54
+ rspec-mocks (~> 3.12.0)
55
+ rspec-core (3.12.1)
56
+ rspec-support (~> 3.12.0)
57
+ rspec-expectations (3.12.2)
58
58
  diff-lcs (>= 1.2.0, < 2.0)
59
- rspec-support (~> 3.11.0)
60
- rspec-mocks (3.11.1)
59
+ rspec-support (~> 3.12.0)
60
+ rspec-mocks (3.12.3)
61
61
  diff-lcs (>= 1.2.0, < 2.0)
62
- rspec-support (~> 3.11.0)
63
- rspec-support (3.11.0)
64
- rubocop (1.36.0)
62
+ rspec-support (~> 3.12.0)
63
+ rspec-support (3.12.0)
64
+ rubocop (1.45.1)
65
65
  json (~> 2.3)
66
66
  parallel (~> 1.10)
67
- parser (>= 3.1.2.1)
67
+ parser (>= 3.2.0.0)
68
68
  rainbow (>= 2.2.2, < 4.0)
69
69
  regexp_parser (>= 1.8, < 3.0)
70
70
  rexml (>= 3.2.5, < 4.0)
71
- rubocop-ast (>= 1.20.1, < 2.0)
71
+ rubocop-ast (>= 1.24.1, < 2.0)
72
72
  ruby-progressbar (~> 1.7)
73
- unicode-display_width (>= 1.4.0, < 3.0)
74
- rubocop-ast (1.21.0)
75
- parser (>= 3.1.1.0)
73
+ unicode-display_width (>= 2.4.0, < 3.0)
74
+ rubocop-ast (1.26.0)
75
+ parser (>= 3.2.1.0)
76
+ rubocop-capybara (2.17.0)
77
+ rubocop (~> 1.41)
76
78
  rubocop-rake (0.6.0)
77
79
  rubocop (~> 1.0)
78
- rubocop-rspec (2.12.1)
79
- rubocop (~> 1.31)
80
+ rubocop-rspec (2.18.1)
81
+ rubocop (~> 1.33)
82
+ rubocop-capybara (~> 2.17)
80
83
  ruby-progressbar (1.11.0)
81
84
  ruby2_keywords (0.0.5)
82
- simplecov (0.21.2)
85
+ simplecov (0.22.0)
83
86
  docile (~> 1.1)
84
87
  simplecov-html (~> 0.11)
85
88
  simplecov_json_formatter (~> 0.1)
@@ -88,18 +91,18 @@ GEM
88
91
  simplecov (~> 0.19)
89
92
  simplecov-html (0.12.3)
90
93
  simplecov_json_formatter (0.1.4)
91
- sinatra (2.2.1)
92
- mustermann (~> 1.0)
94
+ sinatra (2.2.4)
95
+ mustermann (~> 2.0)
93
96
  rack (~> 2.2)
94
- rack-protection (= 2.2.1)
97
+ rack-protection (= 2.2.4)
95
98
  tilt (~> 2.0)
96
- sinatra-contrib (2.2.1)
99
+ sinatra-contrib (2.2.4)
97
100
  multi_json
98
- mustermann (~> 1.0)
99
- rack-protection (= 2.2.1)
100
- sinatra (= 2.2.1)
101
+ mustermann (~> 2.0)
102
+ rack-protection (= 2.2.4)
103
+ sinatra (= 2.2.4)
101
104
  tilt (~> 2.0)
102
- solargraph (0.46.0)
105
+ solargraph (0.48.0)
103
106
  backport (~> 1.2)
104
107
  benchmark
105
108
  bundler (>= 1.17.2)
@@ -116,7 +119,7 @@ GEM
116
119
  yard (~> 0.9, >= 0.9.24)
117
120
  thor (1.2.1)
118
121
  tilt (2.0.11)
119
- unicode-display_width (2.2.0)
122
+ unicode-display_width (2.4.2)
120
123
  webrick (1.7.0)
121
124
  yard (0.9.28)
122
125
  webrick (~> 1.7.0)
data/README.md CHANGED
@@ -1,11 +1,355 @@
1
1
  # Metatron
2
2
 
3
- Metatron in a Ruby library for creating [Metacontroller](https://metacontroller.github.io/metacontroller/)-based custom Kubernetes controllers.
3
+ Metatron is a Ruby library for creating [Metacontroller](https://metacontroller.github.io/metacontroller/)-based custom Kubernetes controllers.
4
4
 
5
5
  The intention is to make it as easy as possible to use Ruby to manage [custom resources](https://kubernetes.io/docs/concepts/api-extension/custom-resources/) within your Kubernetes infrastructure. No Golang required to listen for and respond to resources based on your own [CustomResourceDefinition](https://kubernetes.io/docs/tasks/access-kubernetes-api/extend-api-custom-resource-definitions/) or to modify existing kubernetes resources via a [DecoratorController](https://metacontroller.github.io/metacontroller/api/decoratorcontroller.html).
6
6
 
7
- Your Ruby code doesn't have to have any _real_ knowledge of the Kubernetes environment in which it operates; Metacontroller takes care of all the Kubernetes interactions and Metatron handles providing the JSON interface. Just write a `sync` method can receive and respond with the appropriate Hashes and you're on your way!
7
+ Your Ruby code doesn't have to have any _real_ knowledge of the Kubernetes environment in which it operates; Metacontroller takes care of all the Kubernetes interactions and Metatron handles providing the JSON interface. Just write a `sync` method that can receive and respond with the appropriate Hashes and you're on your way!
8
8
 
9
9
  ## Usage
10
10
 
11
- TODO (still a very early draft)
11
+ For a complete walk-through, check out my [blog mini-series](https://therubyist.org/2022/10/25/kubernetes-controllers-via-metatron-part-1/) about Metatron!
12
+ ### Getting Started
13
+
14
+ To use Metatron, first decide what type of Metacontroller you'd like to create, mostly based on the type(s) of resource(s) you'll manage. Most of the time, what you want is a Custom Resource that has child resources, which means you'll want a [Composite Controller](https://metacontroller.github.io/metacontroller/api/compositecontroller.html).
15
+
16
+ Reading the [Metacontroller user's guide](https://metacontroller.github.io/metacontroller/guide.html) will be pretty helpful but isn't strictly required.
17
+
18
+ You'll need to [install Metacontroller](https://metacontroller.github.io/metacontroller/guide/install.html) into your cluster before proceeding. This guide doesn't provide a recommendation on how to do that, but it isn't very difficult.
19
+
20
+ ### Creating a Composite Controller
21
+
22
+ As an example, let's suppose we want to simplify launching blogs for users. Each `Blog` resource should have its own application server (as a `Deployment`), a database (as a `StatefulSet`), a Kubernetes `Service`, and an `Ingress`. A `Blog` will probably have a `name`, an `hostname` (which we'll derive based on its name), and a `username` and `password` (as a `Secret`) to restrict who can author content.
23
+
24
+ This means we'll want a `Blog` custom resource and it'll need a few basic properties, like those listed above. It'll also need to specify a container image, and a number of replicas (so we can scale it up and down).
25
+
26
+ Here's how that CRD (let's call it `blog-crd.yaml`) might look:
27
+
28
+ ```yaml
29
+ apiVersion: apiextensions.k8s.io/v1
30
+ kind: CustomResourceDefinition
31
+ metadata:
32
+ name: blogs.therubyist.org
33
+ spec:
34
+ group: therubyist.org
35
+ names:
36
+ kind: Blog
37
+ plural: blogs
38
+ singular: blog
39
+ scope: Namespaced
40
+ versions:
41
+ - name: v1
42
+ served: true
43
+ storage: true
44
+ subresources:
45
+ status: {}
46
+ schema:
47
+ openAPIV3Schema:
48
+ type: object
49
+ properties:
50
+ spec:
51
+ type: object
52
+ properties:
53
+ image:
54
+ type: string
55
+ replicas:
56
+ type: integer
57
+ minimum: 1
58
+ storage:
59
+ type: object
60
+ properties:
61
+ app:
62
+ type: string
63
+ db:
64
+ type: string
65
+ ```
66
+
67
+ This means we'll be able to query our Kubernetes cluster for `blogs`. You might eventually want to expand the field list, or simplify it and defer to your controller for validating it. You'll also probably want to make `metadata.name` and `spec.group` use a real domain to avoid potential conflicts. This should be safe to `kubectl apply` as the CRD doesn't do much on its own.
68
+
69
+ Now, you'll need to define a `CompositeController` resource (let's call this `blog-controller.yaml`) that instructs Metacontroller where to send sync requests:
70
+
71
+ ```yaml
72
+ apiVersion: metacontroller.k8s.io/v1alpha1
73
+ kind: CompositeController
74
+ metadata:
75
+ name: blog-controller
76
+ spec:
77
+ generateSelector: true
78
+ parentResource:
79
+ apiVersion: therubyist.org/v1
80
+ resource: blogs
81
+ childResources:
82
+ - apiVersion: apps/v1
83
+ resource: deployments
84
+ updateStrategy:
85
+ method: InPlace
86
+ - apiVersion: apps/v1
87
+ resource: statefulsets
88
+ updateStrategy:
89
+ method: InPlace
90
+ - apiVersion: v1
91
+ resource: services
92
+ updateStrategy:
93
+ method: InPlace
94
+ - apiVersion: networking.k8s.io/v1
95
+ resource: ingresses
96
+ updateStrategy:
97
+ method: InPlace
98
+ - apiVersion: v1
99
+ resource: secrets
100
+ updateStrategy:
101
+ method: InPlace
102
+ hooks:
103
+ sync:
104
+ webhook:
105
+ service:
106
+ name: blog-controller
107
+ namespace: blog-controller
108
+ port: 9292
109
+ protocol: http
110
+ path: /sync
111
+ ```
112
+
113
+ Before applying the above though, we'll need to actually create a service that can response to sync requests. That's where Metatron comes in!
114
+
115
+ ### Creating a Sync Controller with Metatron
116
+
117
+ As Metatron is a tool for creating Ruby projects, you'll need a few prerequistes. First, make a directory (and git repo) for your controller:
118
+
119
+ ```sh
120
+ $ git init blog_controller && cd blog_controller
121
+ ```
122
+
123
+ We'll need a `Gemfile` to ensure we have Metatron installed:
124
+
125
+ ```ruby
126
+ # frozen_string_literal: true
127
+
128
+ source "https://rubygems.org"
129
+
130
+ gem "metatron"
131
+ ```
132
+
133
+ We'll also need a `config.ru` file to instruct [`rack`](https://github.com/rack/rack) how to route requests:
134
+
135
+ ```ruby
136
+ # frozen_string_literal: true
137
+
138
+ # \ -s puma
139
+
140
+ require "metatron"
141
+ require_relative "./lib/blog_controller/sync"
142
+
143
+ use Rack::ShowExceptions
144
+ use Rack::Deflater
145
+
146
+ mappings = {
147
+ # This one is built-in to Metatron and is useful for monitoring
148
+ "/ping" => Metatron::Controllers::Ping.new,
149
+ # We'll need to make this one
150
+ "/sync" => BlogController::Sync.new
151
+ }
152
+
153
+ run Rack::URLMap.new(mappings)
154
+ ```
155
+
156
+ Finally, before we start hacking on some actual Metatron-related code, we'll need a `Dockerfile` to create an image that we can deploy to Kubernetes:
157
+
158
+ ```dockerfile
159
+ FROM ruby:3.1
160
+
161
+ RUN mkdir -p /app
162
+
163
+ COPY config.ru /app/
164
+ COPY Gemfile /app/
165
+ COPY Gemfile.lock /app/
166
+ COPY lib/ /app/lib/
167
+
168
+ RUN apt update && apt upgrade -y
169
+ RUN useradd appuser -d /app -M -c "App User"
170
+ RUN chown appuser /app/Gemfile.lock
171
+
172
+ USER appuser
173
+ WORKDIR /app
174
+ RUN bundle install
175
+
176
+ ENTRYPOINT ["bundle", "exec"]
177
+ CMD ["puma"]
178
+ ```
179
+
180
+ *Phew*, ok, with all that out of the way, we can get started with our development. We'll need to create a `Metatron::SyncController` subclass with a `sync` method. We'll put this in `lib/blog_controller/sync.rb`:
181
+
182
+ ```ruby
183
+ # frozen_string_literal: true
184
+
185
+ module BlogController
186
+ class Sync < Metatron::SyncController
187
+ # This method needs to return a Hash which will be converted to JSON
188
+ # It should have the keys "status" (a Hash) and "children" (an Array)
189
+ def sync
190
+ # request_body is a convenient way to access the data provided by MetaController
191
+ parent = request_body["parent"]
192
+ existing_children = request_body["children"]
193
+ desired_children = []
194
+
195
+ # first, let's create the DB and its service
196
+ desired_children += construct_db_resources(parent, existing_children)
197
+
198
+ # now let's make the app and its parts
199
+ db_secret = desired_children.find { |r| r.kind == "Secret" && r.name.end_with?("db") }
200
+ desired_children += construct_app_resources(parent, db_secret)
201
+
202
+ # We might eventually want a mechanism to build status based on the world:
203
+ # status = compare_children(request_body["children"], desired_children)
204
+ status = {}
205
+
206
+ { status:, children: desired_children.map(&:render) }
207
+ end
208
+
209
+ def construct_app_resources(parent, db_secret)
210
+ resources = []
211
+ app_db_secret = construct_app_secret(parent["metadata"], db_secret)
212
+ resources << app_db_secret
213
+ app_deployment = construct_app_deployment(
214
+ parent["metadata"], parent["spec"], app_db_secret
215
+ )
216
+ resources << app_deployment
217
+ app_service = construct_service(parent["metadata"], app_deployment)
218
+ resources << app_service
219
+ resources << construct_ingress(parent["metadata"], app_service)
220
+ resources
221
+ end
222
+
223
+ def construct_db_resources(parent, existing_children)
224
+ resources = []
225
+ db_secret = construct_db_secret(parent["metadata"], existing_children["Secret.v1"])
226
+ resources << db_secret
227
+ db_stateful_set = construct_db_stateful_set(db_secret)
228
+ resources << db_stateful_set
229
+ db_service = construct_service(
230
+ parent["metadata"], db_stateful_set, name: "db", port: 3306
231
+ )
232
+ resources << db_service
233
+ resources
234
+ end
235
+
236
+ def construct_db_stateful_set(secret)
237
+ stateful_set = Metatron::Templates::StatefulSet.new("db")
238
+ stateful_set.image = "mysql:8.0"
239
+ stateful_set.additional_pod_labels = { "app.kubernetes.io/component": "db" }
240
+ stateful_set.envfrom << secret.name
241
+ stateful_set
242
+ end
243
+
244
+ def construct_app_deployment(meta, spec, auth_secret)
245
+ deployment = Metatron::Templates::Deployment.new(meta["name"], replicas: spec["replicas"])
246
+ deployment.image = spec["image"]
247
+ deployment.additional_pod_labels = { "app.kubernetes.io/component": "app" }
248
+ deployment.envfrom << auth_secret.name
249
+ deployment.ports << { name: "web", containerPort: 3000 }
250
+ deployment
251
+ end
252
+
253
+ def construct_ingress(meta, service)
254
+ ingress = Metatron::Templates::Ingress.new(meta["name"])
255
+ ingress.add_rule(
256
+ "#{meta["name"]}.blogs.therubyist.org": { service.name => service.ports.first[:name] }
257
+ )
258
+ ingress.add_tls("#{meta["name"]}.blogs.therubyist.org")
259
+ ingress
260
+ end
261
+
262
+ def construct_service(meta, resource, name: meta["name"], port: "3000")
263
+ service = Metatron::Templates::Service.new(name, port)
264
+ service.additional_selector_labels = resource.additional_pod_labels
265
+ service
266
+ end
267
+
268
+ def construct_app_secret(meta, db_secret)
269
+ # We'll want to use the password we specified for the DB user
270
+ user_pass = db_secret.data["MYSQL_PASSWORD"]
271
+ Metatron::Templates::Secret.new(
272
+ "#{meta["name"]}app",
273
+ {
274
+ "DATABASE_URL" => "mysql2://#{meta["name"]}:#{user_pass}@db:3306/#{meta["name"]}"
275
+ }
276
+ )
277
+ end
278
+
279
+ def construct_db_secret(meta, existing_secrets)
280
+ name = "#{meta["name"]}db"
281
+ existing = (existing_secrets || {})[name]
282
+ data = if existing
283
+ {
284
+ "MYSQL_ROOT_PASSWORD" => Base64.decode64(existing.dig("data", "MYSQL_ROOT_PASSWORD")),
285
+ "MYSQL_DATABASE" => Base64.decode64(existing.dig("data", "MYSQL_DATABASE")),
286
+ "MYSQL_USER" => Base64.decode64(existing.dig("data", "MYSQL_USER")),
287
+ "MYSQL_PASSWORD" => Base64.decode64(existing.dig("data", "MYSQL_PASSWORD"))
288
+ }
289
+ else
290
+ {
291
+ "MYSQL_ROOT_PASSWORD" => SecureRandom.urlsafe_base64(12),
292
+ "MYSQL_DATABASE" => meta["name"],
293
+ "MYSQL_USER" => meta["name"],
294
+ "MYSQL_PASSWORD" => SecureRandom.urlsafe_base64(8)
295
+ }
296
+ end
297
+ Metatron::Templates::Secret.new(name, data)
298
+ end
299
+ end
300
+ end
301
+ ```
302
+
303
+ That might seem like a lot of code, but it does a **lot** of heavy lifting for you in creating Kubernetes resources. Try creating all the above Kubernetes resources by hand and you'll see what Metatron is doing for you. It is pretty likely you'll want to adjust a lot of the above code, but it should be a decent starting point.
304
+
305
+ To use it, you'll need to create your `Gemfile.lock` file then work on your Docker image:
306
+
307
+ ```sh
308
+ $ bundle install
309
+ $ docker build -t "blogcontroller:latest" .
310
+ ```
311
+
312
+ You can test your controller locally by running the image:
313
+
314
+ ```sh
315
+ $ docker run -it --rm -p 9292:9292 "blogcontroller:latest"
316
+ ```
317
+
318
+ Try POSTing a request via `curl` and inspecting the JSON response to see what your controller is doing for you:
319
+
320
+ ```sh
321
+ $ curl \
322
+ -H "Content-Type: application/json" \
323
+ --data '{"parent": {"metadata": {"name": "foo"}, "spec": {"replicas": 1, "image": "nginx:latest"}}}' \
324
+ http://localhost:9292/sync
325
+ ```
326
+
327
+ Once we've confirmed this works, we'll need to publish our image somewhere and run it. Make sure you update the Service details in `blog-controller.yaml` to reflect its actual location.
328
+
329
+ ### Using the New Composite Controller
330
+
331
+ After your Metatron controller is up and running in your Kubernetes cluster, you'll need to actually `kubectl apply` your `blog-controller.yaml` file we created way above. Once that is deployed, you can create new `Blog` resources that look something like this (let's call it `test-blog.yaml`):
332
+
333
+ ```yaml
334
+ apiVersion: therubyist.org/v1
335
+ kind: Blog
336
+ metadata:
337
+ name: test
338
+ spec:
339
+ image: myapp:tag
340
+ replicas: 2
341
+ storage:
342
+ app: 15Gi
343
+ db: 5Gi
344
+ ```
345
+
346
+ Note that `myapp:tag` should point to some image that is ready to run a blog. This is just an example and, much like the other resources we've created in this guide, it will almost certainly not work as-is. The `DATABASE_URL` secret we create in our Metatron controller should work well for a [Ruby on Rails](https://rubyonrails.org/) app though.
347
+
348
+ Let's make a new namespace for this blog and launch it:
349
+
350
+ ```sh
351
+ $ kubectl create namespace blog-test
352
+ $ kubectl -n blog-test apply -f test-blog.yaml
353
+ ```
354
+
355
+ You should be able to inspect the pods, services, etc. in the `blog-test` namespace and see your resources running!
@@ -11,7 +11,7 @@ module Metatron
11
11
  attr_accessor :image, :image_pull_policy, :additional_labels, :env, :envfrom,
12
12
  :resource_limits, :resource_requests, :probes, :ports, :security_context,
13
13
  :volume_mounts, :volumes, :additional_containers,
14
- :container_security_context, :affinity
14
+ :container_security_context, :affinity, :termination_grace_period_seconds
15
15
 
16
16
  initializer :pod_producer_initialize
17
17
 
@@ -19,6 +19,7 @@ module Metatron
19
19
  alias_method :volumeMounts, :volume_mounts
20
20
  alias_method :securityContext, :security_context
21
21
  alias_method :environment, :env
22
+ alias_method :terminationGracePeriodSeconds, :termination_grace_period_seconds
22
23
  end
23
24
  end
24
25
 
@@ -38,6 +39,7 @@ module Metatron
38
39
  @container_security_context = {}
39
40
  @additional_containers = []
40
41
  @additional_labels = {}
42
+ @termination_grace_period_seconds = 60
41
43
  end
42
44
 
43
45
  def formatted_affinity
@@ -44,6 +44,7 @@ module Metatron
44
44
  labels: { "#{label_namespace}/name": name }.merge(additional_pod_labels)
45
45
  }.merge(formatted_pod_annotations),
46
46
  spec: {
47
+ terminationGracePeriodSeconds:,
47
48
  containers: [
48
49
  {
49
50
  name: "app",
@@ -12,6 +12,7 @@ module Metatron
12
12
  @kind = "Pod"
13
13
  end
14
14
 
15
+ # rubocop:disable Metrics/MethodLength
15
16
  # rubocop:disable Metrics/AbcSize
16
17
  def render
17
18
  {
@@ -22,6 +23,7 @@ module Metatron
22
23
  name:
23
24
  }.merge(formatted_annotations),
24
25
  spec: {
26
+ terminationGracePeriodSeconds:,
25
27
  containers: [
26
28
  {
27
29
  name: "app",
@@ -41,6 +43,7 @@ module Metatron
41
43
  }
42
44
  end
43
45
  # rubocop:enable Metrics/AbcSize
46
+ # rubocop:enable Metrics/MethodLength
44
47
  end
45
48
  end
46
49
  end
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Metatron
4
+ module Templates
5
+ # The ReplicaSet Kubernetes resource
6
+ class ReplicaSet < Template
7
+ include Concerns::Annotated
8
+ include Concerns::PodProducer
9
+
10
+ attr_accessor :replicas, :pod_annotations,
11
+ :additional_labels, :additional_pod_labels
12
+
13
+ def initialize(name, replicas: 2)
14
+ super(name)
15
+ @api_version = "apps/v1"
16
+ @kind = "ReplicaSet"
17
+ @replicas = replicas
18
+ @pod_annotations = {}
19
+ @additional_pod_labels = {}
20
+ end
21
+
22
+ def formatted_pod_annotations
23
+ pod_annotations && !pod_annotations.empty? ? { annotations: pod_annotations } : {}
24
+ end
25
+
26
+ # rubocop:disable Metrics/MethodLength
27
+ # rubocop:disable Metrics/AbcSize
28
+ def render
29
+ {
30
+ apiVersion:,
31
+ kind:,
32
+ metadata: {
33
+ name:,
34
+ labels: { "#{label_namespace}/name": name }.merge(additional_labels)
35
+ }.merge(formatted_annotations),
36
+ spec: {
37
+ replicas:,
38
+ selector: {
39
+ matchLabels: { "#{label_namespace}/name": name }.merge(additional_pod_labels)
40
+ },
41
+ template: {
42
+ metadata: {
43
+ labels: { "#{label_namespace}/name": name }.merge(additional_pod_labels)
44
+ }.merge(formatted_pod_annotations),
45
+ spec: {
46
+ terminationGracePeriodSeconds:,
47
+ containers: [
48
+ {
49
+ name: "app",
50
+ image:,
51
+ imagePullPolicy:,
52
+ stdin: true,
53
+ tty: true,
54
+ resources: { limits: resource_limits, requests: resource_requests }
55
+ }.merge(probes)
56
+ .merge(formatted_environment)
57
+ .merge(formatted_envfrom)
58
+ .merge(formatted_ports)
59
+ .merge(formatted_volume_mounts)
60
+ .merge(formatted_container_security_context)
61
+ ] + additional_containers
62
+ }.merge(formatted_volumes).merge(formatted_security_context)
63
+ }
64
+ }
65
+ }
66
+ end
67
+ # rubocop:enable Metrics/AbcSize
68
+ # rubocop:enable Metrics/MethodLength
69
+ end
70
+ end
71
+ end
@@ -9,9 +9,9 @@ module Metatron
9
9
 
10
10
  attr_accessor :replicas, :pod_annotations, :service_name,
11
11
  :pod_management_policy, :enable_service_links,
12
- :termination_grace_period_seconds, :additional_pod_labels
12
+ :additional_pod_labels
13
13
 
14
- def initialize(name, replicas: 2)
14
+ def initialize(name, replicas: 1)
15
15
  super(name)
16
16
  @replicas = replicas
17
17
  @api_version = "apps/v1"
@@ -21,12 +21,10 @@ module Metatron
21
21
  @additional_pod_labels = {}
22
22
  @enable_service_links = true
23
23
  @service_name = name
24
- @termination_grace_period_seconds = 60
25
24
  end
26
25
 
27
26
  alias enableServiceLinks enable_service_links
28
27
  alias podManagementPolicy pod_management_policy
29
- alias terminationGracePeriodSeconds termination_grace_period_seconds
30
28
  alias serviceName service_name
31
29
 
32
30
  def formatted_pod_annotations
@@ -4,6 +4,6 @@ module Metatron
4
4
  VERSION = [
5
5
  0, # major
6
6
  1, # minor
7
- 1 # patch
7
+ 3 # patch
8
8
  ].join(".")
9
9
  end
data/lib/metatron.rb CHANGED
@@ -30,6 +30,7 @@ require "metatron/templates/concerns/pod_producer"
30
30
  require "metatron/templates/pod"
31
31
  require "metatron/templates/deployment"
32
32
  require "metatron/templates/ingress"
33
+ require "metatron/templates/replica_set"
33
34
  require "metatron/templates/secret"
34
35
  require "metatron/templates/service"
35
36
  require "metatron/templates/stateful_set"
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: metatron
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jonathan Gnagy
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-10-18 00:00:00.000000000 Z
11
+ date: 2023-02-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: json
@@ -261,6 +261,7 @@ files:
261
261
  - lib/metatron/templates/deployment.rb
262
262
  - lib/metatron/templates/ingress.rb
263
263
  - lib/metatron/templates/pod.rb
264
+ - lib/metatron/templates/replica_set.rb
264
265
  - lib/metatron/templates/secret.rb
265
266
  - lib/metatron/templates/service.rb
266
267
  - lib/metatron/templates/stateful_set.rb