logstash-filter-translate 3.2.2 → 3.3.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,18 +1,33 @@
1
1
  # encoding: utf-8
2
2
  require "logstash/devutils/rspec/spec_helper"
3
+ require 'logstash/plugin_mixins/ecs_compatibility_support/spec_helper'
3
4
  require "logstash/filters/translate"
4
5
 
6
+ module TranslateUtil
7
+ def self.build_fixture_path(filename)
8
+ File.join(File.dirname(__FILE__), "..", "fixtures", filename)
9
+ end
10
+ end
11
+
5
12
  describe LogStash::Filters::Translate do
6
13
 
7
14
  let(:config) { Hash.new }
8
15
  subject { described_class.new(config) }
9
16
 
17
+ let(:logger) { double('Logger').as_null_object }
18
+ let(:deprecation_logger) { double('DeprecationLogger').as_null_object }
19
+
20
+ before(:each) do
21
+ allow_any_instance_of(described_class).to receive(:logger).and_return(logger)
22
+ allow_any_instance_of(described_class).to receive(:deprecation_logger).and_return(deprecation_logger)
23
+ end
24
+
10
25
  describe "exact translation" do
11
26
 
12
27
  let(:config) do
13
28
  {
14
- "field" => "status",
15
- "destination" => "translation",
29
+ "source" => "status",
30
+ "target" => "translation",
16
31
  "dictionary" => [ "200", "OK",
17
32
  "300", "Redirect",
18
33
  "400", "Client Error",
@@ -24,20 +39,43 @@ describe LogStash::Filters::Translate do
24
39
 
25
40
  let(:event) { LogStash::Event.new("status" => 200) }
26
41
 
27
- it "return the exact translation" do
42
+ it "coerces field to a string then returns the exact translation" do
28
43
  subject.register
29
44
  subject.filter(event)
30
45
  expect(event.get("translation")).to eq("OK")
31
46
  end
32
47
  end
33
48
 
49
+ describe "translation fails when regex setting is false but keys are regex based" do
50
+
51
+ let(:config) do
52
+ {
53
+ "source" => "status",
54
+ "target" => "translation",
55
+ "dictionary" => [ "^2\\d\\d", "OK",
56
+ "^3\\d\\d", "Redirect",
57
+ "^4\\d\\d", "Client Error",
58
+ "^5\\d\\d", "Server Error" ],
59
+ "exact" => true,
60
+ "regex" => false
61
+ }
62
+ end
63
+
64
+ let(:event) { LogStash::Event.new("status" => 200) }
65
+
66
+ it "does not return the exact translation" do
67
+ subject.register
68
+ subject.filter(event)
69
+ expect(event.get("translation")).to be_nil
70
+ end
71
+ end
34
72
 
35
73
  describe "multi translation" do
36
74
  context "when using an inline dictionary" do
37
75
  let(:config) do
38
76
  {
39
- "field" => "status",
40
- "destination" => "translation",
77
+ "source" => "status",
78
+ "target" => "translation",
41
79
  "dictionary" => [ "200", "OK",
42
80
  "300", "Redirect",
43
81
  "400", "Client Error",
@@ -57,11 +95,11 @@ describe LogStash::Filters::Translate do
57
95
  end
58
96
 
59
97
  context "when using a file based dictionary" do
60
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "regex_union_dict.csv") }
98
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("regex_union_dict.csv") }
61
99
  let(:config) do
62
100
  {
63
- "field" => "status",
64
- "destination" => "translation",
101
+ "source" => "status",
102
+ "target" => "translation",
65
103
  "dictionary_path" => dictionary_path,
66
104
  "refresh_interval" => 0,
67
105
  "exact" => false,
@@ -83,8 +121,8 @@ describe LogStash::Filters::Translate do
83
121
  context "when using an inline dictionary" do
84
122
  let(:config) do
85
123
  {
86
- "field" => "status",
87
- "destination" => "translation",
124
+ "source" => "status",
125
+ "target" => "translation",
88
126
  "dictionary" => [ "^2[0-9][0-9]$", "OK",
89
127
  "^3[0-9][0-9]$", "Redirect",
90
128
  "^4[0-9][0-9]$", "Client Error",
@@ -104,11 +142,11 @@ describe LogStash::Filters::Translate do
104
142
  end
105
143
 
106
144
  context "when using a file based dictionary" do
107
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "regex_dict.csv") }
145
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("regex_dict.csv") }
108
146
  let(:config) do
109
147
  {
110
- "field" => "status",
111
- "destination" => "translation",
148
+ "source" => "status",
149
+ "target" => "translation",
112
150
  "dictionary_path" => dictionary_path,
113
151
  "refresh_interval" => 0,
114
152
  "exact" => true,
@@ -126,53 +164,59 @@ describe LogStash::Filters::Translate do
126
164
  end
127
165
  end
128
166
 
129
- describe "fallback value" do
130
-
131
- context "static configuration" do
132
- let(:config) do
133
- {
134
- "field" => "status",
135
- "destination" => "translation",
136
- "fallback" => "no match"
137
- }
167
+ describe "fallback value", :ecs_compatibility_support do
168
+ ecs_compatibility_matrix(:disabled, :v1) do
169
+ before(:each) do
170
+ allow_any_instance_of(described_class).to receive(:ecs_compatibility).and_return(ecs_compatibility)
138
171
  end
139
172
 
140
- let(:event) { LogStash::Event.new("status" => "200") }
141
-
142
- it "return the exact translation" do
143
- subject.register
144
- subject.filter(event)
145
- expect(event.get("translation")).to eq("no match")
173
+ context "static configuration" do
174
+ let(:config) do
175
+ {
176
+ "source" => "status",
177
+ "target" => "translation",
178
+ "fallback" => "no match"
179
+ }
180
+ end
181
+
182
+ let(:event) { LogStash::Event.new("status" => "200") }
183
+
184
+ it "return the exact translation" do
185
+ subject.register
186
+ subject.filter(event)
187
+ expect(event.get("translation")).to eq("no match")
188
+ end
146
189
  end
147
- end
148
190
 
149
- context "allow sprintf" do
150
- let(:config) do
151
- {
152
- "field" => "status",
153
- "destination" => "translation",
154
- "fallback" => "%{missing_translation}"
155
- }
191
+ context "allow sprintf" do
192
+ let(:config) do
193
+ {
194
+ "source" => "status",
195
+ "target" => "translation",
196
+ "fallback" => "%{missing_translation}"
197
+ }
198
+ end
199
+
200
+ let(:event) { LogStash::Event.new("status" => "200", "missing_translation" => "missing no match") }
201
+
202
+ it "return the exact translation" do
203
+ subject.register
204
+ subject.filter(event)
205
+ expect(event.get("translation")).to eq("missing no match")
206
+ end
156
207
  end
157
208
 
158
- let(:event) { LogStash::Event.new("status" => "200", "missing_translation" => "missing no match") }
159
-
160
- it "return the exact translation" do
161
- subject.register
162
- subject.filter(event)
163
- expect(event.get("translation")).to eq("missing no match")
164
- end
165
209
  end
166
210
  end
167
211
 
168
212
  describe "loading a dictionary" do
169
213
 
170
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict-wrong.yml") }
214
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("dict-wrong.yml") }
171
215
 
172
216
  let(:config) do
173
217
  {
174
- "field" => "status",
175
- "destination" => "translation",
218
+ "source" => "status",
219
+ "target" => "translation",
176
220
  "dictionary_path" => dictionary_path,
177
221
  "refresh_interval" => -1,
178
222
  "exact" => true,
@@ -186,7 +230,7 @@ describe LogStash::Filters::Translate do
186
230
  end
187
231
 
188
232
  context "when using a yml file" do
189
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.yml") }
233
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("dict.yml") }
190
234
  let(:event) { LogStash::Event.new("status" => "a") }
191
235
 
192
236
  it "return the exact translation" do
@@ -197,7 +241,7 @@ describe LogStash::Filters::Translate do
197
241
  end
198
242
 
199
243
  context "when using a map tagged yml file" do
200
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
244
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("tag-map-dict.yml") }
201
245
  let(:event) { LogStash::Event.new("status" => "six") }
