kube-dsl 0.4.0 → 0.5.0

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: 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