codeclimate-fede 0.85.21
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/check +18 -0
- data/bin/codeclimate +21 -0
- data/bin/prep-release +45 -0
- data/bin/publish +47 -0
- data/bin/release +41 -0
- data/bin/validate-release +18 -0
- data/config/engines.yml +322 -0
- data/lib/cc/analyzer/bridge.rb +106 -0
- data/lib/cc/analyzer/composite_container_listener.rb +21 -0
- data/lib/cc/analyzer/container/result.rb +74 -0
- data/lib/cc/analyzer/container.rb +208 -0
- data/lib/cc/analyzer/container_listener.rb +9 -0
- data/lib/cc/analyzer/engine.rb +125 -0
- data/lib/cc/analyzer/engine_output.rb +74 -0
- data/lib/cc/analyzer/engine_output_filter.rb +36 -0
- data/lib/cc/analyzer/engine_output_overrider.rb +31 -0
- data/lib/cc/analyzer/filesystem.rb +50 -0
- data/lib/cc/analyzer/formatters/formatter.rb +53 -0
- data/lib/cc/analyzer/formatters/html_formatter.rb +415 -0
- data/lib/cc/analyzer/formatters/json_formatter.rb +38 -0
- data/lib/cc/analyzer/formatters/plain_text_formatter.rb +101 -0
- data/lib/cc/analyzer/formatters/spinner.rb +35 -0
- data/lib/cc/analyzer/formatters.rb +21 -0
- data/lib/cc/analyzer/issue.rb +69 -0
- data/lib/cc/analyzer/issue_sorter.rb +30 -0
- data/lib/cc/analyzer/issue_validations/category_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/check_name_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/content_validation.rb +21 -0
- data/lib/cc/analyzer/issue_validations/description_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/location_format_validation.rb +72 -0
- data/lib/cc/analyzer/issue_validations/other_locations_format_validation.rb +41 -0
- data/lib/cc/analyzer/issue_validations/path_existence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_is_file_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/path_presence_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/relative_path_validation.rb +32 -0
- data/lib/cc/analyzer/issue_validations/remediation_points_validation.rb +25 -0
- data/lib/cc/analyzer/issue_validations/severity_validation.rb +39 -0
- data/lib/cc/analyzer/issue_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/issue_validations/validation.rb +35 -0
- data/lib/cc/analyzer/issue_validations.rb +26 -0
- data/lib/cc/analyzer/issue_validator.rb +11 -0
- data/lib/cc/analyzer/location_description.rb +45 -0
- data/lib/cc/analyzer/logging_container_listener.rb +24 -0
- data/lib/cc/analyzer/measurement.rb +22 -0
- data/lib/cc/analyzer/measurement_validations/name_validation.rb +23 -0
- data/lib/cc/analyzer/measurement_validations/type_validation.rb +15 -0
- data/lib/cc/analyzer/measurement_validations/validation.rb +27 -0
- data/lib/cc/analyzer/measurement_validations/value_validation.rb +21 -0
- data/lib/cc/analyzer/measurement_validations.rb +16 -0
- data/lib/cc/analyzer/measurement_validator.rb +11 -0
- data/lib/cc/analyzer/mounted_path.rb +80 -0
- data/lib/cc/analyzer/raising_container_listener.rb +32 -0
- data/lib/cc/analyzer/source_buffer.rb +47 -0
- data/lib/cc/analyzer/source_extractor.rb +79 -0
- data/lib/cc/analyzer/source_fingerprint.rb +40 -0
- data/lib/cc/analyzer/statsd_container_listener.rb +51 -0
- data/lib/cc/analyzer/validator.rb +38 -0
- data/lib/cc/analyzer.rb +50 -0
- data/lib/cc/cli/analyze/engine_failure.rb +11 -0
- data/lib/cc/cli/analyze.rb +90 -0
- data/lib/cc/cli/command.rb +85 -0
- data/lib/cc/cli/console.rb +12 -0
- data/lib/cc/cli/engines/engine_command.rb +15 -0
- data/lib/cc/cli/engines/install.rb +35 -0
- data/lib/cc/cli/engines/list.rb +18 -0
- data/lib/cc/cli/engines.rb +5 -0
- data/lib/cc/cli/file_store.rb +42 -0
- data/lib/cc/cli/global_cache.rb +47 -0
- data/lib/cc/cli/global_config.rb +35 -0
- data/lib/cc/cli/help.rb +51 -0
- data/lib/cc/cli/output.rb +34 -0
- data/lib/cc/cli/prepare.rb +98 -0
- data/lib/cc/cli/runner.rb +75 -0
- data/lib/cc/cli/validate_config.rb +84 -0
- data/lib/cc/cli/version.rb +16 -0
- data/lib/cc/cli/version_checker.rb +107 -0
- data/lib/cc/cli.rb +39 -0
- data/lib/cc/config/checks_adapter.rb +40 -0
- data/lib/cc/config/default_adapter.rb +54 -0
- data/lib/cc/config/engine.rb +41 -0
- data/lib/cc/config/engine_set.rb +47 -0
- data/lib/cc/config/json_adapter.rb +17 -0
- data/lib/cc/config/prepare.rb +92 -0
- data/lib/cc/config/validation/check_validator.rb +34 -0
- data/lib/cc/config/validation/engine_validator.rb +93 -0
- data/lib/cc/config/validation/fetch_validator.rb +78 -0
- data/lib/cc/config/validation/file_validator.rb +112 -0
- data/lib/cc/config/validation/hash_validations.rb +52 -0
- data/lib/cc/config/validation/json.rb +31 -0
- data/lib/cc/config/validation/prepare_validator.rb +40 -0
- data/lib/cc/config/validation/yaml.rb +66 -0
- data/lib/cc/config/yaml_adapter.rb +73 -0
- data/lib/cc/config.rb +70 -0
- data/lib/cc/engine_registry.rb +74 -0
- data/lib/cc/resolv.rb +39 -0
- data/lib/cc/workspace/exclusion.rb +34 -0
- data/lib/cc/workspace/path_tree/dir_node.rb +67 -0
- data/lib/cc/workspace/path_tree/file_node.rb +31 -0
- data/lib/cc/workspace/path_tree.rb +49 -0
- data/lib/cc/workspace.rb +39 -0
- metadata +279 -0
@@ -0,0 +1,74 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class Container
|
4
|
+
class Result
|
5
|
+
attr_reader \
|
6
|
+
:container_name,
|
7
|
+
:duration,
|
8
|
+
:exit_status,
|
9
|
+
:output_byte_count,
|
10
|
+
:stderr,
|
11
|
+
:stdout
|
12
|
+
|
13
|
+
def initialize(
|
14
|
+
container_name: "",
|
15
|
+
duration: 0,
|
16
|
+
exit_status: 0,
|
17
|
+
maximum_output_exceeded: false,
|
18
|
+
output_byte_count: 0,
|
19
|
+
skipped: false,
|
20
|
+
stderr: "",
|
21
|
+
stdout: "",
|
22
|
+
timed_out: false
|
23
|
+
)
|
24
|
+
@container_name = container_name
|
25
|
+
@duration = duration
|
26
|
+
@exit_status = exit_status
|
27
|
+
@maximum_output_exceeded = maximum_output_exceeded
|
28
|
+
@output_byte_count = output_byte_count
|
29
|
+
@skipped = skipped
|
30
|
+
@stderr = stderr
|
31
|
+
@stdout = stdout
|
32
|
+
@timed_out = timed_out
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.skipped(ex)
|
36
|
+
new(
|
37
|
+
exit_status: 0,
|
38
|
+
skipped: true,
|
39
|
+
stderr: ex.message,
|
40
|
+
)
|
41
|
+
end
|
42
|
+
|
43
|
+
def merge_from_exception(ex)
|
44
|
+
self.exit_status = 99
|
45
|
+
self.stderr = ex.message
|
46
|
+
self
|
47
|
+
end
|
48
|
+
|
49
|
+
def timed_out?
|
50
|
+
@timed_out
|
51
|
+
end
|
52
|
+
|
53
|
+
def maximum_output_exceeded?
|
54
|
+
@maximum_output_exceeded
|
55
|
+
end
|
56
|
+
|
57
|
+
def errored?
|
58
|
+
timed_out? ||
|
59
|
+
maximum_output_exceeded? ||
|
60
|
+
exit_status.nil? ||
|
61
|
+
exit_status.nonzero?
|
62
|
+
end
|
63
|
+
|
64
|
+
def skipped?
|
65
|
+
@skipped
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
attr_writer :exit_status, :stderr
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,208 @@
|
|
1
|
+
require "posix/spawn"
|
2
|
+
require "thread"
|
3
|
+
|
4
|
+
require "cc/analyzer/container/result"
|
5
|
+
|
6
|
+
module CC
|
7
|
+
module Analyzer
|
8
|
+
#
|
9
|
+
# Running an abstract docker container
|
10
|
+
#
|
11
|
+
# Input:
|
12
|
+
# - image
|
13
|
+
# - name
|
14
|
+
# - command (Optional)
|
15
|
+
#
|
16
|
+
# Output:
|
17
|
+
# - Result
|
18
|
+
# - exit_status
|
19
|
+
# - timed_out?
|
20
|
+
# - duration
|
21
|
+
# - maximum_output_exceeded?
|
22
|
+
# - output_byte_count
|
23
|
+
# - stderr
|
24
|
+
#
|
25
|
+
# Never raises (unless broken)
|
26
|
+
#
|
27
|
+
class Container
|
28
|
+
DEFAULT_TIMEOUT = 15 * 60 # 15m
|
29
|
+
DEFAULT_MAXIMUM_OUTPUT_BYTES = 500_000_000
|
30
|
+
|
31
|
+
def initialize(image:, name:, command: nil)
|
32
|
+
@image = image
|
33
|
+
@name = name
|
34
|
+
@command = command
|
35
|
+
@timed_out = false
|
36
|
+
@maximum_output_exceeded = false
|
37
|
+
@stdout_io = StringIO.new
|
38
|
+
@stderr_io = StringIO.new
|
39
|
+
@output_byte_count = 0
|
40
|
+
@counter_mutex = Mutex.new
|
41
|
+
|
42
|
+
# By default accumulate and include stdout in result
|
43
|
+
@output_delimeter = "\n"
|
44
|
+
@on_output = ->(output) { @stdout_io.puts(output) }
|
45
|
+
end
|
46
|
+
|
47
|
+
def on_output(delimeter = "\n", &block)
|
48
|
+
@output_delimeter = delimeter
|
49
|
+
@on_output = block
|
50
|
+
end
|
51
|
+
|
52
|
+
def run(options = [])
|
53
|
+
started = Time.now
|
54
|
+
|
55
|
+
command = docker_run_command(options)
|
56
|
+
Analyzer.logger.debug("docker run: #{command.inspect}")
|
57
|
+
pid, _, out, err = POSIX::Spawn.popen4(*command)
|
58
|
+
|
59
|
+
@t_out = read_stdout(out)
|
60
|
+
@t_err = read_stderr(err)
|
61
|
+
t_timeout = timeout_thread
|
62
|
+
|
63
|
+
# blocks until the engine stops. this is put in a thread so that we can
|
64
|
+
# explicitly abort it as part of #stop. otherwise a run-away container
|
65
|
+
# could still block here forever if the docker-kill/wait is not
|
66
|
+
# successful. there may still be stdout in flight if it was being
|
67
|
+
# produced more quickly than consumed.
|
68
|
+
@t_wait = Thread.new { _, @status = Process.waitpid2(pid) }
|
69
|
+
@t_wait.join
|
70
|
+
|
71
|
+
# blocks until all readers are done. they're still governed by the
|
72
|
+
# timeout thread at this point. if we hit the timeout while processing
|
73
|
+
# output, the threads will be Thread#killed as part of #stop and this
|
74
|
+
# will unblock with the correct value in @timed_out
|
75
|
+
[@t_out, @t_err].each(&:join)
|
76
|
+
|
77
|
+
duration =
|
78
|
+
if @timed_out
|
79
|
+
timeout * 1000
|
80
|
+
else
|
81
|
+
((Time.now - started) * 1000).round
|
82
|
+
end
|
83
|
+
|
84
|
+
Result.new(
|
85
|
+
container_name: @name,
|
86
|
+
duration: duration,
|
87
|
+
exit_status: @status&.exitstatus,
|
88
|
+
maximum_output_exceeded: @maximum_output_exceeded,
|
89
|
+
output_byte_count: output_byte_count,
|
90
|
+
stderr: @stderr_io.string,
|
91
|
+
stdout: @stdout_io.string,
|
92
|
+
timed_out: @timed_out,
|
93
|
+
)
|
94
|
+
ensure
|
95
|
+
kill_reader_threads
|
96
|
+
t_timeout&.kill
|
97
|
+
end
|
98
|
+
|
99
|
+
def stop(message = nil)
|
100
|
+
reap_running_container(message)
|
101
|
+
kill_reader_threads
|
102
|
+
kill_wait_thread
|
103
|
+
end
|
104
|
+
|
105
|
+
private
|
106
|
+
|
107
|
+
attr_reader :output_byte_count, :counter_mutex
|
108
|
+
|
109
|
+
def docker_run_command(options)
|
110
|
+
[
|
111
|
+
"docker", "run",
|
112
|
+
"--name", @name,
|
113
|
+
options,
|
114
|
+
@image,
|
115
|
+
@command
|
116
|
+
].flatten.compact
|
117
|
+
end
|
118
|
+
|
119
|
+
def read_stdout(out)
|
120
|
+
Thread.new do
|
121
|
+
out.each_line(@output_delimeter) do |chunk|
|
122
|
+
output = chunk.chomp(@output_delimeter)
|
123
|
+
|
124
|
+
Analyzer.logger.debug("engine stdout: #{output}")
|
125
|
+
@on_output.call(output)
|
126
|
+
check_output_bytes(output.bytesize)
|
127
|
+
end
|
128
|
+
ensure
|
129
|
+
out.close
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
def read_stderr(err)
|
134
|
+
Thread.new do
|
135
|
+
err.each_line do |line|
|
136
|
+
Analyzer.logger.debug("engine stderr: #{line.chomp}")
|
137
|
+
@stderr_io.write(line)
|
138
|
+
check_output_bytes(line.bytesize)
|
139
|
+
end
|
140
|
+
ensure
|
141
|
+
err.close
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
def timeout_thread
|
146
|
+
Thread.new do
|
147
|
+
# Doing one long `sleep timeout` seems to fail sometimes, so
|
148
|
+
# we do a series of short timeouts before exiting
|
149
|
+
start_time = Time.now
|
150
|
+
loop do
|
151
|
+
sleep 10
|
152
|
+
duration = Time.now - start_time
|
153
|
+
break if duration >= timeout
|
154
|
+
end
|
155
|
+
|
156
|
+
@timed_out = true
|
157
|
+
stop("timed out")
|
158
|
+
end.run
|
159
|
+
end
|
160
|
+
|
161
|
+
def check_output_bytes(last_read_byte_count)
|
162
|
+
counter_mutex.synchronize do
|
163
|
+
@output_byte_count += last_read_byte_count
|
164
|
+
end
|
165
|
+
|
166
|
+
if output_byte_count > maximum_output_bytes
|
167
|
+
@maximum_output_exceeded = true
|
168
|
+
stop("maximum output exceeded")
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def kill_reader_threads
|
173
|
+
@t_out&.kill
|
174
|
+
@t_err&.kill
|
175
|
+
end
|
176
|
+
|
177
|
+
def kill_wait_thread
|
178
|
+
@t_wait&.kill
|
179
|
+
end
|
180
|
+
|
181
|
+
def reap_running_container(message)
|
182
|
+
Analyzer.logger.warn("killing container name=#{@name} message=#{message.inspect}")
|
183
|
+
POSIX::Spawn::Child.new("docker", "kill", @name, timeout: 2.minutes)
|
184
|
+
POSIX::Spawn::Child.new("docker", "wait", @name, timeout: 2.minutes)
|
185
|
+
rescue POSIX::Spawn::TimeoutExceeded
|
186
|
+
Analyzer.logger.error("unable to kill container name=#{@name} message=#{message.inspect}")
|
187
|
+
Analyzer.statsd.increment("container.zombie")
|
188
|
+
Analyzer.statsd.increment("container.zombie.#{metric_name}") if metric_name
|
189
|
+
end
|
190
|
+
|
191
|
+
def timeout
|
192
|
+
ENV.fetch("CONTAINER_TIMEOUT_SECONDS", DEFAULT_TIMEOUT).to_i
|
193
|
+
end
|
194
|
+
|
195
|
+
def maximum_output_bytes
|
196
|
+
ENV.fetch("CONTAINER_MAXIMUM_OUTPUT_BYTES", DEFAULT_MAXIMUM_OUTPUT_BYTES).to_i
|
197
|
+
end
|
198
|
+
|
199
|
+
def metric_name
|
200
|
+
if /^cc-engines-(?<engine>[^-]+)-(?<channel>[^-]+)-/ =~ @name
|
201
|
+
"engine.#{engine}.#{channel}"
|
202
|
+
elsif /^builder-(?<action>[^-]+)-/ =~ @name
|
203
|
+
"builder.#{action}"
|
204
|
+
end
|
205
|
+
end
|
206
|
+
end
|
207
|
+
end
|
208
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require "securerandom"
|
2
|
+
|
3
|
+
module CC
|
4
|
+
module Analyzer
|
5
|
+
#
|
6
|
+
# Running specifically an Engine container
|
7
|
+
#
|
8
|
+
# Input:
|
9
|
+
# - name
|
10
|
+
# - metadata
|
11
|
+
# - image
|
12
|
+
# - command (optional)
|
13
|
+
# - config (becomes /config.json)
|
14
|
+
# - label
|
15
|
+
# - io (to write filtered, validated output)
|
16
|
+
#
|
17
|
+
# Output:
|
18
|
+
# - Container::Result
|
19
|
+
#
|
20
|
+
class Engine
|
21
|
+
Error = Class.new(StandardError)
|
22
|
+
|
23
|
+
def initialize(name, metadata, config, label)
|
24
|
+
@name = name
|
25
|
+
@metadata = metadata
|
26
|
+
@config = config
|
27
|
+
@label = label.to_s
|
28
|
+
@error = nil
|
29
|
+
end
|
30
|
+
|
31
|
+
def run(io)
|
32
|
+
write_config_file
|
33
|
+
|
34
|
+
container = Container.new(
|
35
|
+
image: metadata.fetch("image"),
|
36
|
+
command: metadata["command"],
|
37
|
+
name: container_name,
|
38
|
+
)
|
39
|
+
|
40
|
+
container.on_output("\0") do |output|
|
41
|
+
handle_output(container, io, output)
|
42
|
+
end
|
43
|
+
|
44
|
+
container.run(container_options).tap do |result|
|
45
|
+
result.merge_from_exception(error) if error.present?
|
46
|
+
end
|
47
|
+
ensure
|
48
|
+
delete_config_file
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
attr_reader :name, :metadata
|
54
|
+
attr_accessor :error
|
55
|
+
|
56
|
+
def handle_output(container, io, raw_output)
|
57
|
+
output = EngineOutput.new(name, raw_output)
|
58
|
+
|
59
|
+
return if output_filter.filter?(output)
|
60
|
+
|
61
|
+
unless output.valid?
|
62
|
+
self.error = Error.new("engine produced invalid output: #{output.error}")
|
63
|
+
container.stop("output invalid")
|
64
|
+
end
|
65
|
+
|
66
|
+
unless io.write(output_overrider.apply(output).to_json)
|
67
|
+
self.error = Error.new("#{io.class}#write returned false, indicating an error")
|
68
|
+
container.stop("output error")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def qualified_name
|
73
|
+
"#{name}:#{@config.fetch("channel", "stable")}"
|
74
|
+
end
|
75
|
+
|
76
|
+
def container_options
|
77
|
+
options = [
|
78
|
+
"--cap-drop", "all",
|
79
|
+
"--label", "com.codeclimate.label=#{@label}",
|
80
|
+
"--log-driver", "none",
|
81
|
+
"--memory-swap", "-1",
|
82
|
+
"--net", "none",
|
83
|
+
"--rm",
|
84
|
+
"--volume", "#{code.host_path}:/code:ro",
|
85
|
+
"--volume", "#{config_file.host_path}:/config.json:ro",
|
86
|
+
"--user", "9000:9000"
|
87
|
+
]
|
88
|
+
if (memory = metadata["memory"]).present?
|
89
|
+
options.concat(["--memory", memory.to_s])
|
90
|
+
end
|
91
|
+
options
|
92
|
+
end
|
93
|
+
|
94
|
+
def container_name
|
95
|
+
@container_name ||= "cc-engines-#{qualified_name.tr(":", "-")}-#{SecureRandom.uuid}"
|
96
|
+
end
|
97
|
+
|
98
|
+
def write_config_file
|
99
|
+
@config["debug"] = ENV["CODECLIMATE_DEBUG"]
|
100
|
+
Analyzer.logger.debug "/config.json content: #{@config.inspect}"
|
101
|
+
config_file.write(@config.to_json)
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete_config_file
|
105
|
+
config_file.delete if config_file.file?
|
106
|
+
end
|
107
|
+
|
108
|
+
def code
|
109
|
+
@code ||= MountedPath.code
|
110
|
+
end
|
111
|
+
|
112
|
+
def config_file
|
113
|
+
@config_file ||= MountedPath.tmp.join(SecureRandom.uuid)
|
114
|
+
end
|
115
|
+
|
116
|
+
def output_filter
|
117
|
+
@output_filter ||= EngineOutputFilter.new(@config)
|
118
|
+
end
|
119
|
+
|
120
|
+
def output_overrider
|
121
|
+
@output_overrider ||= EngineOutputOverrider.new(@config)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class EngineOutput
|
4
|
+
delegate :blank?, to: :raw_output
|
5
|
+
|
6
|
+
def initialize(name, raw_output)
|
7
|
+
@name = name
|
8
|
+
@raw_output = raw_output
|
9
|
+
end
|
10
|
+
|
11
|
+
def issue?
|
12
|
+
valid_with_type?("issue")
|
13
|
+
end
|
14
|
+
|
15
|
+
def measurement?
|
16
|
+
valid_with_type?("measurement")
|
17
|
+
end
|
18
|
+
|
19
|
+
def as_issue
|
20
|
+
Issue.new(name, raw_output)
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_json
|
24
|
+
if issue?
|
25
|
+
as_issue.to_json
|
26
|
+
elsif measurement?
|
27
|
+
Measurement.new(name, raw_output).to_json
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid?
|
32
|
+
valid_json? && validator && validator.valid?
|
33
|
+
end
|
34
|
+
|
35
|
+
def error
|
36
|
+
if !valid_json?
|
37
|
+
{ message: "Invalid JSON", output: raw_output }
|
38
|
+
elsif !validator.present?
|
39
|
+
{ message: "Unsupported document type", output: raw_output }
|
40
|
+
else
|
41
|
+
validator.error
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
attr_accessor :name, :raw_output
|
48
|
+
|
49
|
+
def valid_json?
|
50
|
+
parsed_output.present?
|
51
|
+
end
|
52
|
+
|
53
|
+
def valid_with_type?(type)
|
54
|
+
parsed_output &&
|
55
|
+
parsed_output["type"].present? &&
|
56
|
+
parsed_output["type"].downcase == type
|
57
|
+
end
|
58
|
+
|
59
|
+
def parsed_output
|
60
|
+
@parsed_output ||= JSON.parse(raw_output)
|
61
|
+
rescue JSON::ParserError
|
62
|
+
nil
|
63
|
+
end
|
64
|
+
|
65
|
+
def validator
|
66
|
+
if issue?
|
67
|
+
IssueValidator.new(parsed_output)
|
68
|
+
elsif measurement?
|
69
|
+
MeasurementValidator.new(parsed_output)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class EngineOutputFilter
|
4
|
+
ISSUE_TYPE = "issue".freeze
|
5
|
+
|
6
|
+
def initialize(config = {})
|
7
|
+
@config = config
|
8
|
+
end
|
9
|
+
|
10
|
+
def filter?(output)
|
11
|
+
output.blank? || (output.issue? && ignore_issue?(output.as_issue))
|
12
|
+
end
|
13
|
+
|
14
|
+
private
|
15
|
+
|
16
|
+
def ignore_issue?(issue)
|
17
|
+
check_disabled?(issue) || ignore_fingerprint?(issue)
|
18
|
+
end
|
19
|
+
|
20
|
+
def check_disabled?(issue)
|
21
|
+
!check_config(issue.check_name).fetch("enabled", true)
|
22
|
+
end
|
23
|
+
|
24
|
+
def ignore_fingerprint?(issue)
|
25
|
+
@config.fetch("exclude_fingerprints", []).include?(issue.fingerprint)
|
26
|
+
rescue SourceExtractor::InvalidLocation
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def check_config(check_name)
|
31
|
+
@checks ||= @config.fetch("checks", {})
|
32
|
+
@checks.fetch(check_name, {})
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class EngineOutputOverrider
|
4
|
+
def initialize(config = {})
|
5
|
+
@config = config
|
6
|
+
end
|
7
|
+
|
8
|
+
def apply(output)
|
9
|
+
if output.issue?
|
10
|
+
override_severity(output.as_issue.as_json)
|
11
|
+
else
|
12
|
+
output
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
attr_reader :config
|
19
|
+
|
20
|
+
def override_severity(issue)
|
21
|
+
issue.merge(override("severity"))
|
22
|
+
end
|
23
|
+
|
24
|
+
def override(name)
|
25
|
+
config.
|
26
|
+
fetch("issue_override", {}).
|
27
|
+
slice(name)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
class Filesystem
|
4
|
+
attr_reader :root
|
5
|
+
|
6
|
+
def initialize(root)
|
7
|
+
@root = root
|
8
|
+
end
|
9
|
+
|
10
|
+
def exist?(path)
|
11
|
+
File.exist?(path_for(path))
|
12
|
+
end
|
13
|
+
|
14
|
+
def source_buffer_for(path)
|
15
|
+
SourceBuffer.new(path, read_path(path))
|
16
|
+
end
|
17
|
+
|
18
|
+
def read_path(path)
|
19
|
+
File.read(path_for(path))
|
20
|
+
end
|
21
|
+
|
22
|
+
def write_path(path, content)
|
23
|
+
File.write(path_for(path), content)
|
24
|
+
File.chown(root_uid, root_gid, path_for(path))
|
25
|
+
end
|
26
|
+
|
27
|
+
def ls
|
28
|
+
Dir.entries(root).reject { |entry| [".", ".."].include?(entry) }
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def path_for(path)
|
34
|
+
File.join(root, path)
|
35
|
+
end
|
36
|
+
|
37
|
+
def root_uid
|
38
|
+
root_stat.uid
|
39
|
+
end
|
40
|
+
|
41
|
+
def root_gid
|
42
|
+
root_stat.gid
|
43
|
+
end
|
44
|
+
|
45
|
+
def root_stat
|
46
|
+
@root_stat ||= File.stat(root)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module CC
|
2
|
+
module Analyzer
|
3
|
+
module Formatters
|
4
|
+
class Formatter
|
5
|
+
def initialize(filesystem, output = $stdout)
|
6
|
+
@filesystem = filesystem
|
7
|
+
@output = output
|
8
|
+
end
|
9
|
+
|
10
|
+
def write(data)
|
11
|
+
json = JSON.parse(data)
|
12
|
+
json["engine_name"] = current_engine.name
|
13
|
+
|
14
|
+
case json["type"].downcase
|
15
|
+
when "issue"
|
16
|
+
issues << json
|
17
|
+
when "warning"
|
18
|
+
warnings << json
|
19
|
+
when "measurement"
|
20
|
+
measurements << json
|
21
|
+
else
|
22
|
+
raise "Invalid type found: #{json["type"]}"
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def started
|
27
|
+
end
|
28
|
+
|
29
|
+
def engine_running(engine)
|
30
|
+
@current_engine = engine
|
31
|
+
yield
|
32
|
+
ensure
|
33
|
+
@current_engine = nil
|
34
|
+
end
|
35
|
+
|
36
|
+
def finished
|
37
|
+
end
|
38
|
+
|
39
|
+
def close
|
40
|
+
end
|
41
|
+
|
42
|
+
def failed(_output)
|
43
|
+
end
|
44
|
+
|
45
|
+
InvalidFormatterError = Class.new(StandardError)
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :current_engine
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|