codeclimate-fede 0.85.21

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 (102) hide show
  1. checksums.yaml +7 -0
  2. data/bin/check +18 -0
  3. data/bin/codeclimate +21 -0
  4. data/bin/prep-release +45 -0
  5. data/bin/publish +47 -0
  6. data/bin/release +41 -0
  7. data/bin/validate-release +18 -0
  8. data/config/engines.yml +322 -0
  9. data/lib/cc/analyzer/bridge.rb +106 -0
  10. data/lib/cc/analyzer/composite_container_listener.rb +21 -0
  11. data/lib/cc/analyzer/container/result.rb +74 -0
  12. data/lib/cc/analyzer/container.rb +208 -0
  13. data/lib/cc/analyzer/container_listener.rb +9 -0
  14. data/lib/cc/analyzer/engine.rb +125 -0
  15. data/lib/cc/analyzer/engine_output.rb +74 -0
  16. data/lib/cc/analyzer/engine_output_filter.rb +36 -0
  17. data/lib/cc/analyzer/engine_output_overrider.rb +31 -0
  18. data/lib/cc/analyzer/filesystem.rb +50 -0
  19. data/lib/cc/analyzer/formatters/formatter.rb +53 -0
  20. data/lib/cc/analyzer/formatters/html_formatter.rb +415 -0
  21. data/lib/cc/analyzer/formatters/json_formatter.rb +38 -0
  22. data/lib/cc/analyzer/formatters/plain_text_formatter.rb +101 -0
  23. data/lib/cc/analyzer/formatters/spinner.rb +35 -0
  24. data/lib/cc/analyzer/formatters.rb +21 -0
  25. data/lib/cc/analyzer/issue.rb +69 -0
  26. data/lib/cc/analyzer/issue_sorter.rb +30 -0
  27. data/lib/cc/analyzer/issue_validations/category_validation.rb +32 -0
  28. data/lib/cc/analyzer/issue_validations/check_name_presence_validation.rb +15 -0
  29. data/lib/cc/analyzer/issue_validations/content_validation.rb +21 -0
  30. data/lib/cc/analyzer/issue_validations/description_presence_validation.rb +15 -0
  31. data/lib/cc/analyzer/issue_validations/location_format_validation.rb +72 -0
  32. data/lib/cc/analyzer/issue_validations/other_locations_format_validation.rb +41 -0
  33. data/lib/cc/analyzer/issue_validations/path_existence_validation.rb +15 -0
  34. data/lib/cc/analyzer/issue_validations/path_is_file_validation.rb +15 -0
  35. data/lib/cc/analyzer/issue_validations/path_presence_validation.rb +15 -0
  36. data/lib/cc/analyzer/issue_validations/relative_path_validation.rb +32 -0
  37. data/lib/cc/analyzer/issue_validations/remediation_points_validation.rb +25 -0
  38. data/lib/cc/analyzer/issue_validations/severity_validation.rb +39 -0
  39. data/lib/cc/analyzer/issue_validations/type_validation.rb +15 -0
  40. data/lib/cc/analyzer/issue_validations/validation.rb +35 -0
  41. data/lib/cc/analyzer/issue_validations.rb +26 -0
  42. data/lib/cc/analyzer/issue_validator.rb +11 -0
  43. data/lib/cc/analyzer/location_description.rb +45 -0
  44. data/lib/cc/analyzer/logging_container_listener.rb +24 -0
  45. data/lib/cc/analyzer/measurement.rb +22 -0
  46. data/lib/cc/analyzer/measurement_validations/name_validation.rb +23 -0
  47. data/lib/cc/analyzer/measurement_validations/type_validation.rb +15 -0
  48. data/lib/cc/analyzer/measurement_validations/validation.rb +27 -0
  49. data/lib/cc/analyzer/measurement_validations/value_validation.rb +21 -0
  50. data/lib/cc/analyzer/measurement_validations.rb +16 -0
  51. data/lib/cc/analyzer/measurement_validator.rb +11 -0
  52. data/lib/cc/analyzer/mounted_path.rb +80 -0
  53. data/lib/cc/analyzer/raising_container_listener.rb +32 -0
  54. data/lib/cc/analyzer/source_buffer.rb +47 -0
  55. data/lib/cc/analyzer/source_extractor.rb +79 -0
  56. data/lib/cc/analyzer/source_fingerprint.rb +40 -0
  57. data/lib/cc/analyzer/statsd_container_listener.rb +51 -0
  58. data/lib/cc/analyzer/validator.rb +38 -0
  59. data/lib/cc/analyzer.rb +50 -0
  60. data/lib/cc/cli/analyze/engine_failure.rb +11 -0
  61. data/lib/cc/cli/analyze.rb +90 -0
  62. data/lib/cc/cli/command.rb +85 -0
  63. data/lib/cc/cli/console.rb +12 -0
  64. data/lib/cc/cli/engines/engine_command.rb +15 -0
  65. data/lib/cc/cli/engines/install.rb +35 -0
  66. data/lib/cc/cli/engines/list.rb +18 -0
  67. data/lib/cc/cli/engines.rb +5 -0
  68. data/lib/cc/cli/file_store.rb +42 -0
  69. data/lib/cc/cli/global_cache.rb +47 -0
  70. data/lib/cc/cli/global_config.rb +35 -0
  71. data/lib/cc/cli/help.rb +51 -0
  72. data/lib/cc/cli/output.rb +34 -0
  73. data/lib/cc/cli/prepare.rb +98 -0
  74. data/lib/cc/cli/runner.rb +75 -0
  75. data/lib/cc/cli/validate_config.rb +84 -0
  76. data/lib/cc/cli/version.rb +16 -0
  77. data/lib/cc/cli/version_checker.rb +107 -0
  78. data/lib/cc/cli.rb +39 -0
  79. data/lib/cc/config/checks_adapter.rb +40 -0
  80. data/lib/cc/config/default_adapter.rb +54 -0
  81. data/lib/cc/config/engine.rb +41 -0
  82. data/lib/cc/config/engine_set.rb +47 -0
  83. data/lib/cc/config/json_adapter.rb +17 -0
  84. data/lib/cc/config/prepare.rb +92 -0
  85. data/lib/cc/config/validation/check_validator.rb +34 -0
  86. data/lib/cc/config/validation/engine_validator.rb +93 -0
  87. data/lib/cc/config/validation/fetch_validator.rb +78 -0
  88. data/lib/cc/config/validation/file_validator.rb +112 -0
  89. data/lib/cc/config/validation/hash_validations.rb +52 -0
  90. data/lib/cc/config/validation/json.rb +31 -0
  91. data/lib/cc/config/validation/prepare_validator.rb +40 -0
  92. data/lib/cc/config/validation/yaml.rb +66 -0
  93. data/lib/cc/config/yaml_adapter.rb +73 -0
  94. data/lib/cc/config.rb +70 -0
  95. data/lib/cc/engine_registry.rb +74 -0
  96. data/lib/cc/resolv.rb +39 -0
  97. data/lib/cc/workspace/exclusion.rb +34 -0
  98. data/lib/cc/workspace/path_tree/dir_node.rb +67 -0
  99. data/lib/cc/workspace/path_tree/file_node.rb +31 -0
  100. data/lib/cc/workspace/path_tree.rb +49 -0
  101. data/lib/cc/workspace.rb +39 -0
  102. 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,9 @@
1
+ module CC
2
+ module Analyzer
3
+ class ContainerListener
4
+ def started(_engine, _details); end
5
+
6
+ def finished(_engine, _details, _result); end
7
+ end
8
+ end
9
+ 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