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,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
|