202
246
 
203
247
  it "return the exact translation" do
@@ -208,7 +252,7 @@ describe LogStash::Filters::Translate do
208
252
  end
209
253
 
210
254
  context "when using a omap tagged yml file" do
211
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-omap-dict.yml") }
255
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("tag-omap-dict.yml") }
212
256
  let(:event) { LogStash::Event.new("status" => "nine") }
213
257
 
214
258
  it "return the exact translation" do
@@ -219,7 +263,7 @@ describe LogStash::Filters::Translate do
219
263
  end
220
264
 
221
265
  context "when using a json file" do
222
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.json") }
266
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("dict.json") }
223
267
  let(:event) { LogStash::Event.new("status" => "b") }
224
268
 
225
269
  it "return the exact translation" do
@@ -230,7 +274,7 @@ describe LogStash::Filters::Translate do
230
274
  end
231
275
 
232
276
  context "when using a csv file" do
233
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.csv") }
277
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("dict.csv") }
234
278
  let(:event) { LogStash::Event.new("status" => "c") }
235
279
 
236
280
  it "return the exact translation" do
@@ -241,7 +285,7 @@ describe LogStash::Filters::Translate do
241
285
  end
242
286
 
243
287
  context "when using an unknown file" do
244
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.other") }
288
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("dict.other") }
245
289
 
