aggkit 0.2.6.8250 → 0.2.6.8329
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/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
|