kube-dsl 0.4.0 → 0.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4c028c471596448f6e06ff1aa0bf50bc3ccdd93e22559a9805d4078921571f8a
4
- data.tar.gz: b6e10a2a2f59d21af138e18db5a46b015a331e762642f7d2292daa69e9f4365f
3
+ metadata.gz: 2b49cbbb2071a035cf03ba3b5a945ac776fb3df15573875f8d5790f25335aeae
4
+ data.tar.gz: bc2c169581f445a84dd1104935b6a4b89273bd52d87e4091dd0d6fca884b2d0a
5
5
  SHA512:
6
- metadata.gz: 7bda26da0d442fbe59af67f1a8c60aad632bb91f789332aa951032d6f25be6dd72461b20c0e24e0ed995f15f7a925ead791feca3b9e99a0d226cc6cf4c3dc423
7
- data.tar.gz: 8102bb8df5fdc935f3df34c9ff9a2ae2e3d0f1c6eb3222441d4b082c9cc932a7d5d9856028435726b0d4821e94413e4af377d1869fcfba1e08153052d25f9af0
6
+ metadata.gz: faae392718b9cc76a9d699c77fed71764ef503918e75b3a83e9255259c4db7bb279c0d0db377e6913c78c623dc40d6f14c73921e7d0aa951c4a1a550dc9f5dd6
7
+ data.tar.gz: 61be1d7e1693604c30d6189517e23d260f6c27bfb4926ba8070dd75cb0d8a487beb877a70158801595f2c86bcbeacf7806dc5829aabedf3396692a6168ab76c1
data/CHANGELOG.md CHANGED
@@ -1,3 +1,7 @@
1
+ ## 0.5.0
2
+ * Recursively validate.
3
+ * Add README.
4
+
1
5
  ## 0.4.0
2
6
  * Add validations.
3
7
  - You can now call `#valid?` and `#validate` methods on DSL objects.