246
290
  it "raises error" do
247
291
  expect { subject.register }.to raise_error(RuntimeError, /Dictionary #{dictionary_path} has a non valid format/)
@@ -250,42 +294,42 @@ describe LogStash::Filters::Translate do
250
294
  end
251
295
 
252
296
  describe "iterate_on functionality" do
253
- describe "when iterate_on is the same as field, AKA array of values" do
254
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
255
- let(:config) do
256
- {
257
- "iterate_on" => "foo",
258
- "field" => "foo",
259
- "destination" => "baz",
260
- "fallback" => "nooo",
261
- "dictionary_path" => dictionary_path,
262
- # "override" => true,
263
- "refresh_interval" => 0
264
- }
265
- end
297
+ let(:config) do
298
+ {
299
+ "iterate_on" => "foo",
300
+ "source" => iterate_on_field,
301
+ "target" => "baz",
302
+ "fallback" => "nooo",
303
+ "dictionary_path" => dictionary_path,
304
+ # "override" => true,
305
+ "refresh_interval" => 0
306
+ }
307
+ end
308
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("tag-map-dict.yml") }
266
309
 
310
+ describe "when iterate_on is the same as field, AKA array of values" do
311
+ let(:iterate_on_field) { "foo" }
267
312
  let(:event) { LogStash::Event.new("foo" => ["nine","eight", "seven"]) }
268
- it "adds a translation to destination array for each value in field array" do
313
+ it "adds a translation to target array for each value in field array" do
269
314
  subject.register
270
315
  subject.filter(event)
271
316
  expect(event.get("baz")).to eq(["val-9-1|val-9-2", "val-8-1|val-8-2", "val-7-1|val-7-2"])
272
317
  end
273
318
  end
274
319
 
275
- describe "when iterate_on is not the same as field, AKA array of objects" do
276
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
277
- let(:config) do
278
- {
279
- "iterate_on" => "foo",
280
- "field" => "bar",
281
- "destination" => "baz",
282
- "fallback" => "nooo",
283
- "dictionary_path" => dictionary_path,
284
- # "override" => true,
285
- "refresh_interval" => 0
286
- }
320
+ describe "when iterate_on is the same as field, AKA array of values, coerces integer elements to strings" do
321
+ let(:iterate_on_field) { "foo" }
322
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("regex_union_dict.csv") }
323
+ let(:event) { LogStash::Event.new("foo" => [200, 300, 400]) }
324
+ it "adds a translation to target array for each value in field array" do
325
+ subject.register
326
+ subject.filter(event)
327
+ expect(event.get("baz")).to eq(["OK","Redirect","Client Error"])
287
328
  end
329
+ end
288
330
 
331
+ describe "when iterate_on is not the same as field, AKA array of objects" do
332
+ let(:iterate_on_field) { "bar" }
289
333
  let(:event) { LogStash::Event.new("foo" => [{"bar"=>"two"},{"bar"=>"one"}, {"bar"=>"six"}]) }
290
334
  it "adds a translation to each map" do
291
335
  subject.register
@@ -295,17 +339,31 @@ describe LogStash::Filters::Translate do
295
339
  expect(event.get("[foo][2][baz]")).to eq("val-6-1|val-6-2")
296
340
  end
297
341
  end
342
+
343
+ describe "when iterate_on is not the same as field, AKA array of objects, coerces integer values to strings" do
344
+ let(:iterate_on_field) { "bar" }
345
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("regex_union_dict.csv") }
346
+ let(:event) { LogStash::Event.new("foo" => [{"bar"=>200},{"bar"=>300}, {"bar"=>400}]) }
347
+ it "adds a translation to each map" do
348
+ subject.register
349
+ subject.filter(event)
350
+ expect(event.get("[foo][0][baz]")).to eq("OK")
351
+ expect(event.get("[foo][1][baz]")).to eq("Redirect")
352
+ expect(event.get("[foo][2][baz]")).to eq("Client Error")
353
+ end
354
+ end
298
355
  end
