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,11 @@
1
+ module CC
2
+ module Analyzer
3
+ class MeasurementValidator
4
+ include Validator
5
+
6
+ def self.validations
7
+ MeasurementValidations.validations
8
+ end
9
+ end
10
+ end
11
+ end
@@ -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
@@ -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,11 @@
1
+ module CC
2
+ module CLI
3
+ class Analyze < Command
4
+ class EngineFailure < StandardError
5
+ def initialize(message, _engine_name)
6
+ super(message)
7
+ end
8
+ end
9
+ end
10
+ end
11
+ 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,15 @@
1
+ require "cc/analyzer"
2
+
3
+ module CC
4
+ module CLI
5
+ module Engines
6
+ class EngineCommand < Command
7
+ abstract!
8
+
9
+ def engine_registry
10
+ @engine_registry ||= EngineRegistry.new
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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