matrixeval-ruby 0.1.1 → 0.3.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6bd932a09eb30fdf342cc0596af3e6cd82496deaa0af2746bdc79a06b1c85911
4
- data.tar.gz: b13ef75ed283407d0d3e9ac50f0d0053a81a39dbcab737183e6886b9caf34a99
3
+ metadata.gz: 526b698efa76d623a97a48ec4f9ed2d365ed9c05f2d6ea8230d7778997173dd9
4
+ data.tar.gz: fcb6eb6971632aaf115e00390c7b0230e903e4ef57421e9e0d3a192535b645b8
5
5
  SHA512:
6
- metadata.gz: a8593994584983cde95650942a8ca0479c58412ecdccaddb2ab4aa31f378544f46fead1f05d9e12f33f405b68aeb8d5fdf4017059e39f81c22a8d494d89619e6
7
- data.tar.gz: b83de57cc53b6b3a35dd86e0fd4196218a900219af7868b3870feff925db05a0c6fe6c0820cbef94903dac4c186684ac13d897a31ce75a612149d945f5cf31eb
6
+ metadata.gz: fc21ef4da660bddffb12d523559c7609b6d8b232c2b38409184c9e67260b37d5acf23a0d2456afe977076f09ccf436b0b138502caf3b3db90b4d330a0050d2dd
7
+ data.tar.gz: 35085fc143ce553458ebbab452aec1a062f1faed2915098c6d9753115ca906cd19d2f335bbf5def2310f18bddd380bf43351ad23f00191d3401035892580ba0f
data/CHANGELOG.md CHANGED
@@ -1,8 +1,28 @@
1
1
  ## [Unreleased]
2
2
 
3
+ ## [0.3.0] - 2022-02-21
4
+
5
+ - Support add extra docker compose services and volumes
6
+ - Isolate each job with docker compose project name and network
7
+
8
+ ## [0.2.2] - 2022-02-11
9
+
10
+ - Auto remove containers
11
+
12
+ ## [0.2.1] - 2022-02-11
13
+
14
+ - Fix a assignment method issue
15
+
16
+ ## [0.2.0] - 2022-02-10
17
+
18
+ - Change config format from 0.1 to 0.2
19
+ - CLI description
20
+ - Other errors fix
21
+
3
22
  ## [0.1.1] - 2022-02-09
4
23
 
5
24
  - Fix a execution report issue
25
+
6
26
  ## [0.1.0] - 2022-02-09
7
27
 
8
28
  - Initial release
data/README.md CHANGED
@@ -1,6 +1,15 @@
1
1
  # matrixeval-ruby
2
2
 
3
3
  Test your ruby code against multiple versions of dependencies like Ruby, Rails, Env ...