299
356
 
300
- describe "field and destination are the same (needs override)" do
301
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "tag-map-dict.yml") }
357
+ describe "field and destination are the same (explicit override)" do
358
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("tag-map-dict.yml") }
302
359
  let(:config) do
303
360
  {
304
361
  "field" => "foo",
305
362
  "destination" => "foo",
306
363
  "dictionary_path" => dictionary_path,
307
364
  "override" => true,
308
- "refresh_interval" => -1
365
+ "refresh_interval" => -1,
366
+ "ecs_compatibility" => 'disabled'
309
367
  }
310
368
  end
311
369
 
@@ -318,11 +376,11 @@ describe LogStash::Filters::Translate do
318
376
  end
319
377
  end
320
378
 
321
- describe "general configuration" do
322
- let(:dictionary_path) { File.join(File.dirname(__FILE__), "..", "fixtures", "dict.yml") }
379
+ context "invalid dictionary configuration" do
380
+ let(:dictionary_path) { TranslateUtil.build_fixture_path("dict.yml") }
323
381
  let(:config) do
324
382
  {
325
- "field" => "random field",
383
+ "source" => "random field",
326
384
  "dictionary" => { "a" => "b" },
327
385
  "dictionary_path" => dictionary_path,
328
386
  }
@@ -333,6 +391,77 @@ describe LogStash::Filters::Translate do
333
391
  end
334
392
  end
335
393
 
394
+ context "invalid target+destination configuration" do
395
+ let(:config) do
396
+ {
397
+ "source" => "message",
398
+ "target" => 'foo',
399
+ "destination" => 'bar',
400
+ }
401
+ end
402
+
403
+ it "raises an exception if both 'target' and 'destination' are set" do
404
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError, /remove .*?destination => /)
405
+ end
406
+ end
407
+
408
+ context "invalid source+field configuration" do
409
+ let(:config) do
410
+ {
411
+ "source" => "message",
412
+ "field" => 'foo'
413
+ }
414
+ end
415
+
416
+ it "raises an exception if both 'source' and 'field' are set" do
417
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError, /remove .*?field => /)
418
+ end
419
+ end
420
+
421
+ context "destination option" do
422
+ let(:config) do
423
+ {
424
+ "source" => "message", "destination" => 'bar', "ecs_compatibility" => 'v1'
425
+ }
426
+ end
427
+
428
+ it "sets the target" do
429
+ subject.register
430
+ expect( subject.target ).to eql 'bar'
431
+
432
+ expect(logger).to have_received(:debug).with(a_string_including "intercepting `destination`")
433
+ expect(deprecation_logger).to have_received(:deprecated).with(a_string_including "`destination` option is deprecated; use `target` instead.")
434
+ end
435
+ end
436
+
437
+ context "field option" do
438
+ let(:config) do
439
+ {
440
+ "field" => "message", "target" => 'bar'
441
+ }
442
+ end
443
+
444
+ it "sets the source" do
445
+ subject.register # does not raise
446
+ expect( subject.source ).to eql 'message'
447
+
448
+ expect(logger).to have_received(:debug).with(a_string_including "intercepting `field`")
449
+ expect(deprecation_logger).to have_received(:deprecated).with(a_string_including "`field` option is deprecated; use `source` instead.")
450
+ end
451
+ end
452
+
453
+ context "source option" do
454
+ let(:config) do
455
+ {
456
+ "target" => 'bar'
457
+ }
458
+ end
459
+
460
+ it "is required to be set" do
461
+ expect { subject.register }.to raise_error(LogStash::ConfigurationError, /provide .*?source => /)
462
+ end
463
+ end
464
+
336
465
  describe "refresh_behaviour" do
337
466
  let(:dictionary_content) { "a : 1\nb : 2\nc : 3" }
338
467
  let(:modified_content) { "a : 1\nb : 4" }
@@ -340,8 +469,8 @@ describe LogStash::Filters::Translate do
340
469
  let(:refresh_behaviour) { "merge" }
341
470
  let(:config) do
