kube_schema 1.11.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 +127 -2
- 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 +42 -5
- data/flake.nix +28 -21
- data/gemset.nix +983 -0
- 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 +289 -310
- data/lib/kube/schema/resource.rb +352 -356
- 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 +50 -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,91 +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
|
-
#
|
|
90
|
-
#
|
|
91
|
-
|
|
78
|
+
# Supports pattern matching, e.g. `manifest => [resource, *]`.
|
|
79
|
+
# @returns [Array(Hash)] the same array as #to_a.
|
|
80
|
+
def deconstruct = to_a
|
|
92
81
|
|
|
93
|
-
#
|
|
94
|
-
|
|
95
|
-
#
|
|
96
|
-
# Each document's "kind" is resolved via Kube::Schema.parse to
|
|
97
|
-
# produce the correct Resource subclass (e.g. Deployment, Service).
|
|
98
|
-
# Documents without a recognized "kind" fall back to a bare Resource.
|
|
82
|
+
# File I/O.
|
|
83
|
+
|
|
84
|
+
# Parse multi-document YAML into a Manifest of typed Resources.
|
|
99
85
|
#
|
|
86
|
+
# @example
|
|
100
87
|
# yaml = `helm template my-release bitnami/nginx`
|
|
101
88
|
# manifest = Kube::Schema::Manifest.parse(yaml)
|
|
102
89
|
# manifest.first.class #=> Kube::Schema::Resource (Deployment subclass)
|
|
103
90
|
#
|
|
104
|
-
# @
|
|
105
|
-
# @
|
|
91
|
+
# @parameter yaml_string [String] multi-document YAML.
|
|
92
|
+
# @returns [Manifest] the parsed resources.
|
|
106
93
|
def self.parse(yaml_string)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
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
|
|
112
99
|
|
|
113
100
|
resources = docs.compact.map { |doc| parse_doc(doc) }
|
|
114
101
|
new(*resources)
|
|
115
102
|
end
|
|
116
103
|
|
|
117
|
-
# Read a YAML file
|
|
118
|
-
# and return a Manifest populated with typed Resource objects.
|
|
104
|
+
# Read a YAML file into a Manifest of typed Resources.
|
|
119
105
|
#
|
|
106
|
+
# @example
|
|
120
107
|
# manifest = Kube::Schema::Manifest.open("deploy.yaml")
|
|
121
108
|
# manifest.count #=> 3
|
|
122
109
|
# manifest.filename #=> "deploy.yaml"
|
|
123
110
|
#
|
|
124
|
-
# @
|
|
125
|
-
# @
|
|
111
|
+
# @parameter path [String] path to a YAML file.
|
|
112
|
+
# @returns [Manifest] the parsed resources, with filename set.
|
|
126
113
|
def self.open(path)
|
|
127
114
|
contents = File.read(path)
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
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
|
|
134
120
|
|
|
135
121
|
resources = docs.compact.map { |doc| parse_doc(doc) }
|
|
136
122
|
new(*resources, filename: path)
|
|
137
123
|
end
|
|
138
124
|
|
|
139
125
|
# Write the manifest to a file as multi-document YAML.
|
|
140
|
-
#
|
|
141
|
-
# @
|
|
142
|
-
#
|
|
143
|
-
# @return [String] the path written to
|
|
144
|
-
# @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.
|
|
145
129
|
def write(path = nil)
|
|
146
130
|
path ||= @filename
|
|
147
|
-
|
|
131
|
+
if path.nil?
|
|
132
|
+
raise ArgumentError, "No filename set. Pass a path to #write or use Manifest.open."
|
|
133
|
+
end
|
|
148
134
|
|
|
149
135
|
File.write(path, to_yaml)
|
|
150
136
|
@filename = path
|
|
@@ -152,10 +138,9 @@ module Kube
|
|
|
152
138
|
end
|
|
153
139
|
|
|
154
140
|
# Parse a single YAML document hash into a typed Resource.
|
|
155
|
-
#
|
|
156
|
-
# @
|
|
157
|
-
# @
|
|
158
|
-
# @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.
|
|
159
144
|
def self.parse_doc(doc)
|
|
160
145
|
Kube::Schema.parse(doc)
|
|
161
146
|
end
|
|
@@ -164,7 +149,8 @@ module Kube
|
|
|
164
149
|
private
|
|
165
150
|
|
|
166
151
|
# Deep-stringify keys for clean YAML output.
|
|
167
|
-
#
|
|
152
|
+
# @parameter obj [Object] a Hash, Array, or scalar to stringify.
|
|
153
|
+
# @returns [Object] the same structure with string keys.
|
|
168
154
|
def deep_stringify(obj)
|
|
169
155
|
case obj
|
|
170
156
|
when Hash
|
|
@@ -181,325 +167,318 @@ module Kube
|
|
|
181
167
|
end
|
|
182
168
|
end
|
|
183
169
|
|
|
184
|
-
if __FILE__ == $0
|
|
185
|
-
require "bundler/setup"
|
|
186
|
-
require "rspec/autorun"
|
|
187
|
-
require "kube/schema"
|
|
188
|
-
require "tmpdir"
|
|
189
|
-
|
|
190
|
-
RSpec.describe Kube::Schema::Manifest do
|
|
191
|
-
let(:resource_a) { Kube::Schema["Deployment"].new }
|
|
192
|
-
let(:resource_b) { Kube::Schema["Service"].new }
|
|
193
|
-
let(:resource_c) { Kube::Schema["Namespace"].new }
|
|
194
|
-
|
|
195
|
-
describe "#initialize" do
|
|
196
|
-
it "creates an empty manifest with no arguments" do
|
|
197
|
-
manifest = described_class.new
|
|
198
|
-
expect(manifest.count).to eq(0)
|
|
199
|
-
end
|
|
200
170
|
|
|
201
|
-
|
|
202
|
-
manifest = described_class.new(resource_a, resource_b)
|
|
203
|
-
expect(manifest.count).to eq(2)
|
|
204
|
-
end
|
|
171
|
+
__END__
|
|
205
172
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
end
|
|
173
|
+
require "kube/schema"
|
|
174
|
+
require "tmpdir"
|
|
175
|
+
require "fileutils"
|
|
210
176
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
end
|
|
216
|
-
end
|
|
217
|
-
|
|
218
|
-
describe "#<<" do
|
|
219
|
-
subject(:manifest) { described_class.new }
|
|
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
|
|
220
181
|
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
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
|
|
226
187
|
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
end
|
|
188
|
+
it "accepts resources as arguments" do
|
|
189
|
+
manifest_class.new(resource_a, resource_b).count.should == 2
|
|
190
|
+
end
|
|
231
191
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
expect(manifest.count).to eq(2)
|
|
236
|
-
expect(manifest.to_a).to contain_exactly(resource_a, resource_b)
|
|
237
|
-
end
|
|
192
|
+
it "accepts a filename keyword argument" do
|
|
193
|
+
manifest_class.new(filename: "/tmp/test.yaml").filename.should == "/tmp/test.yaml"
|
|
194
|
+
end
|
|
238
195
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
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
|
|
199
|
+
end
|
|
200
|
+
end
|
|
243
201
|
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
247
209
|
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
210
|
+
it "returns self for chaining" do
|
|
211
|
+
manifest = manifest_class.new
|
|
212
|
+
(manifest << resource_a).should.be.identical_to manifest
|
|
213
|
+
end
|
|
251
214
|
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
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]
|
|
256
220
|
end
|
|
257
221
|
|
|
258
|
-
|
|
259
|
-
|
|
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
|
|
260
227
|
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
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
|
|
264
233
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
end
|
|
234
|
+
it "raises ArgumentError for a String" do
|
|
235
|
+
manifest = manifest_class.new
|
|
236
|
+
lambda { manifest << "not a resource" }.should.raise(ArgumentError)
|
|
237
|
+
end
|
|
270
238
|
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
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
|
|
275
245
|
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
246
|
+
describe "Enumerable" do
|
|
247
|
+
it "includes Enumerable" do
|
|
248
|
+
manifest_class.should.include Enumerable
|
|
249
|
+
end
|
|
280
250
|
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
end
|
|
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]
|
|
286
255
|
end
|
|
287
256
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
expect(manifest.length).to eq(2)
|
|
293
|
-
end
|
|
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
|
|
294
261
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
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
|
|
299
265
|
end
|
|
300
266
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
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
|
|
306
274
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
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
|
|
311
280
|
end
|
|
312
281
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
282
|
+
it "returns 0 for an empty manifest" do
|
|
283
|
+
manifest_class.new.size.should == 0
|
|
284
|
+
end
|
|
285
|
+
end
|
|
317
286
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
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]
|
|
322
291
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
292
|
+
head.should == resource_a
|
|
293
|
+
rest.should == [resource_b]
|
|
294
|
+
end
|
|
326
295
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
|
332
301
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
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]
|
|
337
307
|
|
|
338
|
-
it
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
docs = if YAML.respond_to?(:safe_load_stream)
|
|
342
|
-
YAML.safe_load_stream(yaml_output)
|
|
343
|
-
else
|
|
344
|
-
YAML.load_stream(yaml_output)
|
|
345
|
-
end
|
|
346
|
-
expect(docs.length).to eq(2)
|
|
347
|
-
expect(docs[0]["kind"]).to eq("Deployment")
|
|
348
|
-
expect(docs[1]["kind"]).to eq("Service")
|
|
349
|
-
end
|
|
308
|
+
# Verify it's a copy, not the internal array
|
|
309
|
+
arr << resource_c
|
|
310
|
+
manifest.count.should == 2
|
|
350
311
|
end
|
|
312
|
+
end
|
|
351
313
|
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
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
|
|
356
317
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
318
|
+
yaml.should.include "---"
|
|
319
|
+
yaml.should.include "kind: Deployment"
|
|
320
|
+
yaml.should.include "kind: Service"
|
|
321
|
+
end
|
|
361
322
|
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
{ "kind" => "Deployment", "apiVersion" => "apps/v1" },
|
|
365
|
-
{ "kind" => "Service", "apiVersion" => "v1" }
|
|
366
|
-
].map(&:to_yaml).join("")
|
|
323
|
+
it "uses string keys (not symbol keys) in output" do
|
|
324
|
+
yaml = manifest_class.new(resource_a).to_yaml
|
|
367
325
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
|
371
331
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
332
|
+
it "returns empty string for an empty manifest" do
|
|
333
|
+
manifest_class.new.to_yaml.should == ""
|
|
334
|
+
end
|
|
375
335
|
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
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
|
|
380
348
|
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
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)
|
|
386
353
|
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
354
|
+
manifest.count.should == 1
|
|
355
|
+
manifest.first.should.be.kind_of Kube::Schema::Resource
|
|
356
|
+
manifest.first.kind.should == "Pod"
|
|
357
|
+
end
|
|
391
358
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
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("")
|
|
397
364
|
|
|
398
|
-
|
|
399
|
-
manifest = described_class.parse("---\n")
|
|
400
|
-
expect(manifest.count).to eq(0)
|
|
401
|
-
end
|
|
365
|
+
manifest_class.parse(yaml).count.should == 2
|
|
402
366
|
end
|
|
403
367
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
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
|
|
407
371
|
|
|
408
|
-
|
|
372
|
+
resource.class.defaults.should == { "apiVersion" => "apps/v1", "kind" => "Deployment" }
|
|
373
|
+
resource.kind.should == "Deployment"
|
|
374
|
+
end
|
|
409
375
|
|
|
410
|
-
|
|
411
|
-
|
|
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
|
|
412
380
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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
|
|
417
385
|
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
].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
|
|
423
390
|
|
|
424
|
-
|
|
391
|
+
it "returns an empty manifest for empty YAML" do
|
|
392
|
+
manifest_class.parse("---\n").count.should == 0
|
|
393
|
+
end
|
|
394
|
+
end
|
|
425
395
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
396
|
+
describe ".open" do
|
|
397
|
+
before { @tmpdir = Dir.mktmpdir("manifest_test") }
|
|
398
|
+
after { FileUtils.rm_rf(@tmpdir) }
|
|
429
399
|
|
|
430
|
-
|
|
431
|
-
|
|
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)
|
|
432
403
|
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
404
|
+
manifest = manifest_class.open(path)
|
|
405
|
+
manifest.count.should == 1
|
|
406
|
+
manifest.first.should.be.kind_of Kube::Schema::Resource
|
|
407
|
+
end
|
|
436
408
|
|
|
437
|
-
|
|
438
|
-
|
|
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(""))
|
|
439
415
|
|
|
440
|
-
|
|
441
|
-
expect(manifest.count).to eq(2)
|
|
442
|
-
end
|
|
416
|
+
manifest_class.open(path).count.should == 2
|
|
443
417
|
end
|
|
444
418
|
|
|
445
|
-
|
|
446
|
-
|
|
419
|
+
it "sets the filename" do
|
|
420
|
+
path = File.join(@tmpdir, "resources.yaml")
|
|
421
|
+
File.write(path, { "kind" => "Pod" }.to_yaml)
|
|
447
422
|
|
|
448
|
-
|
|
423
|
+
manifest_class.open(path).filename.should == path
|
|
424
|
+
end
|
|
449
425
|
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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")
|
|
453
429
|
|
|
454
|
-
|
|
455
|
-
|
|
430
|
+
manifest_class.open(path).count.should == 2
|
|
431
|
+
end
|
|
432
|
+
end
|
|
456
433
|
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
434
|
+
describe "#write" do
|
|
435
|
+
before { @tmpdir = Dir.mktmpdir("manifest_test") }
|
|
436
|
+
after { FileUtils.rm_rf(@tmpdir) }
|
|
460
437
|
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
438
|
+
it "writes to the given path" do
|
|
439
|
+
path = File.join(@tmpdir, "output.yaml")
|
|
440
|
+
manifest_class.new(resource_a).write(path)
|
|
464
441
|
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
442
|
+
File.exist?(path).should.be.true
|
|
443
|
+
File.read(path).should.include "kind: Deployment"
|
|
444
|
+
end
|
|
468
445
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
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
|
|
472
449
|
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
end
|
|
450
|
+
File.exist?(path).should.be.true
|
|
451
|
+
end
|
|
476
452
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
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)
|
|
480
456
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
457
|
+
manifest.write(path)
|
|
458
|
+
manifest.filename.should == path
|
|
459
|
+
end
|
|
484
460
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
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
|
|
489
465
|
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
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
|
|
493
471
|
|
|
494
|
-
|
|
495
|
-
|
|
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)
|
|
496
475
|
|
|
497
|
-
|
|
476
|
+
original.write(path)
|
|
477
|
+
manifest_class.open(path).count.should == original.count
|
|
498
478
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
end
|
|
479
|
+
content = File.read(path)
|
|
480
|
+
content.should.include "kind: Deployment"
|
|
481
|
+
content.should.include "kind: Service"
|
|
503
482
|
end
|
|
504
483
|
end
|
|
505
484
|
end
|