kube-dsl 0.4.0 → 0.6.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: 4c028c471596448f6e06ff1aa0bf50bc3ccdd93e22559a9805d4078921571f8a
4
- data.tar.gz: b6e10a2a2f59d21af138e18db5a46b015a331e762642f7d2292daa69e9f4365f
3
+ metadata.gz: ee0373d4a49b731e08d691b25c84ba5c71f8e9a4018f411ba5bb8da87448096a
4
+ data.tar.gz: 7ffcad491b24630b21ae7a8799f75089816b066f4a9671db5774999d0fbac434
5
5
  SHA512:
6
- metadata.gz: 7bda26da0d442fbe59af67f1a8c60aad632bb91f789332aa951032d6f25be6dd72461b20c0e24e0ed995f15f7a925ead791feca3b9e99a0d226cc6cf4c3dc423
7
- data.tar.gz: 8102bb8df5fdc935f3df34c9ff9a2ae2e3d0f1c6eb3222441d4b082c9cc932a7d5d9856028435726b0d4821e94413e4af377d1869fcfba1e08153052d25f9af0
6
+ metadata.gz: 3d584e068dffddfea89b6aaca1baa27633fe8681d161a53cc274daa20426496a1358c71223866dfde883ba80d2119a021110196fac087d20fd5d1d82380e57fd
7
+ data.tar.gz: ef1c9a440f42c5d8777ff0dc08377e264456344754f09f146e848ae2964811d67c23ddb6cbbf4fa18fc1755161bddb6fc73d7646e1041a30c1bf178534076d75
data/CHANGELOG.md CHANGED
@@ -1,3 +1,18 @@
1
+ ## 0.6.1
2
+ * Fix additional naming discrepancy in autoload manifests causing "API" to become "Api."
3
+
4
+ ## 0.6.0
5
+ * Improve path and namespace handling in generator.
6
+
7
+ ## 0.5.1
8
+ * Support Ruby 2.7
9
+ - Stop using the `RubyToken` class from irb, which doesn't exist anymore.
10
+ * Fix bug causing `NoMethodError`s if the type field contains a single string and not an array.
11
+
12
+ ## 0.5.0
13
+ * Recursively validate.
14
+ * Add README.
15
+
1
16
  ## 0.4.0
2
17
  * Add validations.
3
18
  - 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.
data/Rakefile CHANGED
@@ -16,6 +16,7 @@ end
16
16
 
17
17
  task :generate do
18
18
  require 'dry/inflector'
19
+ require 'fileutils'
19
20
 
20
21
  FileUtils.rm_rf('./lib/kube-dsl/entrypoint.rb')
21
22
  FileUtils.rm_rf('./lib/kube-dsl/dsl.rb')
@@ -24,33 +25,36 @@ task :generate do
24
25
  FileUtils.mkdir_p('./vendor')
25
26
 
26
27
  export_url = "https://github.com/instrumenta/kubernetes-json-schema/trunk/v#{KubeDSL::KUBERNETES_VERSION}-local"
27
- local_path = "vendor/kubernetes-json-schema/v#{KubeDSL::KUBERNETES_VERSION}-local"
28
+ local_schema_path = "vendor/kubernetes-json-schema/v#{KubeDSL::KUBERNETES_VERSION}-local"
28
29
 
29
- unless File.exist?(local_path)
30
- system("svn export #{export_url} #{local_path}")
30
+ unless File.exist?(local_schema_path)
31
+ system("svn export #{export_url} #{local_schema_path}")
31
32
  end
32
33
 