342
471
  {
343
- "field" => "status",
344
- "destination" => "translation",
472
+ "source" => "status",
473
+ "target" => "translation",
345
474
  "dictionary_path" => dictionary_path,
346
475
  "refresh_interval" => -1, # we're controlling this manually
347
476
  "exact" => true,
@@ -407,8 +536,8 @@ describe LogStash::Filters::Translate do
407
536
 
408
537
  let(:config) do
409
538
  {
410
- "field" => "status",
411
- "destination" => "translation",
539
+ "source" => "status",
540
+ "target" => "translation",
412
541
  "dictionary_path" => dictionary_path.to_path,
413
542
  "refresh_interval" => -1,
414
543
  "fallback" => "no match",
@@ -457,4 +586,72 @@ describe LogStash::Filters::Translate do
457
586
  end
458
587
  end
459
588
  end
589
+
590
+ describe "default target" do
591
+
592
+ let(:config) do
593
+ {
594
+ "source" => "message",
595
+ "dictionary" => { "foo" => "bar" }
596
+ }
597
+ end
598
+
599
+ let(:event) { LogStash::Event.new("message" => "foo") }
600
+
601
+ before { subject.register }
602
+
603
+ context "legacy mode" do
604
+
605
+ let(:config) { super().merge('ecs_compatibility' => 'disabled') }
606
+
607
+ it "uses the translation target" do
608
+ subject.filter(event)
609
+ expect(event.get("translation")).to eq("bar")
610
+ expect(event.get("message")).to eq("foo")
611
+ end
612
+
613
+ end
614
+
615
+ context "ECS mode" do
616
+
617
+ let(:config) { super().merge('ecs_compatibility' => 'v1') }
618
+
619
+ it "does in place translation" do
620
+ subject.filter(event)
621
+ expect(event.include?("translation")).to be false
622
+ expect(event.get("message")).to eq("bar")
623
+ end
624
+
625
+ end
626
+
627
+ end
628
+
629
+
630
+ describe "error handling" do
631
+
632
+ let(:config) do
633
+ {
634
+ "source" => "message",
635
+ "dictionary" => { "foo" => "bar" }
636
+ }
637
+ end
638
+
639
+ let(:event) { LogStash::Event.new("message" => "foo") }
640
+
641
+ before { subject.register }
642
+
643
+ it "handles unexpected error within filter" do
644
+ expect(subject.updater).to receive(:update).and_raise RuntimeError.new('TEST')
645
+
646
+ expect { subject.filter(event) }.to_not raise_error
647
+ end
648
+
649
+ it "propagates Java errors" do
650
+ expect(subject.updater).to receive(:update).and_raise java.lang.OutOfMemoryError.new('FAKE-OUT!')
651
+
652
+ expect { subject.filter(event) }.to raise_error(java.lang.OutOfMemoryError)
653
+ end
654
+
655
+ end
656
+
460
657
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: logstash-filter-translate
3
3
  version: !ruby/object:Gem::Version
4
- version: 3.2.2
4
+ version: 3.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Elastic
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2018-08-28 00:00:00.000000000 Z
11
+ date: 2022-06-08 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  requirement: !ruby/object:Gem::Requirement
@@ -30,6 +30,48 @@ dependencies:
30
30
  - - "<="
31
31
  - !ruby/object:Gem::Version
32
32
  version: '2.99'
33
+ - !ruby/object:Gem::Dependency
34
+ requirement: !ruby/object:Gem::Requirement
35
+ requirements:
36
+ - - "~>"
37
+ - !ruby/object:Gem::Version
38
+ version: '1.2'
39
+ name: logstash-mixin-ecs_compatibility_support
40
+ prerelease: false
41
+ type: :runtime
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.2'
47
+ - !ruby/object:Gem::Dependency
48
+ requirement: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - "~>"
51
+ - !ruby/object:Gem::Version
52
+ version: '1.0'
53
+ name: logstash-mixin-validator_support
54
+ prerelease: false
55
+ type: :runtime
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - "~>"
59
+ - !ruby/object:Gem::Version
60
+ version: '1.0'
61
+ - !ruby/object:Gem::Dependency
62
+ requirement: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - "~>"
65
+ - !ruby/object:Gem::Version
66
+ version: '1.0'
67
+ name: logstash-mixin-deprecation_logger_support
68
+ prerelease: false
69
+ type: :runtime
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: '1.0'
33
75
  - !ruby/object:Gem::Dependency
34
76
  requirement: !ruby/object:Gem::Requirement
35
77
  requirements:
@@ -164,8 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
164
206
  - !ruby/object:Gem::Version
165
207
  version: '0'
166
208
  requirements: []
167
- rubyforge_project:
168
- rubygems_version: 2.6.13
209
+ rubygems_version: 3.1.6
169
210
  signing_key:
170
211
  specification_version: 4
171
212
  summary: Replaces field contents based on a hash or YAML file