codeclimate-fede 0.85.23

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 (101) 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/release +41 -0
  6. data/bin/validate-release +18 -0
  7. data/config/engines.yml +322 -0
  8. data/lib/cc/analyzer.rb +50 -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.rb +208 -0
  12. data/lib/cc/analyzer/container/result.rb +74 -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.rb +21 -0
  20. data/lib/cc/analyzer/formatters/formatter.rb +53 -0
  21. data/lib/cc/analyzer/formatters/html_formatter.rb +415 -0
  22. data/lib/cc/analyzer/formatters/json_formatter.rb +38 -0
  23. data/lib/cc/analyzer/formatters/plain_text_formatter.rb +101 -0
  24. data/lib/cc/analyzer/formatters/spinner.rb +35 -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.rb +26 -0
  28. data/lib/cc/analyzer/issue_validations/category_validation.rb +32 -0
  29. data/lib/cc/analyzer/issue_validations/check_name_presence_validation.rb +15 -0
  30. data/lib/cc/analyzer/issue_validations/content_validation.rb +21 -0
  31. data/lib/cc/analyzer/issue_validations/description_presence_validation.rb +15 -0
  32. data/lib/cc/analyzer/issue_validations/location_format_validation.rb +72 -0
  33. data/lib/cc/analyzer/issue_validations/other_locations_format_validation.rb +41 -0
  34. data/lib/cc/analyzer/issue_validations/path_existence_validation.rb +15 -0
  35. data/lib/cc/analyzer/issue_validations/path_is_file_validation.rb +15 -0
  36. data/lib/cc/analyzer/issue_validations/path_presence_validation.rb +15 -0
  37. data/lib/cc/analyzer/issue_validations/relative_path_validation.rb +32 -0
  38. data/lib/cc/analyzer/issue_validations/remediation_points_validation.rb +25 -0
  39. data/lib/cc/analyzer/issue_validations/severity_validation.rb +39 -0
  40. data/lib/cc/analyzer/issue_validations/type_validation.rb +15 -0
  41. data/lib/cc/analyzer/issue_validations/validation.rb +35 -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.rb +16 -0
  47. data/lib/cc/analyzer/measurement_validations/name_validation.rb +23 -0
  48. data/lib/cc/analyzer/measurement_validations/type_validation.rb +15 -0
  49. data/lib/cc/analyzer/measurement_validations/validation.rb +27 -0
  50. data/lib/cc/analyzer/measurement_validations/value_validation.rb +21 -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/cli.rb +39 -0
  60. data/lib/cc/cli/analyze.rb +90 -0
  61. data/lib/cc/cli/analyze/engine_failure.rb +11 -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.rb +5 -0
  65. data/lib/cc/cli/engines/engine_command.rb +15 -0
  66. data/lib/cc/cli/engines/install.rb +35 -0
  67. data/lib/cc/cli/engines/list.rb +18 -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/config.rb +70 -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/engine_registry.rb +74 -0
  95. data/lib/cc/resolv.rb +39 -0
  96. data/lib/cc/workspace.rb +39 -0
  97. data/lib/cc/workspace/exclusion.rb +34 -0
  98. data/lib/cc/workspace/path_tree.rb +49 -0
  99. data/lib/cc/workspace/path_tree/dir_node.rb +67 -0
  100. data/lib/cc/workspace/path_tree/file_node.rb +31 -0
  101. metadata +277 -0