4
+
5
+ ![](https://raw.githubusercontent.com/MatrixEval/assets/main/screenshots/summary.png)
6
+
7
+ ## Features
8
+
9
+ - Parallel test your ruby code against multiple versions of dependencies combinations.
10
+ - Test your ruby code against a specific dependencies combination.
11
+ - Choose any docker image you like for each job.
12
+ - Easy to use CLI to speed up your development efficiency
4
13
  ## Installation
5
14
 
6
15
  Add this line to your application's Gemfile:
@@ -19,7 +28,98 @@ Or install it yourself as:
19
28
 
20
29
  ## Usage
21
30
 
22
- TODO: Write usage instructions here
31
+ Initialize
32
+
33
+ ```bash
34
+ matrixeval init
35
+ ```
36
+
37
+ Customize `matrixeval.yml` file and run commands like:
38
+
39
+ ```bash
40
+ matrixeval --all bundle install
41
+ matrixeval --all rspec
42
+ matrixeval --ruby 3.0 rspec a_spec.rb
43
+ matrixeval --ruby 3.1 --rails 7.0 rake test
44
+ matrixeval bash
45
+ ```
46
+ Run `matrixeval --help` for more details
47
+
48
+ ![](https://raw.githubusercontent.com/MatrixEval/assets/main/screenshots/help.png)
49
+
50
+ ### Configuration Example
51
+
52
+ Here is the configuration file `matrixeval.yml` which will auto created by `matrixeval init`
53
+
54
+ ```yaml
55
+ version: 0.3
56
+ project_name: REPLACE_ME
57
+ target: ruby
58
+ parallel_workers: number_of_processors
59
+ # commands:
60
+ # - ps
61
+ # - top
62
+ # - an_additional_command
63
+ # mounts:
64
+ # - /a/path/need/to/mount:/a/path/mount/to
65
+ matrix:
66
+ ruby:
67
+ variants:
68
+ - key: 2.7
69
+ container:
70
+ image: ruby:2.7.1
71
+ - key: 3.0
72
+ default: true
73
+ container:
74
+ image: ruby:3.0.0
75
+ - key: 3.1
76
+ container:
77
+ image: ruby:3.1.0
78
+ # - key: jruby-9.3
79
+ # container:
80
+ # image: jruby:9.3
81
+ # env:
82
+ # PATH: "/opt/jruby/bin:/app/bin:/bundle/bin:$PATH"
83
+ # mounts:
84
+ # - /a/path/need/to/mount:/a/path/mount/to
85
+
86
+ # rails:
87
+ # variants:
88
+ # - key: 6.1
89
+ # default: true
90
+ # env:
91
+ # RAILS_VERSION: "~> 6.1.0"
92
+ # - key: 7.0
93
+ # env:
94
+ # RAILS_VERSION: "~> 7.0.0"
95
+ # another:
96
+ # variants:
97
+ # - key: key1
98
+ # default: true
99
+ # env:
100
+ # ENV_KEY: 1
101
+ # - key: key2
102
+ # env:
103
+ # ENV_KEY: 2
104
+
105
+ exclude:
106
+ # - ruby: 3.0
107
+ # rails: 4.2
108
+ # - ruby: jruby-9.3
109
+ # rails: 7.0
110
+ ```
111
+
112
+ ### Gemfile configuration example
113
+
114
+ Here is an example from [ruby-trello](https://github.com/jeremytregunna/ruby-trello)
115
+
116
+ ```ruby
117
+ if active_model_version = ENV['ACTIVE_MODEL_VERSION']
118
+ gem 'activemodel', active_model_version
119
+ end
120
+ ```
121
+
122
+ You can also check its corresponding [`matrixeval.yml`](https://github.com/jeremytregunna/ruby-trello/blob/master/matrixeval.yml)
23
123
 
24
124
  ## Development
25
125
 
data/exe/matrixeval CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'matrixeval/ruby'
4
- puts "Hello, MatrixEval!"
4
+
5
5
  Matrixeval.start(ARGV)
data/exe/meval CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env ruby
2
2
 
3
3
  require 'matrixeval/ruby'
4
- puts "Hello, MatrixEval!"
4
+
5
5
  Matrixeval.start(ARGV)
@@ -24,11 +24,58 @@ module Matrixeval
24
24
 
25
25
  def parse!
26
26
  OptionParser.new do |opts|
27
- opts.banner = "Usage: meval/matrixeval --[VECTOR_KEY] [VECTOR_CHOICE] [COMMAND] [COMMAND_OPTIONS]"
27
+ opts.version = Matrixeval::Ruby::VERSION
28
+ opts.program_name = ""
29
+ opts.banner = <<~USAGE
30
+ Usage:
31
+ matrixeval(meval) [OPTIONS] COMMAND
32
+ USAGE
33
+
34
+ opts.separator ""
35
+ opts.separator "Options:"
36
+
37
+ opts.on "-a", "--all", "# Run the COMMAND against all matrix combinations"
28
38
 
29
39
  Config.vectors.each do |vector|
30
- opts.on("--#{vector.key} [VERSION]", "Set #{vector.key} version")
40
+ # short = "-#{vector.short_key}"
41
+ long = "--#{vector.key} [VERSION]"
42
+ desc = [
43
+ "# Run the COMMAND against a specific #{vector.key} version",
44
+ "# Options: #{vector.variants.map(&:key).join("/")}",
45
+ "# Default: #{vector.default_variant.key}",
46
+ "# Customizable"
47
+ ]
48
+ opts.separator ""
49
+ opts.on(long, *desc)
50
+ end
51
+
52
+ opts.separator ""
53
+ opts.separator "Commands: #{Config.commands.join("/")} (Customizable)"
54
+
55
+ opts.separator ""
56
+ opts.separator "MatrixEval Options:"
57
+
58
+ opts.on("-h", "--help", "# Show help") do
59
+ puts opts.help
60
+ exit
31
61
  end
62
+
63
+ opts.on("-v", "--version", "# Show version") do
64
+ puts opts.version
65
+ exit
66
+ end
67
+
68
+ opts.separator ""
69
+ opts.separator "Customizations:"
70
+ opts.separator " You can customize all options in matrixeval.yml"
71
+
72
+ opts.separator ""
73
+ opts.separator "Example:"
74
+ opts.separator " matrixeval --all bundle install"
75
+ opts.separator " matrixeval --ruby 3.0 rspec a_spec.rb"
76
+ opts.separator " matrixeval --ruby 3.1 --active_model 7.0 rake test"
77
+ opts.separator " matrixeval bash"
78
+
32
79
  end.parse!(context_arguments, into: options)
33
80
  end
34
81
 
@@ -2,6 +2,12 @@ require_relative "./command_line/parse_context_arguments"
2
2
 
3
3
  module Matrixeval
4
4
  module Ruby
5
+ COMMANDS = [
6
+ 'ruby', 'rake', 'rails', 'rspec', 'bundle',
7
+ 'bin/rake', 'bin/rails', 'bin/rspec', 'bin/test',
8
+ 'bash', 'dash', 'sh', 'zsh'
9
+ ]
10
+
5
11
  class CommandLine
6
12
 
7
13
  attr_reader :argv
@@ -10,38 +16,42 @@ module Matrixeval
10
16
  @argv = argv
11
17
  end
12
18
 
19
+ def valid?
20
+ init? ||
21
+ !context_options.empty? ||
22
+ !seperator_index.nil?
23
+ end
24
+
13
25
  def init?
14
- argv[0] == 'init'
26
+ @argv[0] == 'init'
15
27
  end
16
28
 
17
29
  def all?
18
- argv[0] == 'all'
30
+ context_options[:all]
19
31
  end
20
32
 
21
33
  def context_options
22
- ParseContextArguments.call(context_arguments)
34
+ @context_options ||= ParseContextArguments.call(context_arguments)
23
35
  end
24
36
 
25
37
  def context_arguments
26
- argv[0...seperator_index]
38
+ arguments = @argv[0...seperator_index]
39
+ arguments << "-h" if @argv.empty?
40
+ arguments
27
41
  end
28
42
 
29
43
  def rest_arguments
30
- argv[seperator_index..-1]
44
+ @argv[seperator_index..-1]
31
45
  end
32
46
 
33
47
  private
34
48
 
35
49
  def seperator_index
36
- argv.index do |argument|
37
- seperator_commands.include?(argument)
50
+ @argv.index do |argument|
51
+ Config.commands.include?(argument)
38
52
  end
39
53
  end
40
54
 
41
- def seperator_commands
42
- ['rake', 'rspec', 'bundle', 'bash']
43
- end
44
-
45
55
  end
46
56
  end
47
57
  end
@@ -2,6 +2,9 @@ module Matrixeval
2
2
  module Ruby
3
3
  class Config
4
4
  class YAML
5
+
6
+ class MissingError < StandardError; end
7
+
5
8
  class << self
6
9
 
7
10
  def create
@@ -25,6 +28,8 @@ module Matrixeval
25
28
  end
26
29
 
27
30
  def yaml
31
+ raise MissingError unless File.exist?(path)
32
+
28
33
  ::YAML.load File.read(path)
29
34
  end
30
35
 
@@ -1,6 +1,8 @@
1
1
  require 'yaml'
2
2
  require_relative "./vector"
3
3
  require_relative "./config/yaml"
4
+ require_relative "./docker_compose/extend_raw"
5
+ require_relative "./docker_compose/extend"
4
6
 
5
7
  module Matrixeval
6
8
  module Ruby
@@ -15,6 +17,16 @@ module Matrixeval
15
17
  YAML["target"]
16
18
  end
17
19
 
20
+ def project_name
21
+ name = YAML["project_name"]
22
+
23
+ if name.nil? || name.strip.empty?
24
+ raise Error.new('missing project_name')
25
+ end
26
+
27
+ name
28
+ end
29
+
18
30
  def vectors
19
31
  @vectors = YAML["matrix"].map do |key, vector_config|
20
32
  Vector.new(key, vector_config)
@@ -49,6 +61,37 @@ module Matrixeval
49
61
  YAML["parallel_workers"] || "number_of_processors"
50
62
  end
51
63
 
64
+ def commands
65
+ cmds = YAML["commands"] || []
66
+ COMMANDS + cmds
67
+ end
68
+
69
+ def docker_compose_extend_raw
70
+ DockerCompose::ExtendRaw.new(
71
+ YAML["docker-compose-extend"] || {}
72
+ )
73
+ end
74
+
75
+ def env
76
+ YAML["env"] || {}
77
+ end
78
+
79
+ def mounts
80
+ YAML["mounts"] || []
81
+ end
82
+
83
+ def all_mounts
84
+ mounts + all_variant_mounts
85
+ end
86
+
87
+ private
88
+
89
+ def all_variant_mounts
90
+ Config.vectors
91
+ .map(&:variants).flatten
92
+ .map(&:mounts).flatten
93
+ end
94
+
52
95
  end
53
96
  end
54
97
  end
@@ -0,0 +1,15 @@
1
+ module Matrixeval
2
+ module Ruby
3
+ class Container
4
+
5
+ attr_reader :image, :env
6
+
7
+ def initialize(options)
8
+ options ||= {}
9
+ @image = options["image"]
10
+ @env = options["env"] || {}
11
+ end
12
+
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,43 @@
1
+ require 'erb'
2
+ require 'json'
3
+
4
+ module Matrixeval
5
+ module Ruby
6
+ class Context
7
+ class BuildDockerComposeExtend
8
+ class << self
9
+ def call(context)
10
+ new(context).call
11
+ end
12
+ end
13
+
14
+ attr_reader :context
15
+
16
+ def initialize(context)
17
+ @context = context
18
+ end
19
+
20
+ def matrix_combination_id
21
+ context.id
22
+ end
23
+
24
+ def call
25
+ DockerCompose::Extend.new(docker_compose_extend)
26
+ end
27
+
28
+ private
29
+
30
+ def docker_compose_extend
31
+ JSON.parse(render_erb)
32
+ end
33
+
34
+ def render_erb
35
+ ERB.new(
36
+ Config.docker_compose_extend_raw.content
37
+ ).result(binding)
38
+ end
39
+
40
+ end
41
+ end
42
+ end
43
+ end
@@ -1,4 +1,5 @@
1
1
  require_relative "./context/find_by_command_options"
2
+ require_relative "./context/build_docker_compose_extend"
2
3
 
3
4
  module Matrixeval
4
5
  module Ruby
@@ -52,7 +53,11 @@ module Matrixeval
52
53
  end
53
54
 
54
55
  def gemfile_lock_path
55
- Matrixeval.working_dir.join(".matrixeval/Gemfile.lock.#{id}")
56
+ Matrixeval.working_dir.join(".matrixeval/gemfile_locks/#{id}")
57
+ end
58
+
59
+ def docker_compose_file_path
60
+ Matrixeval.working_dir.join(".matrixeval/docker-compose/#{id}.yml")
56
61
  end
57
62
 
58
63
  def variants
@@ -72,6 +77,10 @@ module Matrixeval
72
77
  end
73
78
  end
74
79
 
80
+ def docker_compose_extend
81
+ BuildDockerComposeExtend.call(self)
82
+ end
83
+
75
84
  end
76
85
  end
77
86
  end
@@ -0,0 +1,21 @@
1
+ module Matrixeval
2
+ module Ruby
3
+ class DockerCompose
4
+ class Extend
5
+
6
+ def initialize(config)
7
+ @config = config || {}
8
+ end
9
+
10
+ def volumes
11
+ @config["volumes"] || {}
12
+ end
13
+
14
+ def services
15
+ @config["services"] || {}
16
+ end
17
+
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,19 @@
1
+ require 'json'
2
+
3
+ module Matrixeval
4
+ module Ruby
5
+ class DockerCompose
6
+ class ExtendRaw
7
+
8
+ def initialize(config)
9
+ @config = config || {}
10
+ end
11
+
12
+ def content
13
+ @config.to_json
14
+ end
15
+
16
+ end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,135 @@
1
+ require "erb"
2
+
3
+ module Matrixeval
4
+ module Ruby
5
+ class DockerCompose
6
+ class File
7
+ class << self
8
+
9
+ def create_all
10
+ FileUtils.mkdir_p folder
11
+
12
+ Context.all.each do |context|
13
+ new(context).create
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def folder
20
+ Matrixeval.working_dir.join(".matrixeval/docker-compose")
21
+ end
22
+ end
23
+
24
+ attr_reader :context
25
+
26
+ def initialize(context)
27
+ @context = context
28
+ end
29
+
30
+ def create
31
+ ::File.open(docker_compose_file_path, 'w+') do |file|
32
+ file.puts build_content
33
+ end
34
+ end
35
+
36
+ private
37
+
38
+ def docker_compose_file_path
39
+ context.docker_compose_file_path
40
+ end
41
+
42
+ def build_content
43
+ {
44
+ "version" => "3",
45
+ "services" => services_json,
46
+ "volumes" => volumes_json
47
+ }.to_yaml.sub(/---\n/, "")
48
+ end
49
+
50
+ def services_json
51
+ services = {}
52
+
53
+ services[main_variant.docker_compose_service_name] = {
54
+ "image" => main_variant.container.image,
55
+ "volumes" => mounts(main_variant),
56
+ "environment" => {
57
+ "BUNDLE_PATH" => "/bundle",
58
+ "GEM_HOME" => "/bundle",
59
+ "BUNDLE_APP_CONFIG" => "/bundle",
60
+ "BUNDLE_BIN" => "/bundle/bin",
61
+ "PATH" => "/app/bin:/bundle/bin:$PATH"
62
+ }.merge(extra_env),
63
+ "working_dir" => "/app"
64
+ }.merge(depends_on)
65
+
66
+ services.merge(docker_compose_extend.services)
67
+ end
68
+
69
+ def volumes_json
70
+ {
71
+ bundle_volume => {
72
+ "name" => bundle_volume
73
+ }
74
+ }.merge(docker_compose_extend.volumes)
75
+ end
76
+
77
+ def depends_on
78
+ if docker_compose_extend.services.keys.empty?
79
+ {}
80
+ else
81
+ { "depends_on" => docker_compose_extend.services.keys }
82
+ end
83
+ end
84
+
85
+ def extra_env
86
+ Config.env.merge(context.env)
87
+ .merge(main_variant.container.env)
88
+ end
89
+
90
+ def main_variant
91
+ context.main_variant
92
+ end
93
+
94
+ def bundle_volume
95
+ main_variant.bundle_volume_name
96
+ end
97
+
98
+ def mounts(variant)
99
+ [
100
+ "../..:/app:cached",
101
+ "#{variant.bundle_volume_name}:/bundle",
102
+ "../gemfile_locks/#{context.id}:/app/Gemfile.lock"
103
+ ] + extra_mounts
104
+ end
105
+
106
+ def extra_mounts
107
+ mounts = Config.mounts + context.variants.map(&:mounts).flatten
108
+ mounts.map do |mount|
109
+ local_path, in_docker_path = mount.split(':')
110
+ next mount if Pathname.new(local_path).absolute?
111
+
112
+ local_path = Matrixeval.working_dir.join(local_path)
113
+ docker_compose_folder_path = Matrixeval.working_dir.join(".matrixeval/docker-compose")
114
+ local_path = local_path.relative_path_from docker_compose_folder_path
115
+
116
+ "#{local_path}:#{in_docker_path}"
117
+ end
118
+ end
119
+
120
+ def docker_compose_extend
121
+ @docker_compose_extend ||= context.docker_compose_extend
122
+ end
123
+
124
+ def working_dir_name
125
+ Matrixeval.working_dir.basename
126
+ end
127
+
128
+ def project_name
129
+ Config.project_name.gsub(/[^A-Za-z0-9-]/,'_').downcase
130
+ end
131
+
132
+ end
133
+ end
134
+ end
135
+ end
@@ -1,5 +1,5 @@
1
1
 
2
- require_relative "./docker_compose/yaml"
2
+ require_relative "./docker_compose/file"
3
3
 
4
4
  module Matrixeval
5
5
  module Ruby
@@ -12,34 +12,55 @@ module Matrixeval
12
12
  end
13
13
 
14
14
  def run(arguments)
15
- forward_arguments = arguments.join(" ")
15
+ forward_arguments = arguments.map do |arg|
16
+ arg.match(/\s/) ? "\"#{arg}\"" : arg
17
+ end.join(" ")
18
+
19
+ no_tty = %w[bash sh zsh dash].include?(arguments[0]) ? '' : '--no-TTY'
16
20
 
17
21
  system(
18
22
  <<~DOCKER_COMPOSE_COMMAND
19
- docker compose -f .matrixeval/docker-compose.yml \
23
+ #{docker_compose} \
20
24
  run --rm \
21
- #{env} \
22
- #{gemfile_mount} \
23
- #{docker_compose_service_name} \
25
+ #{no_tty} \
26
+ #{context.docker_compose_service_name} \
24
27
  #{forward_arguments}
25
28
  DOCKER_COMPOSE_COMMAND
26
29
  )
30
+ ensure
31
+ stop_containers
32
+ clean_containers_and_anonymous_volumes
33
+ turn_on_stty_opost
27
34
  end
28
35
 
29
36
  private
30
37
 
31
- def env
32
- context.env.map do |k, v|
33
- "-e #{k}='#{v}'"
34
- end.join(" ")
38
+ def stop_containers
39
+ system("#{docker_compose} stop >> /dev/null 2>&1")
40
+ end
41
+
42
+ def clean_containers_and_anonymous_volumes
43
+ system("#{docker_compose} rm -v -f >> /dev/null 2>&1")
44
+ end
45
+
46
+ def docker_compose
47
+ <<~DOCKER_COMPOSE_COMMAND.strip
48
+ docker --log-level error compose \
49
+ -f #{yaml_file} \
50
+ -p matrixeval-#{project_name}-#{context.id}
51
+ DOCKER_COMPOSE_COMMAND
52
+ end
53
+
54
+ def yaml_file
55
+ ".matrixeval/docker-compose/#{context.id}.yml"
35
56
  end
36
57
 
37
- def gemfile_mount
38
- "-v ./.matrixeval/Gemfile.lock.#{context.id}:/app/Gemfile.lock"
58
+ def turn_on_stty_opost
59
+ system("stty opost")
39
60
  end
40
61
 
41
- def docker_compose_service_name
42
- context.docker_compose_service_name
62
+ def project_name
63
+ Config.project_name.gsub(/[^A-Za-z0-9-]/,'_').downcase
43
64
  end
44
65
 
45
66
  end
@@ -0,0 +1,23 @@
1
+ module Matrixeval
2
+ module Ruby
3
+ class ExtraMountFiles
4
+ class << self
5
+
6
+ def create
7
+ Config.all_mounts.each do |mount|
8
+ local_path, _ = mount.split(':')
9
+ next mount if Pathname.new(local_path).absolute?
10
+
11
+ local_path = Matrixeval.working_dir.join(local_path)
12
+ next if local_path.extname.empty?
13
+ next if local_path.ascend.none? { |path| path == Matrixeval.working_dir }
14
+
15
+ FileUtils.mkdir_p local_path.dirname
16
+ FileUtils.touch local_path
17
+ end
18
+ end
19
+
20
+ end
21
+ end
22
+ end
23
+ end
@@ -4,7 +4,7 @@ module Matrixeval
4
4
  class << self
5
5
 
6
6
  def create
7
- FileUtils.mkdir_p dot_matrixeval_folder
7
+ FileUtils.mkdir_p gemfile_lock_folder
8
8
 
9
9
  Context.all.each do |context|
10
10
  FileUtils.touch context.gemfile_lock_path
@@ -13,8 +13,8 @@ module Matrixeval
13
13
 
14
14
  private
15
15
 
16
- def dot_matrixeval_folder
17
- Matrixeval.working_dir.join(".matrixeval")
16
+ def gemfile_lock_folder
17
+ Matrixeval.working_dir.join(".matrixeval/gemfile_locks")
18
18
  end
19
19
 
20
20
  end
@@ -41,7 +41,7 @@ module Matrixeval
41
41
  end
42
42
 
43
43
  def gemfile_locks
44
- ".matrixeval/Gemfile.lock.*"
44
+ ".matrixeval/gemfile_locks"
45
45
  end
46
46
 
47
47
  def gitignore_path
@@ -20,9 +20,13 @@ module Matrixeval
20
20
  def initialize(argv)
21
21
  @argv = argv
22
22
  @command = CommandLine.new(argv)
23
+ @threads ||= []
24
+ @matrixeval_results ||= []
23
25
  end
24
26
 
25
27
  def start
28
+ validates
29
+
26
30
  if command.init?
27
31
  init
28
32
  elsif command.all?
@@ -30,12 +34,31 @@ module Matrixeval
30
34
  else
31
35
  run_a_specific_context
32
36
  end
37
+ rescue OptionParser::InvalidOption => e
38
+ puts <<~ERROR
39
+ #{e.message}
40
+ See 'matrixeval --help'
41
+ ERROR
42
+ exit
43
+ rescue Config::YAML::MissingError
44
+ puts "Please run 'matrixeval init' first to generate matrixeval.yml"
45
+ exit
33
46
  ensure
34
47
  turn_on_stty_opost
35
48
  end
36
49
 
37
50
  private
38
51
 
52
+ def validates
53
+ return if command.valid?
54
+
55
+ puts <<~ERROR
56
+ matrixeval: '#{argv.join(' ')}' is not a MatrixEval command.
57
+ See 'matrixeval --help'
58
+ ERROR
59
+ exit
60
+ end
61
+
39
62
  def init
40
63
  Config::YAML.create
41
64
  Gitignore.update
@@ -43,9 +66,12 @@ module Matrixeval
43
66
 
44
67
  def run_all_contexts
45
68
  Config::YAML.create
46
- DockerCompose::YAML.create
69
+ DockerCompose::File.create_all
47
70
  GemfileLocks.create
48
71
  Gitignore.update
72
+ ExtraMountFiles.create
73
+
74
+ pull_all_images
49
75
 
50
76
  if workers_count == 1
51
77
  run_all_contexts_sequentially
@@ -62,17 +88,17 @@ module Matrixeval
62
88
  docker_compose = DockerCompose.new(context)
63
89
  success = docker_compose.run(command.rest_arguments)
64
90
 
65
- self.matrixeval_results << [context, !!success]
91
+ @matrixeval_results << [context, !!success]
66
92
  end
67
93
 
68
94
  report
69
95
  end
70
96
 
71
97
  def run_all_contexts_in_parallel
72
- parallel do |contexts|
98
+ parallel(contexts) do |sub_contexts|
73
99
  Thread.current[:matrixeval_results] = []
74
100
 
75
- contexts.each do |context|
101
+ sub_contexts.each do |context|
76
102
  docker_compose = DockerCompose.new(context)
77
103
  success = docker_compose.run(command.rest_arguments)
78
104
 
@@ -85,9 +111,10 @@ module Matrixeval
85
111
 
86
112
  def run_a_specific_context
87
113
  Config::YAML.create
88
- DockerCompose::YAML.create
114
+ DockerCompose::File.create_all
89
115
  GemfileLocks.create
90
116
  Gitignore.update
117
+ ExtraMountFiles.create
91
118
 
92
119
  context = Context.find_by_command_options!(command.context_options)
93
120
 
@@ -98,15 +125,28 @@ module Matrixeval
98
125
  docker_compose.run(command.rest_arguments)
99
126
  end
100
127
 
128
+ def pull_all_images
129
+ parallel(Config.main_vector_variants) do |sub_variants|
130
+ sub_variants.each do |variant|
131
+ puts "Docker image check/pull #{variant.container.image}"
132
+ image_exists = system %Q{[ -n "$(docker images -q #{variant.container.image})" ]}
133
+ next if image_exists
134
+
135
+ system "docker pull #{variant.container.image}"
136
+ end
137
+ end
138
+ end
139
+
101
140
  def report
102
141
  turn_on_stty_opost
103
142
 
104
143
  table = Terminal::Table.new(title: Rainbow("MatrixEval").blue.bright + " Summary", alignment: :center) do |table|
105
144
 
106
- table.add_row(Config.vectors.map(&:key) + ['result'])
145
+ headers = Config.vectors.map(&:key) + ['result']
146
+ table.add_row headers.map { |value| { value: value, alignment: :center } }
107
147
  table.add_separator
108
148
 
109
- matrixeval_results.each do |context, success|
149
+ @matrixeval_results.each do |context, success|
110
150
  success_cell = [success ? Rainbow('Success').green : Rainbow('Failed').red]
111
151
  row = (context.variants.map(&:key) + success_cell).map do |value|
112
152
  { value: value, alignment: :center }
@@ -120,32 +160,30 @@ module Matrixeval
120
160
  puts table
121
161
  end
122
162
 
123
- def parallel
124
- contexts = Context.all
163
+ def parallel(collection)
164
+ @threads = [] unless @threads.empty?
165
+ @matrixeval_results = [] unless @matrixeval_results.empty?
125
166
 
126
- contexts.each_slice(contexts.count / workers_count) do |sub_contexts|
127
- threads << Thread.new do
128
- yield sub_contexts
167
+ collection.each_slice(per_worker_contexts_count) do |sub_collection|
168
+ @threads << Thread.new do
169
+ yield sub_collection
129
170
  end
130
171
  end
131
172
 
132
- threads.each(&:join)
173
+ @threads.each(&:join)
133
174
 
134
- threads.each do |thread|
135
- self.matrixeval_results += thread[:matrixeval_results]
175
+ @threads.each do |thread|
176
+ @matrixeval_results += (thread[:matrixeval_results] || [])
136
177
  end
137
178
  end
138
179
 
139
- def threads
140
- @threads ||= []
141
- end
142
180
 
143
- def matrixeval_results
144
- @matrixeval_results ||= []
181
+ def per_worker_contexts_count
182
+ [(contexts.count / workers_count), 1].max
145
183
  end
146
184
 
147
- def matrixeval_results=(results)
148
- @matrixeval_results = results
185
+ def contexts
186
+ @contexts ||= Context.all
149
187
  end
150
188
 
151
189
  def workers_count
@@ -1,13 +1,55 @@
1
- version: 0.1
1
+ version: 0.3
2
+ project_name: REPLACE_ME
2
3
  target: ruby
3
4
  parallel_workers: number_of_processors
5
+ # commands:
6
+ # - ps
7
+ # - top
8
+ # - an_additional_command
9
+ # mounts:
10
+ # - /a/path/need/to/mount:/a/path/mount/to
4
11
  matrix:
5
12
  ruby:
6
13
  variants:
7
14
  - key: 2.7
8
- image: ruby:2.7.1
15
+ container:
16
+ image: ruby:2.7.1
9
17
  - key: 3.0
10
- image: ruby:3.0.0
11
18
  default: true
19
+ container:
20
+ image: ruby:3.0.0
12
21
  - key: 3.1
13
- image: ruby:3.1.0
22
+ container:
23
+ image: ruby:3.1.0
24
+ # - key: jruby-9.3
25
+ # container:
26
+ # image: jruby:9.3
27
+ # env:
28
+ # PATH: "/opt/jruby/bin:/app/bin:/bundle/bin:$PATH"
29
+ # mounts:
30
+ # - /a/path/need/to/mount:/a/path/mount/to
31
+
32
+ # rails:
33
+ # variants:
34
+ # - key: 6.1
35
+ # default: true
36
+ # env:
37
+ # RAILS_VERSION: "~> 6.1.0"
38
+ # - key: 7.0
39
+ # env:
40
+ # RAILS_VERSION: "~> 7.0.0"
41
+ # another:
42
+ # variants:
43
+ # - key: key1
44
+ # default: true
45
+ # env:
46
+ # ENV_KEY: 1
47
+ # - key: key2
48
+ # env:
49
+ # ENV_KEY: 2
50
+
51
+ exclude:
52
+ # - ruby: 3.0
53
+ # rails: 4.2
54
+ # - ruby: jruby-9.3
55
+ # rails: 7.0
@@ -1,3 +1,5 @@
1
+ require_relative "./container"
2
+
1
3
  module Matrixeval
2
4
  module Ruby
3
5
  class Variant
@@ -7,16 +9,17 @@ module Matrixeval
7
9
  end
8
10
  end
9
11
 
10
- attr_reader :key, :image, :env, :vector, :default
12
+ attr_reader :key, :env, :vector, :default, :container, :mounts
11
13
 
12
14
  def initialize(config = {}, vector)
13
15
  raise Error.new("Variant#key is missing") if config["key"].nil?
14
16
 
15
17
  @vector = vector
16
18
  @key = config["key"].to_s
17
- @image = config["image"]
19
+ @container = Container.new(config["container"])
18
20
  @env = config["env"] || {}
19
21
  @default = config["default"] || false
22
+ @mounts = config["mounts"] || []
20
23
  end
21
24
 
22
25
  def name
@@ -24,7 +27,7 @@ module Matrixeval
24
27
  end
25
28
 
26
29
  def bundle_volume_name
27
- "bundle_#{image.gsub(/[^A-Za-z0-9]/,'_')}"
30
+ "bundle_#{container.image.gsub(/[^A-Za-z0-9]/,'_')}"
28
31
  end
29
32
 
30
33
  def id
@@ -3,11 +3,10 @@ require_relative "./variant"
3
3
  module Matrixeval
4
4
  module Ruby
5
5
  class Vector
6
- attr_reader :key, :variants, :mounts
6
+ attr_reader :key, :variants
7
7
 
8
8
  def initialize(key, config)
9
9
  @key = key.to_s
10
- @mounts = config["mounts"] || []
11
10
  @variants = (config["variants"] || []).map do |variant_config|
12
11
  config = if variant_config.is_a?(Hash)
13
12
  variant_config
@@ -30,7 +29,7 @@ module Matrixeval
30
29
  def default_variant
31
30
  variant = variants.find(&:default?)
32
31
  if variant.nil?
33
- raise Error.new("Please set a default variant for matrix #{vector.key}")
32
+ raise Error.new("Please set a default variant for matrix #{key}")
34
33
  end
35
34
 
36
35
  variant
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Matrixeval
4
4
  module Ruby
5
- VERSION = "0.1.1"
5
+ VERSION = "0.3.0"
6
6
  end
7
7
  end
@@ -5,6 +5,7 @@ require 'rainbow'
5
5
  require 'matrixeval/ruby/docker_compose'
6
6
  require 'matrixeval/ruby/context'
7
7
  require 'matrixeval/ruby/gemfile_locks'
8
+ require 'matrixeval/ruby/extra_mount_files'
8
9
  require 'matrixeval/ruby/runner'
9
10
  require 'matrixeval/ruby/gitignore'
10
11
 
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: matrixeval-ruby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hopper Gee
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2022-02-09 00:00:00.000000000 Z
11
+ date: 2022-02-20 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rainbow
@@ -64,7 +64,6 @@ files:
64
64
  - CHANGELOG.md
65
65
  - CODE_OF_CONDUCT.md
66
66
  - Gemfile
67
- - Gemfile.lock
68
67
  - LICENSE.txt
69
68
  - README.md
70
69
  - Rakefile
@@ -78,10 +77,15 @@ files:
78
77
  - lib/matrixeval/ruby/command_line/parse_context_arguments.rb
79
78
  - lib/matrixeval/ruby/config.rb
80
79
  - lib/matrixeval/ruby/config/yaml.rb
80
+ - lib/matrixeval/ruby/container.rb
81
81
  - lib/matrixeval/ruby/context.rb
82
+ - lib/matrixeval/ruby/context/build_docker_compose_extend.rb
82
83
  - lib/matrixeval/ruby/context/find_by_command_options.rb
83
84
  - lib/matrixeval/ruby/docker_compose.rb
84
- - lib/matrixeval/ruby/docker_compose/yaml.rb
85
+ - lib/matrixeval/ruby/docker_compose/extend.rb
86
+ - lib/matrixeval/ruby/docker_compose/extend_raw.rb
87
+ - lib/matrixeval/ruby/docker_compose/file.rb
88
+ - lib/matrixeval/ruby/extra_mount_files.rb
85
89
  - lib/matrixeval/ruby/gemfile_locks.rb
86
90
  - lib/matrixeval/ruby/gitignore.rb
87
91
  - lib/matrixeval/ruby/runner.rb
data/Gemfile.lock DELETED
@@ -1,37 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- matrixeval-ruby (0.1.0)
5
- concurrent-ruby
6
- rainbow (~> 3.1)
7
- terminal-table
8
-
9
- GEM
10
- remote: https://rubygems.org/
11
- specs:
12
- byebug (11.1.3)
13
- concurrent-ruby (1.1.9)
14
- minitest (5.15.0)
15
- minitest-focus (1.3.1)
16
- minitest (>= 4, < 6)
17
- mocha (1.13.0)
18
- rainbow (3.1.1)
19
- rake (13.0.6)
20
- terminal-table (3.0.2)
21
- unicode-display_width (>= 1.1.1, < 3)
22
- unicode-display_width (2.1.0)
23
-
24
- PLATFORMS
25
- x86_64-darwin-19
26
- x86_64-linux
27
-
28
- DEPENDENCIES
29
- byebug
30
- matrixeval-ruby!
31
- minitest (~> 5.0)
32
- minitest-focus
33
- mocha
34
- rake (~> 13.0)
35
-
36
- BUNDLED WITH
37
- 2.2.32
@@ -1,77 +0,0 @@
1
- require "erb"
2
-
3
- module Matrixeval
4
- module Ruby
5
- class DockerCompose
6
- class YAML
7
- class << self
8
-
9
- def create
10
- FileUtils.mkdir_p dot_matrixeval_folder
11
-
12
- File.open(path, 'w+') do |file|
13
- file.puts build_content
14
- end
15
- end
16
-
17
- private
18
-
19
- def build_content
20
- {
21
- "version" => "3",
22
- "services" => services_json,
23
- "volumes" => volumes_json
24
- }.to_yaml.sub(/---\n/, "")
25
- end
26
-
27
- def services_json
28
- services = {}
29
-
30
- Config.main_vector_variants.map do |variant|
31
- services[variant.docker_compose_service_name] = {
32
- "image" => variant.image,
33
- "volumes" => mounts(variant),
34
- "environment" => {
35
- "BUNDLE_PATH" => "/bundle",
36
- "GEM_HOME" => "/bundle",
37
- "BUNDLE_APP_CONFIG" => "/bundle",
38
- "BUNDLE_BIN" => "/bundle/bin",
39
- "PATH" => "/app/bin:/bundle/bin:$PATH"
40
- },
41
- "working_dir" => "/app"
42
- }
43
- end
44
-
45
- services
46
- end
47
-
48
- def volumes_json
49
- bundle_volumes.map do |volume|
50
- [volume, {"name" => volume}]
51
- end.to_h
52
- end
53
-
54
- def bundle_volumes
55
- Config.main_vector_variants.map(&:bundle_volume_name)
56
- end
57
-
58
- def mounts(variant)
59
- [
60
- "..:/app:cached",
61
- "#{variant.bundle_volume_name}:/bundle",
62
- ] + Config.main_vector.mounts
63
- end
64
-
65
- def path
66
- dot_matrixeval_folder.join("docker-compose.yml")
67
- end
68
-
69
- def dot_matrixeval_folder
70
- Matrixeval.working_dir.join(".matrixeval")
71
- end
72
-
73
- end
74
- end
75
- end
76
- end
77
- end