foreman_openscap 13.1.1 → 13.1.3

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.
Files changed (64) hide show
  1. checksums.yaml +4 -4
  2. data/app/assets/javascripts/foreman_openscap/locale/cs_CZ/foreman_openscap.js +67 -17
  3. data/app/assets/javascripts/foreman_openscap/locale/de/foreman_openscap.js +67 -17
  4. data/app/assets/javascripts/foreman_openscap/locale/en/foreman_openscap.js +68 -18
  5. data/app/assets/javascripts/foreman_openscap/locale/en_GB/foreman_openscap.js +67 -17
  6. data/app/assets/javascripts/foreman_openscap/locale/es/foreman_openscap.js +67 -17
  7. data/app/assets/javascripts/foreman_openscap/locale/fr/foreman_openscap.js +70 -18
  8. data/app/assets/javascripts/foreman_openscap/locale/gl/foreman_openscap.js +67 -17
  9. data/app/assets/javascripts/foreman_openscap/locale/it/foreman_openscap.js +67 -17
  10. data/app/assets/javascripts/foreman_openscap/locale/ja/foreman_openscap.js +70 -22
  11. data/app/assets/javascripts/foreman_openscap/locale/ka/foreman_openscap.js +88 -38
  12. data/app/assets/javascripts/foreman_openscap/locale/ko/foreman_openscap.js +70 -22
  13. data/app/assets/javascripts/foreman_openscap/locale/pt_BR/foreman_openscap.js +67 -17
  14. data/app/assets/javascripts/foreman_openscap/locale/ru/foreman_openscap.js +67 -17
  15. data/app/assets/javascripts/foreman_openscap/locale/sv_SE/foreman_openscap.js +67 -17
  16. data/app/assets/javascripts/foreman_openscap/locale/zh_CN/foreman_openscap.js +66 -18
  17. data/app/assets/javascripts/foreman_openscap/locale/zh_TW/foreman_openscap.js +67 -17
  18. data/app/models/concerns/foreman_openscap/host_extensions.rb +10 -4
  19. data/app/models/concerns/foreman_openscap/hostgroup_extensions.rb +7 -0
  20. data/app/models/foreman_openscap/asset.rb +21 -1
  21. data/app/models/foreman_openscap/compliance_status.rb +19 -7
  22. data/app/models/foreman_openscap/policy.rb +43 -3
  23. data/app/services/foreman_openscap/compliance_status_resetter.rb +26 -0
  24. data/lib/foreman_openscap/version.rb +1 -1
  25. data/locale/cs_CZ/LC_MESSAGES/foreman_openscap.mo +0 -0
  26. data/locale/cs_CZ/foreman_openscap.po +69 -17
  27. data/locale/de/LC_MESSAGES/foreman_openscap.mo +0 -0
  28. data/locale/de/foreman_openscap.po +69 -17
  29. data/locale/en/LC_MESSAGES/foreman_openscap.mo +0 -0
  30. data/locale/en/foreman_openscap.po +70 -18
  31. data/locale/en_GB/LC_MESSAGES/foreman_openscap.mo +0 -0
  32. data/locale/en_GB/foreman_openscap.po +69 -17
  33. data/locale/es/LC_MESSAGES/foreman_openscap.mo +0 -0
  34. data/locale/es/foreman_openscap.po +69 -17
  35. data/locale/foreman_openscap.pot +154 -70
  36. data/locale/fr/LC_MESSAGES/foreman_openscap.mo +0 -0
  37. data/locale/fr/foreman_openscap.po +72 -18
  38. data/locale/gl/LC_MESSAGES/foreman_openscap.mo +0 -0
  39. data/locale/gl/foreman_openscap.po +69 -17
  40. data/locale/it/LC_MESSAGES/foreman_openscap.mo +0 -0
  41. data/locale/it/foreman_openscap.po +69 -17
  42. data/locale/ja/LC_MESSAGES/foreman_openscap.mo +0 -0
  43. data/locale/ja/foreman_openscap.po +72 -22
  44. data/locale/ka/LC_MESSAGES/foreman_openscap.mo +0 -0
  45. data/locale/ka/foreman_openscap.po +90 -38
  46. data/locale/ko/LC_MESSAGES/foreman_openscap.mo +0 -0
  47. data/locale/ko/foreman_openscap.po +72 -22
  48. data/locale/pt_BR/LC_MESSAGES/foreman_openscap.mo +0 -0
  49. data/locale/pt_BR/foreman_openscap.po +69 -17
  50. data/locale/ru/LC_MESSAGES/foreman_openscap.mo +0 -0
  51. data/locale/ru/foreman_openscap.po +69 -17
  52. data/locale/sv_SE/LC_MESSAGES/foreman_openscap.mo +0 -0
  53. data/locale/sv_SE/foreman_openscap.po +69 -17
  54. data/locale/zh_CN/LC_MESSAGES/foreman_openscap.mo +0 -0
  55. data/locale/zh_CN/foreman_openscap.po +68 -18
  56. data/locale/zh_TW/LC_MESSAGES/foreman_openscap.mo +0 -0
  57. data/locale/zh_TW/foreman_openscap.po +69 -17
  58. data/test/test_plugin_helper.rb +7 -0
  59. data/test/unit/compliance_status_resetter_test.rb +48 -0
  60. data/test/unit/compliance_status_test.rb +53 -4
  61. data/test/unit/concerns/host_extensions_test.rb +55 -0
  62. data/test/unit/concerns/hostgroup_extensions_test.rb +64 -0
  63. data/test/unit/policy_test.rb +90 -0
  64. metadata +4 -1
