kube-dsl 0.4.0 → 0.6.1

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