33
- Dir.chdir('lib') do
34
- generator = KubeDSL::Generator.new(
35
- schema_dir: File.join('..', local_path),
36
- output_dir: File.join('kube-dsl', 'dsl'),
37
- inflector: Dry::Inflector.new do |inflections|
38
- inflections.acronym('DSL')
39
-
40
- inflections.plural('tls', 'tlses')
41
- inflections.singular('tls', 'tls')
42
- inflections.plural('enum', 'enums')
43
- inflections.plural('one_of', 'one_ofs')
44
- inflections.plural('any_of', 'any_ofs')
45
- inflections.plural('all_of', 'all_ofs')
46
- end
47
- )
48
-
49
- generator.generate_resource_files
50
- generator.generate_autoload_files
51
- generator.generate_entrypoint_file do |resource, ns|
52
- version = resource.ref.version || ''
53
- !version.include?('beta') && !version.include?('alpha')
34
+ generator = KubeDSL::Generator.new(
35
+ schema_dir: local_schema_path,
36
+ output_dir: File.join('lib'),
37
+ autoload_prefix: File.join('kube-dsl', 'dsl'),
38
+ dsl_namespace: ['KubeDSL', 'DSL'],
39
+ entrypoint_namespace: ['KubeDSL'],
40
+ inflector: Dry::Inflector.new do |inflections|
41
+ inflections.acronym('DSL')
42
+
43
+ inflections.singular('tls', 'tls')
44
+ inflections.singular('causes', 'cause')
45
+
46
+ inflections.plural('tls', 'tlses')
47
+ inflections.plural('enum', 'enums')
48
+ inflections.plural('one_of', 'one_ofs')
49
+ inflections.plural('any_of', 'any_ofs')
50
+ inflections.plural('all_of', 'all_ofs')
54
51
  end
52
+ )
53
+
54
+ generator.generate_resource_files
55
+ generator.generate_autoload_files
56
+ generator.generate_entrypoint_file do |resource, ns|
57
+ version = resource.ref.version || ''
58
+ !version.include?('beta') && !version.include?('alpha')
55
59
  end
56
60
  end
@@ -4,12 +4,17 @@ module KubeDSL
4
4
  class Builder
5
5
  include StringHelpers
6
6
 
7
- attr_reader :schema_dir, :output_dir, :namespace, :inflector, :resolvers
7
+ attr_reader :schema_dir, :output_dir, :autoload_prefix
8
+ attr_reader :dsl_namespace, :entrypoint_namespace
9
+ attr_reader :inflector, :resolvers
8
10
 
9
- def initialize(schema_dir:, output_dir:, inflector:)
11
+ def initialize(schema_dir:, output_dir:, autoload_prefix:, inflector:, dsl_namespace:, entrypoint_namespace:)
10
12
  @schema_dir = schema_dir
11
13
  @output_dir = output_dir
14
+ @autoload_prefix = autoload_prefix
12
15
  @inflector = inflector
16
+ @dsl_namespace = dsl_namespace
17
+ @entrypoint_namespace = entrypoint_namespace
13
18
  @resolvers ||= {}
14
19
  end
15
20
 
@@ -19,25 +24,17 @@ module KubeDSL
19
24
  end
20
25
  end
21
26
 
22
- def each_resource
27
+ def each_resource_file
23
28
  return to_enum(__method__) unless block_given?
24
29
 
25
- resources.each do |res|
26
- # "External" resources are ones that live outside the current
27
- # schema, i.e. k8s resources like ObjectMeta that other
28
- # k8s-compatible schemas depend on.
29
- #
30
- # Resources can be "empty" if they contain no properties. This
31
- # usually happens for resources that are really just aliases
32
- # for basic types like integer and string. The k8s' Duration
33
- # object is a good example. It's just an alias for string.
34
- yield res if !res.external? && !res.empty?
30
+ each_resource do |res|
31
+ yield File.join(output_dir, res.ref.ruby_autoload_path), res
35
32
  end
36
33
  end
37
34
 
38
35
  def entrypoint(&block)
39
36
  ''.tap do |ruby_code|
40
- ruby_code << "module #{namespace[0..-2].join('::')}::Entrypoint\n"
37
+ ruby_code << "module #{entrypoint_namespace.join('::')}::Entrypoint\n"
41
38
 
42
39
  each_resource do |resource|
43
40
  ns = resource.ref.ruby_namespace.join('::')
@@ -53,26 +50,15 @@ module KubeDSL
53
50
  end
54
51
  end
55
52
 
56
- def namespace
57
- @namespace ||= inflector.classify(
58
- File
59
- .split(output_dir)
60
- .map { |seg| inflector.camelize(underscore(seg)) }
61
- .join('/')
62
- ).split('::')
63
- end
64
-
65
53
  def entrypoint_path
