matrixeval-ruby 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,67 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class Context
4
- class FindByCommandOptions
5
- class << self
6
- def call(options)
7
- new(options).call
8
- end
9
- end
10
-
11
- attr_reader :options
12
-
13
- def initialize(options)
14
- @options = options
15
- end
16
-
17
- def call
18
- context = Context.all.find do |context|
19
- context.main_variant == main_variant &&
20
- context.rest_variants == rest_variants
21
- end
22
-
23
- raise Error.new("Can't find a corresponding matrix") if context.nil?
24
-
25
- context
26
- end
27
-
28
- private
29
-
30
- def main_variant
31
- dig_variant Config.main_vector
32
- end
33
-
34
- def rest_variants
35
- Config.rest_vectors.map do |vector|
36
- dig_variant vector
37
- end.sort do |v1, v2|
38
- v1.id <=> v2.id
39
- end
40
- end
41
-
42
- def dig_variant(vector)
43
- if option_key?(vector.key)
44
- find_variant(vector)
45
- else
46
- vector.default_variant
47
- end
48
- end
49
-
50
- def find_variant(vector)
51
- vector.variants.find do |variant|
52
- option(vector.key) == variant.key
53
- end
54
- end
55
-
56
- def option(key)
57
- options[key.to_sym] || options[key.to_s]
58
- end
59
-
60
- def option_key?(key)
61
- options.key?(key.to_sym) || options.key?(key.to_s)
62
- end
63
-
64
- end
65
- end
66
- end
67
- end
@@ -1,86 +0,0 @@
1
- require_relative "./context/find_by_command_options"
2
- require_relative "./context/build_docker_compose_extend"
3
-
4
- module Matrixeval
5
- module Ruby
6
- class Context
7
-
8
- class << self
9
-
10
- def find_by_command_options!(options)
11
- FindByCommandOptions.call(options)
12
- end
13
-
14
- def all
15
- Config.variant_combinations.map do |variants|
16
- Context.new(
17
- main_variant: variants.find { |v| v.vector.main? },
18
- rest_variants: variants.reject { |v| v.vector.main? }
19
- )
20
- end.select do |context|
21
- Config.exclusions.none? do |exclusion|
22
- context.match_exclusion?(exclusion)
23
- end
24
- end
25
- end
26
-
27
- end
28
-
29
- attr_reader :main_variant, :rest_variants
30
-
31
- def initialize(main_variant:, rest_variants:)
32
- @main_variant = main_variant
33
- @rest_variants = (rest_variants || []).sort do |v1, v2|
34
- v1.id <=> v2.id
35
- end
36
- end
37
-
38
- def name
39
- variants.map(&:name).join(", ")
40
- end
41
-
42
- def id
43
- [[main_variant.id] + rest_variants.map(&:id)].join("_")
44
- end
45
-
46
- def env
47
- rest_variants.map(&:env).reduce({}, &:merge)
48
- .merge(main_variant.env)
49
- end
50
-
51
- def docker_compose_service_name
52
- main_variant.id
53
- end
54
-
55
- def gemfile_lock_path
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")
61
- end
62
-
63
- def variants
64
- [main_variant] + rest_variants
65
- end
66
-
67
- def match_exclusion?(exclusion)
68
- return false if exclusion.empty?
69
-
70
- variants.all? do |variant|
71
- vector_key = variant.vector.key
72
- if exclusion.key?(vector_key)
73
- exclusion[vector_key].to_s == variant.key
74
- else
75
- true
76
- end
77
- end
78
- end
79
-
80
- def docker_compose_extend
81
- BuildDockerComposeExtend.call(self)
82
- end
83
-
84
- end
85
- end
86
- end
@@ -1,21 +0,0 @@
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
@@ -1,19 +0,0 @@
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
@@ -1,135 +0,0 @@
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,68 +0,0 @@
1
-
2
- require_relative "./docker_compose/file"
3
-
4
- module Matrixeval
5
- module Ruby
6
- class DockerCompose
7
-
8
- attr_reader :context
9
-
10
- def initialize(context)
11
- @context = context
12
- end
13
-
14
- def run(arguments)
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'
20
-
21
- system(
22
- <<~DOCKER_COMPOSE_COMMAND
23
- #{docker_compose} \
24
- run --rm \
25
- #{no_tty} \
26
- #{context.docker_compose_service_name} \
27
- #{forward_arguments}
28
- DOCKER_COMPOSE_COMMAND
29
- )
30
- ensure
31
- stop_containers
32
- clean_containers_and_anonymous_volumes
33
- turn_on_stty_opost
34
- end
35
-
36
- private
37
-
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"
56
- end
57
-
58
- def turn_on_stty_opost
59
- system("stty opost")
60
- end
61
-
62
- def project_name
63
- Config.project_name.gsub(/[^A-Za-z0-9-]/,'_').downcase
64
- end
65
-
66
- end
67
- end
68
- end
@@ -1,23 +0,0 @@
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
@@ -1,23 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class GemfileLocks
4
- class << self
5
-
6
- def create
7
- FileUtils.mkdir_p gemfile_lock_folder
8
-
9
- Context.all.each do |context|
10
- FileUtils.touch context.gemfile_lock_path
11
- end
12
- end
13
-
14
- private
15
-
16
- def gemfile_lock_folder
17
- Matrixeval.working_dir.join(".matrixeval/gemfile_locks")
18
- end
19
-
20
- end
21
- end
22
- end
23
- end
@@ -1,54 +0,0 @@
1
- module Matrixeval
2
- module Ruby
3
- class Gitignore
4
- class << self
5
-
6
- def update
7
- add_docker_compose
8
- add_gemfile_locks
9
- end
10
-
11
- private
12
-
13
- def add_docker_compose
14
- return if docker_compose_included?
15
-
16
- File.open(gitignore_path, 'a+') do |file|
17
- file.puts docker_compose
18
- end
19
- end
20
-
21
- def add_gemfile_locks
22
- return if gemfile_locks_included?
23
-
24
- File.open(gitignore_path, 'a+') do |file|
25
- file.puts gemfile_locks
26
- end
27
- end
28
-
29
- def docker_compose_included?
30
- File.exist?(gitignore_path) &&
31
- File.read(gitignore_path).include?(docker_compose)
32
- end
33
-
34
- def gemfile_locks_included?
35
- File.exist?(gitignore_path) &&
36
- File.read(gitignore_path).include?(gemfile_locks)
37
- end
38
-
39
- def docker_compose
40
- ".matrixeval/docker-compose"
41
- end
42
-
43
- def gemfile_locks
44
- ".matrixeval/gemfile_locks"
45
- end
46
-
47
- def gitignore_path
48
- Matrixeval.working_dir.join(".gitignore")
49
- end
50
-
51
- end
52
- end
53
- end
54
- end
@@ -1,205 +0,0 @@
1
- require 'optparse'
2
- require 'pathname'
3
- require 'fileutils'
4
- require 'matrixeval/ruby/config'
5
- require 'matrixeval/ruby/command_line'
6
- require "concurrent/utility/processor_counter"
7
- require 'terminal-table'
8
-
9
- module Matrixeval
10
- module Ruby
11
- class Runner
12
- class << self
13
- def start(argv)
14
- new(argv).start
15
- end
16
- end
17
-
18
- attr_reader :argv, :command
19
-
20
- def initialize(argv)
21
- @argv = argv
22
- @command = CommandLine.new(argv)
23
- @threads ||= []
24
- @matrixeval_results ||= []
25
- end
26
-
27
- def start
28
- validates
29
-
30
- if command.init?
31
- init
32
- elsif command.all?
33
- run_all_contexts
34
- else
35
- run_a_specific_context
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
46
- ensure
47
- turn_on_stty_opost
48
- end
49
-
50
- private
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
-
62
- def init
63
- Config::YAML.create
64
- Gitignore.update
65
- end
66
-
67
- def run_all_contexts
68
- Config::YAML.create
69
- DockerCompose::File.create_all
70
- GemfileLocks.create
71
- Gitignore.update
72
- ExtraMountFiles.create
73
-
74
- pull_all_images
75
-
76
- if workers_count == 1
77
- run_all_contexts_sequentially
78
- else
79
- run_all_contexts_in_parallel
80
- end
81
- end
82
-
83
- def run_all_contexts_sequentially
84
- Context.all.each do |context|
85
- puts Rainbow("[ MatrixEval ] ").blue.bright + Rainbow(" #{context.name} ").white.bright.bg(:blue)
86
- puts Rainbow("[ MatrixEval ] Run \"#{command.rest_arguments.join(" ")}\"").blue.bright
87
-
88
- docker_compose = DockerCompose.new(context)
89
- success = docker_compose.run(command.rest_arguments)
90
-
91
- @matrixeval_results << [context, !!success]
92
- end
93
-
94
- report
95
- end
96
-
97
- def run_all_contexts_in_parallel
98
- parallel(contexts) do |sub_contexts|
99
- Thread.current[:matrixeval_results] = []
100
-
101
- sub_contexts.each do |context|
102
- docker_compose = DockerCompose.new(context)
103
- success = docker_compose.run(command.rest_arguments)
104
-
105
- Thread.current[:matrixeval_results] << [context, !!success]
106
- end
107
- end
108
-
109
- report
110
- end
111
-
112
- def run_a_specific_context
113
- Config::YAML.create
114
- DockerCompose::File.create_all
115
- GemfileLocks.create
116
- Gitignore.update
117
- ExtraMountFiles.create
118
-
119
- context = Context.find_by_command_options!(command.context_options)
120
-
121
- puts Rainbow("[ MatrixEval ] ").blue.bright + Rainbow(" #{context.name} ").white.bright.bg(:blue)
122
- puts Rainbow("[ MatrixEval ] Run \"#{command.rest_arguments.join(" ")}\"").blue.bright
123
-
124
- docker_compose = DockerCompose.new(context)
125
- docker_compose.run(command.rest_arguments)
126
- end
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
-
140
- def report
141
- turn_on_stty_opost
142
-
143
- table = Terminal::Table.new(title: Rainbow("MatrixEval").blue.bright + " Summary", alignment: :center) do |table|
144
-
145
- headers = Config.vectors.map(&:key) + ['result']
146
- table.add_row headers.map { |value| { value: value, alignment: :center } }
147
- table.add_separator
148
-
149
- @matrixeval_results.each do |context, success|
150
- success_cell = [success ? Rainbow('Success').green : Rainbow('Failed').red]
151
- row = (context.variants.map(&:key) + success_cell).map do |value|
152
- { value: value, alignment: :center }
153
- end
154
-
155
- table.add_row row
156
- end
157
-
158
- end
159
-
160
- puts table
161
- end
162
-
163
- def parallel(collection)
164
- @threads = [] unless @threads.empty?
165
- @matrixeval_results = [] unless @matrixeval_results.empty?
166
-
167
- collection.each_slice(per_worker_contexts_count) do |sub_collection|
168
- @threads << Thread.new do
169
- yield sub_collection
170
- end
171
- end
172
-
173
- @threads.each(&:join)
174
-
175
- @threads.each do |thread|
176
- @matrixeval_results += (thread[:matrixeval_results] || [])
177
- end
178
- end
179
-
180
-
181
- def per_worker_contexts_count
182
- [(contexts.count / workers_count), 1].max
183
- end
184
-
185
- def contexts
186
- @contexts ||= Context.all
187
- end
188
-
189
- def workers_count
190
- count = if Config.parallel_workers == "number_of_processors"
191
- Concurrent.physical_processor_count
192
- else
193
- Integer(Config.parallel_workers)
194
- end
195
-
196
- [count, 1].max
197
- end
198
-
199
- def turn_on_stty_opost
200
- system("stty opost")
201
- end
202
-
203
- end
204
- end
205
- end