aggkit 0.2.6.8250 → 0.2.6.8329
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Gemfile.lock +1 -1
- data/aggkit.gemspec +1 -0
- data/bin/agg +16 -7
- data/bin/aggci +90 -0
- data/bin/aggmerge +22 -0
- data/bin/{terminator.rb → aggterm} +0 -0
- data/docker/run_tests.sh +2 -0
- data/lib/aggkit/ci.rb +258 -0
- data/lib/aggkit/env.rb +16 -6
- metadata +7 -12
- data/bin/consul.rb +0 -222
- data/bin/locker.rb +0 -71
- data/bin/merger.rb +0 -118
- data/bin/waiter.rb +0 -262
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: dda760666b51369b116244aa6cc3ead5f69afb41
|
4
|
+
data.tar.gz: 3df47be27d64b70300707667651893475a30ff92
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 63d4ab22451f3cc95e16653f49776cba230e194e8f580bc54828a7e10037ab761352e1dbfa7adccdf35cec1acd0a34586c04c0049961de28045e40584b17928b
|
7
|
+
data.tar.gz: a820f88d3be11610a7f8bff76c1c387c334d5892eede30c96f6a146071d5ac8e859eec9a114af9982a1ab1a7ac7930a17dc3edab17c3daaa7df731b6fa558641
|
data/Gemfile.lock
CHANGED
data/aggkit.gemspec
CHANGED
data/bin/agg
CHANGED
@@ -5,6 +5,8 @@ require 'tty-tree'
|
|
5
5
|
require 'aggkit/env'
|
6
6
|
require 'json'
|
7
7
|
|
8
|
+
UTIL = File.basename(__FILE__)
|
9
|
+
|
8
10
|
@commands = {}
|
9
11
|
|
10
12
|
@exec = (begin
|
@@ -20,13 +22,13 @@ require 'json'
|
|
20
22
|
|
21
23
|
|
22
24
|
exmaple_usage = "
|
23
|
-
#{
|
24
|
-
#{
|
25
|
-
#{
|
25
|
+
#{"#{UTIL} env --list".ljust(30)} - list avaibale environments
|
26
|
+
#{"#{UTIL} <envname> --show".ljust(30)} - show environment details
|
27
|
+
#{"#{UTIL} <envname> exec -- cmd".ljust(30)} - exec command in environment
|
26
28
|
"
|
27
29
|
|
28
30
|
env_cmd_parser = OptionParser.new do |o|
|
29
|
-
o.banner = "Usage:
|
31
|
+
o.banner = "Usage: #{UTIL} env [options]"
|
30
32
|
o.on("-l", "--list", "list environments") do
|
31
33
|
@opts[:list] = true
|
32
34
|
end
|
@@ -36,7 +38,7 @@ env_cmd_parser = OptionParser.new do |o|
|
|
36
38
|
end
|
37
39
|
|
38
40
|
exec_cmd_parser = OptionParser.new do |o|
|
39
|
-
o.banner = "Usage:
|
41
|
+
o.banner = "Usage: #{UTIL} <envname> [options] exec [options] -- cmd"
|
40
42
|
o.on('-p', '--pristine', "not include the parent processes' environment when exec child process") do
|
41
43
|
@opts[:pristine] = true
|
42
44
|
end
|
@@ -54,7 +56,7 @@ end
|
|
54
56
|
}
|
55
57
|
|
56
58
|
single_parser = OptionParser.new do |o|
|
57
|
-
o.banner = "Aggredator environment manager\
|
59
|
+
o.banner = "Aggredator environment manager\nUsage: #{UTIL} <envname> [options] [subcommand [options]]"
|
58
60
|
|
59
61
|
o.on("-s", "--show", "show envname details") do
|
60
62
|
@opts[:show] = true
|
@@ -64,6 +66,10 @@ single_parser = OptionParser.new do |o|
|
|
64
66
|
@opts[:export] = true
|
65
67
|
end
|
66
68
|
|
69
|
+
o.on("-r", "--release", "make release compose-result.yml(without build)") do
|
70
|
+
@opts[:release] = true
|
71
|
+
end
|
72
|
+
|
67
73
|
o.on("-h", "--help", "show help") do
|
68
74
|
puts o.help
|
69
75
|
exit 0
|
@@ -74,7 +80,7 @@ single_parser = OptionParser.new do |o|
|
|
74
80
|
end
|
75
81
|
|
76
82
|
global_parser = OptionParser.new do |o|
|
77
|
-
o.banner = "Aggredator manager\nUsage:
|
83
|
+
o.banner = "Aggredator manager\nUsage: #{UTIL} [envname] [options] [subcommand [options]]"
|
78
84
|
|
79
85
|
o.on("-h", "--help", "show help") do
|
80
86
|
puts o.help
|
@@ -144,6 +150,7 @@ def exec_command opts
|
|
144
150
|
raise "envname not specified!" unless $env
|
145
151
|
raise "cmd not specified!" unless @exec
|
146
152
|
|
153
|
+
$env.prepare release: @opts[:release]
|
147
154
|
e = $env.environment
|
148
155
|
e = ENV.to_h.merge(e) unless opts[:pristine]
|
149
156
|
|
@@ -161,6 +168,7 @@ if @commands[:single]
|
|
161
168
|
if @opts[:show]
|
162
169
|
raise "envname not specified!" unless $env
|
163
170
|
|
171
|
+
$env.prepare release: @opts[:release]
|
164
172
|
puts JSON.pretty_generate($env.show)
|
165
173
|
exit 0
|
166
174
|
end
|
@@ -168,6 +176,7 @@ if @commands[:single]
|
|
168
176
|
if @opts[:export]
|
169
177
|
raise "envname not specified!" unless $env
|
170
178
|
|
179
|
+
$env.prepare release: @opts[:release]
|
171
180
|
$env.environment.each_pair do |k, v|
|
172
181
|
puts "export #{k.to_s}=\"#{v.to_s}\""
|
173
182
|
end
|
data/bin/aggci
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
require 'aggkit/ci'
|
5
|
+
|
6
|
+
UTIL = File.basename(__FILE__)
|
7
|
+
|
8
|
+
include Aggkit::Ci
|
9
|
+
|
10
|
+
@opts = {
|
11
|
+
tag: ENV['CORE_TAG'] || ENV['CI_COMMIT_TAG'] || 'latest'
|
12
|
+
}
|
13
|
+
|
14
|
+
def parse_services services
|
15
|
+
[services].flatten.join(' ').split(/[,;| \n]/).map(&:strip).reject(&:empty?).uniq.sort
|
16
|
+
end
|
17
|
+
|
18
|
+
parser = OptionParser.new do |o|
|
19
|
+
o.banner = "Usage: #{UTIL} [options]"
|
20
|
+
|
21
|
+
o.on("--pull-cache services", 'Pull cache from docker regestry') do |services|
|
22
|
+
@opts[:pull] = parse_services(services)
|
23
|
+
end
|
24
|
+
|
25
|
+
o.on("--promote services", 'Promote service images to --stage') do |services|
|
26
|
+
@opts[:promote] = parse_services(services)
|
27
|
+
end
|
28
|
+
|
29
|
+
o.on("--promote-tag tag", 'Promote service images') do |tag|
|
30
|
+
@opts[:promotetag] = tag.to_s.strip
|
31
|
+
end
|
32
|
+
|
33
|
+
o.on("--stage stage", 'stage for image promotion Stages: dev, stge, tag') do |stage|
|
34
|
+
@opts[:stage] = stage.to_s.strip
|
35
|
+
end
|
36
|
+
|
37
|
+
o.on("--clean services", 'remove tags and images excluding last stable') do |services|
|
38
|
+
@opts[:clean] = parse_services(services)
|
39
|
+
end
|
40
|
+
|
41
|
+
o.on("--ssh-deploy env", 'ssh') do |env_and_server|
|
42
|
+
env, server = env_and_server.strip.split('@')
|
43
|
+
@opts[:deploy] = {env: env, server: server}
|
44
|
+
end
|
45
|
+
|
46
|
+
o.on("--tag tag=#{@opts[:tag].inspect}", 'Use tag as default tag for images') do |tag|
|
47
|
+
@opts[:tag] = tag.to_s.strip
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
parser.parse!
|
52
|
+
|
53
|
+
if services = @opts[:pull]
|
54
|
+
raise "tag not defined" if @opts[:tag].to_s.empty?
|
55
|
+
pull_images_cache services, @opts[:tag]
|
56
|
+
exit 0
|
57
|
+
elsif services = @opts[:promote]
|
58
|
+
raise "tag not defined" if @opts[:tag].to_s.empty?
|
59
|
+
raise "stage not defined" if @opts[:stage].to_s.empty?
|
60
|
+
|
61
|
+
case @opts[:stage]
|
62
|
+
when 'dev'
|
63
|
+
promote_services_to_dev!(services, @opts[:tag])
|
64
|
+
when 'stage'
|
65
|
+
promote_services_to_stage!(services, @opts[:tag])
|
66
|
+
when 'tag'
|
67
|
+
raise "promote-tag not defined" if @opts[:promotetag].to_s.empty?
|
68
|
+
promote_services_to_tag!(services, @opts[:tag], @opts[:promotetag])
|
69
|
+
else
|
70
|
+
raise "invalid stage #{@opts[:stage].inspect} for promotion"
|
71
|
+
end
|
72
|
+
|
73
|
+
exit 0
|
74
|
+
|
75
|
+
elsif services = @opts[:clean]
|
76
|
+
raise "tag not defined" if @opts[:tag].to_s.empty?
|
77
|
+
clean_services services, @opts[:tag]
|
78
|
+
exit 0
|
79
|
+
|
80
|
+
elsif deploy = @opts[:deploy]
|
81
|
+
raise "tag not defined" if @opts[:tag].to_s.empty?
|
82
|
+
raise "stage not defined" if @opts[:stage].to_s.empty?
|
83
|
+
ssh_deploy 'service-fns', deploy[:env], deploy[:server], @opts[:tag], @opts[:stage]
|
84
|
+
exit 0
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
STDERR.puts parser.help
|
89
|
+
exit 1
|
90
|
+
|
data/bin/aggmerge
CHANGED
@@ -11,6 +11,8 @@ if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
|
11
11
|
end
|
12
12
|
|
13
13
|
|
14
|
+
@skipbuild = ARGV.join('')['--skip-build']
|
15
|
+
|
14
16
|
class Hash
|
15
17
|
|
16
18
|
def deep_dup
|
@@ -99,6 +101,23 @@ def process_compose_hash(yml, dirname, parent = {})
|
|
99
101
|
yml
|
100
102
|
end
|
101
103
|
|
104
|
+
def remove_build_section hash
|
105
|
+
if hash.has_key?(:build) || hash.has_key?('build')
|
106
|
+
hash.delete(:build)
|
107
|
+
hash.delete('build')
|
108
|
+
end
|
109
|
+
|
110
|
+
hash.each_pair do |k, v|
|
111
|
+
if v.is_a?(Hash)
|
112
|
+
remove_build_section(v)
|
113
|
+
elsif v.is_a?(Array)
|
114
|
+
v.each do |vv|
|
115
|
+
remove_build_section(vv) if vv.is_a?(Hash)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
102
121
|
if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
103
122
|
result = ENV['COMPOSE_FILE'].split(':').reduce({}) do |parent, file|
|
104
123
|
yml = process_compose_hash(YAML.load(File.read(file)), File.dirname(file), parent)
|
@@ -109,6 +128,9 @@ if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
|
109
128
|
ret
|
110
129
|
end
|
111
130
|
|
131
|
+
|
132
|
+
remove_build_section(result) if @skipbuild
|
133
|
+
|
112
134
|
if ARGV[0].nil? || ARGV[0].strip == '-'
|
113
135
|
puts YAML.dump(result)
|
114
136
|
else
|
File without changes
|
data/docker/run_tests.sh
CHANGED
data/lib/aggkit/ci.rb
ADDED
@@ -0,0 +1,258 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
require 'tempfile'
|
5
|
+
require 'json'
|
6
|
+
require 'aggkit/env'
|
7
|
+
|
8
|
+
module Aggkit
|
9
|
+
|
10
|
+
module Ci
|
11
|
+
|
12
|
+
def log_action action
|
13
|
+
puts " => #{action}"
|
14
|
+
end
|
15
|
+
|
16
|
+
def info message
|
17
|
+
puts " => #{message}"
|
18
|
+
end
|
19
|
+
|
20
|
+
def execute cmd
|
21
|
+
puts " - executing: #{cmd}"
|
22
|
+
result = `#{cmd}`
|
23
|
+
puts result
|
24
|
+
if !$?.success?
|
25
|
+
raise "Execution failed cmd: #{cmd.inspect}"
|
26
|
+
end
|
27
|
+
result
|
28
|
+
end
|
29
|
+
|
30
|
+
#Уровни образов
|
31
|
+
# 1. Без модификатора - не протестирован, отсутсвует в репозитории.
|
32
|
+
# 2. С модификатором dev - протестирован автотестами, ДОЛЖЕН быть в репозитории.
|
33
|
+
# 3. С модификатором stage - протестирован руками, ДОЛЖЕН быть в репозитории(как и его предшественник dev).
|
34
|
+
# 4. С модификатором release - помечен для релиза, ДОЛЖЕН быть в репозитории(как и его предшественник dev и stage).
|
35
|
+
|
36
|
+
#Работа должна вестить ТОЛЬКО с workimage
|
37
|
+
|
38
|
+
# Базовое имя образа
|
39
|
+
# Например docker.rnds.pro/aggredator-mq
|
40
|
+
def baseimage service
|
41
|
+
raise "Service name invalid: #{service.inspect}" if service.to_s.length < 2
|
42
|
+
"docker.rnds.pro/aggredator-#{service}"
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
# Текущее имя образа без модификаторов продвижения, но с тегом
|
47
|
+
# Например docker.rnds.pro/aggredator-mq:7e6420b5
|
48
|
+
def workimage service, tag
|
49
|
+
raise "Service tag invalid: #{tag.inspect}" if tag.to_s.length < 2
|
50
|
+
"#{baseimage(service)}:#{tag}"
|
51
|
+
end
|
52
|
+
|
53
|
+
# Образ с модификатором версии
|
54
|
+
# Например docker.rnds.pro/aggredator-mq-dev:7e6420b5
|
55
|
+
def promotedimage service, stage, tag
|
56
|
+
raise "Service stage invalid: #{stage.inspect}" if stage.to_s.length < 2
|
57
|
+
raise "Service tag invalid: #{tag.inspect}" if tag.to_s.length < 2
|
58
|
+
"#{baseimage(service)}-#{stage}:#{tag}"
|
59
|
+
end
|
60
|
+
|
61
|
+
# Образ с модификатором протестированной автотестами версии
|
62
|
+
# Например docker.rnds.pro/aggredator-mq-dev:7e6420b5
|
63
|
+
def devimage service, tag
|
64
|
+
promotedimage(service, 'dev', tag)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Образ с модификатором стабильной версии
|
68
|
+
# Например docker.rnds.pro/aggredator-mq-stage:7e6420b5
|
69
|
+
def stageimage service, tag
|
70
|
+
promotedimage(service, 'stage', tag)
|
71
|
+
end
|
72
|
+
|
73
|
+
# Образ с модификатором релизной версии
|
74
|
+
#Например docker.rnds.pro/aggredator-mq-release:7e6420b5
|
75
|
+
def releaseimage service, tag
|
76
|
+
promotedimage(service, 'release', tag)
|
77
|
+
end
|
78
|
+
|
79
|
+
# Протегировать образ тегом
|
80
|
+
def tagimage! image, newimage, push: false, ignoreerror: false
|
81
|
+
log_action "Tag #{image.inspect} as #{newimage.inspect}"
|
82
|
+
|
83
|
+
if ignoreerror
|
84
|
+
execute("docker tag #{image} #{newimage} 2> /dev/null || true")
|
85
|
+
else
|
86
|
+
execute("docker tag #{image} #{newimage}")
|
87
|
+
end
|
88
|
+
|
89
|
+
if push
|
90
|
+
execute("docker push #{newimage}")
|
91
|
+
end
|
92
|
+
|
93
|
+
return newimage
|
94
|
+
end
|
95
|
+
|
96
|
+
#Удалить образ образ тегом
|
97
|
+
def rmimage! image
|
98
|
+
log_action "Remove #{image.inspect}"
|
99
|
+
execute("docker rmi #{image} &> /dev/null || true")
|
100
|
+
end
|
101
|
+
|
102
|
+
def label! image, label, value
|
103
|
+
raise "Image label invalid: #{label.inspect}" if label.to_s.length < 2
|
104
|
+
raise "Image label value invalid: #{value.inspect}" if value.to_s.length < 2
|
105
|
+
|
106
|
+
execute("echo #{image} | docker build -t #{image} --label=\"#{label.to_s}=#{value.to_s}\" - ")
|
107
|
+
end
|
108
|
+
|
109
|
+
# Растегировать все модификатора образа сервиса и возможно удалить его
|
110
|
+
def untagservice service, tag
|
111
|
+
if tag != 'latest'
|
112
|
+
rmimage! workimage(service, tag)
|
113
|
+
rmimage! devimage(service, tag)
|
114
|
+
rmimage! stageimage(service, tag)
|
115
|
+
rmimage! releaseimage(service, tag)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
# Продвинуть текущий образ сервиса по уровню - добавить модификатор и залить в репозиторий
|
120
|
+
def promote! service, tag, stage, fromstage: nil
|
121
|
+
fromimage = if fromstage
|
122
|
+
promotedimage(service, fromstage, tag)
|
123
|
+
else
|
124
|
+
workimage(service, tag)
|
125
|
+
end
|
126
|
+
|
127
|
+
toimage = promotedimage(service, stage, tag)
|
128
|
+
|
129
|
+
log_action "Promote #{fromimage} to #{toimage}"
|
130
|
+
return tagimage! fromimage, toimage, push: true
|
131
|
+
end
|
132
|
+
|
133
|
+
# Продвинуть текущий образ по уровню - добавить модификатор dev(протестировано) и залить в репозиторий
|
134
|
+
def promote_to_dev! service, tag
|
135
|
+
promote!(service, tag, 'dev')
|
136
|
+
end
|
137
|
+
|
138
|
+
# Продвинуть текущий образ по уровню - добавить модификатор stage(стабильно) и залить в репозиторий
|
139
|
+
def promote_to_stage! service, tag
|
140
|
+
promoted = tagimage!(devimage(service, tag), stageimage(service, tag), push: true)
|
141
|
+
# Тегируем этот образ как latest
|
142
|
+
return tagimage!(promoted, stageimage(service, 'latest'), push: true)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Продвинуть стабильный образ до указанного тега
|
146
|
+
def promote_to_tag! service, tag, newtag
|
147
|
+
return tagimage!(devimage(service, tag), stageimage(service, newtag), push: true)
|
148
|
+
end
|
149
|
+
|
150
|
+
# Продвинуть список образов или упасть с ошибкой
|
151
|
+
def promote_services_to_dev! services, tag
|
152
|
+
[services].flatten.each do |service|
|
153
|
+
promote_to_dev!(service, tag)
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
157
|
+
# Продвинуть список образов или упасть с ошибкой
|
158
|
+
def promote_services_to_stage! services, tag
|
159
|
+
[services].flatten.each do |service|
|
160
|
+
promote_to_stage!(service, tag)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
#Протегировать образы до указанного тега
|
165
|
+
def promote_services_to_tag! services, tag, newtag
|
166
|
+
[services].flatten.each do |service|
|
167
|
+
promote_to_tag!(service, tag, newtag)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
|
172
|
+
|
173
|
+
# Попытаться взять все модификаторы образов из репозитория
|
174
|
+
# Скачивает образы и тегирует их как workimage
|
175
|
+
def pull_images_cache services, tag
|
176
|
+
log_action "Puling images#{[services].flatten.inspect} from cache...."
|
177
|
+
[services].flatten.each do |service|
|
178
|
+
execute("docker pull #{devimage(service, tag)} 2> /dev/null || true")
|
179
|
+
execute("docker pull #{stageimage(service, tag)} 2> /dev/null || true")
|
180
|
+
execute("docker pull #{releaseimage(service, tag)} 2> /dev/null || true")
|
181
|
+
tagimage! devimage(service, tag), workimage(service, tag), ignoreerror: true
|
182
|
+
tagimage! stageimage(service, tag), workimage(service, tag), ignoreerror: true
|
183
|
+
tagimage! releaseimage(service, tag), workimage(service, tag), ignoreerror: true
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
#Растегировать все образы для дальнейшего удаления. Помечает как latest для поддержки кеширования docker
|
188
|
+
def clean_services services, tag
|
189
|
+
[services].flatten.each do |service|
|
190
|
+
tagimage! workimage(service, tag), workimage(service, 'latest'), ignoreerror: true
|
191
|
+
tagimage! devimage(service, tag), workimage(service, 'latest'), ignoreerror: true
|
192
|
+
tagimage! stageimage(service, tag), workimage(service, 'latest'), ignoreerror: true
|
193
|
+
tagimage! releaseimage(service, tag), workimage(service, 'latest'), ignoreerror: true
|
194
|
+
untagservice(service, tag)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
|
198
|
+
def ssh_deploy services, env, sshserver, tag, stage
|
199
|
+
services = [services].flatten
|
200
|
+
log_action "Deploy [#{services.inspect}] tag:#{tag.inspect} stage:#{stage.inspect} to #{sshserver.inspect}"
|
201
|
+
with_ssh sshserver do |sockname|
|
202
|
+
env_config = JSON(`agg #{env} --show --release`)
|
203
|
+
envs = env_config['envs']
|
204
|
+
config_file = env_config['config_file'].to_s
|
205
|
+
envs['DOCKER_HOST'] = "unix://#{sockname}"
|
206
|
+
envs['CORE_TAG'] = tag
|
207
|
+
envs['CORE_SUFFIX'] = "-#{stage}"
|
208
|
+
|
209
|
+
localid = execute("docker info | grep ID").strip
|
210
|
+
|
211
|
+
Aggkit::Env.with_env(envs) do
|
212
|
+
remoteid = execute("docker info | grep ID").strip
|
213
|
+
|
214
|
+
if localid == remoteid
|
215
|
+
raise "Can't get access to remote Docker"
|
216
|
+
end
|
217
|
+
|
218
|
+
execute('docker-compose ps')
|
219
|
+
execute('docker-compose pull')
|
220
|
+
|
221
|
+
consul = execute('docker ps | grep consul').split(' ').first.strip
|
222
|
+
puts "CONSULT ID: #{consul}"
|
223
|
+
|
224
|
+
if !config_file.empty?
|
225
|
+
execute("docker exec -i #{consul} waiter.rb -t 20 --consul")
|
226
|
+
execute("docker exec -i #{consul} consul.rb --init --override --config - < #{config_file}")
|
227
|
+
end
|
228
|
+
|
229
|
+
execute('docker-compose up -d')
|
230
|
+
|
231
|
+
services.each do |service|
|
232
|
+
deployed = promotedimage(service, stage, tag)
|
233
|
+
current = promotedimage(service, stage, 'current')
|
234
|
+
tagimage! deployed, current
|
235
|
+
untagservice(service, tag)
|
236
|
+
end
|
237
|
+
|
238
|
+
end
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
def with_ssh server, user: 'root', &block
|
243
|
+
file = Tempfile.new
|
244
|
+
@ssh_sockname = file.path
|
245
|
+
file.unlink
|
246
|
+
@ssh_pid = spawn("ssh -NL #{@ssh_sockname}:/var/run/docker.sock root@#{server}")
|
247
|
+
sleep 5
|
248
|
+
yield(@ssh_sockname)
|
249
|
+
ensure
|
250
|
+
Process.kill(:SIGTERM, @ssh_pid)
|
251
|
+
sleep 5
|
252
|
+
Process.wait2(@ssh_pid, Process::WNOHANG)
|
253
|
+
FileUtils.rm_f(@ssh_sockname)
|
254
|
+
end
|
255
|
+
|
256
|
+
end
|
257
|
+
|
258
|
+
end
|
data/lib/aggkit/env.rb
CHANGED
@@ -55,8 +55,10 @@ module Aggkit
|
|
55
55
|
end
|
56
56
|
|
57
57
|
@env_root = File.join(project_root, 'envs', @name)
|
58
|
+
end
|
58
59
|
|
59
|
-
|
60
|
+
def prepare release: false
|
61
|
+
@environment = prepare_environment(release: release)
|
60
62
|
end
|
61
63
|
|
62
64
|
def with_env &block
|
@@ -87,12 +89,17 @@ module Aggkit
|
|
87
89
|
[core_dot_file, project_dot_file, dot_file].compact.select{|f| File.exists?(f)}.uniq
|
88
90
|
end
|
89
91
|
|
92
|
+
def config_file
|
93
|
+
[File.join(env_root, 'config.yml'), File.join(env_root, 'config.yaml')].compact.select{|f| File.exists?(f)}.uniq.first
|
94
|
+
end
|
95
|
+
|
90
96
|
def show
|
91
97
|
{
|
92
98
|
name: name,
|
93
99
|
project_root: project_root,
|
94
100
|
core_root: core_root,
|
95
101
|
envfiles: env_files,
|
102
|
+
config_file: config_file,
|
96
103
|
envs: environment,
|
97
104
|
}
|
98
105
|
end
|
@@ -100,7 +107,7 @@ module Aggkit
|
|
100
107
|
private
|
101
108
|
|
102
109
|
|
103
|
-
def prepare_environment
|
110
|
+
def prepare_environment release: false
|
104
111
|
base_env = {
|
105
112
|
'ENV_ROOT' => env_root,
|
106
113
|
'PROJECT_ROOT' => project_root,
|
@@ -110,7 +117,7 @@ private
|
|
110
117
|
|
111
118
|
dot_env = prepare_dot(base_env)
|
112
119
|
|
113
|
-
compose_env = prepare_compose(dot_env.merge(base_env))
|
120
|
+
compose_env = prepare_compose(dot_env.merge(base_env), release: release)
|
114
121
|
|
115
122
|
dot_env.merge(base_env).merge(compose_env)
|
116
123
|
end
|
@@ -121,7 +128,7 @@ private
|
|
121
128
|
end
|
122
129
|
end
|
123
130
|
|
124
|
-
def prepare_compose env
|
131
|
+
def prepare_compose env, release: false
|
125
132
|
compose_files = if env['COMPOSE_FILE']
|
126
133
|
env['COMPOSE_FILE']
|
127
134
|
elsif File.exists?(File.join(env_root, "docker-compose.yml"))
|
@@ -133,8 +140,11 @@ private
|
|
133
140
|
if compose_files
|
134
141
|
result_file = File.join(env_root, "compose-result.yml")
|
135
142
|
Env.with_env(env.merge('COMPOSE_FILE' => compose_files)) do
|
136
|
-
|
137
|
-
|
143
|
+
if release
|
144
|
+
`aggmerge #{result_file} --skip-build`
|
145
|
+
else
|
146
|
+
`aggmerge #{result_file}`
|
147
|
+
end
|
138
148
|
end
|
139
149
|
{
|
140
150
|
'COMPOSE_FILE' => result_file
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: aggkit
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.2.6.
|
4
|
+
version: 0.2.6.8329
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Godko Ivan
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2019-02-
|
12
|
+
date: 2019-02-08 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: diplomat
|
@@ -101,15 +101,12 @@ email:
|
|
101
101
|
- kinnalru@gmail.com
|
102
102
|
executables:
|
103
103
|
- agg
|
104
|
+
- aggci
|
104
105
|
- aggconsul
|
105
106
|
- agglock
|
106
107
|
- aggmerge
|
108
|
+
- aggterm
|
107
109
|
- aggwait
|
108
|
-
- consul.rb
|
109
|
-
- locker.rb
|
110
|
-
- merger.rb
|
111
|
-
- terminator.rb
|
112
|
-
- waiter.rb
|
113
110
|
extensions: []
|
114
111
|
extra_rdoc_files: []
|
115
112
|
files:
|
@@ -124,15 +121,12 @@ files:
|
|
124
121
|
- README.md
|
125
122
|
- aggkit.gemspec
|
126
123
|
- bin/agg
|
124
|
+
- bin/aggci
|
127
125
|
- bin/aggconsul
|
128
126
|
- bin/agglock
|
129
127
|
- bin/aggmerge
|
128
|
+
- bin/aggterm
|
130
129
|
- bin/aggwait
|
131
|
-
- bin/consul.rb
|
132
|
-
- bin/locker.rb
|
133
|
-
- bin/merger.rb
|
134
|
-
- bin/terminator.rb
|
135
|
-
- bin/waiter.rb
|
136
130
|
- docker/Dockerfile
|
137
131
|
- docker/docker-compose.yml
|
138
132
|
- docker/down.sh
|
@@ -165,6 +159,7 @@ files:
|
|
165
159
|
- lib/aggkit/childprocess/windows/process.rb
|
166
160
|
- lib/aggkit/childprocess/windows/process_builder.rb
|
167
161
|
- lib/aggkit/childprocess/windows/structs.rb
|
162
|
+
- lib/aggkit/ci.rb
|
168
163
|
- lib/aggkit/env.rb
|
169
164
|
- lib/aggkit/runner.rb
|
170
165
|
- lib/aggkit/version.rb
|
data/bin/consul.rb
DELETED
@@ -1,222 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'diplomat'
|
4
|
-
require 'optparse'
|
5
|
-
require 'English'
|
6
|
-
require 'yaml'
|
7
|
-
|
8
|
-
STDOUT.sync = true
|
9
|
-
STDERR.sync = true
|
10
|
-
|
11
|
-
@opts = {}
|
12
|
-
|
13
|
-
@opts[:exec] = (begin
|
14
|
-
ARGV.join(' ').split(' -- ')[1].strip
|
15
|
-
rescue StandardError
|
16
|
-
nil
|
17
|
-
end)
|
18
|
-
|
19
|
-
parser = OptionParser.new do |o|
|
20
|
-
o.banner = 'Usage: consul.rb [options] -- exec'
|
21
|
-
|
22
|
-
o.on('--consul url', 'Set up a custom Consul URL') do |url|
|
23
|
-
Diplomat.configure do |config|
|
24
|
-
config.url = url.strip
|
25
|
-
end
|
26
|
-
end
|
27
|
-
|
28
|
-
o.on('--token token', 'Connect into consul with custom access token (ACL)') do |token|
|
29
|
-
Diplomat.configure do |config|
|
30
|
-
config.acl_token = token.strip
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
o.on('--init [service]', 'Initialize Consul services from config') do |service|
|
35
|
-
@opts[:service] = service
|
36
|
-
@opts[:init] = true
|
37
|
-
end
|
38
|
-
|
39
|
-
o.on('--config file', 'Read service configulation from file') do |file|
|
40
|
-
@opts[:config] = file.strip
|
41
|
-
end
|
42
|
-
|
43
|
-
o.on('--upload', 'Upload files to variables') do
|
44
|
-
@opts[:upload] = true
|
45
|
-
end
|
46
|
-
|
47
|
-
o.on('--show [service]', 'Show service configulation from Consul') do |service|
|
48
|
-
@opts[:service] = service
|
49
|
-
@opts[:show] = true
|
50
|
-
end
|
51
|
-
|
52
|
-
o.on('--override', 'override existed keys') do
|
53
|
-
@opts[:override] = true
|
54
|
-
end
|
55
|
-
|
56
|
-
o.on('-d', '--dereference', 'dereference consul values in form of "consul://key/subkey"') do
|
57
|
-
@opts[:dereference] = true
|
58
|
-
end
|
59
|
-
|
60
|
-
o.on('--env prefix', 'export KV values from prefix as env varaibles') do |prefix|
|
61
|
-
@opts[:env] = (prefix + '/').gsub('//', '/')
|
62
|
-
end
|
63
|
-
|
64
|
-
o.on('--export', 'add export to --env output') do
|
65
|
-
@opts[:export] = true
|
66
|
-
end
|
67
|
-
|
68
|
-
o.on('--pristine', "not include the parent processes' environment when exec child process") do
|
69
|
-
@opts[:pristine] = true
|
70
|
-
end
|
71
|
-
|
72
|
-
o.on('--put path:value', 'put value to path') do |path|
|
73
|
-
@opts[:put] = path.strip
|
74
|
-
end
|
75
|
-
|
76
|
-
o.on('--get path', 'get value from') do |path|
|
77
|
-
@opts[:get] = path.strip
|
78
|
-
end
|
79
|
-
end
|
80
|
-
parser.parse!
|
81
|
-
|
82
|
-
def die(message)
|
83
|
-
STDERR.puts "Error: #{message}"
|
84
|
-
exit 1
|
85
|
-
end
|
86
|
-
|
87
|
-
def key_to_consul(key)
|
88
|
-
key.downcase.gsub(/[^0-9a-z]/i, '_')
|
89
|
-
end
|
90
|
-
|
91
|
-
def key_to_env(key)
|
92
|
-
key.upcase.gsub(/[^0-9a-z]/i, '_')
|
93
|
-
end
|
94
|
-
|
95
|
-
def dereferenced_value(value)
|
96
|
-
if @opts[:dereference] && value && value[/^consul:\/\//]
|
97
|
-
reference_path = value.gsub(/^consul:\/\//, '')
|
98
|
-
dereferenced_value(Diplomat::Kv.get(reference_path))
|
99
|
-
else
|
100
|
-
value
|
101
|
-
end
|
102
|
-
end
|
103
|
-
|
104
|
-
if config = @opts[:config]
|
105
|
-
@opts[:config] = YAML.load(config == '-' ? STDIN.read : File.read(config))
|
106
|
-
end
|
107
|
-
|
108
|
-
if @opts[:init]
|
109
|
-
raise OptionParser::MissingArgument.new('config') unless @opts[:config]
|
110
|
-
|
111
|
-
services = if service = @opts[:service]
|
112
|
-
{
|
113
|
-
service => @opts[:config][service]
|
114
|
-
}
|
115
|
-
else
|
116
|
-
@opts[:config]
|
117
|
-
end
|
118
|
-
|
119
|
-
services.each_pair do |service, config|
|
120
|
-
next unless config
|
121
|
-
next if service[/^\./] # skip hidden keys
|
122
|
-
|
123
|
-
path = "services/env/#{service}"
|
124
|
-
config.each_pair do |env, item|
|
125
|
-
key = "#{path}/#{key_to_consul(env)}"
|
126
|
-
value = if @opts[:upload] && item['file']
|
127
|
-
File.read(item['file'])
|
128
|
-
else
|
129
|
-
item['value'] || item['default'] || item['file']
|
130
|
-
end
|
131
|
-
|
132
|
-
empty = begin
|
133
|
-
Diplomat::Kv.get(key)
|
134
|
-
false
|
135
|
-
rescue Diplomat::KeyNotFound
|
136
|
-
true
|
137
|
-
end
|
138
|
-
|
139
|
-
Diplomat::Kv.put(key, value.to_s.strip) || die("Can't put #{key} to Consul") if empty || @opts[:override]
|
140
|
-
end
|
141
|
-
end
|
142
|
-
|
143
|
-
exit 0
|
144
|
-
end
|
145
|
-
|
146
|
-
if @opts[:show]
|
147
|
-
config = {}
|
148
|
-
|
149
|
-
path = if service = @opts[:service]
|
150
|
-
"services/env/#{service}/"
|
151
|
-
else
|
152
|
-
'services/env/'
|
153
|
-
end
|
154
|
-
|
155
|
-
answer = Diplomat::Kv.get(path, recurse: true, convert_to_hash: true) || die("Can't get #{path} from Consul")
|
156
|
-
answer['services']['env'].each_pair do |service, env|
|
157
|
-
cfg = config[service] ||= {}
|
158
|
-
|
159
|
-
env.each_pair do |key, value|
|
160
|
-
value = dereferenced_value(value)
|
161
|
-
|
162
|
-
cfg[key_to_env(key)] = {
|
163
|
-
env: key_to_env(key),
|
164
|
-
value: value
|
165
|
-
}
|
166
|
-
end
|
167
|
-
end
|
168
|
-
|
169
|
-
STDOUT.puts JSON.parse(config.to_json).to_yaml
|
170
|
-
|
171
|
-
exit 0
|
172
|
-
end
|
173
|
-
|
174
|
-
if put = @opts[:put]
|
175
|
-
path, *value = put.split(':').map(&:strip)
|
176
|
-
value = value.join(':')
|
177
|
-
value = File.read(value) if @opts[:upload] && value && File.exist?(value)
|
178
|
-
|
179
|
-
Diplomat::Kv.put(path, value.to_s.strip) || die("Can't put #{path} to Consul")
|
180
|
-
|
181
|
-
exit 0
|
182
|
-
end
|
183
|
-
|
184
|
-
if path = @opts[:get]
|
185
|
-
value = dereferenced_value(Diplomat::Kv.get(path))
|
186
|
-
|
187
|
-
STDOUT.puts value.to_s.strip
|
188
|
-
|
189
|
-
exit 0
|
190
|
-
end
|
191
|
-
|
192
|
-
if prefix = @opts[:env]
|
193
|
-
keys = begin
|
194
|
-
Diplomat::Kv.get(prefix, keys: true)
|
195
|
-
rescue Diplomat::KeyNotFound => e
|
196
|
-
[]
|
197
|
-
rescue StandardError
|
198
|
-
die("Can't get keys at #{prefix} from Consul")
|
199
|
-
end
|
200
|
-
|
201
|
-
env = keys.reduce({}) do |e, key|
|
202
|
-
value = dereferenced_value(Diplomat::Kv.get(key))
|
203
|
-
|
204
|
-
e.merge(key_to_env(key.gsub(prefix, '')) => value)
|
205
|
-
end
|
206
|
-
|
207
|
-
if cmd = @opts[:exec]
|
208
|
-
env = ENV.to_h.merge(env) unless @opts[:pristine]
|
209
|
-
|
210
|
-
exec(env, cmd, unsetenv_others: true)
|
211
|
-
else
|
212
|
-
env.each_pair do |k, v|
|
213
|
-
STDOUT.puts "#{@opts[:export] ? 'export ' : ''}#{k}=\"#{v}\""
|
214
|
-
end
|
215
|
-
end
|
216
|
-
|
217
|
-
|
218
|
-
exit 0
|
219
|
-
end
|
220
|
-
|
221
|
-
STDOUT.puts parser.help
|
222
|
-
exit 1
|
data/bin/locker.rb
DELETED
@@ -1,71 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'optparse'
|
4
|
-
require 'diplomat'
|
5
|
-
|
6
|
-
@opts = {
|
7
|
-
url: 'http://localhost:8500',
|
8
|
-
timeout: 10,
|
9
|
-
ttl: 30 * 60
|
10
|
-
}
|
11
|
-
|
12
|
-
parser = OptionParser.new do |o|
|
13
|
-
o.banner = 'Usage: locker.rb [options]'
|
14
|
-
|
15
|
-
o.on("--consul url=#{@opts[:url]}", 'Set up a custom Consul URL') do |url|
|
16
|
-
Diplomat.configure do |config|
|
17
|
-
config.url = url.strip
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
o.on('--lock resource', 'resource name to lock with Consul') do |resource|
|
22
|
-
@opts[:lock] = resource.strip
|
23
|
-
end
|
24
|
-
|
25
|
-
o.on("--ttl seconds=#{@opts[:ttl]}", 'TTL to set when session created') do |seconds|
|
26
|
-
@opts[:ttl] = Integer(seconds.strip)
|
27
|
-
end
|
28
|
-
|
29
|
-
o.on('--unlock session', 'session name from previous call lock') do |session|
|
30
|
-
@opts[:unlock] = session.strip
|
31
|
-
end
|
32
|
-
|
33
|
-
o.on("--timeout seconds=#{@opts[:timeout]}", 'timeout to wait lock') do |seconds|
|
34
|
-
@opts[:timeout] = Integer(seconds.strip)
|
35
|
-
end
|
36
|
-
end
|
37
|
-
parser.parse!
|
38
|
-
|
39
|
-
|
40
|
-
def lock(session, locker, timeout)
|
41
|
-
Timeout.timeout(timeout) do
|
42
|
-
return Diplomat::Lock.wait_to_acquire("resource/#{locker[:resource]}/lock", session, locker.to_json, 10)
|
43
|
-
end
|
44
|
-
rescue Timeout::Error => e
|
45
|
-
false
|
46
|
-
end
|
47
|
-
|
48
|
-
|
49
|
-
if resource = @opts[:lock]
|
50
|
-
locker = {
|
51
|
-
Name: "#{resource}_locker_#{rand(999_999)}",
|
52
|
-
Behavior: 'delete',
|
53
|
-
TTL: "#{@opts[:ttl]}s",
|
54
|
-
resource: resource
|
55
|
-
}
|
56
|
-
sessionid = Diplomat::Session.create(locker)
|
57
|
-
|
58
|
-
if lock(sessionid, locker, @opts[:timeout])
|
59
|
-
puts sessionid
|
60
|
-
exit 0
|
61
|
-
else
|
62
|
-
STDERR.puts "Failed to lock resource: #{resource}"
|
63
|
-
exit 1
|
64
|
-
end
|
65
|
-
|
66
|
-
elsif session = @opts[:unlock]
|
67
|
-
Diplomat::Session.destroy(session)
|
68
|
-
else
|
69
|
-
STDERR.puts parser.help
|
70
|
-
exit 1
|
71
|
-
end
|
data/bin/merger.rb
DELETED
@@ -1,118 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'English'
|
4
|
-
require 'yaml'
|
5
|
-
|
6
|
-
if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
7
|
-
unless ENV['COMPOSE_FILE']
|
8
|
-
STDERR.puts 'COMPOSE_FILE environment must point to one or more files'
|
9
|
-
exit 1
|
10
|
-
end
|
11
|
-
end
|
12
|
-
|
13
|
-
|
14
|
-
class Hash
|
15
|
-
|
16
|
-
def deep_dup
|
17
|
-
Marshal.load(Marshal.dump(self))
|
18
|
-
end
|
19
|
-
|
20
|
-
end
|
21
|
-
|
22
|
-
class Array
|
23
|
-
|
24
|
-
def deep_dup
|
25
|
-
Marshal.load(Marshal.dump(self))
|
26
|
-
end
|
27
|
-
|
28
|
-
end
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
def extend_hash(first, second)
|
33
|
-
raise ArgumentError.new('First and second args equal nil') if [first, second].all? &:nil?
|
34
|
-
return second if first.nil?
|
35
|
-
return first if second.nil?
|
36
|
-
|
37
|
-
first.each_pair do |fk, fv|
|
38
|
-
next unless second.key?(fk)
|
39
|
-
|
40
|
-
sv = second[fk]
|
41
|
-
raise "Types of values not match(#{fv.class}, #{sv.class})" if fv.class != sv.class
|
42
|
-
|
43
|
-
# Специальный случай потому что command не мерджится а заменяется
|
44
|
-
if fk == 'command'
|
45
|
-
first[fk] = sv
|
46
|
-
elsif fv.is_a? Hash
|
47
|
-
extend_hash(fv, sv)
|
48
|
-
elsif fv.is_a? Array
|
49
|
-
fv |= sv
|
50
|
-
first[fk] = fv
|
51
|
-
else
|
52
|
-
first[fk] = sv
|
53
|
-
end
|
54
|
-
end
|
55
|
-
|
56
|
-
second.each_pair do |sk, sv|
|
57
|
-
next if first.key?(sk)
|
58
|
-
|
59
|
-
first[sk] = sv
|
60
|
-
end
|
61
|
-
|
62
|
-
first
|
63
|
-
end
|
64
|
-
|
65
|
-
def process_compose_hash(yml, dirname, parent = {})
|
66
|
-
(yml['services'] || {}).each_pair do |name, service|
|
67
|
-
next unless ext = service['extends']
|
68
|
-
base = if ext.is_a? String
|
69
|
-
template = yml['services'][ext]
|
70
|
-
parent_service = (parent['services'] || {})[ext] || {}
|
71
|
-
extend_hash(parent_service.deep_dup, template)
|
72
|
-
elsif file = ext['file']
|
73
|
-
ENV.each_pair do |k, v|
|
74
|
-
file.gsub!("$#{k}", v)
|
75
|
-
file.gsub!("${#{k}}", v)
|
76
|
-
end
|
77
|
-
|
78
|
-
file_to_load = if File.exist?(dirname + '/' + file)
|
79
|
-
dirname + '/' + file
|
80
|
-
else
|
81
|
-
file
|
82
|
-
end
|
83
|
-
|
84
|
-
tmp = process_compose_hash(YAML.load(File.read(file_to_load)), File.dirname(file_to_load), service)
|
85
|
-
|
86
|
-
begin
|
87
|
-
(tmp['services'][ext['service']] || {})
|
88
|
-
rescue StandardError
|
89
|
-
{}
|
90
|
-
end
|
91
|
-
else
|
92
|
-
yml['services'][ext['service']]
|
93
|
-
end.deep_dup
|
94
|
-
|
95
|
-
service.delete 'extends'
|
96
|
-
|
97
|
-
yml['services'][name] = extend_hash(base, service)
|
98
|
-
end
|
99
|
-
yml
|
100
|
-
end
|
101
|
-
|
102
|
-
if File.basename($PROGRAM_NAME) == File.basename(__FILE__)
|
103
|
-
result = ENV['COMPOSE_FILE'].split(':').reduce({}) do |parent, file|
|
104
|
-
yml = process_compose_hash(YAML.load(File.read(file)), File.dirname(file), parent)
|
105
|
-
if yml['version'] && parent['version'] && yml['version'] != parent['version']
|
106
|
-
raise "version mismatch: #{file}"
|
107
|
-
end
|
108
|
-
ret = extend_hash(parent.deep_dup, yml)
|
109
|
-
ret
|
110
|
-
end
|
111
|
-
|
112
|
-
if ARGV[0].nil? || ARGV[0].strip == '-'
|
113
|
-
puts YAML.dump(result)
|
114
|
-
else
|
115
|
-
File.write(ARGV[0].strip, YAML.dump(result))
|
116
|
-
end
|
117
|
-
|
118
|
-
end
|
data/bin/waiter.rb
DELETED
@@ -1,262 +0,0 @@
|
|
1
|
-
#!/usr/bin/env ruby
|
2
|
-
|
3
|
-
require 'optparse'
|
4
|
-
require 'securerandom'
|
5
|
-
require 'English'
|
6
|
-
require 'openssl'
|
7
|
-
require 'tempfile'
|
8
|
-
require 'json'
|
9
|
-
|
10
|
-
STDOUT.sync = true
|
11
|
-
STDERR.sync = true
|
12
|
-
|
13
|
-
TIMEOUT = 15
|
14
|
-
INTERVAL = 3
|
15
|
-
|
16
|
-
@opts = {}
|
17
|
-
|
18
|
-
@opts[:exec] = (begin
|
19
|
-
ARGV.join(' ').split(' -- ')[1].strip
|
20
|
-
rescue StandardError
|
21
|
-
nil
|
22
|
-
end)
|
23
|
-
@opts[:timeout] = TIMEOUT
|
24
|
-
@opts[:interval] = INTERVAL
|
25
|
-
@opts[:consul_addr] = 'http://localhost:8500'
|
26
|
-
@opts[:consul_service_count] = 1
|
27
|
-
|
28
|
-
|
29
|
-
OptionParser.new do |o|
|
30
|
-
o.banner = 'Usage: waiter.rb [options] -- exec'
|
31
|
-
|
32
|
-
o.on('--tcp host:port', 'Wait for tcp accepts on host:port') do |addr|
|
33
|
-
host, port = addr.split(':')
|
34
|
-
@opts[:tcp] = addr.strip
|
35
|
-
@opts[:host] = host.strip
|
36
|
-
@opts[:port] = port.strip
|
37
|
-
end
|
38
|
-
|
39
|
-
o.on('--db dbname', 'Wait for PG database exists. Using --tcp to conenct PG') do |db|
|
40
|
-
@opts[:db] = db.strip
|
41
|
-
end
|
42
|
-
|
43
|
-
o.on('--tb tablename', 'Wait for PG table exists. Using --tcp to conenct PG') do |tb|
|
44
|
-
@opts[:tb] = tb.strip
|
45
|
-
end
|
46
|
-
|
47
|
-
o.on('-f', '--file filename', 'Wait for file exists.') do |file|
|
48
|
-
@opts[:file] = file.strip
|
49
|
-
end
|
50
|
-
|
51
|
-
o.on("--consul-addr addr=#{@opts[:consul_addr]}", 'HTTP addres to connect to consul') do |addr|
|
52
|
-
@opts[:consul_addr] = addr.strip
|
53
|
-
end
|
54
|
-
|
55
|
-
o.on('--consul', 'Wait for local consul agent to be ready') do
|
56
|
-
@opts[:consul] = true
|
57
|
-
end
|
58
|
-
|
59
|
-
o.on('--consul-service service', 'Wait for service appear in consul') do |service|
|
60
|
-
@opts[:consul_service] = service
|
61
|
-
end
|
62
|
-
|
63
|
-
o.on("--consul-service-count count=#{@opts[:consul_service_count]}", 'Wait for this count of service appear in consul') do |count|
|
64
|
-
@opts[:consul_service_count] = count.to_i
|
65
|
-
end
|
66
|
-
|
67
|
-
o.on('--consul-tag tag', 'Filter consul service by tag') do |tag|
|
68
|
-
@opts[:consul_tag] = tag.to_s
|
69
|
-
end
|
70
|
-
|
71
|
-
o.on('--user user', 'username') do |user|
|
72
|
-
@opts[:user] = user.strip
|
73
|
-
end
|
74
|
-
|
75
|
-
o.on('--pass pass', 'password') do |pass|
|
76
|
-
@opts[:pass] = pass.strip
|
77
|
-
end
|
78
|
-
|
79
|
-
o.on('-t', '--timeout secs=15', 'Total timeout') do |timeout|
|
80
|
-
@opts[:timeout] = timeout.to_i
|
81
|
-
end
|
82
|
-
|
83
|
-
o.on('-i', '--interval secs=2', 'Interval between attempts') do |interval|
|
84
|
-
@opts[:interval] = interval.to_i
|
85
|
-
end
|
86
|
-
|
87
|
-
o.on('-q', '--quiet', 'Do not output any status messages') do
|
88
|
-
@opts[:quiet] = true
|
89
|
-
end
|
90
|
-
end.parse!
|
91
|
-
|
92
|
-
def log(message)
|
93
|
-
puts message unless @opts[:quiet]
|
94
|
-
end
|
95
|
-
|
96
|
-
@opts[:timeout] = @opts[:timeout].to_i
|
97
|
-
@opts[:interval] = @opts[:interval].to_i
|
98
|
-
|
99
|
-
if @opts[:db]
|
100
|
-
@pg = {}
|
101
|
-
@pg[:db] = "-d #{@opts[:db]}"
|
102
|
-
@pg[:user] = "-U #{@opts[:user]}" if @opts[:user]
|
103
|
-
@pg[:pass] = if @opts[:pass] && !@opts[:pass].empty?
|
104
|
-
"PGPASSWORD=#{@opts[:pass]}"
|
105
|
-
else
|
106
|
-
''
|
107
|
-
end
|
108
|
-
|
109
|
-
@pg[:host] = "-h #{@opts[:host]}" if @opts[:host]
|
110
|
-
@pg[:port] = "-p #{@opts[:port]}" if @opts[:port]
|
111
|
-
|
112
|
-
@pg[:tb] = @opts[:tb] if @opts[:tb]
|
113
|
-
end
|
114
|
-
|
115
|
-
def wait_for(timeout)
|
116
|
-
starttime = Time.now
|
117
|
-
loop do
|
118
|
-
success = yield
|
119
|
-
|
120
|
-
return success if success
|
121
|
-
|
122
|
-
return false if (Time.now - starttime) > timeout
|
123
|
-
sleep @opts[:interval]
|
124
|
-
end
|
125
|
-
|
126
|
-
false
|
127
|
-
end
|
128
|
-
|
129
|
-
def complete!(success)
|
130
|
-
if success
|
131
|
-
if @opts[:exec]
|
132
|
-
exec @opts[:exec]
|
133
|
-
else
|
134
|
-
exit 0
|
135
|
-
end
|
136
|
-
end
|
137
|
-
|
138
|
-
STDERR.puts 'Operation timed out'
|
139
|
-
exit 1
|
140
|
-
end
|
141
|
-
|
142
|
-
def wait_for_consul
|
143
|
-
log('Waiting for consul...')
|
144
|
-
ret = wait_for @opts[:timeout] do
|
145
|
-
cmd = "consul operator raft list-peers -http-addr=#{@opts[:consul_addr]} > /dev/null 2>&1"
|
146
|
-
system(cmd)
|
147
|
-
$?.success?
|
148
|
-
end
|
149
|
-
|
150
|
-
yield(ret)
|
151
|
-
end
|
152
|
-
|
153
|
-
def wait_for_consul_service(service)
|
154
|
-
log("Waiting for consul service #{service}...")
|
155
|
-
ret = wait_for @opts[:timeout] do
|
156
|
-
cmd = "curl -s #{@opts[:consul_addr]}/v1/health/service/#{service}?passing"
|
157
|
-
cmd += "&tag=#{@opts[:consul_tag]}" if @opts[:consul_tag]
|
158
|
-
result = `#{cmd}`
|
159
|
-
result = begin
|
160
|
-
JSON.parse(result)
|
161
|
-
rescue StandardError
|
162
|
-
[]
|
163
|
-
end
|
164
|
-
result.count >= @opts[:consul_service_count]
|
165
|
-
end
|
166
|
-
|
167
|
-
yield(ret)
|
168
|
-
end
|
169
|
-
|
170
|
-
def wait_for_tcp
|
171
|
-
log("Waiting for TCP: #{@opts[:host]}:#{@opts[:port]}...")
|
172
|
-
ret = wait_for @opts[:timeout] do
|
173
|
-
cmd = "nc -z #{@opts[:host]} #{@opts[:port]} > /dev/null 2>&1"
|
174
|
-
system(cmd)
|
175
|
-
$?.success?
|
176
|
-
end
|
177
|
-
|
178
|
-
yield(ret)
|
179
|
-
end
|
180
|
-
|
181
|
-
def wait_for_db
|
182
|
-
log("Waiting for DB: pg://#{@opts[:user]}:#{@opts[:pass]}@#{@opts[:host]}:#{@opts[:port]}/#{@opts[:db]}...")
|
183
|
-
ret = wait_for @opts[:timeout] do
|
184
|
-
cmd = "#{@pg[:pass]} psql -lqt #{@pg[:user]} #{@pg[:host]} #{@pg[:port]} #{@pg[:db]} 2>/dev/null | cut -d \\| -f 1 | grep -qw #{@opts[:db]} > /dev/null 2>&1"
|
185
|
-
system(cmd)
|
186
|
-
$?.success?
|
187
|
-
end
|
188
|
-
|
189
|
-
yield(ret)
|
190
|
-
end
|
191
|
-
|
192
|
-
def wait_for_tb
|
193
|
-
log("Waiting for TABLE: pg://#{@opts[:user]}:#{@opts[:pass]}@#{@opts[:host]}:#{@opts[:port]}/#{@opts[:db]}##{@opts[:tb]}...")
|
194
|
-
ret = wait_for @opts[:timeout] do
|
195
|
-
cmd = "echo \"\\dt\" | psql -qt #{@pg[:user]} #{@pg[:pass]} #{@pg[:host]} #{@pg[:port]} #{@pg[:db]} 2>/dev/null | cut -d \\| -f 2 | grep -qw #{@pg[:tb]} > /dev/null 2>&1"
|
196
|
-
system(cmd)
|
197
|
-
$?.success?
|
198
|
-
end
|
199
|
-
|
200
|
-
yield(ret)
|
201
|
-
end
|
202
|
-
|
203
|
-
def wait_for_file(file = @opts[:file], timeout = @opts[:timeout])
|
204
|
-
log("Waiting for FILE: #{file}")
|
205
|
-
ret = wait_for timeout do
|
206
|
-
File.exist? file
|
207
|
-
end
|
208
|
-
yield(ret)
|
209
|
-
end
|
210
|
-
|
211
|
-
if @opts[:tb]
|
212
|
-
wait_for_tcp do |success|
|
213
|
-
if success
|
214
|
-
wait_for_db do |success|
|
215
|
-
if success
|
216
|
-
wait_for_tb do |success|
|
217
|
-
complete!(success)
|
218
|
-
end
|
219
|
-
end
|
220
|
-
end
|
221
|
-
end
|
222
|
-
|
223
|
-
complete!(false)
|
224
|
-
end
|
225
|
-
end
|
226
|
-
|
227
|
-
if @opts[:db]
|
228
|
-
wait_for_tcp do |success|
|
229
|
-
if success
|
230
|
-
wait_for_db do |success|
|
231
|
-
complete!(success)
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
complete!(false)
|
236
|
-
end
|
237
|
-
end
|
238
|
-
|
239
|
-
|
240
|
-
if @opts[:consul]
|
241
|
-
wait_for_consul do |success|
|
242
|
-
complete!(success)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
if @opts[:consul_service]
|
247
|
-
wait_for_consul_service(@opts[:consul_service]) do |success|
|
248
|
-
complete!(success)
|
249
|
-
end
|
250
|
-
end
|
251
|
-
|
252
|
-
if @opts[:tcp]
|
253
|
-
wait_for_tcp do |success|
|
254
|
-
complete!(success)
|
255
|
-
end
|
256
|
-
end
|
257
|
-
|
258
|
-
if @opts[:file]
|
259
|
-
wait_for_file(@opts[:file], @opts[:timeout]) do |success|
|
260
|
-
complete!(success)
|
261
|
-
end
|
262
|
-
end
|