satorix 0.0.1 → 1.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitignore +4 -1
- data/.gitlab-ci.yml +45 -0
- data/.rspec +2 -1
- data/.rubocop.yml +11 -0
- data/.ruby-version +1 -0
- data/Gemfile +2 -0
- data/Gemfile.lock +25 -0
- data/Procfile +1 -0
- data/README.md +93 -1
- data/Rakefile +8 -4
- data/bin/console +3 -3
- data/bin/satorix +8 -0
- data/lib/satorix.rb +338 -2
- data/lib/satorix/CI/deploy/flynn.rb +129 -0
- data/lib/satorix/CI/deploy/flynn/environment_variables.rb +121 -0
- data/lib/satorix/CI/deploy/flynn/resources.rb +77 -0
- data/lib/satorix/CI/deploy/flynn/routes.rb +266 -0
- data/lib/satorix/CI/deploy/flynn/scale.rb +52 -0
- data/lib/satorix/CI/shared/buildpack_manager.rb +213 -0
- data/lib/satorix/CI/shared/buildpack_manager/buildpack.rb +153 -0
- data/lib/satorix/CI/shared/ruby/gem_manager.rb +79 -0
- data/lib/satorix/CI/shared/yarn_manager.rb +22 -0
- data/lib/satorix/CI/test/python/django_test.rb +35 -0
- data/lib/satorix/CI/test/python/safety.rb +27 -0
- data/lib/satorix/CI/test/ruby/brakeman.rb +32 -0
- data/lib/satorix/CI/test/ruby/bundler_audit.rb +32 -0
- data/lib/satorix/CI/test/ruby/cucumber.rb +26 -0
- data/lib/satorix/CI/test/ruby/rails_test.rb +26 -0
- data/lib/satorix/CI/test/ruby/rspec.rb +26 -0
- data/lib/satorix/CI/test/ruby/rubocop.rb +98 -0
- data/lib/satorix/CI/test/shared/database.rb +74 -0
- data/lib/satorix/shared/console.rb +180 -0
- data/lib/satorix/version.rb +1 -1
- data/satorix.gemspec +13 -11
- data/satorix/CI/deploy/ie_gem_server.rb +76 -0
- data/satorix/CI/deploy/rubygems.rb +77 -0
- data/satorix/custom.rb +21 -0
- metadata +57 -29
- data/.travis.yml +0 -5
@@ -0,0 +1,22 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Shared
|
4
|
+
|
5
|
+
module YarnManager
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Installing yarn dependencies...') { run_command(%w[yarn install]) }
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Python
|
5
|
+
module DjangoTest
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Python version...') { run_command(%w[python --version]) }
|
14
|
+
log_bench('Running tests...') { run_tests }
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def run_tests
|
19
|
+
run_command([manage, 'collectstatic'])
|
20
|
+
run_command([manage, 'test', 'public'])
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
|
27
|
+
def manage
|
28
|
+
File.join(Satorix.app_dir, 'public', 'manage.py')
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Python
|
5
|
+
module Safety
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Python version...') { run_command(%w[python --version]) }
|
14
|
+
log_bench('Installing Safety...') { run_command(%w[pip install safety]) }
|
15
|
+
log_bench('Auditing requirements.txt...') { run_scan }
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def run_scan
|
20
|
+
run_command(%w[safety check -r requirements.txt])
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Ruby
|
5
|
+
module Brakeman
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Ruby version...') { run_command(%w[ruby -v]) }
|
14
|
+
log_bench('Installing Brakeman...') { install_gem }
|
15
|
+
log_bench('Running Brakeman scan...') { run_scan }
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def install_gem
|
20
|
+
run_command(['gem', 'install', 'brakeman', '--no-document', '--bindir', Satorix.bin_dir])
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def run_scan
|
25
|
+
run_command(['brakeman', '-z', '--table-width', '1200', '--exit-on-error', '--path', Satorix.app_dir])
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Ruby
|
5
|
+
module BundlerAudit
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Ruby version...') { run_command(%w[ruby -v]) }
|
14
|
+
log_bench('Installing bundler-audit...') { install_gem }
|
15
|
+
log_bench('Auditing Gemfile.lock...') { run_scan }
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def install_gem
|
20
|
+
run_command(['gem', 'install', 'bundler-audit', '--no-document', '--bindir', Satorix.bin_dir])
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def run_scan
|
25
|
+
run_command(%w[bundle-audit check --update])
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Ruby
|
5
|
+
module Cucumber
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Ruby version...') { run_command(%w[ruby -v]) }
|
14
|
+
log_bench('Running Cucumber...') { run_cucumber }
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def run_cucumber
|
19
|
+
run_command(%w[bundle exec cucumber])
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Ruby
|
5
|
+
module RailsTest
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Ruby version...') { run_command(%w[ruby -v]) }
|
14
|
+
log_bench('Running Rails test...') { run_scan }
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def run_scan
|
19
|
+
run_command(%w[rails test])
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Ruby
|
5
|
+
module Rspec
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Ruby version...') { run_command(%w[ruby -v]) }
|
14
|
+
log_bench('Running specs...') { run_specs }
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def run_specs
|
19
|
+
run_command(%w[rspec spec])
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Ruby
|
5
|
+
module Rubocop
|
6
|
+
|
7
|
+
include Satorix::Shared::Console
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def go
|
13
|
+
log_bench('Displaying current Ruby version...') { run_command(%w[ruby -v]) }
|
14
|
+
log_bench('Installing Rubocop...') { install_gems }
|
15
|
+
log_bench('Configuring Rubocop...') { create_rubocop_config }
|
16
|
+
log_bench('Running Rubocop inspection...') { run_scan }
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
def install_gems
|
21
|
+
run_command(['gem', 'install', 'rubocop', '--no-document', '--bindir', Satorix.bin_dir])
|
22
|
+
run_command(['gem', 'install', 'rubocop-performance', '--no-document', '--bindir', Satorix.bin_dir])
|
23
|
+
run_command(['gem', 'install', 'rubocop-rails_config', '--no-document', '--bindir', Satorix.bin_dir]) if Satorix.rails_app?
|
24
|
+
end
|
25
|
+
|
26
|
+
|
27
|
+
def create_rubocop_config
|
28
|
+
if config_exist?
|
29
|
+
log 'Using existing .rubocop.yml file from project.'
|
30
|
+
else
|
31
|
+
log 'A .rubocop.yml file was not found, generating a default configuration file for this project.'
|
32
|
+
content = Satorix.rails_app? ? config_content_rails : config_content_default
|
33
|
+
save_rubocop_config content
|
34
|
+
end
|
35
|
+
log 'For more information, please refer to https://www.satorix.com/docs/articles/app_using_ruby_on_rails#rubocop.'
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def run_scan
|
40
|
+
command = %w[rubocop --require rubocop-performance --display-cop-names --extra-details --display-style-guide --parallel]
|
41
|
+
command.concat(%w[--require rubocop-rails]) if Satorix.rails_app?
|
42
|
+
|
43
|
+
run_command(command)
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
|
50
|
+
def rubocop_config_file
|
51
|
+
File.join(Satorix.app_dir, '.rubocop.yml')
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def save_rubocop_config(config)
|
56
|
+
File.open(rubocop_config_file, 'w') { |f| f.write(config) }
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def config_exist?
|
61
|
+
File.exist? rubocop_config_file
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def ruby_version
|
66
|
+
version = run_command(%w[bundle platform --ruby], quiet: true)
|
67
|
+
version.match(/\d+\.\d+\.\d+/)[0]
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def config_content_default
|
72
|
+
<<~CONTENT
|
73
|
+
AllCops:
|
74
|
+
TargetRubyVersion: #{ ruby_version }
|
75
|
+
Exclude:
|
76
|
+
- '**/tmp/**/*'
|
77
|
+
- '**/templates/**/*'
|
78
|
+
- '**/vendor/**/*'
|
79
|
+
CONTENT
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
def config_content_rails
|
84
|
+
<<~CONTENT
|
85
|
+
inherit_gem:
|
86
|
+
rubocop-rails_config:
|
87
|
+
- config/rails.yml
|
88
|
+
|
89
|
+
AllCops:
|
90
|
+
TargetRubyVersion: #{ ruby_version }
|
91
|
+
CONTENT
|
92
|
+
end
|
93
|
+
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
module Satorix
|
2
|
+
module CI
|
3
|
+
module Test
|
4
|
+
module Shared
|
5
|
+
module Database
|
6
|
+
|
7
|
+
|
8
|
+
include Satorix::Shared::Console
|
9
|
+
|
10
|
+
|
11
|
+
extend self
|
12
|
+
|
13
|
+
|
14
|
+
def host
|
15
|
+
ENV['DB_HOST'].to_s
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
def name
|
20
|
+
name_key.empty? ? '' : ENV[name_key].to_s
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
def name_key
|
25
|
+
{
|
26
|
+
mariadb: 'MYSQL_DATABASE',
|
27
|
+
mysql: 'MYSQL_DATABASE',
|
28
|
+
postgres: 'POSTGRES_DB'
|
29
|
+
}[host.to_sym].to_s
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
def password
|
34
|
+
password_key.empty? ? '' : ENV[password_key].to_s
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
def password_key
|
39
|
+
{
|
40
|
+
mariadb: 'MYSQL_ROOT_PASSWORD',
|
41
|
+
mysql: 'MYSQL_ROOT_PASSWORD',
|
42
|
+
postgres: 'POSTGRES_PASSWORD'
|
43
|
+
}[host.to_sym].to_s
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def url
|
48
|
+
unset = [host, user, password, name].select(&:empty?)
|
49
|
+
if unset.empty?
|
50
|
+
"#{ host }://#{ user }:#{ password }@#{ host }/#{ name }"
|
51
|
+
else
|
52
|
+
log 'No database has been configured.'
|
53
|
+
log 'If you would like to use a database for this job, ensure that your gitlab-config.yml is configured.'
|
54
|
+
log 'Most databases require you to specify a type, host, database, username, and password.'
|
55
|
+
nil
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def user
|
61
|
+
(ENV[user_key] || 'root').to_s
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def user_key
|
66
|
+
"#{ host.upcase }_USER"
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,180 @@
|
|
1
|
+
module Satorix
|
2
|
+
module Shared
|
3
|
+
module Console
|
4
|
+
|
5
|
+
require 'benchmark'
|
6
|
+
require 'English' # http://ruby-doc.org/stdlib-2.0.0/libdoc/English/rdoc/English.html
|
7
|
+
require 'shellwords'
|
8
|
+
|
9
|
+
extend self
|
10
|
+
|
11
|
+
|
12
|
+
def colorize(text, color = nil)
|
13
|
+
"\033[#{ colors[color] }m#{ text }\033[0m"
|
14
|
+
end
|
15
|
+
|
16
|
+
|
17
|
+
def colors
|
18
|
+
Hash.new(39).merge(red: '0;31', light_red: '1;31',
|
19
|
+
green: '0;32', light_green: '1;32',
|
20
|
+
yellow: '0;33', light_yellow: '1;33',
|
21
|
+
blue: '0;34', light_blue: '1;34',
|
22
|
+
magenta: '0;35', light_magenta: '1;35',
|
23
|
+
cyan: '0;36', light_cyan: '1;36',
|
24
|
+
white: '0;37', light_white: '1;37')
|
25
|
+
end
|
26
|
+
|
27
|
+
|
28
|
+
def humanize_time(seconds)
|
29
|
+
return 'less than 1 second' if seconds < 1
|
30
|
+
[[60, :second], [60, :minute], [24, :hour], [1000, :day]].map do |count, name|
|
31
|
+
if seconds > 0
|
32
|
+
seconds, n = seconds.divmod(count)
|
33
|
+
n = n.to_i
|
34
|
+
"#{ n } #{ name }#{ 's' if n > 1 }" if n > 0
|
35
|
+
end
|
36
|
+
end.compact.reverse.join(' ')
|
37
|
+
end
|
38
|
+
|
39
|
+
|
40
|
+
def log(text, color = nil)
|
41
|
+
puts color ? colorize(text, color) : text
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
def log_command(text)
|
46
|
+
log text, :cyan
|
47
|
+
end
|
48
|
+
|
49
|
+
|
50
|
+
def log_duration(text)
|
51
|
+
log text, :light_cyan
|
52
|
+
end
|
53
|
+
|
54
|
+
|
55
|
+
def log_header(text)
|
56
|
+
log("\n#{ text }", :light_green)
|
57
|
+
end
|
58
|
+
|
59
|
+
|
60
|
+
def log_error(text)
|
61
|
+
log text, :light_red
|
62
|
+
end
|
63
|
+
|
64
|
+
|
65
|
+
def log_error_and_abort(text)
|
66
|
+
log_error text
|
67
|
+
abort text
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
def log_bench(message)
|
72
|
+
log_header message
|
73
|
+
result = nil
|
74
|
+
log_duration "Time elapsed: #{ humanize_time Benchmark.realtime { result = yield } }."
|
75
|
+
result
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
# The preferred way to use this method is by passing an array containing the command.
|
80
|
+
# This will ensure that items are properly escaped.
|
81
|
+
# It is also possible to use nested arrays. See tests for example use cases.
|
82
|
+
#
|
83
|
+
# If you want to see how your string command translates into an arry, you can use shellsplit method.
|
84
|
+
#
|
85
|
+
# For example:
|
86
|
+
# 'ruby -v'.shellsplit
|
87
|
+
# => ["ruby", "-v"]
|
88
|
+
#
|
89
|
+
# If, for some reason, you need to supply a string that is already escaped or should not be escaped, it is
|
90
|
+
# possible to supply a string argument instead of an array. If you do this, you will need to supply
|
91
|
+
# an additional parameter of string_verified_safe, to ensure that the operation is deliberate.
|
92
|
+
def run_command(command, filtered_text: [], quiet: false, string_verified_safe: false)
|
93
|
+
# This method is used *EVERYWHERE*.
|
94
|
+
# Seemingly small changes can have far-ranging and extreme consequences.
|
95
|
+
# Modify only with extreme caution.
|
96
|
+
#
|
97
|
+
# Note: Many applications (like echo and the Flynn CLI) append a newline to their output.
|
98
|
+
# If you are using the command result, it may be desirable to chomp the trailing
|
99
|
+
# newline. It is left to the implementing call to handle this, as it proved
|
100
|
+
# cumbersome to handle in this method in a way that worked perfectly in all cases.
|
101
|
+
raise('Potentially unsafe or problematic command. Please refer to the method documentation for more information.') if command.is_a?(String) && !string_verified_safe
|
102
|
+
|
103
|
+
command = normalized_command(command)
|
104
|
+
logged_command = logged_command(command, filtered_text)
|
105
|
+
log_command(logged_command) unless quiet
|
106
|
+
|
107
|
+
''.tap do |output|
|
108
|
+
IO.popen(bash(command)) do |io|
|
109
|
+
until io.eof?
|
110
|
+
line = io.gets
|
111
|
+
puts line unless quiet || line.empty?
|
112
|
+
$stdout.flush
|
113
|
+
output.concat line
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
handle_run_command_error(logged_command, quiet) unless $CHILD_STATUS.success?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
|
122
|
+
# http://stackoverflow.com/questions/1197224/source-shell-script-into-environment-within-a-ruby-script
|
123
|
+
# TODO : reduce / consolidate?
|
124
|
+
# Find variables changed as a result of sourcing the given file, and update in ENV.
|
125
|
+
def source_env_from(file)
|
126
|
+
bash_source(file).each { |k, v| ENV[k] = v }
|
127
|
+
end
|
128
|
+
|
129
|
+
|
130
|
+
private
|
131
|
+
|
132
|
+
|
133
|
+
def bash(command)
|
134
|
+
['bash -c', escaped_command(command), '2>&1'].join(' ')
|
135
|
+
end
|
136
|
+
|
137
|
+
|
138
|
+
def logged_command(command, filtered_text)
|
139
|
+
filtered_text = [filtered_text].flatten.delete_if { |text| text.nil? || text.strip.empty? }
|
140
|
+
unescaped_command = command.shellsplit.join(' ')
|
141
|
+
unescaped_command.gsub(Regexp.union(filtered_text.sort_by(&:length).reverse), '[MASKED]')
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
def escaped_command(command)
|
146
|
+
command.shellescape
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
def normalized_command(command)
|
151
|
+
command.is_a?(Array) ? command.flatten.shelljoin : command
|
152
|
+
end
|
153
|
+
|
154
|
+
|
155
|
+
def handle_run_command_error(logged_command, quiet)
|
156
|
+
error_message = "\nAn error has occurred while running the following command:\n#{ logged_command }\n"
|
157
|
+
quiet ? abort : log_error_and_abort(error_message)
|
158
|
+
end
|
159
|
+
|
160
|
+
|
161
|
+
# http://stackoverflow.com/questions/1197224/source-shell-script-into-environment-within-a-ruby-script
|
162
|
+
# TODO : reduce / consolidate?
|
163
|
+
# Read in the bash environment, after an optional command. Returns Array of key/value pairs.
|
164
|
+
def bash_env(cmd = nil)
|
165
|
+
env_cmd = bash("#{ cmd + ';' if cmd } printenv")
|
166
|
+
env = `#{ env_cmd }`
|
167
|
+
env.split(/\n/).map { |l| l.split(/=/, 2) }
|
168
|
+
end
|
169
|
+
|
170
|
+
|
171
|
+
# http://stackoverflow.com/questions/1197224/source-shell-script-into-environment-within-a-ruby-script
|
172
|
+
# TODO : reduce / consolidate?
|
173
|
+
# Source a given file, and compare environment before and after. Returns Hash of any keys that have changed.
|
174
|
+
def bash_source(file)
|
175
|
+
Hash[bash_env("source #{ File.realpath file }") - bash_env]
|
176
|
+
end
|
177
|
+
|
178
|
+
end
|
179
|
+
end
|
180
|
+
end
|