codeclimate-fede 0.85.23

Sign up to get free protection for your applications and to get access to all the features.
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