aggkit 0.2.5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.dockerignore +20 -0
- data/.gitignore +109 -0
- data/.gitlab-ci.yml +66 -0
- data/.rspec +4 -0
- data/.rubocop.yml +98 -0
- data/.travis.yml +30 -0
- data/Gemfile +12 -0
- data/Gemfile.lock +55 -0
- data/README.md +96 -0
- data/aggkit.gemspec +38 -0
- data/bin/agg +167 -0
- data/bin/aggconsul +222 -0
- data/bin/agglock +71 -0
- data/bin/aggmerge +118 -0
- data/bin/aggwait +262 -0
- data/bin/consul.rb +222 -0
- data/bin/locker.rb +71 -0
- data/bin/merger.rb +118 -0
- data/bin/terminator.rb +71 -0
- data/bin/waiter.rb +262 -0
- data/docker/Dockerfile +112 -0
- data/docker/docker-compose.yml +12 -0
- data/docker/down.sh +4 -0
- data/docker/run_tests.sh +23 -0
- data/lib/aggkit/childprocess/abstract_io.rb +38 -0
- data/lib/aggkit/childprocess/abstract_process.rb +194 -0
- data/lib/aggkit/childprocess/errors.rb +28 -0
- data/lib/aggkit/childprocess/jruby/io.rb +17 -0
- data/lib/aggkit/childprocess/jruby/process.rb +161 -0
- data/lib/aggkit/childprocess/jruby/pump.rb +55 -0
- data/lib/aggkit/childprocess/jruby.rb +58 -0
- data/lib/aggkit/childprocess/tools/generator.rb +148 -0
- data/lib/aggkit/childprocess/unix/fork_exec_process.rb +72 -0
- data/lib/aggkit/childprocess/unix/io.rb +22 -0
- data/lib/aggkit/childprocess/unix/lib.rb +188 -0
- data/lib/aggkit/childprocess/unix/platform/i386-linux.rb +14 -0
- data/lib/aggkit/childprocess/unix/platform/i386-solaris.rb +13 -0
- data/lib/aggkit/childprocess/unix/platform/x86_64-linux.rb +14 -0
- data/lib/aggkit/childprocess/unix/platform/x86_64-macosx.rb +13 -0
- data/lib/aggkit/childprocess/unix/posix_spawn_process.rb +135 -0
- data/lib/aggkit/childprocess/unix/process.rb +91 -0
- data/lib/aggkit/childprocess/unix.rb +11 -0
- data/lib/aggkit/childprocess/version.rb +5 -0
- data/lib/aggkit/childprocess/windows/handle.rb +93 -0
- data/lib/aggkit/childprocess/windows/io.rb +25 -0
- data/lib/aggkit/childprocess/windows/lib.rb +418 -0
- data/lib/aggkit/childprocess/windows/process.rb +132 -0
- data/lib/aggkit/childprocess/windows/process_builder.rb +177 -0
- data/lib/aggkit/childprocess/windows/structs.rb +151 -0
- data/lib/aggkit/childprocess/windows.rb +35 -0
- data/lib/aggkit/childprocess.rb +213 -0
- data/lib/aggkit/env.rb +219 -0
- data/lib/aggkit/runner.rb +80 -0
- data/lib/aggkit/version.rb +5 -0
- data/lib/aggkit/watcher.rb +239 -0
- data/lib/aggkit.rb +15 -0
- metadata +196 -0
@@ -0,0 +1,151 @@
|
|
1
|
+
module Aggkit
|
2
|
+
module ChildProcess
|
3
|
+
module Windows
|
4
|
+
# typedef struct _STARTUPINFO {
|
5
|
+
# DWORD cb;
|
6
|
+
# LPTSTR lpReserved;
|
7
|
+
# LPTSTR lpDesktop;
|
8
|
+
# LPTSTR lpTitle;
|
9
|
+
# DWORD dwX;
|
10
|
+
# DWORD dwY;
|
11
|
+
# DWORD dwXSize;
|
12
|
+
# DWORD dwYSize;
|
13
|
+
# DWORD dwXCountChars;
|
14
|
+
# DWORD dwYCountChars;
|
15
|
+
# DWORD dwFillAttribute;
|
16
|
+
# DWORD dwFlags;
|
17
|
+
# WORD wShowWindow;
|
18
|
+
# WORD cbReserved2;
|
19
|
+
# LPBYTE lpReserved2;
|
20
|
+
# HANDLE hStdInput;
|
21
|
+
# HANDLE hStdOutput;
|
22
|
+
# HANDLE hStdError;
|
23
|
+
# } STARTUPINFO, *LPSTARTUPINFO;
|
24
|
+
|
25
|
+
class StartupInfo < FFI::Struct
|
26
|
+
layout :cb, :ulong,
|
27
|
+
:lpReserved, :pointer,
|
28
|
+
:lpDesktop, :pointer,
|
29
|
+
:lpTitle, :pointer,
|
30
|
+
:dwX, :ulong,
|
31
|
+
:dwY, :ulong,
|
32
|
+
:dwXSize, :ulong,
|
33
|
+
:dwYSize, :ulong,
|
34
|
+
:dwXCountChars, :ulong,
|
35
|
+
:dwYCountChars, :ulong,
|
36
|
+
:dwFillAttribute, :ulong,
|
37
|
+
:dwFlags, :ulong,
|
38
|
+
:wShowWindow, :ushort,
|
39
|
+
:cbReserved2, :ushort,
|
40
|
+
:lpReserved2, :pointer,
|
41
|
+
:hStdInput, :pointer, # void ptr
|
42
|
+
:hStdOutput, :pointer, # void ptr
|
43
|
+
:hStdError, :pointer # void ptr
|
44
|
+
end
|
45
|
+
|
46
|
+
#
|
47
|
+
# typedef struct _PROCESS_INFORMATION {
|
48
|
+
# HANDLE hProcess;
|
49
|
+
# HANDLE hThread;
|
50
|
+
# DWORD dwProcessId;
|
51
|
+
# DWORD dwThreadId;
|
52
|
+
# } PROCESS_INFORMATION, *LPPROCESS_INFORMATION;
|
53
|
+
#
|
54
|
+
|
55
|
+
class ProcessInfo < FFI::Struct
|
56
|
+
layout :hProcess, :pointer, # void ptr
|
57
|
+
:hThread, :pointer, # void ptr
|
58
|
+
:dwProcessId, :ulong,
|
59
|
+
:dwThreadId, :ulong
|
60
|
+
end
|
61
|
+
|
62
|
+
#
|
63
|
+
# typedef struct _SECURITY_ATTRIBUTES {
|
64
|
+
# DWORD nLength;
|
65
|
+
# LPVOID lpSecurityDescriptor;
|
66
|
+
# BOOL bInheritHandle;
|
67
|
+
# } SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
|
68
|
+
#
|
69
|
+
|
70
|
+
class SecurityAttributes < FFI::Struct
|
71
|
+
layout :nLength, :ulong,
|
72
|
+
:lpSecurityDescriptor, :pointer, # void ptr
|
73
|
+
:bInheritHandle, :int
|
74
|
+
|
75
|
+
def initialize(opts = {})
|
76
|
+
super()
|
77
|
+
|
78
|
+
self[:nLength] = self.class.size
|
79
|
+
self[:lpSecurityDescriptor] = nil
|
80
|
+
self[:bInheritHandle] = opts[:inherit] ? 1 : 0
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
#
|
85
|
+
# typedef struct _JOBOBJECT_BASIC_LIMIT_INFORMATION {
|
86
|
+
# LARGE_INTEGER PerProcessUserTimeLimit;
|
87
|
+
# LARGE_INTEGER PerJobUserTimeLimit;
|
88
|
+
# DWORD LimitFlags;
|
89
|
+
# SIZE_T MinimumWorkingSetSize;
|
90
|
+
# SIZE_T MaximumWorkingSetSize;
|
91
|
+
# DWORD ActiveProcessLimit;
|
92
|
+
# ULONG_PTR Affinity;
|
93
|
+
# DWORD PriorityClass;
|
94
|
+
# DWORD SchedulingClass;
|
95
|
+
# } JOBOBJECT_BASIC_LIMIT_INFORMATION, *PJOBOBJECT_BASIC_LIMIT_INFORMATION;
|
96
|
+
#
|
97
|
+
class JobObjectBasicLimitInformation < FFI::Struct
|
98
|
+
layout :PerProcessUserTimeLimit, :int64,
|
99
|
+
:PerJobUserTimeLimit, :int64,
|
100
|
+
:LimitFlags, :ulong,
|
101
|
+
:MinimumWorkingSetSize, :size_t,
|
102
|
+
:MaximumWorkingSetSize, :size_t,
|
103
|
+
:ActiveProcessLimit, :ulong,
|
104
|
+
:Affinity, :pointer,
|
105
|
+
:PriorityClass, :ulong,
|
106
|
+
:SchedulingClass, :ulong
|
107
|
+
end
|
108
|
+
|
109
|
+
#
|
110
|
+
# typedef struct _IO_COUNTERS {
|
111
|
+
# ULONGLONG ReadOperationCount;
|
112
|
+
# ULONGLONG WriteOperationCount;
|
113
|
+
# ULONGLONG OtherOperationCount;
|
114
|
+
# ULONGLONG ReadTransferCount;
|
115
|
+
# ULONGLONG WriteTransferCount;
|
116
|
+
# ULONGLONG OtherTransferCount;
|
117
|
+
# } IO_COUNTERS, *PIO_COUNTERS;
|
118
|
+
#
|
119
|
+
|
120
|
+
class IoCounters < FFI::Struct
|
121
|
+
layout :ReadOperationCount, :ulong_long,
|
122
|
+
:WriteOperationCount, :ulong_long,
|
123
|
+
:OtherOperationCount, :ulong_long,
|
124
|
+
:ReadTransferCount, :ulong_long,
|
125
|
+
:WriteTransferCount, :ulong_long,
|
126
|
+
:OtherTransferCount, :ulong_long
|
127
|
+
end
|
128
|
+
#
|
129
|
+
# typedef struct _JOBOBJECT_EXTENDED_LIMIT_INFORMATION {
|
130
|
+
# JOBOBJECT_BASIC_LIMIT_INFORMATION BasicLimitInformation;
|
131
|
+
# IO_COUNTERS IoInfo;
|
132
|
+
# SIZE_T ProcessMemoryLimit;
|
133
|
+
# SIZE_T JobMemoryLimit;
|
134
|
+
# SIZE_T PeakProcessMemoryUsed;
|
135
|
+
# SIZE_T PeakJobMemoryUsed;
|
136
|
+
# } JOBOBJECT_EXTENDED_LIMIT_INFORMATION, *PJOBOBJECT_EXTENDED_LIMIT_INFORMATION;
|
137
|
+
#
|
138
|
+
|
139
|
+
class JobObjectExtendedLimitInformation < FFI::Struct
|
140
|
+
layout :BasicLimitInformation, JobObjectBasicLimitInformation,
|
141
|
+
:IoInfo, IoCounters,
|
142
|
+
:ProcessMemoryLimit, :size_t,
|
143
|
+
:JobMemoryLimit, :size_t,
|
144
|
+
:PeakProcessMemoryUsed, :size_t,
|
145
|
+
:PeakJobMemoryUsed, :size_t
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
end # Windows
|
150
|
+
end # ChildProcess
|
151
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require "ffi"
|
2
|
+
require "rbconfig"
|
3
|
+
|
4
|
+
module Aggkit
|
5
|
+
module ChildProcess
|
6
|
+
module Windows
|
7
|
+
module Lib
|
8
|
+
extend FFI::Library
|
9
|
+
|
10
|
+
def self.msvcrt_name
|
11
|
+
host_part = RbConfig::CONFIG['host_os'].split("_")[1]
|
12
|
+
manifest = File.join(RbConfig::CONFIG['bindir'], 'ruby.exe.manifest')
|
13
|
+
|
14
|
+
if host_part && host_part.to_i > 80 && File.exists?(manifest)
|
15
|
+
"msvcr#{host_part}"
|
16
|
+
else
|
17
|
+
"msvcrt"
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ffi_lib "kernel32", msvcrt_name
|
22
|
+
ffi_convention :stdcall
|
23
|
+
|
24
|
+
|
25
|
+
end # Library
|
26
|
+
end # Windows
|
27
|
+
end # ChildProcess
|
28
|
+
end Aggkit
|
29
|
+
|
30
|
+
require "aggkit/childprocess/windows/lib"
|
31
|
+
require "aggkit/childprocess/windows/structs"
|
32
|
+
require "aggkit/childprocess/windows/handle"
|
33
|
+
require "aggkit/childprocess/windows/io"
|
34
|
+
require "aggkit/childprocess/windows/process_builder"
|
35
|
+
require "aggkit/childprocess/windows/process"
|
@@ -0,0 +1,213 @@
|
|
1
|
+
require 'aggkit/childprocess/version'
|
2
|
+
require 'aggkit/childprocess/errors'
|
3
|
+
require 'aggkit/childprocess/abstract_process'
|
4
|
+
require 'aggkit/childprocess/abstract_io'
|
5
|
+
require 'fcntl'
|
6
|
+
require 'logger'
|
7
|
+
|
8
|
+
module Aggkit
|
9
|
+
|
10
|
+
module ChildProcess
|
11
|
+
|
12
|
+
|
13
|
+
@posix_spawn = false
|
14
|
+
|
15
|
+
class << self
|
16
|
+
|
17
|
+
|
18
|
+
attr_writer :logger
|
19
|
+
|
20
|
+
def new(*args)
|
21
|
+
case os
|
22
|
+
when :macosx, :linux, :solaris, :bsd, :cygwin, :aix
|
23
|
+
if posix_spawn?
|
24
|
+
Unix::PosixSpawnProcess.new(args)
|
25
|
+
elsif jruby?
|
26
|
+
JRuby::Process.new(args)
|
27
|
+
else
|
28
|
+
Unix::ForkExecProcess.new(args)
|
29
|
+
end
|
30
|
+
when :windows
|
31
|
+
Windows::Process.new(args)
|
32
|
+
else
|
33
|
+
raise Error.new("unsupported platform #{platform_name.inspect}")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
alias build new
|
37
|
+
|
38
|
+
def logger
|
39
|
+
return @logger if defined?(@logger) && @logger
|
40
|
+
|
41
|
+
@logger = Logger.new($stderr)
|
42
|
+
@logger.level = $DEBUG ? Logger::DEBUG : Logger::INFO
|
43
|
+
|
44
|
+
@logger
|
45
|
+
end
|
46
|
+
|
47
|
+
def platform
|
48
|
+
if RUBY_PLATFORM == 'java'
|
49
|
+
:jruby
|
50
|
+
elsif defined?(RUBY_ENGINE) && RUBY_ENGINE == 'ironruby'
|
51
|
+
:ironruby
|
52
|
+
else
|
53
|
+
os
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def platform_name
|
58
|
+
@platform_name ||= "#{arch}-#{os}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def unix?
|
62
|
+
!windows?
|
63
|
+
end
|
64
|
+
|
65
|
+
def linux?
|
66
|
+
os == :linux
|
67
|
+
end
|
68
|
+
|
69
|
+
def jruby?
|
70
|
+
platform == :jruby
|
71
|
+
end
|
72
|
+
|
73
|
+
def windows?
|
74
|
+
os == :windows
|
75
|
+
end
|
76
|
+
|
77
|
+
def posix_spawn?
|
78
|
+
enabled = @posix_spawn || %w[1 true].include?(ENV['CHILDPROCESS_POSIX_SPAWN'])
|
79
|
+
return false unless enabled
|
80
|
+
|
81
|
+
require 'ffi'
|
82
|
+
begin
|
83
|
+
require "childprocess/unix/platform/#{ChildProcess.platform_name}"
|
84
|
+
rescue LoadError
|
85
|
+
raise ChildProcess::MissingPlatformError
|
86
|
+
end
|
87
|
+
|
88
|
+
require 'childprocess/unix/lib'
|
89
|
+
require 'childprocess/unix/posix_spawn_process'
|
90
|
+
|
91
|
+
true
|
92
|
+
rescue ChildProcess::MissingPlatformError => ex
|
93
|
+
warn_once ex.message
|
94
|
+
false
|
95
|
+
end
|
96
|
+
|
97
|
+
#
|
98
|
+
# Set this to true to enable experimental use of posix_spawn.
|
99
|
+
#
|
100
|
+
|
101
|
+
attr_writer :posix_spawn
|
102
|
+
|
103
|
+
def os
|
104
|
+
@os ||= begin
|
105
|
+
require 'rbconfig'
|
106
|
+
host_os = RbConfig::CONFIG['host_os'].downcase
|
107
|
+
|
108
|
+
case host_os
|
109
|
+
when /linux/
|
110
|
+
:linux
|
111
|
+
when /darwin|mac os/
|
112
|
+
:macosx
|
113
|
+
when /mswin|msys|mingw32/
|
114
|
+
:windows
|
115
|
+
when /cygwin/
|
116
|
+
:cygwin
|
117
|
+
when /solaris|sunos/
|
118
|
+
:solaris
|
119
|
+
when /bsd|dragonfly/
|
120
|
+
:bsd
|
121
|
+
when /aix/
|
122
|
+
:aix
|
123
|
+
else
|
124
|
+
raise Error.new("unknown os: #{host_os.inspect}")
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
def arch
|
130
|
+
@arch ||= begin
|
131
|
+
host_cpu = RbConfig::CONFIG['host_cpu'].downcase
|
132
|
+
case host_cpu
|
133
|
+
when /i[3456]86/
|
134
|
+
if workaround_older_macosx_misreported_cpu?
|
135
|
+
# Workaround case: older 64-bit Darwin Rubies misreported as i686
|
136
|
+
'x86_64'
|
137
|
+
else
|
138
|
+
'i386'
|
139
|
+
end
|
140
|
+
when /amd64|x86_64/
|
141
|
+
'x86_64'
|
142
|
+
when /ppc|powerpc/
|
143
|
+
'powerpc'
|
144
|
+
else
|
145
|
+
host_cpu
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# By default, a child process will inherit open file descriptors from the
|
152
|
+
# parent process. This helper provides a cross-platform way of making sure
|
153
|
+
# that doesn't happen for the given file/io.
|
154
|
+
#
|
155
|
+
|
156
|
+
def close_on_exec(file)
|
157
|
+
if file.respond_to?(:close_on_exec=)
|
158
|
+
file.close_on_exec = true
|
159
|
+
elsif file.respond_to?(:fcntl) && defined?(Fcntl::FD_CLOEXEC)
|
160
|
+
file.fcntl Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
|
161
|
+
|
162
|
+
if jruby? && posix_spawn?
|
163
|
+
# on JRuby, the fcntl call above apparently isn't enough when
|
164
|
+
# we're launching the process through posix_spawn.
|
165
|
+
fileno = JRuby.posix_fileno_for(file)
|
166
|
+
Unix::Lib.fcntl fileno, Fcntl::F_SETFD, Fcntl::FD_CLOEXEC
|
167
|
+
end
|
168
|
+
elsif windows?
|
169
|
+
Windows::Lib.dont_inherit file
|
170
|
+
else
|
171
|
+
raise Error.new("not sure how to set close-on-exec for #{file.inspect} on #{platform_name.inspect}")
|
172
|
+
end
|
173
|
+
end
|
174
|
+
|
175
|
+
private
|
176
|
+
|
177
|
+
def warn_once(msg)
|
178
|
+
@warnings ||= {}
|
179
|
+
|
180
|
+
unless @warnings[msg]
|
181
|
+
@warnings[msg] = true
|
182
|
+
logger.warn msg
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Workaround: detect the situation that an older Darwin Ruby is actually
|
187
|
+
# 64-bit, but is misreporting cpu as i686, which would imply 32-bit.
|
188
|
+
#
|
189
|
+
# @return [Boolean] `true` if:
|
190
|
+
# (a) on Mac OS X
|
191
|
+
# (b) actually running in 64-bit mode
|
192
|
+
def workaround_older_macosx_misreported_cpu?
|
193
|
+
os == :macosx && is_64_bit?
|
194
|
+
end
|
195
|
+
|
196
|
+
# @return [Boolean] `true` if this Ruby represents `1` in 64 bits (8 bytes).
|
197
|
+
def is_64_bit?
|
198
|
+
1.size == 8
|
199
|
+
end
|
200
|
+
|
201
|
+
|
202
|
+
end # class << self
|
203
|
+
|
204
|
+
|
205
|
+
end # ChildProcess
|
206
|
+
|
207
|
+
end # Aggkit
|
208
|
+
|
209
|
+
require 'jruby' if Aggkit::ChildProcess.jruby?
|
210
|
+
|
211
|
+
require 'aggkit/childprocess/unix' if Aggkit::ChildProcess.unix?
|
212
|
+
require 'aggkit/childprocess/windows' if Aggkit::ChildProcess.windows?
|
213
|
+
require 'aggkit/childprocess/jruby' if Aggkit::ChildProcess.jruby?
|
data/lib/aggkit/env.rb
ADDED
@@ -0,0 +1,219 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
require 'dotenv'
|
5
|
+
|
6
|
+
module Dotenv
|
7
|
+
module_function
|
8
|
+
|
9
|
+
def load_without_export(*filenames)
|
10
|
+
with(*filenames) do |f|
|
11
|
+
ignoring_nonexistent_files do
|
12
|
+
Environment.new(f, true)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
module Aggkit
|
19
|
+
|
20
|
+
class Env
|
21
|
+
attr_accessor :project, :name, :env_root, :environment
|
22
|
+
|
23
|
+
def self.list path = Dir.pwd
|
24
|
+
root = Project.new(path).project_root
|
25
|
+
Pathfinder.new(root).each_env
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.with_env env, &block
|
29
|
+
current_env = ENV.to_h
|
30
|
+
env.each_pair do |k, v|
|
31
|
+
ENV[k.to_s] = v.to_s
|
32
|
+
end
|
33
|
+
yield
|
34
|
+
ensure
|
35
|
+
(env.keys.map(&:to_s) - current_env.keys).each do |key|
|
36
|
+
ENV.delete(key.to_s)
|
37
|
+
end
|
38
|
+
|
39
|
+
current_env.each_pair do |k, v|
|
40
|
+
ENV[k.to_s] = v.to_s
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def initialize path
|
45
|
+
@project = if File.directory?(File.expand_path(path))
|
46
|
+
@name = File.basename(File.realpath(File.expand_path(path)))
|
47
|
+
Project.new(path)
|
48
|
+
else
|
49
|
+
@name = path.strip
|
50
|
+
Project.new(Dir.pwd)
|
51
|
+
end
|
52
|
+
|
53
|
+
if !self.class.list.include?(@name)
|
54
|
+
raise "There no env #{@name.inspect} in project: #{project.project_root.inspect}"
|
55
|
+
end
|
56
|
+
|
57
|
+
@env_root = File.join(project_root, 'envs', @name)
|
58
|
+
|
59
|
+
@environment = prepare_environment
|
60
|
+
end
|
61
|
+
|
62
|
+
def with_env &block
|
63
|
+
Env.with_env(environment) &block
|
64
|
+
end
|
65
|
+
|
66
|
+
def project_root
|
67
|
+
project.project_root
|
68
|
+
end
|
69
|
+
|
70
|
+
def core_root
|
71
|
+
project.core_root
|
72
|
+
end
|
73
|
+
|
74
|
+
def dot_file
|
75
|
+
File.join(env_root, '.env')
|
76
|
+
end
|
77
|
+
|
78
|
+
def project_dot_file
|
79
|
+
File.join(project_root, '.env')
|
80
|
+
end
|
81
|
+
|
82
|
+
def core_dot_file
|
83
|
+
File.join(core_root, '.env') rescue nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def env_files
|
87
|
+
[core_dot_file, project_dot_file, dot_file].compact.select{|f| File.exists?(f)}.uniq
|
88
|
+
end
|
89
|
+
|
90
|
+
def show
|
91
|
+
{
|
92
|
+
name: name,
|
93
|
+
project_root: project_root,
|
94
|
+
core_root: core_root,
|
95
|
+
envfiles: env_files,
|
96
|
+
envs: environment,
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
private
|
101
|
+
|
102
|
+
|
103
|
+
def prepare_environment
|
104
|
+
base_env = {
|
105
|
+
'ENV_ROOT' => env_root,
|
106
|
+
'PROJECT_ROOT' => project_root,
|
107
|
+
'CORE_ROOT' => core_root,
|
108
|
+
'PATH' => check_path_variable(env_root),
|
109
|
+
}
|
110
|
+
|
111
|
+
dot_env = prepare_dot(base_env)
|
112
|
+
|
113
|
+
compose_env = prepare_compose(dot_env.merge(base_env))
|
114
|
+
|
115
|
+
dot_env.merge(base_env).merge(compose_env)
|
116
|
+
end
|
117
|
+
|
118
|
+
def prepare_dot env
|
119
|
+
Env.with_env(env) do
|
120
|
+
Dotenv.load_without_export(*env_files)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def prepare_compose env
|
125
|
+
compose_files = if env['COMPOSE_FILE']
|
126
|
+
env['COMPOSE_FILE']
|
127
|
+
elsif File.exists?(File.join(env_root, "docker-compose.yml"))
|
128
|
+
File.join(env_root, "docker-compose.yml")
|
129
|
+
elsif File.exists?(File.join(env_root, "docker-compose.yaml"))
|
130
|
+
File.join(env_root, "docker-compose.yaml")
|
131
|
+
end
|
132
|
+
|
133
|
+
if compose_files
|
134
|
+
result_file = File.join(env_root, "compose-result.yml")
|
135
|
+
Env.with_env(env.merge('COMPOSE_FILE' => compose_files)) do
|
136
|
+
result_content = `merger.rb`
|
137
|
+
File.write(result_file, result_content)
|
138
|
+
end
|
139
|
+
{
|
140
|
+
'COMPOSE_FILE' => result_file
|
141
|
+
}
|
142
|
+
else
|
143
|
+
{}
|
144
|
+
end
|
145
|
+
end
|
146
|
+
|
147
|
+
def check_path_variable path
|
148
|
+
if ENV['PATH'][path]
|
149
|
+
ENV['PATH']
|
150
|
+
else
|
151
|
+
"#{ENV['PATH']}:#{path}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
class Pathfinder
|
156
|
+
attr_accessor :path
|
157
|
+
|
158
|
+
def initialize path
|
159
|
+
@path = File.realpath(File.expand_path(path))
|
160
|
+
end
|
161
|
+
|
162
|
+
def each_parent
|
163
|
+
current = path.split(File::SEPARATOR)
|
164
|
+
Enumerator.new do |y|
|
165
|
+
while !current.empty? do
|
166
|
+
y << current.join(File::SEPARATOR)
|
167
|
+
current.pop
|
168
|
+
end
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def each_env
|
173
|
+
Dir.chdir(File.join(path, 'envs')) do
|
174
|
+
Dir.glob('**/*').select do |f|
|
175
|
+
File.directory? f
|
176
|
+
end.select do |f|
|
177
|
+
File.exists?(File.join(f, '.env')) || File.exists?(File.join(f, 'docker-compose.yml')) || File.exists?(File.join(f, 'docker-compose.yaml'))
|
178
|
+
end.sort
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
|
183
|
+
class Project
|
184
|
+
attr_accessor :env_root
|
185
|
+
|
186
|
+
def initialize path
|
187
|
+
raise "env path #{path} is not directory!" if !File.directory?(File.expand_path(path))
|
188
|
+
@env_root = File.realpath(File.expand_path(path))
|
189
|
+
end
|
190
|
+
|
191
|
+
def project_root
|
192
|
+
@project_root = Pathfinder.new(env_root).each_parent.find do |path|
|
193
|
+
root?(path)
|
194
|
+
end || (raise "Can't find project root")
|
195
|
+
end
|
196
|
+
|
197
|
+
def core_root
|
198
|
+
@core_root = Pathfinder.new(project_root).each_parent.find do |path|
|
199
|
+
core?(path)
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def root? path
|
204
|
+
return File.realpath(File.expand_path(path)) if File.directory?("#{path}/.git") ||
|
205
|
+
File.exists?("#{path}/base-service.yml") ||
|
206
|
+
File.exists?("#{path}/common-services.yml") ||
|
207
|
+
File.directory?("#{path}/envs")
|
208
|
+
end
|
209
|
+
|
210
|
+
def core? path
|
211
|
+
return File.realpath(File.expand_path(path)) if File.exists?("#{path}/.core")
|
212
|
+
end
|
213
|
+
|
214
|
+
end
|
215
|
+
|
216
|
+
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
@@ -0,0 +1,80 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
|
5
|
+
module Aggkit
|
6
|
+
|
7
|
+
module Runner
|
8
|
+
|
9
|
+
def init_service(service)
|
10
|
+
STDOUT.sync = true
|
11
|
+
STDERR.sync = true
|
12
|
+
|
13
|
+
puts "Starting #{service}..."
|
14
|
+
|
15
|
+
trap('EXIT') do
|
16
|
+
puts "Stopping #{service}..."
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def error(message)
|
21
|
+
STDERR.puts "Error: #{message}"
|
22
|
+
end
|
23
|
+
|
24
|
+
def die(message)
|
25
|
+
error(message)
|
26
|
+
exit 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def load_envs(string, env = ENV)
|
30
|
+
envs = Dotenv::Parser.new(string).call
|
31
|
+
envs.each_pair do |k, v|
|
32
|
+
env[k] = v
|
33
|
+
end
|
34
|
+
envs
|
35
|
+
end
|
36
|
+
|
37
|
+
def load_envs_from_consul(consul, service)
|
38
|
+
envs = execute!("consul.rb --consul=http://#{consul}:8500 --env services/env/#{service} --pristine -d", "Can't load envs")
|
39
|
+
load_envs(envs)
|
40
|
+
end
|
41
|
+
|
42
|
+
def execute(cmd)
|
43
|
+
puts "Executing: #{cmd}"
|
44
|
+
output = `#{cmd}`
|
45
|
+
@last_result = $?
|
46
|
+
output
|
47
|
+
end
|
48
|
+
|
49
|
+
def execute!(cmd, error = nil)
|
50
|
+
output = execute(cmd)
|
51
|
+
($? || @last_result).success? || die(error || "Can't execute: #{cmd}")
|
52
|
+
output
|
53
|
+
end
|
54
|
+
|
55
|
+
def ensure_env_defined!(key, env = ENV)
|
56
|
+
env.key?(key) || die("Environment variable #{key} must present!")
|
57
|
+
env[key]
|
58
|
+
end
|
59
|
+
|
60
|
+
def envsubst(*paths)
|
61
|
+
paths = paths.flatten.map{|c| c.to_s.strip }.reject(&:empty?)
|
62
|
+
paths.each do |path|
|
63
|
+
Dir.glob("#{path}/**/*.in") do |templ|
|
64
|
+
output = templ.sub(/\.in$/, '')
|
65
|
+
cmd = "cat '#{templ}' | envsubst > '#{output}'"
|
66
|
+
system(cmd) || die("envsubst failed: #{cmd}")
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def envsubst_file(templ, output = nil)
|
72
|
+
output ||= templ.sub(/\.in$/, '')
|
73
|
+
die('filename must ends with .in or output must be provided') if output.strip == templ.strip
|
74
|
+
cmd = "cat '#{templ}' | envsubst > '#{output}'"
|
75
|
+
system(cmd) || die("envsubst failed: #{cmd}")
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
|
80
|
+
end
|