data/README.md ADDED
@@ -0,0 +1,293 @@
1
+ ## kube-dsl
2
+ A Ruby DSL for defining Kubernetes resources.
3
+
4
+ ## What is this thing?
5
+
6
+ KubeDSL provides a [domain-specific language](https://en.wikipedia.org/wiki/Domain-specific_language) for defining Kubernetes resource objects in Ruby. Why would you want to do this? Well,
7
+
8
+ 1. I think Ruby code is easier to read than YAML.
9
+ 1. every property is a Ruby method, meaning Ruby will blow up if you try to configure the object the wrong way.
10
+ 1. doing so follows the principle of [infrastructure as code](https://en.wikipedia.org/wiki/Infrastructure_as_code).
11
+ 1. validations are built-in.
12
+
13
+ ## Installation
14
+
15
+ Either run `gem install kube-dsl` or add it to your Gemfile:
16
+
17
+ ```ruby
18
+ gem 'kube-dsl', '< 1'
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ KubeDSL directly mirrors the fields and nesting structure of each Kubernetes YAML (or JSON) object. Ruby fields are snake_cased while Kubernetes fields are camelCased. Let's take a look at a short example where we define a Namespace:
24
+
25
+ ```ruby
26
+ ns = KubeDSL.namespace do
27
+ metadata do
28
+ name 'my-namespace'
29
+ end
30
+ end
31
+ ```
32
+
33
+ In the example above, we've defined a `KubeDSL::DSL::V1::Namespace` object and assigned it to a local variable called `ns`. Now let's convert it into a YAML string and print it out:
34
+
35
+ ```ruby
36
+ # ---
37
+ # apiVersion: v1
38
+ # kind: Namespace
39
+ # metadata:
40
+ # name: foo
41
+ puts ns.to_resource.to_yaml
42
+ ```
43
+
44
+ The `#to_resource` method returns an instance of the `KubeDSL::Resource` class while `#to_yaml` converts the resource into a YAML string. Pretty cool, eh? See the next few sections for examples creating other types of common Kubernetes objects.
45
+
46
+ ### ServiceAccount Example
47
+
48
+ Note how key/value pairs are added to the `labels` field.
49
+
50
+ ```ruby
51
+ KubeDSL.service_account do
52
+ metadata do
53
+ name 'my-service-account'
54
+ namespace 'my-namespace'
55
+
56
+ labels do
57
+ add :app, 'my-app'
58
+ add :role, 'web'
59
+ end
60
+ end
61
+ end
62
+ ```
63
+
64
+ ### Service Example
65
+
66
+ ```ruby
67
+ KubeDSL.service do
68
+ metadata do
69
+ name 'my-service'
70
+ namespace 'my-namespace'
71
+
72
+ labels do
73
+ add :app, 'my-app'
74
+ add :role, 'web'
75
+ end
76
+ end
77
+
78
+ spec do
79
+ type 'NodePort'
80
+
81
+ selector do
82
+ add :app, 'my-app'
83
+ add :role, 'web'
84
+ end
85
+
86
+ port do
87
+ name 'http'
88
+ port 3000
89
+ protocol 'TCP'
90
+ target_port 'http'
91
+ end
92
+ end
93
+ end
94
+ ```
95
+
96
+ ### Deployment Example
97
+
98
+ Note:
99
+
100
+ 1. Elements of arrays can be given names (see the use of the `#container` method below) so they can be easily retrieved and/or modified later.
101
+ 1. The example below shows how to add config maps and secrets to a deployment via references.
102
+
103
+ ```ruby
104
+ KubeDSL.deployment do
105
+ metadata do
106
+ name 'my-deployment'
107
+ namespace 'my-namespace'
108
+
109
+ labels do
110
+ add :app, 'my-app'
111
+ add :role, 'web'
112
+ end
113
+ end
114
+
115
+ spec do
116
+ replicas 2
117
+
118
+ selector do
119
+ match_labels do
120
+ add :app, 'my-app'
121
+ add :role, 'web'
122
+ end
123
+ end
124
+
125
+ strategy do
126
+ type 'RollingUpdate'
127
+
128
+ rolling_update do
129
+ max_surge '25%'
130
+ max_unavailable 1
131
+ end
132
+ end
133
+
134
+ template do
135
+ metadata do
136
+ labels do
137
+ add :app, 'my-app'
138
+ add :role, 'web'
139
+ end
140
+ end
141
+
142
+ spec do
143
+ # elements of arrays can be given names (:web in this case) so they can be
144
+ # easily retrieved and/or modified later
145
+ container(:web) do
146
+ name 'my-web-container'
147
+ image_pull_policy 'IfNotPresent'
148
+
149
+ port do
150
+ container_port 3000
151
+ name 'http'
152
+ protocol 'TCP'
153
+ end
154
+
155
+ env_from do
156
+ config_map_ref do
157
+ name 'my-config-map'
158
+ end
159
+ end
160
+
161
+ env_from do
162
+ secret_ref do
163
+ name 'my-secrets'
164
+ end
165
+ end
166
+
167
+ readiness_probe do
168
+ success_threshold 1
169
+ failure_threshold 2
170
+ initial_delay_seconds 15
171
+ period_seconds 3
172
+ timeout_seconds 1
173
+
174
+ http_get do
175
+ path '/healthz'
176
+ port 3000
177
+ scheme 'HTTP'
178
+ end
179
+ end
180
+ end
181
+
182
+ image_pull_secret do
183
+ name 'my-registry-secret'
184
+ end
185
+ end
186
+
187
+ restart_policy 'Always'
188
+ service_account_name 'my-service-account'
189
+ end
190
+ end
191
+ end
192
+ ```
193
+
194
+ ### Ingress Example
195
+
196
+ NOTE: the example below includes an annotation that is specific to the Nginx ingress controller.
197
+
198
+ ```ruby
199
+ KubeDSL::DSL::Extensions::V1beta1::Ingress.new do
200
+ metadata do
201
+ name 'my-ingress'
202
+ namespace 'my-namespace'
203
+
204
+ annotations do
205
+ add :'kubernetes.io/ingress.class', 'nginx'
206
+ end
207
+ end
208
+
209
+ spec do
210
+ rule do
211
+ host 'my-website.com'
212
+
213
+ http do
214
+ path do
215
+ path '/'
216
+
217
+ backend do
218
+ service_name 'my-service'
219
+ service_port 80
220
+ end
221
+ end
222
+ end
223
+ end
224
+ end
225
+ end
226
+ ```
227
+
228
+ ### ConfigMap Example
229
+
230
+ ```ruby
231
+ KubeDSL.config_map do
232
+ metadata do
233
+ name 'my-config-map'
234
+ namespace 'my-namespace'
235
+ end
236
+
237
+ data do
238
+ add :MY_VAR, 'value'
239
+ add :MY_OTHER_VAR, 'value'
240
+ end
241
+ end
242
+ ```
243
+
244
+ ### Secret Example
245
+
246
+ ```ruby
247
+ KubeDSL.secret do
248
+ metadata do
249
+ name 'my-secrets'
250
+ namespace 'my-namespace'
251
+ end
252
+
253
+ type 'Opaque'
254
+
255
+ data do
256
+ add :MY_SECRET, 'value'
257
+ end
258
+ end
259
+ ```
260
+
261
+ ## Validations
262
+
263
+ All `KubeDSL::DSLObject`s respond to `#valid?` and `#validate` methods. Use `#valid?` to determine whether or not an object is valid. Use `#validate` to retrieve a list of validation errors:
264
+
265
+ ```ruby
266
+ ns = KubeDSL.namespace do
267
+ metadata do
268
+ name 123
269
+ end
270
+ end
271
+
272
+ ns.valid? # => false
273
+ ns.validate # => #<KubeDSL::Validations::ValidationErrors:0x00007fc8ce276e80 ... >
274
+ ns.validate.messages # => {"metadata.name"=>["is not a String"]}
275
+ ```
276
+
277
+ The handy `#validate!` method will raise a `KubeDSL::ValidationError` if the object is invalid.
278
+
279
+ ## Code Generation
280
+
281
+ All the Ruby code present in KubeDSL is generated from the Kubernetes JSON schema available [here](https://github.com/instrumenta/kubernetes-json-schema). Run the following rake task to regenerate:
282
+
283
+ ```bash
284
+ bundle exec rake generate
285
+ ```
286
+
287
+ ## Authors
288
+
289
+ * Cameron C. Dutro: http://github.com/camertron
290
+
291
+ ## License
292
+
293
+ Licensed under the MIT license.
@@ -1,24 +1,6 @@
1
1
  module KubeDSL::DSL::Pkg::Version
2
2
  class Info < ::KubeDSL::DSLObject
3
- value_field :build_date
4
- value_field :compiler
5
- value_field :git_commit
6
- value_field :git_tree_state
7
- value_field :git_version
8
- value_field :go_version
9
- value_field :major
10
- value_field :minor
11
- value_field :platform
12
-
13
- validates :build_date, field: { format: :string }, presence: false
14
- validates :compiler, field: { format: :string }, presence: false
15
- validates :git_commit, field: { format: :string }, presence: false
16
- validates :git_tree_state, field: { format: :string }, presence: false
17
- validates :git_version, field: { format: :string }, presence: false
18
- validates :go_version, field: { format: :string }, presence: false
19
- validates :major, field: { format: :string }, presence: false
20
- validates :minor, field: { format: :string }, presence: false
21
- validates :platform, field: { format: :string }, presence: false
3
+ value_fields :build_date, :compiler, :git_commit, :git_tree_state, :git_version, :go_version, :major, :minor, :platform
22
4
 
23
5
  def serialize
24
6
  {}.tap do |result|
@@ -27,9 +27,9 @@ module KubeDSL
27
27
  @presence = presence
28
28
  end
29
29
 
30
- def validate(obj, errors)
30
+ def validate(obj, errors, nesting)
31
31
  if presence && obj_empty?(obj)
32
- errors.add(field_name, 'is required')
32
+ errors.add([*nesting, field_name].join('.'), 'is required')
33
33
  end
34
34
  end
35
35
 
@@ -52,17 +52,17 @@ module KubeDSL
52
52
  @kind_of = opts.fetch(:kind_of)
53
53
  end
54
54
 
55
- def validate(obj, errors)
55
+ def validate(obj, errors, nesting)
56
56
  unless obj.is_a?(Array)
57
- errors.add(field_name, 'is not an array')
57
+ errors.add([*nesting, field_name].join('.'), 'is not an array')
58
58
  return
59
59
  end
60
60
 
61
61
  obj.each_with_index do |elem, idx|
62
62
  unless elem.nil? || elem.is_a?(kind_of)
63
63
  errors.add(
64
- field_name, "contains an object at index #{idx} of type '#{elem.class.name}', "\
65
- "expected '#{kind_of.name}'"
64
+ [*nesting, field_name].join('.'), "contains an object at index #{idx} "\
65
+ "of type '#{elem.class.name}', expected '#{kind_of.name}'"
66
66
  )
67
67
  end
68
68
  end
@@ -102,9 +102,9 @@ module KubeDSL
102
102
  @format_validator ||= FormatValidator.new(options[:format])
103
103
  end
104
104
 
105
- def validate(obj, errors)
105
+ def validate(obj, errors, nesting)
106
106
  unless format_validator.valid?(obj)
107
- errors.add(field_name, "is not a #{format_validator.klasses.map(&:to_s).join(', ')}")
107
+ errors.add([*nesting, field_name].join('.'), "is not a #{format_validator.klasses.map(&:to_s).join(', ')}")
108
108
  end
109
109
  end
110
110
  end
@@ -117,9 +117,9 @@ module KubeDSL
117
117
  @kind_of = opts.fetch(:kind_of)
118
118
  end
119
119
 
120
- def validate(obj, errors)
120
+ def validate(obj, errors, nesting)
121
121
  unless obj.nil? || obj.is_a?(kind_of)
122
- errors.add(field_name, "'#{obj.class.name}', expected '#{kind_of.name}'")
122
+ errors.add([*nesting, field_name].join('.'), "'#{obj.class.name}', expected '#{kind_of.name}'")
123
123
  end
124
124
  end
125
125
  end
@@ -132,10 +132,12 @@ module KubeDSL
132
132
  @format_validator ||= FormatValidator.new(options[:value_format])
133
133
  end
134
134
 
135
- def validate(obj, errors)
135
+ def validate(obj, errors, nesting)
136
136
  obj.kv_pairs.each_pair do |k, v|
137
137
  unless format_validator.valid?(v)
138
- errors.add(field_name, "expected element '#{k}' to be a #{format_validator.klasses.map(&:to_s).join(', ')}, got #{v.class.name}")
138
+ errors.add(
139
+ [*nesting, field_name].join('.'),
140
+ "expected element '#{k}' to be a #{format_validator.klasses.map(&:to_s).join(', ')}, got #{v.class.name}")
139
141
  end
140
142
  end
141
143
  end
@@ -149,9 +151,9 @@ module KubeDSL
149
151
  @list = opts[:in]
150
152
  end
151
153
 
152
- def validate(obj, errors)
154
+ def validate(obj, errors, nesting)
153
155
  unless list.include?(obj)
154
- errors.add(field_name, "is not in #{list.join(', ')}")
156
+ errors.add([*nesting, field_name].join('.'), "is not in #{list.join(', ')}")
155
157
  end
156
158
  end
157
159
  end
@@ -186,12 +188,16 @@ module KubeDSL
186
188
  end
187
189
 
188
190
  module InstanceMethods
189
- def validate
190
- errors = ValidationErrors.new
191
+ def validate(errors = nil, nesting = [])
192
+ errors ||= ValidationErrors.new
191
193
 
192
194
  self.class.validators.each do |field_name, validators|
193
195
  field = send(field_name)
194
- validators.each { |val| val.validate(field, errors) }
196
+ validators.each { |val| val.validate(field, errors, nesting) }
197
+
198
+ if field.respond_to?(:validate)
199
+ field.validate(errors, nesting + [field_name.to_s])
200
+ end
195
201
  end
196
202
 
197
203
  errors
@@ -15,7 +15,7 @@ module KubeDSL
15
15
  define_method(field) do |*args|
16
16
  if args.empty?
17
17
  instance_variable_get(:"@#{field}") || (
18
- default.respond_to?(:call) ? default.call : default
18
+ default.respond_to?(:call) ? default.call(self) : default
19
19
  )
20
20
  else
21
21
  instance_variable_set(:"@#{field}", args.first)
@@ -1,3 +1,3 @@
1
1
  module KubeDSL
2
- VERSION = '0.4.0'
2
+ VERSION = '0.5.0'
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: kube-dsl
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Dutro
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-30 00:00:00.000000000 Z
11
+ date: 2021-05-02 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dry-inflector
@@ -34,6 +34,7 @@ files:
34
34
  - CHANGELOG.md
35
35
  - Gemfile
36
36
  - LICENSE
37
+ - README.md
37
38
  - Rakefile
38
39
  - kube-dsl.gemspec
39
40
  - lib/kube-dsl.rb