aggkit 0.2.5
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 +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
|