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.
- 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,80 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module Analyzer
|
|
3
|
+
class MountedPath
|
|
4
|
+
DEFAULT_CODECLIMATE_TMP = "/tmp/cc".freeze
|
|
5
|
+
|
|
6
|
+
def self.code
|
|
7
|
+
host_prefix = ENV["CODECLIMATE_CODE"]
|
|
8
|
+
host_prefix ||= ENV["CODE_PATH"] # deprecated
|
|
9
|
+
|
|
10
|
+
if ENV["CODECLIMATE_DOCKER"]
|
|
11
|
+
new(host_prefix, "/code")
|
|
12
|
+
else
|
|
13
|
+
host_prefix ||= Dir.pwd
|
|
14
|
+
|
|
15
|
+
new(host_prefix, host_prefix)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.tmp
|
|
20
|
+
host_prefix = ENV["CODECLIMATE_TMP"]
|
|
21
|
+
host_prefix ||= DEFAULT_CODECLIMATE_TMP
|
|
22
|
+
|
|
23
|
+
if ENV["CODECLIMATE_DOCKER"]
|
|
24
|
+
new(host_prefix, "/tmp/cc")
|
|
25
|
+
else
|
|
26
|
+
new(host_prefix, host_prefix)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def initialize(host_prefix, container_prefix, path = nil)
|
|
31
|
+
@host_prefix = host_prefix
|
|
32
|
+
@container_prefix = container_prefix
|
|
33
|
+
@path = path
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def host_path
|
|
37
|
+
if path
|
|
38
|
+
File.join(host_prefix, path)
|
|
39
|
+
else
|
|
40
|
+
host_prefix
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def container_path
|
|
45
|
+
if path
|
|
46
|
+
File.join(container_prefix, path)
|
|
47
|
+
else
|
|
48
|
+
container_prefix
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def join(path)
|
|
53
|
+
@path = path
|
|
54
|
+
|
|
55
|
+
self
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def file?
|
|
59
|
+
File.file?(container_path)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def read
|
|
63
|
+
File.read(container_path)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def write(content)
|
|
67
|
+
FileUtils.mkdir_p(File.dirname(container_path))
|
|
68
|
+
File.write(container_path, content)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def delete
|
|
72
|
+
File.delete(container_path)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
private
|
|
76
|
+
|
|
77
|
+
attr_reader :host_prefix, :container_prefix, :path
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module Analyzer
|
|
3
|
+
class RaisingContainerListener < ContainerListener
|
|
4
|
+
def initialize(failure_ex, timeout_ex = nil, maximum_output_ex = nil)
|
|
5
|
+
@failure_ex = failure_ex
|
|
6
|
+
@timeout_ex = timeout_ex || failure_ex
|
|
7
|
+
@maximum_output_ex = maximum_output_ex || failure_ex
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def finished(engine, _details, result)
|
|
11
|
+
if result.timed_out?
|
|
12
|
+
message = "engine #{engine.name} ran for #{result.duration / 1000}"
|
|
13
|
+
message << " seconds and was killed"
|
|
14
|
+
raise timeout_ex.new(message, engine.name)
|
|
15
|
+
elsif result.maximum_output_exceeded?
|
|
16
|
+
message = "engine #{engine.name} produced too much output"
|
|
17
|
+
message << " (#{result.output_byte_count} bytes)"
|
|
18
|
+
raise maximum_output_ex.new(message, engine.name)
|
|
19
|
+
elsif result.exit_status.nonzero?
|
|
20
|
+
message = "engine #{engine.name} failed"
|
|
21
|
+
message << " with status #{result.exit_status}"
|
|
22
|
+
message << " and stderr \n#{result.stderr}"
|
|
23
|
+
raise failure_ex.new(message, engine.name)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :failure_ex, :timeout_ex, :maximum_output_ex
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Adapted from https://github.com/whitequark/parser/blob/master/lib/parser/source/buffer.rb
|
|
2
|
+
module CC
|
|
3
|
+
module Analyzer
|
|
4
|
+
class SourceBuffer
|
|
5
|
+
attr_reader :name
|
|
6
|
+
attr_reader :source
|
|
7
|
+
|
|
8
|
+
def initialize(name, source)
|
|
9
|
+
@name = name
|
|
10
|
+
@source = source
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def decompose_position(position)
|
|
14
|
+
line_no, line_begin = line_for(position)
|
|
15
|
+
|
|
16
|
+
[1 + line_no, position - line_begin]
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def line_count
|
|
20
|
+
@source.lines.count
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def line_for(position)
|
|
26
|
+
line_begins.bsearch do |_, line_begin|
|
|
27
|
+
line_begin <= position
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def line_begins
|
|
32
|
+
unless @line_begins
|
|
33
|
+
@line_begins = [[0, 0]]
|
|
34
|
+
index = 1
|
|
35
|
+
|
|
36
|
+
@source.each_char do |char|
|
|
37
|
+
@line_begins.unshift [@line_begins.length, index] if char == "\n"
|
|
38
|
+
|
|
39
|
+
index += 1
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
@line_begins
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module Analyzer
|
|
3
|
+
class SourceExtractor
|
|
4
|
+
InvalidLocation = Class.new(StandardError)
|
|
5
|
+
|
|
6
|
+
def initialize(source)
|
|
7
|
+
@source = source
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def extract(location)
|
|
11
|
+
validate_location(location)
|
|
12
|
+
|
|
13
|
+
if (lines = location["lines"])
|
|
14
|
+
extract_from_lines(lines)
|
|
15
|
+
elsif (positions = location["positions"])
|
|
16
|
+
extract_from_positions(positions)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
attr_reader :source
|
|
23
|
+
|
|
24
|
+
def validate_location(location)
|
|
25
|
+
validator = IssueValidations::LocationFormatValidation::Validator.new(location)
|
|
26
|
+
unless validator.valid?
|
|
27
|
+
raise InvalidLocation, validator.message
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def extract_from_lines(lines)
|
|
32
|
+
begin_index = lines.fetch("begin") - 1
|
|
33
|
+
end_index = lines.fetch("end") - 1
|
|
34
|
+
range = (begin_index..end_index)
|
|
35
|
+
|
|
36
|
+
source.each_line.with_object("").with_index do |(source_line, memo), index|
|
|
37
|
+
memo << source_line if range.include?(index)
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def extract_from_positions(positions)
|
|
42
|
+
positions = convert_to_offsets(positions)
|
|
43
|
+
begin_offset = positions.fetch("begin").fetch("offset")
|
|
44
|
+
end_offset = positions.fetch("end").fetch("offset")
|
|
45
|
+
length = end_offset - begin_offset
|
|
46
|
+
|
|
47
|
+
source[begin_offset, length + 1]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def convert_to_offsets(positions)
|
|
51
|
+
positions.each_with_object({}) do |(key, value), memo|
|
|
52
|
+
memo[key] =
|
|
53
|
+
if value.key?("offset")
|
|
54
|
+
value
|
|
55
|
+
else
|
|
56
|
+
{
|
|
57
|
+
"offset" => to_offset(value["line"] - 1, value["column"] - 1),
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def to_offset(line, column, offset = 0)
|
|
64
|
+
source.each_line.with_index do |source_line, index|
|
|
65
|
+
offset +=
|
|
66
|
+
if line == index
|
|
67
|
+
column
|
|
68
|
+
else
|
|
69
|
+
source_line.length
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
break if index >= line
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
offset
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
require "digest/md5"
|
|
2
|
+
|
|
3
|
+
module CC
|
|
4
|
+
module Analyzer
|
|
5
|
+
class SourceFingerprint
|
|
6
|
+
def initialize(issue)
|
|
7
|
+
@issue = issue
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def compute
|
|
11
|
+
md5 = Digest::MD5.new
|
|
12
|
+
md5 << issue.path
|
|
13
|
+
md5 << issue.check_name.to_s
|
|
14
|
+
md5 << relevant_source.gsub(/\s+/, "") if relevant_source
|
|
15
|
+
md5.hexdigest
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
attr_reader :issue
|
|
21
|
+
|
|
22
|
+
def relevant_source
|
|
23
|
+
source = SourceExtractor.new(raw_source).extract(issue.location)
|
|
24
|
+
|
|
25
|
+
if source && !source.empty?
|
|
26
|
+
source.encode(Encoding::UTF_8, "binary", invalid: :replace, undef: :replace, replace: "")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def raw_source
|
|
31
|
+
@raw_source ||=
|
|
32
|
+
if File.file?(issue.path)
|
|
33
|
+
File.read(issue.path)
|
|
34
|
+
else
|
|
35
|
+
""
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module Analyzer
|
|
3
|
+
class StatsdContainerListener < ContainerListener
|
|
4
|
+
def initialize(statsd)
|
|
5
|
+
@statsd = statsd
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def started(engine, _details)
|
|
9
|
+
increment(engine, "started")
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def finished(engine, _details, result)
|
|
13
|
+
timing(engine, "time", result.duration)
|
|
14
|
+
increment(engine, "finished")
|
|
15
|
+
|
|
16
|
+
if result.timed_out?
|
|
17
|
+
timing(engine, "time", result.duration)
|
|
18
|
+
increment(engine, "result.error")
|
|
19
|
+
increment(engine, "result.error.timeout")
|
|
20
|
+
elsif result.maximum_output_exceeded?
|
|
21
|
+
increment(engine, "result.error")
|
|
22
|
+
increment(engine, "result.error.output_exceeded")
|
|
23
|
+
elsif result.exit_status.nonzero?
|
|
24
|
+
increment(engine, "result.error")
|
|
25
|
+
else
|
|
26
|
+
increment(engine, "result.success")
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
attr_reader :statsd
|
|
33
|
+
|
|
34
|
+
def increment(engine, metric)
|
|
35
|
+
statsd.increment("engines.#{metric}")
|
|
36
|
+
statsd.increment("engines.names.#{engine.name}.#{metric}")
|
|
37
|
+
if engine.respond_to?(:channel) && engine.channel
|
|
38
|
+
statsd.increment("engines.names.#{engine.name}.#{engine.channel}.#{metric}")
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def timing(engine, metric, ms)
|
|
43
|
+
statsd.timing("engines.#{metric}", ms)
|
|
44
|
+
statsd.timing("engines.names.#{engine.name}.#{metric}", ms)
|
|
45
|
+
if engine.respond_to?(:channel) && engine.channel
|
|
46
|
+
statsd.timing("engines.names.#{engine.name}.#{engine.channel}.#{metric}", ms)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module Analyzer
|
|
3
|
+
module Validator
|
|
4
|
+
attr_reader :error
|
|
5
|
+
|
|
6
|
+
def initialize(document)
|
|
7
|
+
@document = document
|
|
8
|
+
validate
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def validate
|
|
12
|
+
return @valid unless @valid.nil?
|
|
13
|
+
|
|
14
|
+
if document && invalid_messages.any?
|
|
15
|
+
@error = {
|
|
16
|
+
message: "#{invalid_messages.join("; ")}: `#{document}`.",
|
|
17
|
+
document: document,
|
|
18
|
+
}
|
|
19
|
+
@valid = false
|
|
20
|
+
else
|
|
21
|
+
@valid = true
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
alias valid? validate
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
attr_reader :document
|
|
29
|
+
|
|
30
|
+
def invalid_messages
|
|
31
|
+
@invalid_messages ||= self.class.validations.each_with_object([]) do |check, result|
|
|
32
|
+
validator = check.new(document)
|
|
33
|
+
result << validator.message unless validator.valid?
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
data/lib/cc/analyzer.rb
ADDED
|
@@ -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,90 @@
|
|
|
1
|
+
require "cc/cli/command"
|
|
2
|
+
|
|
3
|
+
module CC
|
|
4
|
+
module CLI
|
|
5
|
+
class Analyze < Command
|
|
6
|
+
ARGUMENT_LIST = "[-f format] [-e engine[:channel]] [path]".freeze
|
|
7
|
+
SHORT_HELP = "Run analysis with the given arguments".freeze
|
|
8
|
+
HELP = "#{SHORT_HELP}\n" \
|
|
9
|
+
"\n" \
|
|
10
|
+
" -f <format>, --format <format> Format of output. Possible values: #{CC::Analyzer::Formatters::FORMATTERS.keys.join ", "}\n" \
|
|
11
|
+
" -e <engine[:channel]> Engine to run. Can be specified multiple times.\n" \
|
|
12
|
+
" --dev Run in development mode. Engines installed locally that are not in the manifest will be run.\n" \
|
|
13
|
+
" path Path to check. Can be specified multiple times.".freeze
|
|
14
|
+
|
|
15
|
+
autoload :EngineFailure, "cc/cli/analyze/engine_failure"
|
|
16
|
+
|
|
17
|
+
include CC::Analyzer
|
|
18
|
+
|
|
19
|
+
def run
|
|
20
|
+
# Load config here so it sees ./.codeclimate.yml
|
|
21
|
+
@config = Config.load
|
|
22
|
+
|
|
23
|
+
# process args after, so it modifies loaded configuration
|
|
24
|
+
process_args
|
|
25
|
+
|
|
26
|
+
bridge = Bridge.new(
|
|
27
|
+
config: config,
|
|
28
|
+
formatter: formatter,
|
|
29
|
+
listener: CompositeContainerListener.new(
|
|
30
|
+
LoggingContainerListener.new(Analyzer.logger),
|
|
31
|
+
RaisingContainerListener.new(EngineFailure),
|
|
32
|
+
),
|
|
33
|
+
registry: EngineRegistry.new,
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
bridge.run
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
attr_reader :config, :engines_disabled, :listener, :registry
|
|
42
|
+
|
|
43
|
+
def process_args
|
|
44
|
+
while (arg = @args.shift)
|
|
45
|
+
case arg
|
|
46
|
+
when "-f", "--format"
|
|
47
|
+
@formatter = Formatters.resolve(@args.shift).new(filesystem)
|
|
48
|
+
when "-e", "--engine"
|
|
49
|
+
disable_all_engines!
|
|
50
|
+
name, channel = @args.shift.split(":", 2)
|
|
51
|
+
enable_engine(name, channel)
|
|
52
|
+
when "--dev"
|
|
53
|
+
config.development = true
|
|
54
|
+
when "--no-plugins"
|
|
55
|
+
config.disable_plugins!
|
|
56
|
+
else
|
|
57
|
+
config.analysis_paths << arg
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
rescue Formatters::Formatter::InvalidFormatterError => ex
|
|
61
|
+
fatal(ex.message)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def formatter
|
|
65
|
+
@formatter ||= Formatters::PlainTextFormatter.new(filesystem)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def disable_all_engines!
|
|
69
|
+
unless engines_disabled
|
|
70
|
+
config.engines.each { |e| e.enabled = false }
|
|
71
|
+
@engines_disabled = true
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def enable_engine(name, channel)
|
|
76
|
+
existing_engine = config.engines.detect { |e| e.name == name }
|
|
77
|
+
if existing_engine.present?
|
|
78
|
+
existing_engine.enabled = true
|
|
79
|
+
existing_engine.channel = channel if channel.present?
|
|
80
|
+
else
|
|
81
|
+
config.engines << Config::Engine.new(
|
|
82
|
+
name,
|
|
83
|
+
channel: channel,
|
|
84
|
+
enabled: true,
|
|
85
|
+
)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
require "highline"
|
|
2
|
+
require "active_support"
|
|
3
|
+
require "active_support/core_ext"
|
|
4
|
+
require "rainbow"
|
|
5
|
+
require "cc/cli/output"
|
|
6
|
+
|
|
7
|
+
module CC
|
|
8
|
+
module CLI
|
|
9
|
+
class Command
|
|
10
|
+
include CC::CLI::Output
|
|
11
|
+
|
|
12
|
+
CODECLIMATE_YAML = ".codeclimate.yml".freeze
|
|
13
|
+
NAMESPACE = name.split("::")[0..-2].join("::").freeze
|
|
14
|
+
|
|
15
|
+
def self.abstract!
|
|
16
|
+
@abstract = true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def self.abstract?
|
|
20
|
+
@abstract == true
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def self.all
|
|
24
|
+
@@subclasses.reject(&:abstract?)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def self.[](name)
|
|
28
|
+
all.find { |command| command.name == "#{NAMESPACE}::#{name}" || command.command_name == name }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# rubocop: disable Style/ClassVars
|
|
32
|
+
def self.inherited(subclass)
|
|
33
|
+
@@subclasses ||= []
|
|
34
|
+
@@subclasses << subclass
|
|
35
|
+
end
|
|
36
|
+
# rubocop: enable Style/ClassVars
|
|
37
|
+
|
|
38
|
+
def self.synopsis
|
|
39
|
+
"#{command_name} #{self::ARGUMENT_LIST if const_defined?(:ARGUMENT_LIST)}".strip
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def self.short_help
|
|
43
|
+
if const_defined? :SHORT_HELP
|
|
44
|
+
self::SHORT_HELP
|
|
45
|
+
else
|
|
46
|
+
""
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def self.help
|
|
51
|
+
if const_defined? :HELP
|
|
52
|
+
self::HELP
|
|
53
|
+
else
|
|
54
|
+
short_help
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def initialize(args = [])
|
|
59
|
+
@args = args
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def run
|
|
63
|
+
$stderr.puts "unknown command #{self.class.name.split("::").last.underscore}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def self.command_name
|
|
67
|
+
name.gsub(/^#{NAMESPACE}::/, "").split("::").map do |part|
|
|
68
|
+
part.split(/(?=[A-Z])/).map(&:downcase).join("-")
|
|
69
|
+
end.join(":")
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def execute
|
|
73
|
+
run
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
private
|
|
77
|
+
|
|
78
|
+
def filesystem
|
|
79
|
+
@filesystem ||= CC::Analyzer::Filesystem.new(
|
|
80
|
+
CC::Analyzer::MountedPath.code.container_path,
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module CLI
|
|
3
|
+
class Console < Command
|
|
4
|
+
SHORT_HELP = "Open a ruby console for the CLI. Useful for developing against the CLI.".freeze
|
|
5
|
+
|
|
6
|
+
def run
|
|
7
|
+
require "pry"
|
|
8
|
+
binding.pry(quiet: true, prompt: Pry::SIMPLE_PROMPT, output: $stdout)
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
module CC
|
|
2
|
+
module CLI
|
|
3
|
+
module Engines
|
|
4
|
+
class Install < EngineCommand
|
|
5
|
+
SHORT_HELP = "Pull the latest images for enabled engines in your configuration".freeze
|
|
6
|
+
|
|
7
|
+
ImagePullFailure = Class.new(StandardError)
|
|
8
|
+
|
|
9
|
+
def run
|
|
10
|
+
say "Pulling docker images."
|
|
11
|
+
pull_docker_images
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def config
|
|
17
|
+
@config ||= CC::Config.load
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def pull_docker_images
|
|
21
|
+
config.engines.each(&method(:pull_engine))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def pull_engine(engine)
|
|
25
|
+
metadata = engine_registry.fetch_engine_details(engine)
|
|
26
|
+
unless system("docker pull #{metadata.image}")
|
|
27
|
+
raise ImagePullFailure, "unable to pull image #{metadata.image}"
|
|
28
|
+
end
|
|
29
|
+
rescue EngineRegistry::EngineDetailsNotFoundError
|
|
30
|
+
warn("unknown engine <#{engine.name}:#{engine.channel}>")
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|