@@ -3,7 +3,7 @@
3
3
  "locale_data": {
4
4
  "foreman_openscap": {
5
5
  "": {
6
- "Project-Id-Version": "foreman_openscap 12.0.1",
6
+ "Project-Id-Version": "foreman_openscap 13.1.1",
7
7
  "Report-Msgid-Bugs-To": "",
8
8
  "PO-Revision-Date": "2016-04-14 07:48+0000",
9
9
  "Last-Translator": "FIRST AUTHOR <EMAIL@ADDRESS>, 2016",
@@ -45,6 +45,9 @@
45
45
  "%s reports over time": [
46
46
  "%s 一直以來的報告"
47
47
  ],
48
+ "%s selected hosts.": [
49
+ ""
50
+ ],
48
51
  "%s spool errors detected, inspect the appropriate file directly on proxy": [
49
52
  ""
50
53
  ],
@@ -63,6 +66,9 @@
63
66
  "A summary of reports for OpenSCAP policies": [
64
67
  ""
65
68
  ],
69
+ "ALL selected hosts.": [
70
+ ""
71
+ ],
66
72
  "Actions": [
67
73
  "動作"
68
74
  ],
@@ -81,12 +87,21 @@
81
87
  "Apply policy to hosts": [
82
88
  ""
83
89
  ],
84
- "Are you sure you want to delete %s?": [
90
+ "Assign": [
85
91
  ""
86
92
  ],
87
93
  "Assign Compliance Policy": [
88
94
  "指定合規政策"
89
95
  ],
96
+ "Assign OpenSCAP Proxy": [
97
+ ""
98
+ ],
99
+ "Assign OpenSCAP Proxy for ": [
100
+ ""
101
+ ],
102
+ "Assign OpenSCAP Proxy to multiple hosts": [
103
+ ""
104
+ ],
90
105
  "Back": [
91
106
  "上一頁"
92
107
  ],
@@ -162,9 +177,6 @@
162
177
  "Compliant with the policy": [
163
178
  "合於政策"
164
179
  ],
165
- "Confirm": [
166
- ""
167
- ],
168
180
  "Content": [
169
181
  ""
170
182
  ],
@@ -306,6 +318,10 @@
306
318
  "Failed saving %s:": [
307
319
  ""
308
320
  ],
321
+ "Failed to assign OpenSCAP Proxy to %{failed} of %{total} host.": [
322
+ "",
323
+ ""
324
+ ],
309
325
  "Failed to delete %s compliance reports": [
310
326
  "刪除 %s 合規報告失敗"
311
327
  ],
@@ -393,12 +409,21 @@
393
409
  "Hosts passing this rule": [
394
410
  ""
395
411
  ],
412
+ "Hosts to explicitly exclude in the action. All other hosts will be included in the action, unless an included parameter is passed as well.": [
413
+ ""
414
+ ],
415
+ "Hosts to include in the action": [
416
+ ""
417
+ ],
396
418
  "How the policy should be deployed": [
397
419
  ""
398
420
  ],
399
421
  "ID of OpenSCAP Proxy": [
400
422
  ""
401
423
  ],
424
+ "ID of the OpenSCAP Proxy to assign to the hosts": [
425
+ ""
426
+ ],
402
427
  "In Foreman, a compliance policy checklist is defined via %s.": [
403
428
  ""
404
429
  ],
@@ -432,6 +457,9 @@
432
457
  "Latest reports for policy: %s": [
433
458
  "最新的政策報告:%s"
434
459
  ],
460
+ "Line chart": [
461
+ ""
462
+ ],
435
463
  "List ARF reports": [
436
464
  ""
437
465
  ],
@@ -447,6 +475,15 @@
447
475
  "List Tailoring files": [
448
476
  ""
449
477
  ],
478
+ "List of host ids to exclude and not perform the action on": [
479
+ ""
480
+ ],
481
+ "List of host ids to perform the action on": [
482
+ ""
483
+ ],
484
+ "Loading": [
485
+ "載入中"
486
+ ],
450
487
  "Loading...": [
451
488
  "載入中…"
452
489
  ],
@@ -492,6 +529,9 @@
492
529
  "No ARF reports for this policy": [
493
530
  "此政策沒有 ARF 報告"
494
531
  ],
532
+ "No OpenSCAP Proxies available. Please configure a Smart Proxy with the OpenSCAP feature.": [
533
+ ""
534
+ ],
495
535
  "No OpenSCAP Proxy selected.": [
496
536
  ""
497
537
  ],
@@ -513,6 +553,9 @@
513
553
  "No compliance reports were found.": [
514
554
  ""
515
555
  ],
556
+ "No data available": [
557
+ ""
558
+ ],
516
559
  "No hosts selected": [
517
560
  "未選擇主機"
518
561
  ],
@@ -576,9 +619,15 @@
576
619
  "OpenSCAP Proxy": [
577
620
  ""
578
621
  ],
622
+ "OpenSCAP Proxy is set to %s": [
623
+ ""
624
+ ],
579
625
  "OpenSCAP Proxy to use for fetching SCAP content and uploading ARF reports. Leave blank and override appropriate parameters when using proxy load balancer.": [
580
626
  ""
581
627
  ],
628
+ "OpenSCAP Proxy with id %s not found": [
629
+ ""
630
+ ],
582
631
  "Openscap Proxy": [
583
632
  ""
584
633
  ],
@@ -606,9 +655,6 @@
606
655
  "Passed|P": [
607
656
  "Passed|P"
608
657
  ],
609
- "Permission denied": [
610
- "使用權限被拒"
611
- ],
612
658
  "Please Confirm": [
613
659
  "請確認"
614
660
  ],
@@ -714,9 +760,6 @@
714
760
  "Reports": [
715
761
  "報告"
716
762
  ],
717
- "Request the following permissions from administrator: %s.": [
718
- ""
719
- ],
720
763
  "Request timed out. Please try increasing Settings -> proxy_request_timeout": [
721
764
  ""
722
765
  ],
@@ -786,6 +829,9 @@
786
829
  "Schedule": [
787
830
  "排程"
788
831
  ],
832
+ "Search string describing which hosts to perform the action on": [
833
+ ""
834
+ ],
789
835
  "Select Action": [
790
836
  "選擇動作"
791
837
  ],
@@ -867,6 +913,10 @@
867
913
  "Successfully deleted ARF report.": [
868
914
  ""
869
915
  ],
916
+ "Successfully updated %{success} host.": [
917
+ "",
918
+ ""
919
+ ],
870
920
  "Summary from %{time} ago to now": [
871
921
  "%{time} 之前到現在的摘要"
872
922
  ],
@@ -909,13 +959,13 @@
909
959
  "The job has started on selected host(s), you can check the status on the job details page.": [
910
960
  ""
911
961
  ],
912
- "There are significant differences in deployment options.": [
962
+ "The selected OpenSCAP Proxy does not have the OpenSCAP feature enabled.": [
913
963
  ""
914
964
  ],
915
- "There is no job to remediate with. Please remediate manually.": [
965
+ "There are significant differences in deployment options.": [
916
966
  ""
917
967
  ],
918
- "There was a following error when deleting %(name)s: %(error)s": [
968
+ "There is no job to remediate with. Please remediate manually.": [
919
969
  ""
920
970
  ],
921
971
  "There's no available report for this host": [
@@ -927,6 +977,9 @@
927
977
  "This profile will be used to override the one from scap content": [
928
978
  ""
929
979
  ],
980
+ "This will change previous proxy assignments on the selected hosts.": [
981
+ ""
982
+ ],
930
983
  "Title": [
931
984
  "標題"
932
985
  ],
@@ -1021,9 +1074,6 @@
1021
1074
  "Yes": [
1022
1075
  "是"
1023
1076
  ],
1024
- "You are not authorized to view the page. ": [
1025
- ""
1026
- ],
1027
1077
  "You can remediate by running a remote job or you can display a snippet for manual remediation.": [
1028
1078
  ""
1029
1079
  ],
@@ -11,6 +11,8 @@ module ForemanOpenscap
11
11
 
12
12
  base.validate :openscap_proxy_in_taxonomy, :if => Proc.new { |host| host.openscap_proxy_id.present? }
13
13
 
14
+ base.after_update :reset_compliance_status_on_hostgroup_change, :if => :saved_change_to_hostgroup_id?
15
+
14
16
  base.scoped_search :relation => :policies, :on => :name, :complete_value => true, :rename => :compliance_policy,
15
17
  :only_explicit => true, :operators => ['= '], :ext_method => :search_by_policy_name
16
18
 
@@ -129,18 +131,22 @@ module ForemanOpenscap
129
131
  report_scope
130
132
  end
131
133
 
132
- def compliance_status(options = {})
133
- @compliance_status ||= get_status(ForemanOpenscap::ComplianceStatus).to_status(options)
134
+ def compliance_status(_options = {})
135
+ @compliance_status ||= get_status(ForemanOpenscap::ComplianceStatus).status
134
136
  end
135
137
 
136
- def compliance_status_label(options = {})
137
- @compliance_status_label ||= get_status(ForemanOpenscap::ComplianceStatus).to_label(options)
138
+ def compliance_status_label(_options = {})
139
+ @compliance_status_label ||= get_status(ForemanOpenscap::ComplianceStatus).to_label
138
140
  end
139
141
 
140
142
  def openscap_proxy_in_taxonomy
141
143
  validate_association_taxonomy(:openscap_proxy)
142
144
  end
143
145
 
146
+ def reset_compliance_status_on_hostgroup_change
147
+ ForemanOpenscap::ComplianceStatusResetter.to_inconclusive([id])
148
+ end
149
+
144
150
  module ClassMethods
145
151
  def search_by_removed_from_policy(key, operator, policy_name)
146
152
  policy = ForemanOpenscap::Policy.find_by :name => policy_name
@@ -8,12 +8,19 @@ module ForemanOpenscap
8
8
  has_many :assets, :as => :assetable, :class_name => "::ForemanOpenscap::Asset", dependent: :destroy
9
9
  has_many :asset_policies, :through => :assets, :class_name => "::ForemanOpenscap::AssetPolicy"
10
10
  has_many :policies, :through => :asset_policies, :class_name => "::ForemanOpenscap::Policy"
11
+
12
+ after_update :reset_compliance_status_on_ancestry_change, :if => :saved_change_to_ancestry?
11
13
  end
12
14
 
13
15
  def inherited_policies
14
16
  find_inherited_policies :policies
15
17
  end
16
18
 
19
+ def reset_compliance_status_on_ancestry_change
20
+ host_ids = ::Host.where(:hostgroup_id => subtree_ids).pluck(:id)
21
+ ForemanOpenscap::ComplianceStatusResetter.to_inconclusive(host_ids)
22
+ end
23
+
17
24
  def openscap_proxy
18
25
  return super if ancestry.nil? || self.openscap_proxy_id.present?
19
26
  ::SmartProxy.find_by(:id => inherited_openscap_proxy_id)
@@ -1,7 +1,9 @@
1
1
  module ForemanOpenscap
2
2
  class Asset < ApplicationRecord
3
3
  has_many :asset_policies, :dependent => :delete_all
4
- has_many :policies, :through => :asset_policies
4
+ has_many :policies, :through => :asset_policies,
5
+ :after_add => :reset_compliance_status,
6
+ :after_remove => :reset_compliance_status
5
7
  belongs_to :assetable, :polymorphic => true
6
8
 
7
9
  scope :hosts, lambda { where(:assetable_type => 'Host::Base') }
@@ -20,6 +22,24 @@ module ForemanOpenscap
20
22
 
21
23
  private
22
24
 
25
+ def reset_compliance_status(_policy)
26
+ ForemanOpenscap::ComplianceStatusResetter.to_inconclusive(host_ids_reached_by_asset)
27
+ end
28
+
29
+ def host_ids_reached_by_asset
30
+ case assetable_type
31
+ when 'Host::Base'
32
+ [assetable_id]
33
+ when 'Hostgroup'
34
+ hostgroup = Hostgroup.find_by(:id => assetable_id)
35
+ return [] unless hostgroup
36
+
37
+ Host.where(:hostgroup_id => hostgroup.subtree_ids).pluck(:id)
38
+ else
39
+ []
40
+ end
41
+ end
42
+
23
43
  def fetch_asset(type)
24
44
  assetable if assetable_type == type
25
45
  end
@@ -16,8 +16,8 @@ module ForemanOpenscap
16
16
  "#{ArfReport::BIT_NUM * ArfReport::METRIC.index(status)} & #{ArfReport::MAX}"
17
17
  end
18
18
 
19
- def to_label(options = {})
20
- case to_status
19
+ def to_label(_options = {})
20
+ case status
21
21
  when COMPLIANT
22
22
  N_('Compliant')
23
23
  when INCONCLUSIVE
@@ -29,8 +29,8 @@ module ForemanOpenscap
29
29
  end
30
30
  end
31
31
 
32
- def to_global(options = {})
33
- case to_status
32
+ def to_global(_options = {})
33
+ case status
34
34
  when COMPLIANT
35
35
  ::HostStatus::Global::OK
36
36
  when INCONCLUSIVE
@@ -40,17 +40,29 @@ module ForemanOpenscap
40
40
  end
41
41
  end
42
42
 
43
- def relevant?(options = {})
43
+ def relevant?(_options = {})
44
44
  # May fail host status during migration
45
45
  return false unless ForemanOpenscap::Asset.table_exists?
46
- host.combined_policies.present?
46
+ host.policies.any? || host_has_hostgroup_policies?
47
47
  end
48
48
 
49
- def to_status(options = {})
49
+ def to_status(_options = {})
50
50
  latest_reports = host.combined_policies.flat_map { |p| host.last_report_for_policy p }
51
51
  return INCOMPLIANT if latest_reports.any?(&:failed?)
52
52
  return INCONCLUSIVE if latest_reports.any?(&:othered?)
53
53
  COMPLIANT
54
54
  end
55
+
56
+ private
57
+
58
+ def host_has_hostgroup_policies?
59
+ return false if host.hostgroup_id.blank?
60
+
61
+ ids = [host.hostgroup_id] + host.hostgroup.ancestor_ids
62
+ ForemanOpenscap::Policy.joins(:assets)
63
+ .where('foreman_openscap_assets.assetable_type' => 'Hostgroup',
64
+ 'foreman_openscap_assets.assetable_id' => ids)
65
+ .exists?
66
+ end
55
67
  end
56
68
  end
@@ -42,6 +42,7 @@ module ForemanOpenscap
42
42
  validate :valid_tailoring, :valid_tailoring_profile, :no_mixed_deployments
43
43
  validate :valid_cron_line, :valid_weekday, :valid_day_of_month, :if => Proc.new { |policy| policy.should_validate?('Schedule') }
44
44
  after_save :assign_policy_to_hostgroups
45
+ after_save :reset_changed_assignment_hosts_to_inconclusive
45
46
  # before_destroy - ensure that the policy has no hostgroups, or classes
46
47
 
47
48
  default_scope do
@@ -179,10 +180,14 @@ module ForemanOpenscap
179
180
  :assetable_type => 'Host::Base',
180
181
  :assetable_id => hosts.map(&:id),
181
182
  :foreman_openscap_asset_policies => { :policy_id => id }
182
- ).pluck(:id)
183
+ ).pluck(:id, :assetable_id)
183
184
 
184
- self.asset_ids = self.asset_ids - policy_host_assets
185
- ForemanOpenscap::Asset.where(:id => policy_host_assets).destroy_all
185
+ asset_ids = policy_host_assets.map(&:first)
186
+ host_ids_to_reset = policy_host_assets.map(&:second)
187
+
188
+ self.asset_ids = self.asset_ids - asset_ids
189
+ ForemanOpenscap::Asset.where(:id => asset_ids).destroy_all
190
+ ForemanOpenscap::ComplianceStatusResetter.to_inconclusive(host_ids_to_reset)
186
191
  end
187
192
 
188
193
  def to_enc
@@ -251,12 +256,47 @@ module ForemanOpenscap
251
256
  end
252
257
 
253
258
  def assign_ids(ids, class_name)
259
+ before_host_ids = host_ids_reached_by_assignments(class_name)
254
260
  new_assets = ids.uniq.reject { |id| id.respond_to?(:empty?) && id.empty? }.reduce([]) do |memo, id|
255
261
  memo << assets.where(:assetable_type => class_name, :assetable_id => id).first_or_initialize
256
262
  end
257
263
  complimentary_class_name = class_name == 'Host::Base' ? 'Hostgroup' : 'Host::Base'
258
264
  existing_assets = self.assets.select { |assigned_asset| assigned_asset.assetable_type == complimentary_class_name }
259
265
  self.assets = existing_assets + new_assets
266
+ after_host_ids = host_ids_reached_by_assignments(class_name, self.assets)
267
+
268
+ return unless host_assignment_changed?(before_host_ids, after_host_ids)
269
+
270
+ remember_hosts_for_compliance_status_reset(before_host_ids + after_host_ids)
271
+ end
272
+
273
+ def host_ids_reached_by_assignments(class_name, assigned_assets = assets)
274
+ assetable_ids = assigned_assets.select { |asset| asset.assetable_type == class_name }.map { |asset| asset.assetable_id.to_i }
275
+
276
+ if class_name == 'Host::Base'
277
+ assetable_ids
278
+ else
279
+ hostgroup_ids = ::Hostgroup.where(:id => assetable_ids).flat_map(&:subtree_ids).uniq
280
+ ::Host.where(:hostgroup_id => hostgroup_ids).pluck(:id)
281
+ end
282
+ end
283
+
284
+ def remember_hosts_for_compliance_status_reset(host_ids)
285
+ @compliance_status_reset_host_ids ||= []
286
+ @compliance_status_reset_host_ids.concat(host_ids)
287
+ end
288
+
289
+ def host_assignment_changed?(before_host_ids, after_host_ids)
290
+ before_host_ids.to_set != after_host_ids.to_set
291
+ end
292
+
293
+ def reset_changed_assignment_hosts_to_inconclusive
294
+ host_ids = Array(@compliance_status_reset_host_ids).compact.uniq
295
+ return if host_ids.empty?
296
+
297
+ ForemanOpenscap::ComplianceStatusResetter.to_inconclusive(host_ids)
298
+ ensure
299
+ @compliance_status_reset_host_ids = nil
260
300
  end
261
301
 
262
302
  def no_mixed_deployments
@@ -0,0 +1,26 @@
1
+ module ForemanOpenscap
2
+ class ComplianceStatusResetter
3
+ def self.to_inconclusive(host_ids)
4
+ host_ids = Array(host_ids).compact.uniq
5
+ return if host_ids.empty?
6
+
7
+ now = Time.current
8
+ rows = host_ids.map do |host_id|
9
+ {
10
+ :host_id => host_id,
11
+ :type => ComplianceStatus.to_s,
12
+ :status => ComplianceStatus::INCONCLUSIVE,
13
+ :reported_at => now
14
+ }
15
+ end
16
+
17
+ ComplianceStatus.upsert_all(rows, :unique_by => %i[type host_id], :update_only => %i[status reported_at])
18
+
19
+ refresh_global_statuses(host_ids)
20
+ end
21
+
22
+ def self.refresh_global_statuses(host_ids)
23
+ ::Host::Managed.where(:id => host_ids).find_each(&:refresh_global_status!)
24
+ end
25
+ end
26
+ end
@@ -1,3 +1,3 @@
1
1
  module ForemanOpenscap
2
- VERSION = '13.1.1'.freeze
2
+ VERSION = '13.1.3'.freeze
3
3
  end