matrixeval-ruby 0.1.1 → 0.3.0

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