inspec-core 4.22.8 → 4.23.15
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile +2 -1
- data/inspec-core.gemspec +3 -5
- data/lib/bundles/inspec-supermarket/cli.rb +1 -1
- data/lib/inspec/base_cli.rb +11 -1
- data/lib/inspec/cli.rb +4 -2
- data/lib/inspec/config.rb +19 -1
- data/lib/inspec/input.rb +4 -3
- data/lib/inspec/input_registry.rb +7 -1
- data/lib/inspec/plugin/v2/plugin_types/reporter.rb +4 -31
- data/lib/inspec/reporters.rb +0 -3
- data/lib/inspec/reporters/automate.rb +3 -3
- data/lib/inspec/reporters/base.rb +7 -29
- data/lib/inspec/resources/apt.rb +5 -5
- data/lib/inspec/resources/bridge.rb +1 -1
- data/lib/inspec/resources/host.rb +1 -1
- data/lib/inspec/resources/mysql_session.rb +9 -5
- data/lib/inspec/resources/postgres.rb +1 -1
- data/lib/inspec/resources/postgres_session.rb +5 -3
- data/lib/inspec/resources/processes.rb +1 -1
- data/lib/inspec/resources/windows_firewall.rb +110 -0
- data/lib/inspec/resources/windows_firewall_rule.rb +137 -0
- data/lib/inspec/rule.rb +8 -8
- data/lib/inspec/run_data/profile.rb +3 -2
- data/lib/inspec/schema/exec_json.rb +1 -1
- data/lib/inspec/shell.rb +3 -3
- data/lib/inspec/utils/parser.rb +1 -1
- data/lib/inspec/utils/run_data_filters.rb +104 -0
- data/lib/inspec/version.rb +1 -1
- data/lib/plugins/inspec-compliance/lib/inspec-compliance/api.rb +4 -4
- data/lib/plugins/inspec-compliance/lib/inspec-compliance/cli.rb +1 -1
- data/lib/plugins/inspec-init/templates/profiles/aws/README.md +1 -1
- data/lib/plugins/inspec-reporter-html2/README.md +1 -1
- data/lib/plugins/inspec-reporter-junit/README.md +17 -0
- data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit.rb +21 -0
- data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit/reporter.rb +155 -0
- data/lib/plugins/inspec-reporter-junit/lib/inspec-reporter-junit/version.rb +5 -0
- data/lib/plugins/shared/core_plugin_test_helper.rb +0 -16
- metadata +17 -34
- data/README.md +0 -474
- data/lib/inspec/reporters/junit.rb +0 -77
@@ -19,7 +19,7 @@ module Inspec::Resources
|
|
19
19
|
@conf_path = File.join @conf_dir, "postgresql.conf"
|
20
20
|
else
|
21
21
|
@conf_path = nil
|
22
|
-
|
22
|
+
skip_resource "Seems like PostgreSQL is not installed on your system"
|
23
23
|
end
|
24
24
|
end
|
25
25
|
|
@@ -26,12 +26,13 @@ module Inspec::Resources
|
|
26
26
|
supports platform: "windows"
|
27
27
|
desc "Use the postgres_session InSpec audit resource to test SQL commands run against a PostgreSQL database."
|
28
28
|
example <<~EXAMPLE
|
29
|
-
sql = postgres_session('username', 'password', 'host')
|
29
|
+
sql = postgres_session('username', 'password', 'host', 'port')
|
30
30
|
query('sql_query', ['database_name'])` contains the query and (optional) database to execute
|
31
31
|
|
32
32
|
# default values:
|
33
33
|
# username: 'postgres'
|
34
34
|
# host: 'localhost'
|
35
|
+
# port: 5432
|
35
36
|
# db: databse == db_user running the sql query
|
36
37
|
|
37
38
|
describe sql.query('SELECT * FROM pg_shadow WHERE passwd IS NULL;') do
|
@@ -39,10 +40,11 @@ module Inspec::Resources
|
|
39
40
|
end
|
40
41
|
EXAMPLE
|
41
42
|
|
42
|
-
def initialize(user, pass, host = nil)
|
43
|
+
def initialize(user, pass, host = nil, port = nil)
|
43
44
|
@user = user || "postgres"
|
44
45
|
@pass = pass
|
45
46
|
@host = host || "localhost"
|
47
|
+
@port = port || 5432
|
46
48
|
end
|
47
49
|
|
48
50
|
def query(query, db = [])
|
@@ -64,7 +66,7 @@ module Inspec::Resources
|
|
64
66
|
|
65
67
|
def create_psql_cmd(query, db = [])
|
66
68
|
dbs = db.map { |x| "-d #{x}" }.join(" ")
|
67
|
-
"PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -h #{@host} -A -t -c #{escaped_query(query)}"
|
69
|
+
"PGPASSWORD='#{@pass}' psql -U #{@user} #{dbs} -h #{@host} -p #{@port} -A -t -c #{escaped_query(query)}"
|
68
70
|
end
|
69
71
|
end
|
70
72
|
end
|
@@ -138,7 +138,7 @@ module Inspec::Resources
|
|
138
138
|
command: 8,
|
139
139
|
}
|
140
140
|
else
|
141
|
-
command = "ps
|
141
|
+
command = "ps wwaxo label,pid,pcpu,pmem,vsz,rss,tty,stat,start,time,user:32,command"
|
142
142
|
regex = /^(.+?)\s+(\d+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+([^ ]+)\s+(\w{3} \d{2}|\d{2}:\d{2}:\d{2})\s+([^ ]+)\s+([^ ]+)\s+(.*)$/
|
143
143
|
field_map = {
|
144
144
|
label: 1,
|
@@ -0,0 +1,110 @@
|
|
1
|
+
module Inspec::Resources
|
2
|
+
class WindowsFirewall < Inspec.resource(1)
|
3
|
+
name "windows_firewall"
|
4
|
+
supports platform: "windows"
|
5
|
+
desc "Check properties of the Windows Firewall for a specific profile."
|
6
|
+
example <<~EXAMPLE
|
7
|
+
describe windows_firewall("Public") do
|
8
|
+
it { should be_enabled }
|
9
|
+
its("default_inbound_action") { should_not cmp "NotConfigured" }
|
10
|
+
its("num_rules") { should be 19 }
|
11
|
+
end
|
12
|
+
EXAMPLE
|
13
|
+
|
14
|
+
def initialize(profile = "Public")
|
15
|
+
@profile = profile
|
16
|
+
@state = {}
|
17
|
+
|
18
|
+
load_profile_cmd = load_firewall_profile(profile)
|
19
|
+
cmd = inspec.powershell(load_profile_cmd)
|
20
|
+
|
21
|
+
@state = JSON.load(cmd.stdout) unless cmd.stdout.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"Windows Firewall (Profile #{@profile})"
|
26
|
+
end
|
27
|
+
|
28
|
+
def exist?
|
29
|
+
!@state.empty?
|
30
|
+
end
|
31
|
+
|
32
|
+
def enabled?
|
33
|
+
@state["enabled"]
|
34
|
+
end
|
35
|
+
|
36
|
+
def default_inbound_allowed?
|
37
|
+
@state["default_inbound_action"] == "Allow"
|
38
|
+
end
|
39
|
+
|
40
|
+
def default_outbound_allowed?
|
41
|
+
@state["default_outbound_action"] == "Allow"
|
42
|
+
end
|
43
|
+
|
44
|
+
# Access to return values from Powershell via `its("PROPERTY")` and `have_PROPERTY "VALUE"`
|
45
|
+
def method_missing(method_name, *arguments, &_block)
|
46
|
+
property = normalize_for_have_access(method_name)
|
47
|
+
|
48
|
+
if method_name.to_s.start_with? "has_"
|
49
|
+
expected_value = arguments.first
|
50
|
+
respond_to_have(property, expected_value)
|
51
|
+
else
|
52
|
+
access_property(property)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def respond_to_missing?(method_name, _include_private = false)
|
57
|
+
property = normalize_for_have_access(method_name)
|
58
|
+
|
59
|
+
@state.key? property
|
60
|
+
end
|
61
|
+
|
62
|
+
private
|
63
|
+
|
64
|
+
def normalize_for_have_access(property)
|
65
|
+
property.to_s
|
66
|
+
.delete_prefix("has_")
|
67
|
+
.delete_suffix("?")
|
68
|
+
end
|
69
|
+
|
70
|
+
def access_property(property)
|
71
|
+
@state[property]
|
72
|
+
end
|
73
|
+
|
74
|
+
def respond_to_have(property, value)
|
75
|
+
@state[property] == value
|
76
|
+
end
|
77
|
+
|
78
|
+
def load_firewall_profile(profile_name)
|
79
|
+
<<-EOH
|
80
|
+
Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
|
81
|
+
$profile = Get-NetFirewallProfile -Name "#{profile_name}"
|
82
|
+
$count = @($profile | Get-NetFirewallRule).Count
|
83
|
+
([PSCustomObject]@{
|
84
|
+
profile_name = $profile.Name
|
85
|
+
profile = $profile.Profile.ToString()
|
86
|
+
description = $profile.Description
|
87
|
+
enabled = [bool]::Parse($profile.Enabled.ToString())
|
88
|
+
default_inbound_action = $profile.DefaultInboundAction.ToString()
|
89
|
+
default_outbound_action = $profile.DefaultOutboundAction.ToString()
|
90
|
+
|
91
|
+
allow_inbound_rules = $profile.AllowInboundRules.ToString()
|
92
|
+
allow_local_firewall_rules = $profile.AllowLocalFirewallRules.ToString()
|
93
|
+
allow_local_ipsec_rules = $profile.AllowLocalIPsecRules.ToString()
|
94
|
+
allow_user_apps = $profile.AllowUserApps.ToString()
|
95
|
+
allow_user_ports = $profile.AllowUserPorts.ToString()
|
96
|
+
allow_unicast_response_to_multicast = $profile.AllowUnicastResponseToMulticast.ToString()
|
97
|
+
|
98
|
+
notify_on_listen = $profile.NotifyOnListen.ToString()
|
99
|
+
enable_stealth_mode_for_ipsec = $profile.EnableStealthModeForIPsec.ToString()
|
100
|
+
log_max_size_kilobytes = $profile.LogMaxSizeKilobytes
|
101
|
+
log_allowed = $profile.LogAllowed.ToString()
|
102
|
+
log_blocked = $profile.LogBlocked.ToString()
|
103
|
+
log_ignored = $profile.LogIgnored.ToString()
|
104
|
+
|
105
|
+
num_rules = $count
|
106
|
+
}) | ConvertTo-Json
|
107
|
+
EOH
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
module Inspec::Resources
|
2
|
+
class WindowsFirewallRule < Inspec.resource(1)
|
3
|
+
name "windows_firewall_rule"
|
4
|
+
supports platform: "windows"
|
5
|
+
desc "Check properties of a Windows Firewall rule."
|
6
|
+
example <<~EXAMPLE
|
7
|
+
describe windows_firewall_rule("Name") do
|
8
|
+
it { should exist }
|
9
|
+
it { should be_enabled }
|
10
|
+
|
11
|
+
it { should be_outbound}
|
12
|
+
it { should be_tcp }
|
13
|
+
it { should have_remote_port 80 }
|
14
|
+
end
|
15
|
+
EXAMPLE
|
16
|
+
|
17
|
+
def initialize(name)
|
18
|
+
@name = name
|
19
|
+
@state = {}
|
20
|
+
|
21
|
+
query = load_firewall_state(name)
|
22
|
+
cmd = inspec.powershell(query)
|
23
|
+
@state = JSON.load(cmd.stdout) unless cmd.stdout.empty?
|
24
|
+
end
|
25
|
+
|
26
|
+
def to_s
|
27
|
+
"Windows Firewall Rule #{@name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def exist?
|
31
|
+
!@state.empty?
|
32
|
+
end
|
33
|
+
|
34
|
+
def enabled?
|
35
|
+
@state["enabled"]
|
36
|
+
end
|
37
|
+
|
38
|
+
def allowed?
|
39
|
+
@state["action"] == "Allow"
|
40
|
+
end
|
41
|
+
|
42
|
+
def inbound?
|
43
|
+
@state["direction"] == "Inbound"
|
44
|
+
end
|
45
|
+
|
46
|
+
def outbound?
|
47
|
+
! inbound?
|
48
|
+
end
|
49
|
+
|
50
|
+
def tcp?
|
51
|
+
@state["protocol"] == "TCP"
|
52
|
+
end
|
53
|
+
|
54
|
+
def udp?
|
55
|
+
@state["protocol"] == "UDP"
|
56
|
+
end
|
57
|
+
|
58
|
+
def icmp?
|
59
|
+
@state["protocol"].start_with? "ICMP"
|
60
|
+
end
|
61
|
+
|
62
|
+
def icmpv4?
|
63
|
+
@state["protocol"] == "ICMPv4"
|
64
|
+
end
|
65
|
+
|
66
|
+
def icmpv6?
|
67
|
+
@state["protocol"] == "ICMPv6"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Access to return values from Powershell via `its("PROPERTY")` and `have_PROPERTY? "VALUE"`
|
71
|
+
def method_missing(method_name, *arguments, &_block)
|
72
|
+
property = normalize_for_have_access(method_name)
|
73
|
+
|
74
|
+
if method_name.to_s.start_with? "has_"
|
75
|
+
expected_value = arguments.first
|
76
|
+
respond_to_have(property, expected_value)
|
77
|
+
else
|
78
|
+
access_property(property)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def respond_to_missing?(method_name, _include_private = false)
|
83
|
+
property = normalize_for_have_access(method_name)
|
84
|
+
|
85
|
+
@state.key? property
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
|
90
|
+
def normalize_for_have_access(property)
|
91
|
+
property.to_s
|
92
|
+
.delete_prefix("has_")
|
93
|
+
.delete_suffix("?")
|
94
|
+
end
|
95
|
+
|
96
|
+
def access_property(property)
|
97
|
+
@state[property]
|
98
|
+
end
|
99
|
+
|
100
|
+
def respond_to_have(property, value)
|
101
|
+
@state[property] == value
|
102
|
+
end
|
103
|
+
|
104
|
+
# Taken from Chef, but changed `firewall_action` to `action` for consistency
|
105
|
+
# @see https://github.com/chef/chef/blob/master/lib/chef/resource/windows_firewall_rule.rb
|
106
|
+
def load_firewall_state(rule_name)
|
107
|
+
<<-EOH
|
108
|
+
Remove-TypeData System.Array # workaround for PS bug here: https://bit.ly/2SRMQ8M
|
109
|
+
$rule = Get-NetFirewallRule -Name "#{rule_name}"
|
110
|
+
$addressFilter = $rule | Get-NetFirewallAddressFilter
|
111
|
+
$portFilter = $rule | Get-NetFirewallPortFilter
|
112
|
+
$applicationFilter = $rule | Get-NetFirewallApplicationFilter
|
113
|
+
$serviceFilter = $rule | Get-NetFirewallServiceFilter
|
114
|
+
$interfaceTypeFilter = $rule | Get-NetFirewallInterfaceTypeFilter
|
115
|
+
([PSCustomObject]@{
|
116
|
+
rule_name = $rule.Name
|
117
|
+
description = $rule.Description
|
118
|
+
displayname = $rule.DisplayName
|
119
|
+
group = $rule.Group
|
120
|
+
local_address = $addressFilter.LocalAddress
|
121
|
+
local_port = $portFilter.LocalPort
|
122
|
+
remote_address = $addressFilter.RemoteAddress
|
123
|
+
remote_port = $portFilter.RemotePort
|
124
|
+
direction = $rule.Direction.ToString()
|
125
|
+
protocol = $portFilter.Protocol
|
126
|
+
icmp_type = $portFilter.IcmpType
|
127
|
+
action = $rule.Action.ToString()
|
128
|
+
profile = $rule.Profile.ToString()
|
129
|
+
program = $applicationFilter.Program
|
130
|
+
service = $serviceFilter.Service
|
131
|
+
interface_type = $interfaceTypeFilter.InterfaceType.ToString()
|
132
|
+
enabled = [bool]::Parse($rule.Enabled.ToString())
|
133
|
+
}) | ConvertTo-Json
|
134
|
+
EOH
|
135
|
+
end
|
136
|
+
end
|
137
|
+
end
|
data/lib/inspec/rule.rb
CHANGED
@@ -343,14 +343,8 @@ module Inspec
|
|
343
343
|
__waiver_data["skipped_due_to_waiver"] = false
|
344
344
|
__waiver_data["message"] = ""
|
345
345
|
|
346
|
-
#
|
347
|
-
#
|
348
|
-
# is false-like, since all non-skipped waiver operations are handled
|
349
|
-
# during reporting phase.
|
350
|
-
return unless __waiver_data.key?("run") && !__waiver_data["run"]
|
351
|
-
|
352
|
-
# OK, the intent is to skip. Does it have an expiration date, and
|
353
|
-
# if so, is it in the future?
|
346
|
+
# Does it have an expiration date, and if so, is it in the future?
|
347
|
+
# This sets a waiver message before checking `run: true`
|
354
348
|
expiry = __waiver_data["expiration_date"]
|
355
349
|
if expiry
|
356
350
|
# YAML will automagically give us a Date or a Time.
|
@@ -370,6 +364,12 @@ module Inspec
|
|
370
364
|
end
|
371
365
|
end
|
372
366
|
|
367
|
+
# Waivers should have a hash value with keys possibly including "run" and
|
368
|
+
# expiration_date. We only care here if it has a "run" key and it
|
369
|
+
# is false-like, since all non-skipped waiver operations are handled
|
370
|
+
# during reporting phase.
|
371
|
+
return unless __waiver_data.key?("run") && !__waiver_data["run"]
|
372
|
+
|
373
373
|
# OK, apply a skip.
|
374
374
|
@__skip_rule[:result] = true
|
375
375
|
@__skip_rule[:type] = :waiver
|
@@ -96,11 +96,12 @@ module Inspec
|
|
96
96
|
# There are probably others
|
97
97
|
:value,
|
98
98
|
:type,
|
99
|
-
:required
|
99
|
+
:required,
|
100
|
+
:sensitive
|
100
101
|
) do
|
101
102
|
include HashLikeStruct
|
102
103
|
def initialize(raw_opts_data)
|
103
|
-
%i{value type required}.each { |f| self[f] = raw_opts_data[f] }
|
104
|
+
%i{value type required sensitive}.each { |f| self[f] = raw_opts_data[f] }
|
104
105
|
end
|
105
106
|
end
|
106
107
|
end
|
@@ -74,7 +74,7 @@ module Inspec
|
|
74
74
|
},
|
75
75
|
}, [CONTROL_DESCRIPTION, Primitives::REFERENCE, Primitives::SOURCE_LOCATION, CONTROL_RESULT])
|
76
76
|
|
77
|
-
# Based loosely on https://
|
77
|
+
# Based loosely on https://docs.chef.io/inspec/profiles/ as of July 3, 2019
|
78
78
|
# However, concessions were made to the reality of current reporters, specifically
|
79
79
|
# with how description is omitted and version/inspec_version aren't as advertised online
|
80
80
|
PROFILE = Primitives::SchemaType.new("Exec JSON Profile", {
|
data/lib/inspec/shell.rb
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
|
1
|
+
autoload :Pry, "pry"
|
2
2
|
|
3
3
|
module Inspec
|
4
4
|
# A pry based shell for inspec. Given a runner (with a configured backend and
|
@@ -137,7 +137,7 @@ module Inspec
|
|
137
137
|
end
|
138
138
|
|
139
139
|
info += "#{mark "Web Reference:"}\n\n"
|
140
|
-
info += "https://
|
140
|
+
info += "https://docs.chef.io/inspec/resources/#{topic}\n\n"
|
141
141
|
puts info
|
142
142
|
else
|
143
143
|
begin
|
@@ -208,7 +208,7 @@ module Inspec
|
|
208
208
|
|
209
209
|
its('content') { should_not match /^MyKey:\\s+some value/ }
|
210
210
|
|
211
|
-
For more examples, see: https://
|
211
|
+
For more examples, see: https://docs.chef.io/inspec/matchers/
|
212
212
|
|
213
213
|
EOL
|
214
214
|
end
|
data/lib/inspec/utils/parser.rb
CHANGED
@@ -0,0 +1,104 @@
|
|
1
|
+
module Inspec
|
2
|
+
module Utils
|
3
|
+
# RunDataFilters is a mixin for core Reporters and plugin reporters.
|
4
|
+
# The methods operate on the run_data Hash (prior to any conversion to a
|
5
|
+
# full RunData object).
|
6
|
+
# All methods here operate using the run_data accessor and modify
|
7
|
+
# its contents in place (if needed).
|
8
|
+
module RunDataFilters
|
9
|
+
|
10
|
+
# Long name, but we want to be clear this operates on the Hash
|
11
|
+
# This is the only method that client libraries need to call; any future
|
12
|
+
# feature growth should be handled internally here.
|
13
|
+
def apply_run_data_filters_to_hash
|
14
|
+
@config[:runtime_config] = Inspec::Config.cached || {}
|
15
|
+
apply_report_resize_options
|
16
|
+
redact_sensitive_inputs
|
17
|
+
suppress_diff_output
|
18
|
+
sort_controls
|
19
|
+
end
|
20
|
+
|
21
|
+
# Apply options such as message truncation and removal of backtraces
|
22
|
+
def apply_report_resize_options
|
23
|
+
runtime_config = @config[:runtime_config]
|
24
|
+
|
25
|
+
message_truncation = runtime_config[:reporter_message_truncation] || "ALL"
|
26
|
+
@trunc = message_truncation == "ALL" ? -1 : message_truncation.to_i
|
27
|
+
include_backtrace = runtime_config[:reporter_backtrace_inclusion].nil? ? true : runtime_config[:reporter_backtrace_inclusion]
|
28
|
+
|
29
|
+
@run_data[:profiles]&.each do |p|
|
30
|
+
p[:controls].each do |c|
|
31
|
+
c[:results]&.map! do |r|
|
32
|
+
r.delete(:backtrace) unless include_backtrace
|
33
|
+
process_message_truncation(r)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Find any inputs with :sensitive = true and replace their values with "***"
|
40
|
+
def redact_sensitive_inputs
|
41
|
+
@run_data[:profiles]&.each do |p|
|
42
|
+
p[:inputs]&.each do |i|
|
43
|
+
next unless i[:options][:sensitive]
|
44
|
+
|
45
|
+
i[:options][:value] = "***"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Optionally suppress diff output in the message field
|
51
|
+
def suppress_diff_output
|
52
|
+
return if @config[:runtime_config][:diff]
|
53
|
+
|
54
|
+
@run_data[:profiles]&.each do |p|
|
55
|
+
p[:controls]&.each do |c|
|
56
|
+
c[:results]&.each do |r|
|
57
|
+
next unless r[:message] # :message only set on failure
|
58
|
+
|
59
|
+
pos = r[:message].index("\n\nDiff:")
|
60
|
+
next unless pos # Only textual tests get Diffs
|
61
|
+
|
62
|
+
r[:message] = r[:message].slice(0, pos)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
# Optionally sort controls within each profile in report
|
69
|
+
def sort_controls
|
70
|
+
sort_type = @config[:runtime_config][:sort_results_by]
|
71
|
+
return unless sort_type
|
72
|
+
return if sort_type == "none"
|
73
|
+
|
74
|
+
@run_data[:profiles]&.each do |p|
|
75
|
+
p[:controls] ||= []
|
76
|
+
p[:groups] ||= []
|
77
|
+
|
78
|
+
case sort_type
|
79
|
+
when "control"
|
80
|
+
p[:controls].sort_by! { |c| c[:id] }
|
81
|
+
when "random"
|
82
|
+
p[:controls].shuffle!
|
83
|
+
when "file"
|
84
|
+
# Sort the controls by file, but preserve order within the file.
|
85
|
+
# Files are called "groups" in the run_data, and the filename is in the id.
|
86
|
+
sorted_control_ids = p[:groups].sort_by { |g| g[:id] }.map { |g| g[:controls] }.flatten
|
87
|
+
controls_by_id = {}
|
88
|
+
p[:controls].each { |c| controls_by_id[c[:id]] = c }
|
89
|
+
p[:controls] = sorted_control_ids.map { |cid| controls_by_id[cid] }
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
private
|
95
|
+
|
96
|
+
def process_message_truncation(result)
|
97
|
+
if result.key?(:message) && result[:message] != "" && @trunc > -1 && result[:message].length > @trunc
|
98
|
+
result[:message] = result[:message][0...@trunc] + "[Truncated to #{@trunc} characters]"
|
99
|
+
end
|
100
|
+
result
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|