66
- File.join(File.dirname(output_dir), 'entrypoint.rb')
54
+ File.join(output_dir, File.dirname(autoload_prefix), 'entrypoint.rb')
67
55
  end
68
56
 
69
57
  def each_autoload_file(&block)
70
58
  return to_enum(__method__) unless block
71
59
 
72
- start = output_dir.split(File::SEPARATOR).first
73
-
74
60
  each_autoload_file_helper(
75
- autoload_map[start], [start], &block
61
+ autoload_map[:root], [], &block
76
62
  )
77
63
  end
78
64
 
@@ -88,11 +74,27 @@ module KubeDSL
88
74
  end
89
75
 
90
76
  def parse_ref(ref_str)
91
- Ref.new(ref_str, namespace, output_dir, inflector, schema_dir)
77
+ Ref.new(ref_str, dsl_namespace, inflector, schema_dir, autoload_prefix)
92
78
  end
93
79
 
94
80
  private
95
81
 
82
+ def each_resource
83
+ return to_enum(__method__) unless block_given?
84
+
85
+ resources.each do |res|
86
+ # "External" resources are ones that live outside the current
87
+ # schema, i.e. k8s resources like ObjectMeta that other
88
+ # k8s-compatible schemas depend on.
89
+ #
90
+ # Resources can be "empty" if they contain no properties. This
91
+ # usually happens for resources that are really just aliases
92
+ # for basic types like integer and string. The k8s' Duration
93
+ # object is a good example. It's just an alias for string.
94
+ yield res if !res.external? && !res.empty?
95
+ end
96
+ end
97
+
96
98
  def resources
97
99
  JSON.parse(File.read(start_path))['oneOf'].map do |entry|
98
100
  resource_from_ref(resolve_ref(entry['$ref']))
@@ -105,7 +107,7 @@ module KubeDSL
105
107
  parts = res.ref.ruby_autoload_path.split(File::SEPARATOR)
106
108
  parts.reject!(&:empty?)
107
109
 
108
- parts.inject(amap) do |ret, seg|
110
+ [:root, *parts].inject(amap) do |ret, seg|
109
111
  if seg.end_with?('.rb')
110
112
  ret[seg] = res
111
113
  else
@@ -130,14 +132,14 @@ module KubeDSL
130
132
  autoload_path = File.join(*path, ns, child_ns).chomp('.rb')
131
133
 
132
134
  if res.is_a?(Hash)
133
- ruby_code << " autoload :#{capitalize(child_ns)}, '#{autoload_path}'\n"
135
+ ruby_code << " autoload :#{inflector.camelize(child_ns)}, '#{autoload_path}'\n"
134
136
  else
135
137
  ruby_code << " autoload :#{res.ref.kind}, '#{autoload_path}'\n"
136
138
  end
137
139
  end
138
140
 
139
141
  ruby_code << "end\n"
140
- yield File.join(*path, "#{ns}.rb"), ruby_code
142
+ yield File.join(output_dir, *path, "#{ns}.rb"), ruby_code
141
143
  each_autoload_file_helper(children, path + [ns], &block)
142
144
  end
143
145
  end
@@ -194,7 +196,7 @@ module KubeDSL
194
196
  res.fields[name] = DefaultFieldRes.new(name, res, enum)
195
197
  else
196
198
  res.fields[name] = FieldRes.new(
197
- name, prop['type'].first, required
199
+ name, Array(prop['type']).first, required
198
200
  )
199
201
  end
200
202
 
@@ -216,7 +218,7 @@ module KubeDSL
216
218
  end
217
219
 
218
220
  def start_path
219
- @entrypoint_path ||= File.join(schema_dir, 'all.json')
221
+ @start_path ||= File.join(schema_dir, 'all.json')
220
222
  end
221
223
 
222
224
  def resource_cache
@@ -1,5 +1,3 @@
1
- require 'irb/ruby-token'
2
-
3
1
  module KubeDSL
4
2
  class FieldRes
5
3
  include StringHelpers
@@ -13,14 +13,14 @@ module KubeDSL
13
13
  end
14
14
 
15
15
  def generate_resource_files
