performa 0.1.0 → 0.2.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 +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
|