@@ -0,0 +1,50 @@
1
+ require "yaml"
2
+
3
+ module CC
4
+ module Analyzer
5
+ autoload :Bridge, "cc/analyzer/bridge"
6
+ autoload :CompositeContainerListener, "cc/analyzer/composite_container_listener"
7
+ autoload :Container, "cc/analyzer/container"
8
+ autoload :ContainerListener, "cc/analyzer/container_listener"
9
+ autoload :Engine, "cc/analyzer/engine"
10
+ autoload :EngineOutput, "cc/analyzer/engine_output"
11
+ autoload :EngineOutputFilter, "cc/analyzer/engine_output_filter"
12
+ autoload :EngineOutputOverrider, "cc/analyzer/engine_output_overrider"
13
+ autoload :Filesystem, "cc/analyzer/filesystem"
14
+ autoload :Formatters, "cc/analyzer/formatters"
15
+ autoload :Issue, "cc/analyzer/issue"
16
+ autoload :IssueSorter, "cc/analyzer/issue_sorter"
17
+ autoload :IssueValidations, "cc/analyzer/issue_validations"
18
+ autoload :IssueValidator, "cc/analyzer/issue_validator"
19
+ autoload :LocationDescription, "cc/analyzer/location_description"
20
+ autoload :LoggingContainerListener, "cc/analyzer/logging_container_listener"
21
+ autoload :Measurement, "cc/analyzer/measurement"
22
+ autoload :MeasurementValidations, "cc/analyzer/measurement_validations"
23
+ autoload :MeasurementValidator, "cc/analyzer/measurement_validator"
24
+ autoload :MountedPath, "cc/analyzer/mounted_path"
25
+ autoload :RaisingContainerListener, "cc/analyzer/raising_container_listener"
26
+ autoload :SourceBuffer, "cc/analyzer/source_buffer"
27
+ autoload :SourceExtractor, "cc/analyzer/source_extractor"
28
+ autoload :SourceFingerprint, "cc/analyzer/source_fingerprint"
29
+ autoload :StatsdContainerListener, "cc/analyzer/statsd_container_listener"
30
+ autoload :Validator, "cc/analyzer/validator"
31
+
32
+ class DummyStatsd
33
+ def method_missing(*)
34
+ yield if block_given?
35
+ end
36
+ end
37
+
38
+ class DummyLogger
39
+ def method_missing(*)
40
+ yield if block_given?
41
+ end
42
+ end
43
+
44
+ cattr_accessor :statsd, :logger
45
+ self.statsd = DummyStatsd.new
46
+ self.logger = DummyLogger.new
47
+
48
+ UnreadableFileError = Class.new(StandardError)
49
+ end
50
+ end
@@ -0,0 +1,106 @@
1
+ module CC
2
+ module Analyzer
3
+ # The shared interface, invoked by Builder or CLI::Analyze
4
+ #
5
+ # Input:
6
+ # - config
7
+ # - engines
8
+ # - exclude_patterns
9
+ # - development?
10
+ # - analysis_paths
11
+ # - formatter
12
+ # - started
13
+ # - engine_running
14
+ # - finished
15
+ # - close
16
+ # - listener
17
+ # - started(engine, details)
18
+ # - finished(engine, details, result)
19
+ # - registry
20
+ #
21
+ # Only raises if Listener raises
22
+ #
23
+ class Bridge
24
+ def initialize(config:, formatter:, listener:, registry:)
25
+ @config = config
26
+ @formatter = formatter
27
+ @listener = listener
28
+ @registry = registry
29
+ end
30
+
31
+ def run
32
+ formatter.started
33
+
34
+ config.engines.each do |engine|
35
+ next unless engine.enabled?
36
+
37
+ formatter.engine_running(engine) do
38
+ result = nil
39
+ engine_details = nil
40
+
41
+ begin
42
+ engine_details = registry.fetch_engine_details(
43
+ engine,
44
+ development: config.development?,
45
+ )
46
+ listener.started(engine, engine_details)
47
+ result = run_engine(engine, engine_details)
48
+ rescue CC::EngineRegistry::EngineDetailsNotFoundError => ex
49
+ result = Container::Result.skipped(ex)
50
+ end
51
+
52
+ listener.finished(engine, engine_details, result)
53
+ result
54
+ end
55
+ end
56
+
57
+ formatter.finished
58
+ ensure
59
+ formatter.close
60
+ end
61
+
62
+ private
63
+
64
+ attr_reader :config, :formatter, :listener, :registry
65
+
66
+ def run_engine(engine, engine_details)
67
+ # Analyzer::Engine doesn't have the best interface, but we're limiting
68
+ # our refactors for now.
69
+ Engine.new(
70
+ engine.name,
71
+ {
72
+ "image" => engine_details.image,
73
+ "command" => engine_details.command,
74
+ "memory" => engine_details.memory,
75
+ },
76
+ engine.config.merge(
77
+ "channel" => engine.channel,
78
+ "include_paths" => engine_workspace(engine).paths,
79
+ ),
80
+ engine.container_label,
81
+ ).run(formatter)
82
+ end
83
+
84
+ def engine_workspace(engine)
85
+ if engine.exclude_patterns.any?
86
+ workspace.clone.tap do |engine_workspace|
87
+ engine_workspace.remove(engine.exclude_patterns)
88
+ end
89
+ else
90
+ workspace
91
+ end
92
+ end
93
+
94
+ def workspace
95
+ @workspace ||= Workspace.new.tap do |workspace|
96
+ workspace.add(config.analysis_paths)
97
+
98
+ unless config.analysis_paths.any?
99
+ workspace.remove([".git"])
100
+ workspace.remove(config.exclude_patterns)
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,21 @@
1
+ module CC
2
+ module Analyzer
3
+ class CompositeContainerListener < ContainerListener
4
+ def initialize(*listeners)
5
+ @listeners = listeners
6
+ end
7
+
8
+ def started(*args)
9
+ listeners.each { |listener| listener.started(*args) }
10
+ end
11
+
12
+ def finished(*args)
13
+ listeners.each { |listener| listener.finished(*args) }
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :listeners
19
+ end
20
+ end
21
+ 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,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