performa 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.ruby-version +1 -1
- data/Gemfile.lock +4 -4
- data/exe/performa +14 -2
- data/lib/performa/command_result.rb +15 -0
- data/lib/performa/configuration-template.yml +30 -0
- data/lib/performa/configuration.rb +13 -4
- data/lib/performa/container_registry.rb +1 -1
- data/lib/performa/coordinator.rb +6 -8
- data/lib/performa/environment.rb +16 -12
- data/lib/performa/errors.rb +6 -0
- data/lib/performa/images.rb +33 -10
- data/lib/performa/logger.rb +16 -2
- data/lib/performa/shell_helper.rb +27 -20
- data/lib/performa/stage.rb +39 -0
- data/lib/performa/version.rb +1 -1
- data/lib/performa.rb +3 -3
- metadata +6 -4
- data/lib/performa/container_id.rb +0 -18
- data/lib/performa/stages.rb +0 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 610de8109b1e5e60dabaf00a4082876e02aadfeb30ade8c3d1c4029002f65ad8
|
4
|
+
data.tar.gz: f67c8f636156222f878b8a8ba7ab599e4eb3d768cbf8858ec74b461cf2f6263b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 3e6b1b838b7b31388acfb7f63cd6f7da63ff93a1640d11fc1069f8bd3184c1ed998dee0a11ee5ba86a4a528ebb3a47dae256f4be61fcacc55b031eea1e92d4a4
|
7
|
+
data.tar.gz: 00fde074f5669f69a4b31c50db8ba6c0183abddfa25b2d53ce447fb1b2f547f9d9f110058c48b3f5b225cfd7d155f291ad6ff78baefee262bd78bbe50af7e82d
|
data/.ruby-version
CHANGED
@@ -1 +1 @@
|
|
1
|
-
2.6.0-
|
1
|
+
2.6.0-preview3
|
data/Gemfile.lock
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
GIT
|
2
2
|
remote: https://github.com/rubocop-hq/rubocop
|
3
|
-
revision:
|
3
|
+
revision: 52319e485b733e6780a38986e79e7b542b0442aa
|
4
4
|
specs:
|
5
5
|
rubocop (0.60.0)
|
6
6
|
jaro_winkler (~> 1.5.1)
|
@@ -14,7 +14,7 @@ GIT
|
|
14
14
|
PATH
|
15
15
|
remote: .
|
16
16
|
specs:
|
17
|
-
performa (0.
|
17
|
+
performa (0.2.0)
|
18
18
|
colorize (~> 0.8)
|
19
19
|
|
20
20
|
GEM
|
@@ -30,7 +30,7 @@ GEM
|
|
30
30
|
ast (~> 2.4.0)
|
31
31
|
powerpack (0.1.2)
|
32
32
|
rainbow (3.0.0)
|
33
|
-
rake (
|
33
|
+
rake (12.3.1)
|
34
34
|
rspec (3.8.0)
|
35
35
|
rspec-core (~> 3.8.0)
|
36
36
|
rspec-expectations (~> 3.8.0)
|
@@ -59,4 +59,4 @@ DEPENDENCIES
|
|
59
59
|
rubocop!
|
60
60
|
|
61
61
|
BUNDLED WITH
|
62
|
-
|
62
|
+
1.17.1
|
data/exe/performa
CHANGED
@@ -4,7 +4,7 @@
|
|
4
4
|
lib = File.expand_path("../lib", __dir__)
|
5
5
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
6
6
|
|
7
|
-
require
|
7
|
+
require("byebug") if ENV["DEBUG"] && !Gem::Specification.find_all_by_name("byebug").empty?
|
8
8
|
require "performa"
|
9
9
|
require "optionparser"
|
10
10
|
|
@@ -12,7 +12,7 @@ options = Struct.new(:config_file).new
|
|
12
12
|
|
13
13
|
OptionParser.new do |opts|
|
14
14
|
opts.banner = [
|
15
|
-
"Usage: performa [-c config-file.yml]",
|
15
|
+
"Usage: performa [-c performa-config-file.yml]",
|
16
16
|
"Default config files considered: #{Performa::Configuration::DEFAULT_FILES.join(', ')}\n\n"
|
17
17
|
].join("\n")
|
18
18
|
|
@@ -25,6 +25,18 @@ OptionParser.new do |opts|
|
|
25
25
|
exit 0
|
26
26
|
end
|
27
27
|
|
28
|
+
opts.on("-i", "--init [.performa.yml]", "Generate a new config file") do |file|
|
29
|
+
file ||= ".performa.yml"
|
30
|
+
begin
|
31
|
+
Performa::Configuration.generate_file(file)
|
32
|
+
rescue Errno::EEXIST
|
33
|
+
puts "Error: `#{file}` already exists. Please delete it first."
|
34
|
+
exit 1
|
35
|
+
end
|
36
|
+
|
37
|
+
exit 0
|
38
|
+
end
|
39
|
+
|
28
40
|
opts.on("-h", "--help", "Prints this help") do
|
29
41
|
puts opts
|
30
42
|
exit 0
|
@@ -0,0 +1,30 @@
|
|
1
|
+
---
|
2
|
+
|
3
|
+
## [Optional] Config file version (default: latest)
|
4
|
+
version: 1
|
5
|
+
|
6
|
+
## [Required] Base docker images to run command on
|
7
|
+
images:
|
8
|
+
# - ruby:2.4
|
9
|
+
# - ruby:2.5
|
10
|
+
|
11
|
+
## [Optional] Commands setting up each image before running command.
|
12
|
+
## Environments generated = images * stages
|
13
|
+
# stages:
|
14
|
+
# activerecord_4:
|
15
|
+
# - gem install sqlite3
|
16
|
+
# - gem install activerecord -v=4.0.0
|
17
|
+
# activerecord_5:
|
18
|
+
# - gem install sqlite3
|
19
|
+
# - gem install activerecord -v=5.0.0
|
20
|
+
|
21
|
+
## [Optional] Cache environments (as performa docker images)
|
22
|
+
# cache_environments: true
|
23
|
+
|
24
|
+
## [Optional] Volumes to mount
|
25
|
+
# volumes:
|
26
|
+
# - .:/app
|
27
|
+
|
28
|
+
## [Required] Command to run on all environments
|
29
|
+
command: |
|
30
|
+
ruby -e "puts RUBY_VERSION"
|
@@ -5,8 +5,7 @@ require "yaml"
|
|
5
5
|
module Performa
|
6
6
|
class Configuration
|
7
7
|
DEFAULT_FILES = %w[
|
8
|
-
performa.yml config/performa.yml
|
9
|
-
spec/performa.yml test/performa.yml
|
8
|
+
.performa.yml performa.yml config/performa.yml
|
10
9
|
].freeze
|
11
10
|
|
12
11
|
ERR_READING_CONFIG_FILE = "Could not read config file %s (%s)"
|
@@ -40,8 +39,7 @@ module Performa
|
|
40
39
|
end
|
41
40
|
|
42
41
|
def validate_data
|
43
|
-
raise InvalidDataError if
|
44
|
-
!@data["images"]&.is_a?(Array) ||
|
42
|
+
raise InvalidDataError if !@data["images"]&.is_a?(Array) ||
|
45
43
|
@data["command"]&.empty?
|
46
44
|
rescue InvalidDataError
|
47
45
|
raise Error, "Invalid config"
|
@@ -50,5 +48,16 @@ module Performa
|
|
50
48
|
def [](name)
|
51
49
|
@data[name]
|
52
50
|
end
|
51
|
+
|
52
|
+
def cachable_envs?
|
53
|
+
!(@data["cache_environments"] == false || @data["stages"].nil?)
|
54
|
+
end
|
55
|
+
|
56
|
+
def self.generate_file(file)
|
57
|
+
raise Errno::EEXIST if File.exist?(file)
|
58
|
+
|
59
|
+
template_file = File.join(__dir__, "configuration-template.yml")
|
60
|
+
FileUtils.cp(template_file, file, verbose: true)
|
61
|
+
end
|
53
62
|
end
|
54
63
|
end
|
data/lib/performa/coordinator.rb
CHANGED
@@ -20,15 +20,13 @@ module Performa
|
|
20
20
|
end
|
21
21
|
|
22
22
|
def process_env(env, config:)
|
23
|
+
LOG.info_notice("Processing #{env.name}")
|
23
24
|
container_id = Images.process(env, config: config)
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
result = run_command("docker container exec #{container_id} #{config['command']}")
|
30
|
-
ContainerRegistry.kill(container_id)
|
31
|
-
result
|
25
|
+
run_container_command(container_id, config["command"], success_only: false)
|
26
|
+
rescue CommandFailureError => error
|
27
|
+
error.message
|
28
|
+
ensure
|
29
|
+
ContainerRegistry.kill(container_id) if container_id
|
32
30
|
end
|
33
31
|
end
|
34
32
|
end
|
data/lib/performa/environment.rb
CHANGED
@@ -5,15 +5,24 @@ require "digest"
|
|
5
5
|
module Performa
|
6
6
|
class Environment
|
7
7
|
def self.all(config)
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
8
|
+
config["stages"] ? all_with_active_stages(config) : all_without_stages(config)
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.all_without_stages(config)
|
12
|
+
config["images"].map do |image|
|
13
|
+
new(image: image, volumes: config["volumes"])
|
12
14
|
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.all_with_active_stages(config)
|
18
|
+
skipped = config["skip"]&.flat_map { |image, stages_names| [image].product(stages_names) }
|
13
19
|
|
14
|
-
config["images"].product(config["stages"].to_a).map do |image,
|
20
|
+
config["images"].product(config["stages"].to_a).map do |image, config_stage|
|
21
|
+
next if skipped&.include?([image, config_stage[0]])
|
22
|
+
|
23
|
+
stage = Stage.from_config(config_stage, image: image)
|
15
24
|
new(image: image, stage: stage, volumes: config["volumes"])
|
16
|
-
end
|
25
|
+
end.compact
|
17
26
|
end
|
18
27
|
|
19
28
|
attr_reader :image, :stage, :volumes, :name, :hash
|
@@ -23,15 +32,10 @@ module Performa
|
|
23
32
|
@stage = stage
|
24
33
|
@volumes = volumes || []
|
25
34
|
assign_name
|
26
|
-
assign_hash
|
27
35
|
end
|
28
36
|
|
29
37
|
def assign_name
|
30
|
-
@name = @image.tr(":", "_") + ("-#{@stage
|
31
|
-
end
|
32
|
-
|
33
|
-
def assign_hash
|
34
|
-
@hash = Digest::SHA1.hexdigest(@image + @stage.to_s)
|
38
|
+
@name = @image.tr(":", "_") + ("-#{@stage.name}" if @stage).to_s
|
35
39
|
end
|
36
40
|
end
|
37
41
|
end
|
data/lib/performa/images.rb
CHANGED
@@ -8,22 +8,41 @@ module Performa
|
|
8
8
|
CACHED_IMAGES_NAME = "performa_env"
|
9
9
|
|
10
10
|
def process(env, config:)
|
11
|
-
|
11
|
+
if config.cachable_envs?
|
12
12
|
container_id = container_id_for_cached_image(env)
|
13
13
|
return container_id if container_id
|
14
14
|
end
|
15
15
|
|
16
16
|
pull_if_missing(env.image)
|
17
|
-
|
18
|
-
|
17
|
+
container_id = start_image_container(env.image, volumes: env.volumes)
|
18
|
+
|
19
|
+
if env.stage&.commands
|
20
|
+
run_and_cache_commands(
|
21
|
+
env.stage.commands,
|
22
|
+
cacheable_envs: config.cachable_envs?,
|
23
|
+
container_id: container_id
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
container_id
|
19
28
|
end
|
20
29
|
|
21
30
|
def container_id_for_cached_image(env)
|
22
|
-
|
23
|
-
|
31
|
+
commands = env.stage.commands
|
32
|
+
last_cached_stage_command = commands.reverse.find(&:cache)
|
33
|
+
return unless last_cached_stage_command
|
34
|
+
|
35
|
+
container_id = start_image_container(last_cached_stage_command.cache, volumes: env.volumes)
|
36
|
+
uncached_commands = commands[commands.index(last_cached_stage_command) + 1..-1]
|
37
|
+
run_and_cache_commands(uncached_commands, container_id: container_id)
|
38
|
+
container_id
|
39
|
+
end
|
24
40
|
|
25
|
-
|
26
|
-
|
41
|
+
def run_and_cache_commands(commands, cacheable_envs: true, container_id:)
|
42
|
+
commands.each do |command|
|
43
|
+
run_container_command(container_id, command.value, success_only: false)
|
44
|
+
cache_container(container_id, tag: command.hash) if cacheable_envs
|
45
|
+
end
|
27
46
|
end
|
28
47
|
|
29
48
|
def pull(image)
|
@@ -31,11 +50,15 @@ module Performa
|
|
31
50
|
end
|
32
51
|
|
33
52
|
def pull_if_missing(image)
|
34
|
-
pull(image) unless
|
53
|
+
pull(image) unless presence(image)
|
54
|
+
end
|
55
|
+
|
56
|
+
def presence(image)
|
57
|
+
image unless run_command("docker images -q #{image}").empty?
|
35
58
|
end
|
36
59
|
|
37
|
-
def
|
38
|
-
|
60
|
+
def cache_presence(hash)
|
61
|
+
presence("#{CACHED_IMAGES_NAME}:#{hash}")
|
39
62
|
end
|
40
63
|
|
41
64
|
def start_image_container(image, volumes: [])
|
data/lib/performa/logger.rb
CHANGED
@@ -18,7 +18,21 @@ module Performa
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
-
|
22
|
-
|
21
|
+
class << LOG
|
22
|
+
def info_success(message)
|
23
|
+
LOG.info(message.colorize(:green))
|
24
|
+
end
|
25
|
+
|
26
|
+
def info_error(message)
|
27
|
+
LOG.info(message.colorize(:red))
|
28
|
+
end
|
29
|
+
|
30
|
+
def info_warning(message)
|
31
|
+
LOG.info(message.colorize(:yellow))
|
32
|
+
end
|
33
|
+
|
34
|
+
def info_notice(message)
|
35
|
+
LOG.info(message.colorize(:light_blue))
|
36
|
+
end
|
23
37
|
end
|
24
38
|
end
|
@@ -1,43 +1,50 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require "pty"
|
4
3
|
require "English"
|
4
|
+
require "open3"
|
5
|
+
require "shellwords"
|
5
6
|
|
6
7
|
module Performa
|
7
8
|
module ShellHelper
|
8
9
|
def run_command(command, success_only: true, no_capture: false)
|
9
|
-
LOG.
|
10
|
+
LOG.info("Running `#{command.colorize(:light_yellow)}` ...")
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
result = ""
|
14
|
-
else
|
15
|
-
result = pty_spawn(command)
|
16
|
-
end
|
17
|
-
|
18
|
-
raise "(non-zero exit code)" if success_only && !$CHILD_STATUS.success?
|
12
|
+
exit_status, result_str = no_capture ? run_no_capture_command(command) : run_capture_command(command)
|
13
|
+
raise "(non-zero exit code: #{exit_status.exitstatus})" if success_only && !exit_status.success?
|
19
14
|
|
20
|
-
result
|
15
|
+
CommandResult.new(result_str).tap do |result|
|
16
|
+
result.success = exit_status.success?
|
17
|
+
end
|
21
18
|
rescue StandardError => e
|
22
19
|
raise Error, <<~MSG
|
23
20
|
Error running the command `#{command}`:
|
24
21
|
=> error: #{e.message}
|
25
|
-
=> command output: #{
|
22
|
+
=> command output: #{result_str}
|
26
23
|
MSG
|
27
24
|
end
|
28
25
|
|
29
|
-
def
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
26
|
+
def run_no_capture_command(command)
|
27
|
+
system(command)
|
28
|
+
[$CHILD_STATUS, ""]
|
29
|
+
end
|
30
|
+
|
31
|
+
def run_capture_command(command)
|
32
|
+
exit_status = nil
|
33
|
+
result_str = +""
|
34
|
+
|
35
|
+
Open3.popen2e(command) do |_stdin, stdout_and_stderr, wait_thr|
|
36
|
+
stdout_and_stderr.each do |line|
|
37
|
+
result_str << line
|
35
38
|
end
|
39
|
+
exit_status = wait_thr.value
|
36
40
|
end
|
41
|
+
exit_status.success? ? LOG.info_success(result_str) : LOG.info_error(result_str)
|
37
42
|
|
38
|
-
|
43
|
+
[exit_status, result_str]
|
44
|
+
end
|
39
45
|
|
40
|
-
|
46
|
+
def run_container_command(container_id, command, **options)
|
47
|
+
run_command("docker container exec #{container_id} sh -c #{Shellwords.escape(command)}", options)
|
41
48
|
end
|
42
49
|
end
|
43
50
|
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "digest/sha1"
|
4
|
+
|
5
|
+
module Performa
|
6
|
+
class Stage
|
7
|
+
def self.from_config(config_stage, image:)
|
8
|
+
hash = Digest::SHA1.hexdigest(image)
|
9
|
+
|
10
|
+
commands = config_stage[1].map do |config_command|
|
11
|
+
hash = Digest::SHA1.hexdigest(hash + config_command)
|
12
|
+
StageCommand.new(config_command, hash: hash)
|
13
|
+
end
|
14
|
+
|
15
|
+
new(name: config_stage[0], commands: commands)
|
16
|
+
end
|
17
|
+
|
18
|
+
attr_reader :name, :commands, :hash
|
19
|
+
|
20
|
+
def initialize(name:, commands:)
|
21
|
+
@name = name
|
22
|
+
@commands = commands
|
23
|
+
@hash = Digest::SHA1.hexdigest(@name + @commands.map(&:hash).join)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class StageCommand
|
28
|
+
attr_reader :value, :hash
|
29
|
+
|
30
|
+
def initialize(value, hash:)
|
31
|
+
@value = value
|
32
|
+
@hash = hash
|
33
|
+
end
|
34
|
+
|
35
|
+
def cache
|
36
|
+
@cache ||= Images.cache_presence(@hash)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
data/lib/performa/version.rb
CHANGED
data/lib/performa.rb
CHANGED
@@ -1,17 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "performa/version"
|
4
|
+
require "performa/errors"
|
4
5
|
require "performa/logger"
|
5
6
|
require "performa/configuration"
|
7
|
+
require "performa/command_result"
|
6
8
|
require "performa/shell_helper"
|
7
9
|
require "performa/container_registry"
|
8
10
|
require "performa/environment"
|
9
|
-
require "performa/
|
11
|
+
require "performa/stage"
|
10
12
|
require "performa/images"
|
11
|
-
require "performa/stages"
|
12
13
|
require "performa/coordinator"
|
13
14
|
require "performa/results_helper"
|
14
15
|
|
15
16
|
module Performa
|
16
|
-
Error = Class.new(StandardError)
|
17
17
|
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: performa
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Christophe Maximin
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2018-
|
11
|
+
date: 2018-12-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: colorize
|
@@ -46,16 +46,18 @@ files:
|
|
46
46
|
- bin/setup
|
47
47
|
- exe/performa
|
48
48
|
- lib/performa.rb
|
49
|
+
- lib/performa/command_result.rb
|
50
|
+
- lib/performa/configuration-template.yml
|
49
51
|
- lib/performa/configuration.rb
|
50
|
-
- lib/performa/container_id.rb
|
51
52
|
- lib/performa/container_registry.rb
|
52
53
|
- lib/performa/coordinator.rb
|
53
54
|
- lib/performa/environment.rb
|
55
|
+
- lib/performa/errors.rb
|
54
56
|
- lib/performa/images.rb
|
55
57
|
- lib/performa/logger.rb
|
56
58
|
- lib/performa/results_helper.rb
|
57
59
|
- lib/performa/shell_helper.rb
|
58
|
-
- lib/performa/
|
60
|
+
- lib/performa/stage.rb
|
59
61
|
- lib/performa/version.rb
|
60
62
|
- performa.gemspec
|
61
63
|
homepage: https://github.com/christophemaximin/performa
|
@@ -1,18 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Performa
|
4
|
-
class ContainerId < String
|
5
|
-
attr_accessor :from_cache
|
6
|
-
|
7
|
-
def initialize(*args)
|
8
|
-
@from_cache = false
|
9
|
-
super
|
10
|
-
end
|
11
|
-
|
12
|
-
def self.from_cache(string)
|
13
|
-
str = new(string)
|
14
|
-
str.from_cache = true
|
15
|
-
str
|
16
|
-
end
|
17
|
-
end
|
18
|
-
end
|
data/lib/performa/stages.rb
DELETED
@@ -1,17 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
module Performa
|
4
|
-
module Stages
|
5
|
-
module_function
|
6
|
-
|
7
|
-
extend ShellHelper
|
8
|
-
|
9
|
-
def process(env, container_id:)
|
10
|
-
return unless env.stage
|
11
|
-
|
12
|
-
env.stage[1].each do |command|
|
13
|
-
run_command("docker container exec #{container_id} #{command}")
|
14
|
-
end
|
15
|
-
end
|
16
|
-
end
|
17
|
-
end
|