codeclimate-fede 0.85.21

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