16
- builder.each_resource do |res|
17
- FileUtils.mkdir_p(File.dirname(res.ref.ruby_autoload_path))
16
+ builder.each_resource_file do |path, res|
17
+ FileUtils.mkdir_p(File.dirname(path))
18
18
 
19
- if File.exist?(res.ref.ruby_autoload_path)
20
- puts "Skipping #{res.ref.ruby_autoload_path} because it already exists"
19
+ if File.exist?(path)
20
+ puts "Skipping #{path} because it already exists"
21
21
  else
22
- puts "Writing #{res.ref.ruby_autoload_path}"
23
- File.write(res.ref.ruby_autoload_path, res.to_ruby)
22
+ puts "Writing #{path}"
23
+ File.write(path, res.to_ruby)
24
24
  end
25
25
  end
26
26
  end
data/lib/kube-dsl/ref.rb CHANGED
@@ -5,12 +5,12 @@ module KubeDSL
5
5
  attr_reader :str, :kind, :namespace, :version, :inflector, :schema_dir
6
6
  attr_reader :ruby_namespace_prefix, :autoload_prefix
7
7
 
8
- def initialize(str, ruby_namespace_prefix, autoload_prefix, inflector, schema_dir)
8
+ def initialize(str, ruby_namespace_prefix, inflector, schema_dir, autoload_prefix)
9
9
  @str = str
10
10
  @ruby_namespace_prefix = ruby_namespace_prefix
11
- @autoload_prefix = autoload_prefix
12
11
  @inflector = inflector
13
12
  @schema_dir = schema_dir
13
+ @autoload_prefix = autoload_prefix
14
14
 
15
15
  ns, v, k = str.split('.').last(3)
16
16
 
@@ -41,15 +41,18 @@ module KubeDSL
41
41
  def ruby_namespace
42
42
  @ruby_namespace ||= begin
43
43
  [*ruby_namespace_prefix].tap do |mods|
44
- mods << capitalize(namespace) if namespace
45
- mods << capitalize(version) if version
44
+ mods << namespace if namespace
45
+ mods << version if version
46
+ mods.map! do |m|
47
+ inflector.camelize(underscore(m))
48
+ end
46
49
  end
47
50
  end
48
51
  end
49
52
 
50
53
  def ruby_autoload_path
51
54
  @ruby_autoload_path ||= File.join(
52
- [*autoload_prefix.split(File::SEPARATOR)].tap do |path|
55
+ [autoload_prefix].tap do |path|
53
56
  path << underscore(namespace) if namespace
54
57
  path << underscore(version) if version
55
58
  path << "#{underscore(kind)}.rb"
@@ -1,10 +1,10 @@
1
1
  module KubeDSL
2
2
  module StringHelpers
3
- RUBY_KEYWORDS = RubyToken::TokenDefinitions
4
- .select { |definition| definition[1] == RubyToken::TkId }
5
- .map { |definition| definition[2] }
6
- .compact
7
- .freeze
3
+ RUBY_KEYWORDS = %w(
4
+ BEGIN END alias and begin break case class def defined? do else elsif
5
+ end ensure false for if in module next nil not or redo rescue retry
6
+ return self super then true undef unless until when while yield
7
+ )
8
8
 
9
9
  def capitalize(str)
10
10
  str.sub(/\A(.)/) { $1.upcase }
@@ -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.6.1'
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.6.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Cameron Dutro
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-09-30 00:00:00.000000000 Z
11
+ date: 2021-08-24 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
@@ -802,7 +803,7 @@ files:
802
803
  homepage: http://github.com/getkuby/kube-dsl
803
804
  licenses: []
804
805
  metadata: {}
805
- post_install_message:
806
+ post_install_message:
806
807
  rdoc_options: []
807
808
  require_paths:
808
809
  - lib
@@ -817,8 +818,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
817
818
  - !ruby/object:Gem::Version
818
819
  version: '0'
819
820
  requirements: []
820
- rubygems_version: 3.1.4
821
- signing_key:
821
+ rubygems_version: 3.1.6
822
+ signing_key:
822
823
  specification_version: 4
823
824
  summary: A Ruby DSL for defining Kubernetes resources.
824
825
  test_files: []