kube_schema 1.12.0 → 2.0.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 +4 -4
- data/.envrc +1 -0
- data/.github/FUNDING.yml +3 -0
- data/.rubocop.yml +226 -5984
- data/CHANGELOG.md +110 -3
- data/DEPRECATIONS.md +172 -0
- data/Gemfile +13 -3
- data/Gemfile.lock +41 -31
- data/RELEASE.md +161 -0
- data/Rakefile +1 -1
- data/bin/rubocop +3 -0
- data/bin/test +2 -9
- data/cops/begin_block_assignment.rb +34 -0
- data/cops/comment_structure.rb +307 -0
- data/cops/constant_matches_path.rb +142 -0
- data/cops/multiline_method_arguments.rb +89 -0
- data/cops/no_conditional_assignment.rb +186 -0
- data/cops/no_console_logging.rb +62 -0
- data/cops/no_modifier_if.rb +74 -0
- data/cops/no_return.rb +152 -0
- data/cops/support/prism_source.rb +234 -0
- data/cops/use_tap.rb +100 -0
- data/flake.lock +3 -3
- data/flake.nix +22 -15
- data/gemset.nix +142 -73
- data/kube_schema.gemspec +4 -2
- data/lib/kube/errors.rb +652 -645
- data/lib/kube/monkey_patches.rb +5 -6
- data/lib/kube/schema/instance.rb +344 -363
- data/lib/kube/schema/lazy.rb +370 -0
- data/lib/kube/schema/manifest.rb +286 -325
- data/lib/kube/schema/resource.rb +348 -393
- data/lib/kube/schema/sub_spec.rb +261 -293
- data/lib/kube/schema.rb +173 -172
- data/lib/{kube/schema → kube_schema}/version.rb +1 -1
- data/lib/kube_schema.rb +6 -0
- data/readme.md +5 -0
- metadata +49 -9
- data/bin/increment-version +0 -45
- data/bin/release-gem +0 -32
- data/bin/tag-version +0 -19
- data/lib/kube/schema/version.rb.erb +0 -7
data/lib/kube/schema/manifest.rb
CHANGED
|
@@ -4,30 +4,26 @@ require "yaml"
|
|
|
4
4
|
|
|
5
5
|
module Kube
|
|
6
6
|
module Schema
|
|
7
|
-
# A flat, ordered collection of Kubernetes resources.
|
|
8
|
-
#
|
|
9
|
-
# Manifest is Enumerable over Resource objects. It cannot be nested —
|
|
10
|
-
# appending one Manifest into another extracts and flattens its resources.
|
|
7
|
+
# A flat, ordered, Enumerable collection of Kubernetes resources.
|
|
11
8
|
#
|
|
9
|
+
# @example
|
|
12
10
|
# manifest = Kube::Schema::Manifest.new
|
|
13
11
|
# manifest << deployment_resource
|
|
14
12
|
# manifest << another_manifest # flattened automatically
|
|
15
13
|
# puts manifest.to_yaml # multi-document YAML output
|
|
16
14
|
#
|
|
17
|
-
# File I/O
|
|
15
|
+
# @example File I/O
|
|
18
16
|
# manifest = Kube::Schema::Manifest.open("cluster.yaml")
|
|
19
17
|
# manifest << new_resource
|
|
20
18
|
# manifest.write
|
|
21
|
-
#
|
|
22
19
|
class Manifest
|
|
23
20
|
include Enumerable
|
|
24
21
|
|
|
25
22
|
attr_reader :filename
|
|
26
23
|
|
|
27
24
|
# Create a new Manifest, optionally seeded with resources.
|
|
28
|
-
#
|
|
29
|
-
# @
|
|
30
|
-
# @param filename [String, nil] optional filename for file-backed manifests
|
|
25
|
+
# @parameter resources [Array(Resource, Manifest)] initial resources to include.
|
|
26
|
+
# @parameter filename [String, nil] an optional filename for file-backed manifests.
|
|
31
27
|
def initialize(*resources, filename: nil)
|
|
32
28
|
@resources = []
|
|
33
29
|
@filename = filename
|
|
@@ -35,14 +31,9 @@ module Kube
|
|
|
35
31
|
resources.each { |r| self << r }
|
|
36
32
|
end
|
|
37
33
|
|
|
38
|
-
# Append a resource, manifest, or array of
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
# - Manifest objects are flattened — their resources are extracted.
|
|
42
|
-
# - Arrays are iterated and each element is appended.
|
|
43
|
-
#
|
|
44
|
-
# @param item [Resource, Manifest, Array]
|
|
45
|
-
# @return [self]
|
|
34
|
+
# Append a resource, manifest, or array of either.
|
|
35
|
+
# @parameter item [Resource, Manifest, Array] what to append.
|
|
36
|
+
# @returns [self] appended; a Manifest is flattened, an Array iterated.
|
|
46
37
|
def <<(item)
|
|
47
38
|
case item
|
|
48
39
|
when Manifest
|
|
@@ -60,94 +51,86 @@ module Kube
|
|
|
60
51
|
self
|
|
61
52
|
end
|
|
62
53
|
|
|
63
|
-
# @
|
|
54
|
+
# @yields {|resource| ...} each resource in insertion order.
|
|
64
55
|
def each(&block)
|
|
65
56
|
@resources.each(&block)
|
|
66
57
|
end
|
|
67
58
|
|
|
68
59
|
# Number of resources in the manifest.
|
|
60
|
+
# @returns [Integer] the resource count.
|
|
69
61
|
def size
|
|
70
62
|
@resources.size
|
|
71
63
|
end
|
|
72
64
|
alias_method :length, :size
|
|
73
65
|
|
|
74
|
-
#
|
|
75
|
-
#
|
|
76
|
-
#
|
|
77
|
-
# @return [String]
|
|
66
|
+
# The manifest as "---"-separated multi-document YAML.
|
|
67
|
+
# @returns [String] the standard Kubernetes manifest file format.
|
|
78
68
|
def to_yaml
|
|
79
69
|
@resources.map { |r| r.to_yaml }.join("")
|
|
80
70
|
end
|
|
81
71
|
|
|
82
72
|
# Returns an array of resource hashes.
|
|
83
|
-
#
|
|
84
|
-
# @return [Array<Hash>]
|
|
73
|
+
# @returns [Array(Hash)] a copy of the resources, in order.
|
|
85
74
|
def to_a
|
|
86
75
|
@resources.dup
|
|
87
76
|
end
|
|
88
77
|
|
|
89
|
-
#
|
|
78
|
+
# Supports pattern matching, e.g. `manifest => [resource, *]`.
|
|
79
|
+
# @returns [Array(Hash)] the same array as #to_a.
|
|
90
80
|
def deconstruct = to_a
|
|
91
81
|
|
|
92
|
-
#
|
|
93
|
-
# File I/O
|
|
94
|
-
# -------------------------------------------------------------------
|
|
82
|
+
# File I/O.
|
|
95
83
|
|
|
96
|
-
# Parse
|
|
97
|
-
# documents and return a Manifest populated with typed Resource objects.
|
|
98
|
-
#
|
|
99
|
-
# Each document's "kind" is resolved via Kube::Schema.parse to
|
|
100
|
-
# produce the correct Resource subclass (e.g. Deployment, Service).
|
|
101
|
-
# Documents without a recognized "kind" fall back to a bare Resource.
|
|
84
|
+
# Parse multi-document YAML into a Manifest of typed Resources.
|
|
102
85
|
#
|
|
86
|
+
# @example
|
|
103
87
|
# yaml = `helm template my-release bitnami/nginx`
|
|
104
88
|
# manifest = Kube::Schema::Manifest.parse(yaml)
|
|
105
89
|
# manifest.first.class #=> Kube::Schema::Resource (Deployment subclass)
|
|
106
90
|
#
|
|
107
|
-
# @
|
|
108
|
-
# @
|
|
91
|
+
# @parameter yaml_string [String] multi-document YAML.
|
|
92
|
+
# @returns [Manifest] the parsed resources.
|
|
109
93
|
def self.parse(yaml_string)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
94
|
+
if YAML.respond_to?(:safe_load_stream)
|
|
95
|
+
docs = YAML.safe_load_stream(yaml_string, permitted_classes: [Symbol])
|
|
96
|
+
else
|
|
97
|
+
docs = YAML.load_stream(yaml_string)
|
|
98
|
+
end
|
|
115
99
|
|
|
116
100
|
resources = docs.compact.map { |doc| parse_doc(doc) }
|
|
117
101
|
new(*resources)
|
|
118
102
|
end
|
|
119
103
|
|
|
120
|
-
# Read a YAML file
|
|
121
|
-
# and return a Manifest populated with typed Resource objects.
|
|
104
|
+
# Read a YAML file into a Manifest of typed Resources.
|
|
122
105
|
#
|
|
106
|
+
# @example
|
|
123
107
|
# manifest = Kube::Schema::Manifest.open("deploy.yaml")
|
|
124
108
|
# manifest.count #=> 3
|
|
125
109
|
# manifest.filename #=> "deploy.yaml"
|
|
126
110
|
#
|
|
127
|
-
# @
|
|
128
|
-
# @
|
|
111
|
+
# @parameter path [String] path to a YAML file.
|
|
112
|
+
# @returns [Manifest] the parsed resources, with filename set.
|
|
129
113
|
def self.open(path)
|
|
130
114
|
contents = File.read(path)
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
end
|
|
115
|
+
if YAML.respond_to?(:safe_load_stream)
|
|
116
|
+
docs = YAML.safe_load_stream(contents, permitted_classes: [Symbol])
|
|
117
|
+
else
|
|
118
|
+
docs = YAML.load_stream(contents)
|
|
119
|
+
end
|
|
137
120
|
|
|
138
121
|
resources = docs.compact.map { |doc| parse_doc(doc) }
|
|
139
122
|
new(*resources, filename: path)
|
|
140
123
|
end
|
|
141
124
|
|
|
142
125
|
# Write the manifest to a file as multi-document YAML.
|
|
143
|
-
#
|
|
144
|
-
# @
|
|
145
|
-
#
|
|
146
|
-
# @return [String] the path written to
|
|
147
|
-
# @raise [ArgumentError] if no path is given and no filename is set
|
|
126
|
+
# @parameter path [String, nil] destination path, defaulting to #filename.
|
|
127
|
+
# @returns [String] the path written to.
|
|
128
|
+
# @raises [ArgumentError] if no path is given and no filename is set.
|
|
148
129
|
def write(path = nil)
|
|
149
130
|
path ||= @filename
|
|
150
|
-
|
|
131
|
+
if path.nil?
|
|
132
|
+
raise ArgumentError, "No filename set. Pass a path to #write or use Manifest.open."
|
|
133
|
+
end
|
|
151
134
|
|
|
152
135
|
File.write(path, to_yaml)
|
|
153
136
|
@filename = path
|
|
@@ -155,10 +138,9 @@ module Kube
|
|
|
155
138
|
end
|
|
156
139
|
|
|
157
140
|
# Parse a single YAML document hash into a typed Resource.
|
|
158
|
-
#
|
|
159
|
-
# @
|
|
160
|
-
# @
|
|
161
|
-
# @raise [RuntimeError] if the kind is not recognized
|
|
141
|
+
# @parameter doc [Hash] a parsed YAML document.
|
|
142
|
+
# @returns [Resource] the typed resource.
|
|
143
|
+
# @raises [RuntimeError] if the kind is not recognized.
|
|
162
144
|
def self.parse_doc(doc)
|
|
163
145
|
Kube::Schema.parse(doc)
|
|
164
146
|
end
|
|
@@ -167,7 +149,8 @@ module Kube
|
|
|
167
149
|
private
|
|
168
150
|
|
|
169
151
|
# Deep-stringify keys for clean YAML output.
|
|
170
|
-
#
|
|
152
|
+
# @parameter obj [Object] a Hash, Array, or scalar to stringify.
|
|
153
|
+
# @returns [Object] the same structure with string keys.
|
|
171
154
|
def deep_stringify(obj)
|
|
172
155
|
case obj
|
|
173
156
|
when Hash
|
|
@@ -184,340 +167,318 @@ module Kube
|
|
|
184
167
|
end
|
|
185
168
|
end
|
|
186
169
|
|
|
187
|
-
if __FILE__ == $0
|
|
188
|
-
require "bundler/setup"
|
|
189
|
-
require "rspec/autorun"
|
|
190
|
-
require "kube/schema"
|
|
191
|
-
require "tmpdir"
|
|
192
|
-
|
|
193
|
-
RSpec.describe Kube::Schema::Manifest do
|
|
194
|
-
let(:resource_a) { Kube::Schema["Deployment"].new }
|
|
195
|
-
let(:resource_b) { Kube::Schema["Service"].new }
|
|
196
|
-
let(:resource_c) { Kube::Schema["Namespace"].new }
|
|
197
|
-
|
|
198
|
-
describe "#initialize" do
|
|
199
|
-
it "creates an empty manifest with no arguments" do
|
|
200
|
-
manifest = described_class.new
|
|
201
|
-
expect(manifest.count).to eq(0)
|
|
202
|
-
end
|
|
203
170
|
|
|
204
|
-
|
|
205
|
-
manifest = described_class.new(resource_a, resource_b)
|
|
206
|
-
expect(manifest.count).to eq(2)
|
|
207
|
-
end
|
|
171
|
+
__END__
|
|
208
172
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
it "flattens manifests passed as arguments" do
|
|
215
|
-
inner = described_class.new(resource_a, resource_b)
|
|
216
|
-
outer = described_class.new(inner, resource_c)
|
|
217
|
-
expect(outer.count).to eq(3)
|
|
218
|
-
end
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
describe "#<<" do
|
|
222
|
-
subject(:manifest) { described_class.new }
|
|
223
|
-
|
|
224
|
-
it "appends a Resource" do
|
|
225
|
-
manifest << resource_a
|
|
226
|
-
expect(manifest.count).to eq(1)
|
|
227
|
-
expect(manifest.first).to eq(resource_a)
|
|
228
|
-
end
|
|
229
|
-
|
|
230
|
-
it "returns self for chaining" do
|
|
231
|
-
result = manifest << resource_a
|
|
232
|
-
expect(result).to be(manifest)
|
|
233
|
-
end
|
|
173
|
+
require "kube/schema"
|
|
174
|
+
require "tmpdir"
|
|
175
|
+
require "fileutils"
|
|
234
176
|
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
expect(manifest.to_a).to contain_exactly(resource_a, resource_b)
|
|
240
|
-
end
|
|
177
|
+
manifest_class = Kube::Schema::Manifest
|
|
178
|
+
resource_a = Kube::Schema["Deployment"].new
|
|
179
|
+
resource_b = Kube::Schema["Service"].new
|
|
180
|
+
resource_c = Kube::Schema["Namespace"].new
|
|
241
181
|
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
182
|
+
describe "Kube::Schema::Manifest" do
|
|
183
|
+
describe "#initialize" do
|
|
184
|
+
it "creates an empty manifest with no arguments" do
|
|
185
|
+
manifest_class.new.count.should == 0
|
|
186
|
+
end
|
|
246
187
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
188
|
+
it "accepts resources as arguments" do
|
|
189
|
+
manifest_class.new(resource_a, resource_b).count.should == 2
|
|
190
|
+
end
|
|
250
191
|
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
192
|
+
it "accepts a filename keyword argument" do
|
|
193
|
+
manifest_class.new(filename: "/tmp/test.yaml").filename.should == "/tmp/test.yaml"
|
|
194
|
+
end
|
|
254
195
|
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
end
|
|
196
|
+
it "flattens manifests passed as arguments" do
|
|
197
|
+
inner = manifest_class.new(resource_a, resource_b)
|
|
198
|
+
manifest_class.new(inner, resource_c).count.should == 3
|
|
259
199
|
end
|
|
200
|
+
end
|
|
260
201
|
|
|
261
|
-
|
|
262
|
-
|
|
202
|
+
describe "#<<" do
|
|
203
|
+
it "appends a Resource" do
|
|
204
|
+
manifest = manifest_class.new
|
|
205
|
+
manifest << resource_a
|
|
206
|
+
manifest.count.should == 1
|
|
207
|
+
manifest.first.should == resource_a
|
|
208
|
+
end
|
|
263
209
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
210
|
+
it "returns self for chaining" do
|
|
211
|
+
manifest = manifest_class.new
|
|
212
|
+
(manifest << resource_a).should.be.identical_to manifest
|
|
213
|
+
end
|
|
267
214
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
215
|
+
it "flattens a Manifest (cannot be nested)" do
|
|
216
|
+
manifest = manifest_class.new
|
|
217
|
+
manifest << manifest_class.new(resource_a, resource_b)
|
|
218
|
+
manifest.count.should == 2
|
|
219
|
+
manifest.to_a.should == [resource_a, resource_b]
|
|
220
|
+
end
|
|
273
221
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
222
|
+
it "flattens an Array of Resources" do
|
|
223
|
+
manifest = manifest_class.new
|
|
224
|
+
manifest << [resource_a, resource_b]
|
|
225
|
+
manifest.count.should == 2
|
|
226
|
+
end
|
|
278
227
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
228
|
+
it "raises ArgumentError for a Hash" do
|
|
229
|
+
manifest = manifest_class.new
|
|
230
|
+
error = lambda { manifest << { "kind" => "Pod" } }.should.raise(ArgumentError)
|
|
231
|
+
error.message.should.match(/Expected a Kube::Schema::Resource/)
|
|
232
|
+
end
|
|
283
233
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
expect(manifest.to_a.last).to eq(resource_c)
|
|
288
|
-
end
|
|
234
|
+
it "raises ArgumentError for a String" do
|
|
235
|
+
manifest = manifest_class.new
|
|
236
|
+
lambda { manifest << "not a resource" }.should.raise(ArgumentError)
|
|
289
237
|
end
|
|
290
238
|
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
239
|
+
it "supports chaining multiple appends" do
|
|
240
|
+
manifest = manifest_class.new
|
|
241
|
+
manifest << resource_a << resource_b << resource_c
|
|
242
|
+
manifest.count.should == 3
|
|
243
|
+
end
|
|
244
|
+
end
|
|
297
245
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
end
|
|
246
|
+
describe "Enumerable" do
|
|
247
|
+
it "includes Enumerable" do
|
|
248
|
+
manifest_class.should.include Enumerable
|
|
302
249
|
end
|
|
303
250
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
251
|
+
it "yields resources in insertion order via #each" do
|
|
252
|
+
resources = []
|
|
253
|
+
manifest_class.new(resource_a, resource_b, resource_c).each { |r| resources << r }
|
|
254
|
+
resources.should == [resource_a, resource_b, resource_c]
|
|
255
|
+
end
|
|
308
256
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
257
|
+
it "supports #map" do
|
|
258
|
+
manifest = manifest_class.new(resource_a, resource_b, resource_c)
|
|
259
|
+
manifest.map { |r| r.to_h[:kind] }.should == ["Deployment", "Service", "Namespace"]
|
|
260
|
+
end
|
|
312
261
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
end
|
|
262
|
+
it "supports #select" do
|
|
263
|
+
manifest = manifest_class.new(resource_a, resource_b, resource_c)
|
|
264
|
+
manifest.select { |r| r.to_h[:kind] == "Service" }.length.should == 1
|
|
317
265
|
end
|
|
318
266
|
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
267
|
+
it "supports #first and #last" do
|
|
268
|
+
manifest = manifest_class.new(resource_a, resource_b, resource_c)
|
|
269
|
+
manifest.first.should == resource_a
|
|
270
|
+
# Enumerable doesn't provide #last by default, but #to_a does
|
|
271
|
+
manifest.to_a.last.should == resource_c
|
|
272
|
+
end
|
|
273
|
+
end
|
|
324
274
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
275
|
+
describe "#size / #length" do
|
|
276
|
+
it "returns the number of resources" do
|
|
277
|
+
manifest = manifest_class.new(resource_a, resource_b)
|
|
278
|
+
manifest.size.should == 2
|
|
279
|
+
manifest.length.should == 2
|
|
329
280
|
end
|
|
330
281
|
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
282
|
+
it "returns 0 for an empty manifest" do
|
|
283
|
+
manifest_class.new.size.should == 0
|
|
284
|
+
end
|
|
285
|
+
end
|
|
335
286
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
287
|
+
describe "pattern matching" do
|
|
288
|
+
it "destructures into an array of resources" do
|
|
289
|
+
manifest = manifest_class.new(resource_a, resource_b)
|
|
290
|
+
manifest => [Kube::Schema::Resource => head, *rest]
|
|
340
291
|
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
292
|
+
head.should == resource_a
|
|
293
|
+
rest.should == [resource_b]
|
|
294
|
+
end
|
|
344
295
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
296
|
+
it "enforces arity" do
|
|
297
|
+
manifest = manifest_class.new(resource_a, resource_b)
|
|
298
|
+
lambda { manifest => [Kube::Schema::Resource] }.should.raise(NoMatchingPatternError)
|
|
299
|
+
end
|
|
300
|
+
end
|
|
350
301
|
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
302
|
+
describe "#to_a" do
|
|
303
|
+
it "returns a copy of the internal resources array" do
|
|
304
|
+
manifest = manifest_class.new(resource_a, resource_b)
|
|
305
|
+
arr = manifest.to_a
|
|
306
|
+
arr.should == [resource_a, resource_b]
|
|
355
307
|
|
|
356
|
-
it
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
docs = if YAML.respond_to?(:safe_load_stream)
|
|
360
|
-
YAML.safe_load_stream(yaml_output)
|
|
361
|
-
else
|
|
362
|
-
YAML.load_stream(yaml_output)
|
|
363
|
-
end
|
|
364
|
-
expect(docs.length).to eq(2)
|
|
365
|
-
expect(docs[0]["kind"]).to eq("Deployment")
|
|
366
|
-
expect(docs[1]["kind"]).to eq("Service")
|
|
367
|
-
end
|
|
308
|
+
# Verify it's a copy, not the internal array
|
|
309
|
+
arr << resource_c
|
|
310
|
+
manifest.count.should == 2
|
|
368
311
|
end
|
|
312
|
+
end
|
|
369
313
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
manifest = described_class.parse(yaml)
|
|
314
|
+
describe "#to_yaml" do
|
|
315
|
+
it "returns multi-document YAML" do
|
|
316
|
+
yaml = manifest_class.new(resource_a, resource_b).to_yaml
|
|
374
317
|
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
318
|
+
yaml.should.include "---"
|
|
319
|
+
yaml.should.include "kind: Deployment"
|
|
320
|
+
yaml.should.include "kind: Service"
|
|
321
|
+
end
|
|
379
322
|
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
{ "kind" => "Deployment", "apiVersion" => "apps/v1" },
|
|
383
|
-
{ "kind" => "Service", "apiVersion" => "v1" }
|
|
384
|
-
].map(&:to_yaml).join("")
|
|
323
|
+
it "uses string keys (not symbol keys) in output" do
|
|
324
|
+
yaml = manifest_class.new(resource_a).to_yaml
|
|
385
325
|
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
326
|
+
# Should NOT contain Ruby symbol syntax like `:kind:`
|
|
327
|
+
yaml.should.not.match(/:\w+:/)
|
|
328
|
+
yaml.should.include "kind: Deployment"
|
|
329
|
+
yaml.should.include "apiVersion: apps/v1"
|
|
330
|
+
end
|
|
389
331
|
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
332
|
+
it "returns empty string for an empty manifest" do
|
|
333
|
+
manifest_class.new.to_yaml.should == ""
|
|
334
|
+
end
|
|
393
335
|
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
336
|
+
it "produces parseable YAML that round-trips" do
|
|
337
|
+
yaml_output = manifest_class.new(resource_a, resource_b).to_yaml
|
|
338
|
+
docs = if YAML.respond_to?(:safe_load_stream)
|
|
339
|
+
YAML.safe_load_stream(yaml_output)
|
|
340
|
+
else
|
|
341
|
+
YAML.load_stream(yaml_output)
|
|
342
|
+
end
|
|
343
|
+
docs.length.should == 2
|
|
344
|
+
docs[0]["kind"].should == "Deployment"
|
|
345
|
+
docs[1]["kind"].should == "Service"
|
|
346
|
+
end
|
|
347
|
+
end
|
|
398
348
|
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
end
|
|
349
|
+
describe ".parse" do
|
|
350
|
+
it "parses a single-document YAML string" do
|
|
351
|
+
yaml = { "kind" => "Pod", "apiVersion" => "v1", "metadata" => { "name" => "test" } }.to_yaml
|
|
352
|
+
manifest = manifest_class.parse(yaml)
|
|
404
353
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
354
|
+
manifest.count.should == 1
|
|
355
|
+
manifest.first.should.be.kind_of Kube::Schema::Resource
|
|
356
|
+
manifest.first.kind.should == "Pod"
|
|
357
|
+
end
|
|
409
358
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
359
|
+
it "parses a multi-document YAML string" do
|
|
360
|
+
yaml = [
|
|
361
|
+
{ "kind" => "Deployment", "apiVersion" => "apps/v1" },
|
|
362
|
+
{ "kind" => "Service", "apiVersion" => "v1" }
|
|
363
|
+
].map(&:to_yaml).join("")
|
|
415
364
|
|
|
416
|
-
|
|
417
|
-
manifest = described_class.parse("---\n")
|
|
418
|
-
expect(manifest.count).to eq(0)
|
|
419
|
-
end
|
|
365
|
+
manifest_class.parse(yaml).count.should == 2
|
|
420
366
|
end
|
|
421
367
|
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
368
|
+
it "returns typed Resource subclasses" do
|
|
369
|
+
yaml = { "kind" => "Deployment", "apiVersion" => "apps/v1", "metadata" => { "name" => "web" } }.to_yaml
|
|
370
|
+
resource = manifest_class.parse(yaml).first
|
|
425
371
|
|
|
426
|
-
|
|
372
|
+
resource.class.defaults.should == { "apiVersion" => "apps/v1", "kind" => "Deployment" }
|
|
373
|
+
resource.kind.should == "Deployment"
|
|
374
|
+
end
|
|
427
375
|
|
|
428
|
-
|
|
429
|
-
|
|
376
|
+
it "skips nil documents (empty YAML docs)" do
|
|
377
|
+
yaml = "---\nkind: Pod\napiVersion: v1\n---\n---\nkind: Service\napiVersion: v1\n"
|
|
378
|
+
manifest_class.parse(yaml).count.should == 2
|
|
379
|
+
end
|
|
430
380
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
381
|
+
it "raises for unknown kinds" do
|
|
382
|
+
yaml = { "kind" => "UnknownCRD", "apiVersion" => "custom.io/v1" }.to_yaml
|
|
383
|
+
lambda { manifest_class.parse(yaml) }.should.raise(RuntimeError)
|
|
384
|
+
end
|
|
435
385
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
].map(&:to_yaml).join("")
|
|
386
|
+
it "does not set a filename" do
|
|
387
|
+
yaml = { "kind" => "Pod", "apiVersion" => "v1" }.to_yaml
|
|
388
|
+
manifest_class.parse(yaml).filename.should.be.nil
|
|
389
|
+
end
|
|
441
390
|
|
|
442
|
-
|
|
391
|
+
it "returns an empty manifest for empty YAML" do
|
|
392
|
+
manifest_class.parse("---\n").count.should == 0
|
|
393
|
+
end
|
|
394
|
+
end
|
|
443
395
|
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
396
|
+
describe ".open" do
|
|
397
|
+
before { @tmpdir = Dir.mktmpdir("manifest_test") }
|
|
398
|
+
after { FileUtils.rm_rf(@tmpdir) }
|
|
447
399
|
|
|
448
|
-
|
|
449
|
-
|
|
400
|
+
it "reads a single-document YAML file" do
|
|
401
|
+
path = File.join(@tmpdir, "resources.yaml")
|
|
402
|
+
File.write(path, { "kind" => "Pod", "apiVersion" => "v1" }.to_yaml)
|
|
450
403
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
404
|
+
manifest = manifest_class.open(path)
|
|
405
|
+
manifest.count.should == 1
|
|
406
|
+
manifest.first.should.be.kind_of Kube::Schema::Resource
|
|
407
|
+
end
|
|
454
408
|
|
|
455
|
-
|
|
456
|
-
|
|
409
|
+
it "reads a multi-document YAML file" do
|
|
410
|
+
path = File.join(@tmpdir, "resources.yaml")
|
|
411
|
+
File.write(path, [
|
|
412
|
+
{ "kind" => "Deployment", "apiVersion" => "apps/v1" },
|
|
413
|
+
{ "kind" => "Service", "apiVersion" => "v1" }
|
|
414
|
+
].map(&:to_yaml).join(""))
|
|
457
415
|
|
|
458
|
-
|
|
459
|
-
expect(manifest.count).to eq(2)
|
|
460
|
-
end
|
|
416
|
+
manifest_class.open(path).count.should == 2
|
|
461
417
|
end
|
|
462
418
|
|
|
463
|
-
|
|
464
|
-
|
|
419
|
+
it "sets the filename" do
|
|
420
|
+
path = File.join(@tmpdir, "resources.yaml")
|
|
421
|
+
File.write(path, { "kind" => "Pod" }.to_yaml)
|
|
465
422
|
|
|
466
|
-
|
|
423
|
+
manifest_class.open(path).filename.should == path
|
|
424
|
+
end
|
|
467
425
|
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
426
|
+
it "skips nil documents (empty YAML docs)" do
|
|
427
|
+
path = File.join(@tmpdir, "resources.yaml")
|
|
428
|
+
File.write(path, "---\nkind: Pod\n---\n---\nkind: Service\n")
|
|
471
429
|
|
|
472
|
-
|
|
473
|
-
|
|
430
|
+
manifest_class.open(path).count.should == 2
|
|
431
|
+
end
|
|
432
|
+
end
|
|
474
433
|
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
434
|
+
describe "#write" do
|
|
435
|
+
before { @tmpdir = Dir.mktmpdir("manifest_test") }
|
|
436
|
+
after { FileUtils.rm_rf(@tmpdir) }
|
|
478
437
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
438
|
+
it "writes to the given path" do
|
|
439
|
+
path = File.join(@tmpdir, "output.yaml")
|
|
440
|
+
manifest_class.new(resource_a).write(path)
|
|
482
441
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
442
|
+
File.exist?(path).should.be.true
|
|
443
|
+
File.read(path).should.include "kind: Deployment"
|
|
444
|
+
end
|
|
486
445
|
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
446
|
+
it "writes to the stored filename when no path is given" do
|
|
447
|
+
path = File.join(@tmpdir, "stored.yaml")
|
|
448
|
+
manifest_class.new(resource_a, filename: path).write
|
|
490
449
|
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
end
|
|
450
|
+
File.exist?(path).should.be.true
|
|
451
|
+
end
|
|
494
452
|
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
453
|
+
it "updates the filename after writing to a new path" do
|
|
454
|
+
path = File.join(@tmpdir, "new.yaml")
|
|
455
|
+
manifest = manifest_class.new(resource_a)
|
|
498
456
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
457
|
+
manifest.write(path)
|
|
458
|
+
manifest.filename.should == path
|
|
459
|
+
end
|
|
502
460
|
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
461
|
+
it "returns the path written to" do
|
|
462
|
+
path = File.join(@tmpdir, "result.yaml")
|
|
463
|
+
manifest_class.new(resource_a).write(path).should == path
|
|
464
|
+
end
|
|
507
465
|
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
466
|
+
it "raises ArgumentError when no path is available" do
|
|
467
|
+
manifest = manifest_class.new(resource_a)
|
|
468
|
+
error = lambda { manifest.write }.should.raise(ArgumentError)
|
|
469
|
+
error.message.should.match(/No filename set/)
|
|
470
|
+
end
|
|
511
471
|
|
|
512
|
-
|
|
513
|
-
|
|
472
|
+
it "round-trips through write and open" do
|
|
473
|
+
path = File.join(@tmpdir, "roundtrip.yaml")
|
|
474
|
+
original = manifest_class.new(resource_a, resource_b)
|
|
514
475
|
|
|
515
|
-
|
|
476
|
+
original.write(path)
|
|
477
|
+
manifest_class.open(path).count.should == original.count
|
|
516
478
|
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
end
|
|
479
|
+
content = File.read(path)
|
|
480
|
+
content.should.include "kind: Deployment"
|
|
481
|
+
content.should.include "kind: Service"
|
|
521
482
|
end
|
|
522
483
|
end
|
|
523
484
|
end
|