app-rb 0.1.0 → 0.2.0
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/CHANGELOG.md +9 -0
- data/README.md +3 -0
- data/exe/app-rb +3 -311
- data/lib/app-rb/cli.rb +48 -0
- data/lib/app-rb/command.rb +232 -0
- data/lib/app-rb/config.rb +38 -0
- data/lib/app-rb/util/build.rb +46 -0
- data/lib/app-rb/util/consul.rb +67 -0
- data/lib/app-rb/util/docker.rb +47 -0
- data/lib/app-rb/util/registry.rb +13 -0
- data/lib/app-rb/util.rb +48 -0
- data/lib/app-rb/version.rb +1 -1
- data/lib/app-rb.rb +10 -1
- metadata +9 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b45f2f46e1dcd69be448e51337faaded1bd09579
|
4
|
+
data.tar.gz: 29460c4f73febf5601a536e76ffe0af0b9ed1c4b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28186b82f29304966a4faf69fbd533fe5272142b2ca14f45d188b730cab30b340a2fa3debd4a77f46b6b2c4807a52a0315d5975f5c9fba434a7e066850c5bc0c
|
7
|
+
data.tar.gz: 7c56418bf676f3f7dd4ebe60c3c919e515d3b6cf7bfad7d610527e67afeed04909937d52bde1eee24acebda48adc75545e9231da583e7be0fd839f9d595e422c
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,12 @@
|
|
1
|
+
## App-rb 0.2.0 (March 25, 2017) ##
|
2
|
+
|
3
|
+
* Add `opts` stanza for `pre_deploy` and `deploy` sections.
|
4
|
+
* Remember work nodes.
|
5
|
+
* Add `restart` and `clean` cli commands.
|
6
|
+
* Add backgound jobs.
|
7
|
+
* Add `version` config param.
|
8
|
+
* Extract all code to classes.
|
9
|
+
|
1
10
|
## App-rb 0.1.0 (March 25, 2017) ##
|
2
11
|
|
3
12
|
* First public version.
|
data/README.md
CHANGED
data/exe/app-rb
CHANGED
@@ -1,313 +1,5 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
|
-
require
|
3
|
-
require
|
4
|
-
require 'pp'
|
5
|
-
require 'open3'
|
6
|
-
Thread.abort_on_exception = true
|
7
|
-
|
8
|
-
def yellow(txt); "\e[0;33m#{txt}\e[0m"; end
|
9
|
-
def red(txt); "\e[0;31m#{txt}\e[0m"; end
|
10
|
-
def green(txt); "\e[0;32m#{txt}\e[0m"; end
|
11
|
-
def blue(txt); "\e[0;34m#{txt}\e[0m"; end
|
12
|
-
|
13
|
-
def do_it(cmd)
|
14
|
-
puts "[exec] #{cmd}"
|
15
|
-
system(cmd)
|
16
|
-
unless $?.success?
|
17
|
-
puts red("FATAL :(")
|
18
|
-
exit
|
19
|
-
end
|
20
|
-
end
|
21
|
-
|
22
|
-
def just_cmd(cmd, skip_exit_status = false)
|
23
|
-
puts "[exec] #{cmd}"
|
24
|
-
output = `#{cmd}`
|
25
|
-
if $?.success? || skip_exit_status
|
26
|
-
output.strip
|
27
|
-
else
|
28
|
-
puts red(output)
|
29
|
-
puts red("FATAL :(")
|
30
|
-
exit
|
31
|
-
end
|
32
|
-
end
|
33
|
-
|
34
|
-
if ARGV.count < 2
|
35
|
-
puts "Just deploy your apps with docker and consul. Nothing else."
|
36
|
-
puts ""
|
37
|
-
puts " #{$0} <yml> <command>"
|
38
|
-
puts ""
|
39
|
-
puts " deploy [hash] - deploy new version of app"
|
40
|
-
puts " status - status of app"
|
41
|
-
puts " stop - stop app"
|
42
|
-
exit
|
43
|
-
end
|
44
|
-
|
45
|
-
CONFIG = YAML.load(File.read(ARGV[0]))
|
46
|
-
COMMAND = ARGV[1]
|
47
|
-
|
48
|
-
MIN_PORT = 10_000
|
49
|
-
MAX_PORT = 50_000
|
50
|
-
Node = Struct.new(:name, :ip, :roles)
|
51
|
-
NODES = JSON.load(just_cmd("curl -s #{CONFIG["consul"]}/v1/catalog/nodes")).sort_by { |n|
|
52
|
-
n["Node"]
|
53
|
-
}.map { |n|
|
54
|
-
Node.new(n["Node"], n["Address"], (n["Meta"] || {})["roles"].to_s.split(",").reject{ |s| s.to_s.empty? })
|
55
|
-
}
|
56
|
-
|
57
|
-
def nodes(constraint)
|
58
|
-
constraint ||= {}
|
59
|
-
out = NODES
|
60
|
-
if constraint["role"]
|
61
|
-
out = out.select { |n| n.roles.index(constraint["role"]) }
|
62
|
-
end
|
63
|
-
if constraint["name"]
|
64
|
-
out = out.select { |n| n.name == constraint["name"] }
|
65
|
-
end
|
66
|
-
out
|
67
|
-
end
|
68
|
-
|
69
|
-
def node(constraint)
|
70
|
-
nodes(constraint).sample
|
71
|
-
end
|
72
|
-
|
73
|
-
|
74
|
-
def deploy(target = nil)
|
75
|
-
build_node = node(CONFIG["image"]["constraint"])
|
76
|
-
puts "build_node=#{build_node.to_h.inspect}"
|
77
|
-
|
78
|
-
puts blue("+++ CLONE or UPDATE repository")
|
79
|
-
do_it "ssh #{CONFIG["user"]}@#{build_node.ip} bash <<EOF
|
80
|
-
set -e
|
81
|
-
tmpfile=\\$(mktemp /tmp/git-ssh-#{CONFIG["app"]}.XXXXXX)
|
82
|
-
echo '#!/bin/sh' > \\$tmpfile
|
83
|
-
echo 'exec /usr/bin/ssh -o StrictHostKeyChecking=no -i #{CONFIG["image"]["key"]} \"\\$@\"' >> \\$tmpfile
|
84
|
-
chmod +x \\$tmpfile
|
85
|
-
export GIT_SSH=\\$tmpfile
|
86
|
-
if [ -d #{CONFIG["app"]}-cache ]; then
|
87
|
-
echo update cache...
|
88
|
-
cd #{CONFIG["app"]}-cache
|
89
|
-
git checkout . && git clean -dfx && git checkout master && git pull
|
90
|
-
git branch | grep -v master | xargs -r git branch -D
|
91
|
-
else
|
92
|
-
echo clone...
|
93
|
-
git clone git@github.com:#{CONFIG["image"]["repo"]} #{CONFIG["app"]}-cache && cd #{CONFIG["app"]}-cache
|
94
|
-
fi
|
95
|
-
git checkout #{target || CONFIG["image"]["target"]}
|
96
|
-
rm \\$tmpfile\nEOF"
|
97
|
-
|
98
|
-
puts blue("+++ calculate HASH and VERSION")
|
99
|
-
hash = ARGV[2] || `ssh #{CONFIG["user"]}@#{build_node.ip} 'cd #{CONFIG["app"]}-cache && git rev-parse HEAD'`.strip
|
100
|
-
puts "hash: #{hash}"
|
101
|
-
|
102
|
-
o = JSON.load(`curl -s https://#{CONFIG["registry"]}/v2/#{CONFIG["app"]}/tags/list`)
|
103
|
-
tags = o.is_a?(Hash) && o["errors"] ? [] : o["tags"]
|
104
|
-
puts "tags: #{JSON.dump(tags)}"
|
105
|
-
|
106
|
-
unless tags.index(hash)
|
107
|
-
puts blue("+++ BUILD image")
|
108
|
-
do_it "ssh #{CONFIG["user"]}@#{build_node.ip} bash <<EOF
|
109
|
-
set -e
|
110
|
-
cd #{CONFIG["app"]}-cache
|
111
|
-
#{(CONFIG["image"]["pre_build"] || []).join("\n")}
|
112
|
-
docker build -t #{CONFIG["registry"]}/#{CONFIG["app"]}:#{hash} .
|
113
|
-
docker push #{CONFIG["registry"]}/#{CONFIG["app"]}:#{hash}
|
114
|
-
echo Done.\nEOF"
|
115
|
-
end
|
116
|
-
vv = Time.now.to_i
|
117
|
-
new_service = "#{CONFIG["app"]}-#{vv}"
|
118
|
-
|
119
|
-
(CONFIG["deploy"]["pre"] || []).each_with_index do |section, index|
|
120
|
-
puts blue("+++ PRE: #{section.inspect}")
|
121
|
-
n = node(section["constraint"] || CONFIG["deploy"]["constraint"])
|
122
|
-
puts "node=#{n.inspect}"
|
123
|
-
do_it "ssh #{CONFIG["user"]}@#{n.ip} docker run " +
|
124
|
-
"--label app=#{CONFIG["app"]} " +
|
125
|
-
"--label service=#{new_service} " +
|
126
|
-
"--name #{CONFIG["app"]}-pre-#{vv}-#{index} " +
|
127
|
-
"#{(CONFIG["env"] || {}).map { |k, v| "-e #{k}='#{v}'" }.join(" ")} " +
|
128
|
-
"#{CONFIG["registry"]}/#{CONFIG["app"]}:#{hash} #{section["cmd"]}"
|
129
|
-
do_it "ssh #{CONFIG["user"]}@#{n.ip} docker rm #{CONFIG["app"]}-pre-#{vv}-#{index}"
|
130
|
-
end
|
131
|
-
|
132
|
-
deploy_nodes = nodes(CONFIG["deploy"]["constraint"])
|
133
|
-
|
134
|
-
puts blue("+++ PULL")
|
135
|
-
pull_threads = []
|
136
|
-
deploy_nodes.each do |node|
|
137
|
-
pull_threads << Thread.new do
|
138
|
-
host = "#{CONFIG["user"]}@#{node.ip}"
|
139
|
-
Open3.popen2e("ssh #{host} docker pull #{CONFIG["registry"]}/#{CONFIG["app"]}:#{hash}") { |i,o,w|
|
140
|
-
while line = o.gets do
|
141
|
-
puts "[#{node.name}] " + line
|
142
|
-
end
|
143
|
-
raise "FATAL" unless w.value.success?
|
144
|
-
}
|
145
|
-
end
|
146
|
-
end
|
147
|
-
pull_threads.each(&:join)
|
148
|
-
|
149
|
-
plan = {}
|
150
|
-
if CONFIG["deploy"]["per"]
|
151
|
-
amount = CONFIG["deploy"]["per"]*deploy_nodes.count
|
152
|
-
else
|
153
|
-
amount = CONFIG["deploy"]["amount"]
|
154
|
-
end
|
155
|
-
|
156
|
-
amount.times do |index|
|
157
|
-
ip = deploy_nodes[index % deploy_nodes.length].ip
|
158
|
-
plan[ip] ||= []
|
159
|
-
plan[ip].push(index)
|
160
|
-
end
|
161
|
-
|
162
|
-
puts blue("+++ DEPLOY")
|
163
|
-
deploy_threads = []
|
164
|
-
deploy_nodes.each do |node|
|
165
|
-
deploy_threads << Thread.new do
|
166
|
-
host = "#{CONFIG["user"]}@#{node.ip}"
|
167
|
-
|
168
|
-
puts "[#{node.name}] run"
|
169
|
-
(plan[node.ip] || []).each do |index|
|
170
|
-
port = nil
|
171
|
-
10.times do
|
172
|
-
a = MIN_PORT + rand(MAX_PORT - MIN_PORT)
|
173
|
-
if just_cmd("ssh #{host} ss -ln src :#{a} | fgrep -c ':#{a}'", true) == "0"
|
174
|
-
port = a
|
175
|
-
break
|
176
|
-
end
|
177
|
-
end
|
178
|
-
raise "Dont find free port :-((" unless port
|
179
|
-
puts "[#{node.name}] port=#{port}"
|
180
|
-
|
181
|
-
do_it("ssh #{host} docker run -d " +
|
182
|
-
"--label app=#{CONFIG["app"]} " +
|
183
|
-
"--label service=#{new_service} " +
|
184
|
-
"--name=#{CONFIG["app"]}-#{vv}-#{index} " +
|
185
|
-
(CONFIG["env"] || {}).map { |k, v| "-e #{k}='#{v}'" }.join(" ") + " " +
|
186
|
-
"--restart unless-stopped " +
|
187
|
-
"-p #{node.ip}:#{port}:#{CONFIG["deploy"]["port"]} " +
|
188
|
-
"#{CONFIG["registry"]}/#{CONFIG["app"]}:#{hash} " +
|
189
|
-
"#{CONFIG["deploy"]["cmd"]}")
|
190
|
-
|
191
|
-
do_it %(curl -s -X PUT #{node.ip}:8500/v1/agent/service/register -d'{
|
192
|
-
"Id": "#{CONFIG["app"]}-#{vv}-#{index}",
|
193
|
-
"Name": "#{new_service}",
|
194
|
-
"Port": #{port},
|
195
|
-
"Check": {
|
196
|
-
"DeregisterCriticalServiceAfter": "20m",
|
197
|
-
"Interval": "7s",
|
198
|
-
"HTTP": "http://#{node.ip}:#{port}#{CONFIG["deploy"]["check_url"] || "/"}"
|
199
|
-
}
|
200
|
-
}')
|
201
|
-
end
|
202
|
-
|
203
|
-
puts blue("+++ CONSUL wait for #{node.name}")
|
204
|
-
loop do
|
205
|
-
statuses = JSON.load(just_cmd("curl -s #{node.ip}:8500/v1/health/service/#{CONFIG["app"]}-#{vv}")).select { |s|
|
206
|
-
s["Node"]["Address"] == node.ip
|
207
|
-
}.flat_map { |s| s["Checks"] }.map { |c| c["Status"] }
|
208
|
-
puts "#{node.name} => #{statuses.inspect}"
|
209
|
-
break if statuses.uniq == ["passing"] or (plan[node.ip] || []).empty?
|
210
|
-
sleep 3
|
211
|
-
end
|
212
|
-
end
|
213
|
-
end
|
214
|
-
deploy_threads.each(&:join)
|
215
|
-
|
216
|
-
current_service = just_cmd("curl #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}/service?raw")
|
217
|
-
current_hash = just_cmd("curl #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}/hash?raw")
|
218
|
-
puts "CURRENT_SERVICE=#{current_service}"
|
219
|
-
puts "CURRENT_HASH=#{current_hash}"
|
220
|
-
puts "NEW_SERVICE=#{new_service}"
|
221
|
-
do_it "curl -X PUT #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}/service -d#{new_service}"
|
222
|
-
do_it "curl -X PUT #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}/hash -d#{hash}"
|
223
|
-
puts "\n\n" + green(">>>>>>>>>>>>>>> BLUE/GREEN switch <<<<<<<<<<<<<<<")
|
224
|
-
sleep 3
|
225
|
-
|
226
|
-
puts blue("+++ STOP OLD containers")
|
227
|
-
JSON.load(just_cmd("curl -s #{CONFIG["consul"]}/v1/catalog/services")).keys.select { |service|
|
228
|
-
service != new_service && service =~ /^#{CONFIG["app"]}-\d+$/
|
229
|
-
}.each do |service|
|
230
|
-
JSON.load(just_cmd("curl -s #{CONFIG["consul"]}/v1/health/service/#{service}")).each do |s|
|
231
|
-
do_it %(curl -s -X DELETE #{s["Node"]["Address"]}:8500/v1/agent/service/deregister/#{s["Service"]["ID"]})
|
232
|
-
end
|
233
|
-
end
|
234
|
-
|
235
|
-
NODES.each do |n|
|
236
|
-
keep_ids = just_cmd("ssh #{CONFIG["user"]}@#{n.ip} docker ps -q -f label=app=#{CONFIG["app"]} -f label=service=#{new_service}").split("\n")
|
237
|
-
all_ids = just_cmd("ssh #{CONFIG["user"]}@#{n.ip} docker ps -q -f label=app=#{CONFIG["app"]}").split("\n")
|
238
|
-
if (all_ids - keep_ids).length > 0
|
239
|
-
do_it("ssh #{CONFIG["user"]}@#{n.ip} docker stop #{(all_ids - keep_ids).join(" ")}")
|
240
|
-
do_it("ssh #{CONFIG["user"]}@#{n.ip} docker rm #{(all_ids - keep_ids).join(" ")}")
|
241
|
-
end
|
242
|
-
end
|
243
|
-
|
244
|
-
puts blue("+++ CLEAN REGISTRY")
|
245
|
-
(tags - [hash, current_hash]).each do |hash|
|
246
|
-
digest = just_cmd("curl -s --head -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' https://#{CONFIG["registry"]}/v2/#{CONFIG["app"]}/manifests/#{hash} | grep Docker-Content-Digest | cut -d' ' -f2")
|
247
|
-
puts "digest = #{digest}"
|
248
|
-
system "curl -X DELETE https://#{CONFIG["registry"]}/v2/#{CONFIG["app"]}/manifests/#{digest}"
|
249
|
-
end
|
250
|
-
|
251
|
-
puts green("Done.")
|
252
|
-
if current_hash != "" && !target
|
253
|
-
puts "to rollback execute: #{$0} #{ARGV[0]} deploy #{current_hash}"
|
254
|
-
end
|
255
|
-
end
|
256
|
-
|
257
|
-
|
258
|
-
def print_status
|
259
|
-
max_name_len = NODES.map { |n| n.name.length }.max
|
260
|
-
max_ip_len = NODES.map { |n| n.ip.length }.max
|
261
|
-
max_roles_len = NODES.map { |n| n.roles.inspect.length }.max
|
262
|
-
current_service = just_cmd("curl -s #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}/service?raw")
|
263
|
-
current_hash = just_cmd("curl -s #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}/hash?raw")
|
264
|
-
current_dockers = NODES.map { |n|
|
265
|
-
just_cmd("ssh #{CONFIG["user"]}@#{n.ip} 'docker ps -q -f label=app=#{CONFIG["app"]} -f label=service=#{current_service} | wc -l'").to_i
|
266
|
-
}
|
267
|
-
dockers = NODES.map { |n|
|
268
|
-
just_cmd("ssh #{CONFIG["user"]}@#{n.ip} 'docker ps -q -f label=app=#{CONFIG["app"]} | wc -l'").to_i
|
269
|
-
}
|
270
|
-
puts ""
|
271
|
-
puts green("App: ") + CONFIG["app"]
|
272
|
-
puts green("Service: ") + current_service
|
273
|
-
puts green("Hash: ") + current_hash
|
274
|
-
NODES.each_with_index do |n, i|
|
275
|
-
puts(
|
276
|
-
" "*5 + n.name.rjust(max_name_len) +
|
277
|
-
" "*2 + n.ip.ljust(max_ip_len) +
|
278
|
-
" "*2 + n.roles.inspect.ljust(max_roles_len) +
|
279
|
-
" "*2 + green(current_dockers[i]) + " / " + (dockers[i] - current_dockers[i] == 0 ? "0" : red(dockers[i] - current_dockers[i]))
|
280
|
-
)
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
|
285
|
-
def do_stop
|
286
|
-
current_service = just_cmd("curl -s #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}/service?raw")
|
287
|
-
if current_service != ""
|
288
|
-
JSON.load(just_cmd("curl -s #{CONFIG["consul"]}/v1/health/service/#{current_service}")).each do |s|
|
289
|
-
do_it %(curl -s -X DELETE #{s["Node"]["Address"]}:8500/v1/agent/service/deregister/#{s["Service"]["ID"]})
|
290
|
-
end
|
291
|
-
end
|
292
|
-
NODES.map { |n|
|
293
|
-
ids = just_cmd("ssh #{CONFIG["user"]}@#{n.ip} docker ps -q -f label=app=#{CONFIG["app"]}").gsub("\n", " ")
|
294
|
-
if ids != ""
|
295
|
-
do_it "ssh #{CONFIG["user"]}@#{n.ip} docker stop #{ids}"
|
296
|
-
do_it "ssh #{CONFIG["user"]}@#{n.ip} docker rm #{ids}"
|
297
|
-
end
|
298
|
-
}
|
299
|
-
do_it "curl -X DELETE #{CONFIG["consul"]}/v1/kv/apps/#{CONFIG["app"]}?recurse"
|
300
|
-
puts ""
|
301
|
-
end
|
302
|
-
|
303
|
-
|
304
|
-
if COMMAND == "deploy" || COMMAND == "d"
|
305
|
-
deploy(ARGV[2])
|
306
|
-
elsif COMMAND == "status" || COMMAND == "s"
|
307
|
-
print_status
|
308
|
-
elsif COMMAND == "stop"
|
309
|
-
do_stop
|
310
|
-
else
|
311
|
-
puts "FATAL: unknown command '#{COMMAND}'"
|
312
|
-
end
|
2
|
+
require "bundler/setup"
|
3
|
+
require "app-rb"
|
313
4
|
|
5
|
+
AppRb::Cli.new(ARGV).run
|
data/lib/app-rb/cli.rb
CHANGED
@@ -1,4 +1,52 @@
|
|
1
|
+
require 'yaml'
|
2
|
+
|
1
3
|
module AppRb
|
2
4
|
class Cli
|
5
|
+
def initialize(args)
|
6
|
+
@args = args
|
7
|
+
Thread.abort_on_exception = true
|
8
|
+
end
|
9
|
+
|
10
|
+
def run
|
11
|
+
if @args.count < 2
|
12
|
+
usage
|
13
|
+
exit
|
14
|
+
end
|
15
|
+
config = Config.new(YAML.load(File.read(@args[0])))
|
16
|
+
command = @args[1]
|
17
|
+
|
18
|
+
if AppRb::Util.compare_versions(config.tool_version, AppRb::VERSION) > 0
|
19
|
+
puts "FATAL: need at least '#{config.tool_version}' tool version but current version is '#{AppRb::VERSION}'"
|
20
|
+
exit -1
|
21
|
+
end
|
22
|
+
|
23
|
+
if command == "deploy" || command == "d"
|
24
|
+
Command.new(config).deploy(@args[2])
|
25
|
+
elsif command == "status" || command == "s"
|
26
|
+
Command.new(config).status
|
27
|
+
elsif command == "restart"
|
28
|
+
Command.new(config).restart
|
29
|
+
elsif command == "clean"
|
30
|
+
Command.new(config).clean
|
31
|
+
elsif command == "stop"
|
32
|
+
Command.new(config).stop
|
33
|
+
else
|
34
|
+
puts "FATAL: unknown command '#{command}'"
|
35
|
+
exit -1
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def usage
|
42
|
+
puts "Just deploy your apps with docker and consul. Nothing else."
|
43
|
+
puts "Version: #{AppRb::VERSION}"
|
44
|
+
puts ""
|
45
|
+
puts " app-rb <yml> <command>"
|
46
|
+
puts ""
|
47
|
+
puts " deploy [hash] - deploy new version of app"
|
48
|
+
puts " status - status of app"
|
49
|
+
puts " stop - stop app"
|
50
|
+
end
|
3
51
|
end
|
4
52
|
end
|
@@ -0,0 +1,232 @@
|
|
1
|
+
module AppRb
|
2
|
+
class Command
|
3
|
+
def initialize(config)
|
4
|
+
@config = config
|
5
|
+
end
|
6
|
+
|
7
|
+
def deploy(target)
|
8
|
+
@base = "#{@config.app}-#{Time.now.to_i}"
|
9
|
+
|
10
|
+
# init
|
11
|
+
current_hash = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "hash")
|
12
|
+
build_nodes = @config.nodes(@config.image["constraint"])
|
13
|
+
pre_payloads = @config.pre_deploy.map do |pre|
|
14
|
+
{
|
15
|
+
"nodes" => @config.nodes(pre["constraint"]),
|
16
|
+
"cmd" => pre["cmd"],
|
17
|
+
"opts" => pre["opts"] || [],
|
18
|
+
}
|
19
|
+
end
|
20
|
+
deploy_payloads = @config.deploy.map { |key, section|
|
21
|
+
nodes = @config.nodes(section["constraint"])
|
22
|
+
{
|
23
|
+
"key" => key,
|
24
|
+
"nodes" => nodes,
|
25
|
+
"amount" => (section["per"] ? section["per"]*nodes.count : section["amount"]),
|
26
|
+
"cmd" => section["cmd"],
|
27
|
+
"port" => section["port"],
|
28
|
+
"check_url" => section["check_url"],
|
29
|
+
"opts" => section["opts"] || [],
|
30
|
+
}
|
31
|
+
}
|
32
|
+
old_ips = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "nodes").split(",")
|
33
|
+
new_ips = (
|
34
|
+
build_nodes.map(&:ip) +
|
35
|
+
pre_payloads.flat_map { |p| p["nodes"].map(&:ip) } +
|
36
|
+
deploy_payloads.flat_map { |p| p["nodes"].map(&:ip) }
|
37
|
+
).uniq
|
38
|
+
ips = (old_ips + new_ips).uniq
|
39
|
+
AppRb::Util::Consul.kv_set(@config.consul, @config.app, "nodes", ips.join(","))
|
40
|
+
|
41
|
+
# pre
|
42
|
+
new_hash = prepare_image(build_nodes, target)
|
43
|
+
pre_deploy(pre_payloads, new_hash)
|
44
|
+
stop_bg_jobs(ips)
|
45
|
+
|
46
|
+
# deploy
|
47
|
+
do_deploy(deploy_payloads, new_hash)
|
48
|
+
|
49
|
+
# switch
|
50
|
+
blue_green(deploy_payloads, new_hash)
|
51
|
+
|
52
|
+
# clean
|
53
|
+
stop_services(ips)
|
54
|
+
clean_registry(current_hash, [current_hash, new_hash].uniq)
|
55
|
+
|
56
|
+
# finish
|
57
|
+
AppRb::Util::Consul.kv_set(@config.consul, @config.app, "nodes", new_ips.join(","))
|
58
|
+
puts AppRb::Util.green("Done.")
|
59
|
+
if current_hash != "" && !target && current_hash != target
|
60
|
+
puts "to rollback fire: app-rb #{ARGV[0]} deploy #{current_hash}"
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def status
|
65
|
+
ips = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "nodes").split(",")
|
66
|
+
nodes = @config.nodes.select { |n| ips.index(n.ip) }
|
67
|
+
max_name_len = nodes.map { |n| n.name.length }.max
|
68
|
+
max_ip_len = nodes.map { |n| n.ip.length }.max
|
69
|
+
max_roles_len = nodes.map { |n| n.roles.inspect.length }.max
|
70
|
+
current_base = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "base")
|
71
|
+
current_hash = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "hash")
|
72
|
+
current_dockers = nodes.map { |n|
|
73
|
+
AppRb::Util::Docker.ids(@config.user, n.ip, {app: @config.app, build: current_base}).count
|
74
|
+
}
|
75
|
+
dockers = nodes.map { |n|
|
76
|
+
AppRb::Util::Docker.ids(@config.user, n.ip, {app: @config.app}).count
|
77
|
+
}
|
78
|
+
puts ""
|
79
|
+
puts AppRb::Util.green("App: ") + @config.app
|
80
|
+
puts AppRb::Util.green("Base: ") + current_base
|
81
|
+
puts AppRb::Util.green("Hash: ") + current_hash
|
82
|
+
nodes.each_with_index do |n, i|
|
83
|
+
puts(
|
84
|
+
" "*5 + n.name.rjust(max_name_len) +
|
85
|
+
" "*2 + n.ip.ljust(max_ip_len) +
|
86
|
+
" "*2 + n.roles.inspect.ljust(max_roles_len) +
|
87
|
+
" "*2 + AppRb::Util.green(current_dockers[i]) + " / " + (dockers[i] - current_dockers[i] == 0 ? "0" : AppRb::Util.red(dockers[i] - current_dockers[i]))
|
88
|
+
)
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def stop
|
93
|
+
ips = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "nodes").split(",")
|
94
|
+
stop_all(ips)
|
95
|
+
end
|
96
|
+
|
97
|
+
def clean
|
98
|
+
base = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "base")
|
99
|
+
ips = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "nodes").split(",")
|
100
|
+
stop_services(ips, base)
|
101
|
+
end
|
102
|
+
|
103
|
+
def restart
|
104
|
+
hash = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "hash")
|
105
|
+
raise "FATAL: app is not started?" if hash == ""
|
106
|
+
puts "hash=#{hash}"
|
107
|
+
deploy(hash)
|
108
|
+
end
|
109
|
+
|
110
|
+
private
|
111
|
+
|
112
|
+
def full_image_name(hash)
|
113
|
+
"#{@config.registry}/#{@config.app}:#{hash}"
|
114
|
+
end
|
115
|
+
|
116
|
+
def prepare_image(build_nodes, target)
|
117
|
+
puts AppRb::Util.blue("+++ CLONE or UPDATE repository")
|
118
|
+
AppRb::Util::Build.build(
|
119
|
+
@config.user, build_nodes.sample.ip,
|
120
|
+
@config.image["repo"], @config.image["key"], target || @config.image["target"],
|
121
|
+
@config.registry, @config.app, @config.image["pre_build"] || []
|
122
|
+
)
|
123
|
+
end
|
124
|
+
|
125
|
+
def pre_deploy(pre_payloads, hash)
|
126
|
+
pre_payloads.each_with_index do |payload, index|
|
127
|
+
puts AppRb::Util.blue("+++ PRE: #{payload["cmd"].inspect}")
|
128
|
+
AppRb::Util::Docker.run_batch(
|
129
|
+
@config.user, payload["nodes"].sample.ip,
|
130
|
+
"#{@base}-pre-#{index}", full_image_name(hash), payload["cmd"],
|
131
|
+
{app: @config.app, build: @base},
|
132
|
+
@config.env,
|
133
|
+
payload["opts"]
|
134
|
+
)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
def do_deploy(deploy_payloads, hash)
|
139
|
+
puts AppRb::Util.blue("+++ PULL")
|
140
|
+
deploy_payloads.flat_map { |p| p["nodes"] }.uniq.map { |node|
|
141
|
+
Thread.new do
|
142
|
+
AppRb::Util::Docker.pull(@config.user, node.ip, full_image_name(hash))
|
143
|
+
end
|
144
|
+
}.each(&:join)
|
145
|
+
|
146
|
+
deploy_payloads.each do |payload|
|
147
|
+
puts AppRb::Util.blue("+++ DEPLOY '#{payload["key"]}'")
|
148
|
+
|
149
|
+
# naive scheduling
|
150
|
+
plan = payload["nodes"].map{ |n| [n.ip, []] }.to_h
|
151
|
+
payload["amount"].times do |index|
|
152
|
+
ip = payload["nodes"][index % payload["nodes"].length].ip
|
153
|
+
plan[ip].push("#{payload["key"]}-#{index}")
|
154
|
+
end
|
155
|
+
|
156
|
+
payload["nodes"].map { |node|
|
157
|
+
Thread.new do
|
158
|
+
(plan[node.ip] || []).each do |name|
|
159
|
+
port = AppRb::Util.get_free_port(@config.user, node.ip)
|
160
|
+
puts "[#{node.name}] port=#{port}"
|
161
|
+
|
162
|
+
AppRb::Util::Docker.run_daemon(
|
163
|
+
@config.user, node.ip,
|
164
|
+
"#{@base}-#{name}", full_image_name(hash), payload["cmd"],
|
165
|
+
{app: @config.app, build: @base, has_port: (payload["port"] ? "yes" : "no")},
|
166
|
+
@config.env,
|
167
|
+
payload["opts"],
|
168
|
+
(payload["port"] ? {"#{node.ip}:#{port}" => payload["port"]} : {})
|
169
|
+
)
|
170
|
+
|
171
|
+
if payload["port"]
|
172
|
+
AppRb::Util::Consul.register_service(
|
173
|
+
node.ip,
|
174
|
+
"#{@base}-#{name}", "#{@base}-#{payload["key"]}", port, payload["check_url"] || "/",
|
175
|
+
[@config.app, @base]
|
176
|
+
)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
}.each(&:join)
|
181
|
+
|
182
|
+
if payload["port"]
|
183
|
+
puts AppRb::Util.blue("+++ CONSUL wait '#{payload["key"]}'")
|
184
|
+
AppRb::Util::Consul.consul_wait(@config.consul, "#{@base}-#{payload["key"]}")
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
188
|
+
|
189
|
+
def blue_green(deploy_payloads, new_hash)
|
190
|
+
service_payloads = deploy_payloads.select { |p| p["port"] }
|
191
|
+
AppRb::Util::Consul.kv_set(@config.consul, @config.app, "hash", new_hash)
|
192
|
+
AppRb::Util::Consul.kv_set(@config.consul, @config.app, "base", @base)
|
193
|
+
service_payloads.each do |payload|
|
194
|
+
AppRb::Util::Consul.kv_set(@config.consul, @config.app, "services/#{payload["key"]}", "#{@base}-#{payload["key"]}")
|
195
|
+
end
|
196
|
+
puts "\n" + AppRb::Util.green(">>>>>>>>>>>>>>> BLUE/GREEN switch <<<<<<<<<<<<<<<") + "\n\n"
|
197
|
+
sleep 3
|
198
|
+
(AppRb::Util::Consul.kv_keys(@config.consul, @config.app + "/services") - service_payloads.map { |p| p["key"] }).each do |remove|
|
199
|
+
AppRb::Util::Consul.kv_unset(@config.consul, @config.app + "/services/#{remove}")
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def stop_bg_jobs(ips)
|
204
|
+
puts AppRb::Util.blue("+++ STOP OLD backgroud jobs")
|
205
|
+
ips.each do |ip|
|
206
|
+
AppRb::Util::Docker.stop(@config.user, ip, {app: @config.app, has_port: "no"})
|
207
|
+
end
|
208
|
+
end
|
209
|
+
|
210
|
+
def stop_services(ips, base = @base)
|
211
|
+
puts AppRb::Util.blue("+++ STOP services")
|
212
|
+
AppRb::Util::Consul.remove_services(@config.consul, [@config.app], base)
|
213
|
+
ips.each do |ip|
|
214
|
+
AppRb::Util::Docker.stop(@config.user, ip, {app: @config.app, has_port: "yes"}, {build: base})
|
215
|
+
end
|
216
|
+
end
|
217
|
+
|
218
|
+
def stop_all(ips)
|
219
|
+
puts AppRb::Util.blue("+++ STOP")
|
220
|
+
AppRb::Util::Consul.remove_services(@config.consul, [@config.app])
|
221
|
+
ips.each do |ip|
|
222
|
+
AppRb::Util::Docker.stop(@config.user, ip, {app: @config.app})
|
223
|
+
end
|
224
|
+
AppRb::Util::Consul.kv_unset(@config.consul, @config.app)
|
225
|
+
end
|
226
|
+
|
227
|
+
def clean_registry(current_hash, keep_hashes = [])
|
228
|
+
puts AppRb::Util.blue("+++ CLEAN REGISTRY")
|
229
|
+
AppRb::Util::Registry.clean(@config.registry, @config.app, keep_hashes)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class AppRb::Config
|
2
|
+
def initialize(yml)
|
3
|
+
@body = yml
|
4
|
+
end
|
5
|
+
|
6
|
+
def tool_version; @body["tool_version"]; end
|
7
|
+
def app; @body["app"]; end
|
8
|
+
def consul; @body["consul"]; end
|
9
|
+
def registry; @body["registry"]; end
|
10
|
+
def user; @body["user"]; end
|
11
|
+
def image; @body["image"]; end
|
12
|
+
def env; @body["env"] || {}; end
|
13
|
+
def pre_deploy; @body["pre_deploy"] || []; end
|
14
|
+
def deploy; @body["deploy"] || {}; end
|
15
|
+
|
16
|
+
def nodes(constraint = nil)
|
17
|
+
constraint ||= {}
|
18
|
+
out = __nodes
|
19
|
+
if constraint["role"]
|
20
|
+
out = out.select { |n| n.roles.index(constraint["role"]) }
|
21
|
+
end
|
22
|
+
if constraint["name"]
|
23
|
+
out = out.select { |n| n.name == constraint["name"] }
|
24
|
+
end
|
25
|
+
out
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
Node = Struct.new(:name, :ip, :roles)
|
31
|
+
def __nodes
|
32
|
+
@__nodes ||= JSON.load(AppRb::Util.just_cmd("curl -s #{@body["consul"]}/v1/catalog/nodes")).sort_by { |n|
|
33
|
+
n["Node"]
|
34
|
+
}.map { |n|
|
35
|
+
Node.new(n["Node"], n["Address"], (n["Meta"] || {})["roles"].to_s.split(",").reject{ |s| s.to_s.empty? })
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module AppRb::Util::Build
|
2
|
+
def self.build(user, host, repo, ssh_key, target, registry, image_name, pre_build_cmds = [])
|
3
|
+
AppRb::Util.do_it "ssh #{user}@#{host} bash <<EOF
|
4
|
+
set -e
|
5
|
+
tmpfile=\\$(mktemp /tmp/git-ssh-#{repo_cache(repo)}.XXXXXX)
|
6
|
+
echo '#!/bin/sh' > \\$tmpfile
|
7
|
+
echo 'exec /usr/bin/ssh -o StrictHostKeyChecking=no -i #{ssh_key} \"\\$@\"' >> \\$tmpfile
|
8
|
+
chmod +x \\$tmpfile
|
9
|
+
export GIT_SSH=\\$tmpfile
|
10
|
+
if [ -d #{repo_cache(repo)} ]; then
|
11
|
+
echo update cache...
|
12
|
+
cd #{repo_cache(repo)}
|
13
|
+
git checkout . && git clean -dfx && git checkout master && git pull
|
14
|
+
git branch | grep -v master | xargs -r git branch -D
|
15
|
+
else
|
16
|
+
echo clone...
|
17
|
+
git clone git@github.com:#{repo} #{repo_cache(repo)} && cd #{repo_cache(repo)}
|
18
|
+
fi
|
19
|
+
git checkout #{target}
|
20
|
+
rm \\$tmpfile\nEOF"
|
21
|
+
|
22
|
+
hash = AppRb::Util.just_cmd("ssh #{user}@#{host} 'cd #{repo_cache(repo)} && git rev-parse HEAD'")
|
23
|
+
tags = AppRb::Util::Registry.tags_list(registry, image_name)
|
24
|
+
puts "hash: #{hash}"
|
25
|
+
puts "tags: #{tags.inspect}"
|
26
|
+
|
27
|
+
unless tags.index(hash)
|
28
|
+
puts AppRb::Util.blue("+++ BUILD image")
|
29
|
+
AppRb::Util.do_it "ssh #{user}@#{host} bash <<EOF
|
30
|
+
set -e
|
31
|
+
cd #{repo_cache(repo)}
|
32
|
+
#{pre_build_cmds.join("\n")}
|
33
|
+
docker build -t #{registry}/#{image_name}:#{hash} .
|
34
|
+
docker push #{registry}/#{image_name}:#{hash}
|
35
|
+
echo Done.\nEOF"
|
36
|
+
end
|
37
|
+
|
38
|
+
return hash
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def self.repo_cache(repo)
|
44
|
+
repo.gsub("/", "-") + "-cache"
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module AppRb::Util::Consul
|
2
|
+
def self.register_service(host, id, name, port, url, tags = [])
|
3
|
+
AppRb::Util.do_it %(curl -s -X PUT #{host}:8500/v1/agent/service/register -d'{
|
4
|
+
"Id": "#{id}",
|
5
|
+
"Name": "#{name}",
|
6
|
+
"Port": #{port},
|
7
|
+
"Tags": #{JSON.dump(tags)},
|
8
|
+
"Check": {
|
9
|
+
"DeregisterCriticalServiceAfter": "20m",
|
10
|
+
"Interval": "7s",
|
11
|
+
"HTTP": "http://#{host}:#{port}#{url}"
|
12
|
+
}
|
13
|
+
}')
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.remove_services(consul, tags = [], except = nil)
|
17
|
+
JSON.load(AppRb::Util.just_cmd("curl -s #{consul}/v1/catalog/services")).select { |k, v|
|
18
|
+
(v & tags) == tags
|
19
|
+
}.keys.each do |service|
|
20
|
+
JSON.load(AppRb::Util.just_cmd("curl -s #{consul}/v1/catalog/service/#{service}")).each do |s|
|
21
|
+
if except && s["ServiceTags"].index(except)
|
22
|
+
# keep this service
|
23
|
+
else
|
24
|
+
AppRb::Util.do_it %(curl -s -X DELETE #{s["Address"]}:8500/v1/agent/service/deregister/#{s["ServiceID"]})
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def self.consul_wait(consul, service)
|
31
|
+
loop do
|
32
|
+
url = "curl -s #{consul}/v1/health/service/#{service}"
|
33
|
+
statuses = JSON.load(AppRb::Util.just_cmd(url)).flat_map { |s|
|
34
|
+
s["Checks"]
|
35
|
+
}.reject { |c|
|
36
|
+
c["CheckID"] == "serfHealth"
|
37
|
+
}.map { |c|
|
38
|
+
c["Status"]
|
39
|
+
}
|
40
|
+
puts "statuses: #{statuses.inspect}"
|
41
|
+
break if statuses.uniq == ["passing"] || statuses.uniq == []
|
42
|
+
sleep 3
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
CONSUL_DIR = "apps"
|
47
|
+
|
48
|
+
def self.kv_get(consul, relative_path, k)
|
49
|
+
AppRb::Util.just_cmd "curl -s #{consul}/v1/kv/#{CONSUL_DIR}/#{relative_path}/#{k}?raw"
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.kv_set(consul, relative_path, k, v)
|
53
|
+
AppRb::Util.do_it "curl -s -X PUT #{consul}/v1/kv/#{CONSUL_DIR}/#{relative_path}/#{k} -d#{v}"
|
54
|
+
puts ""
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.kv_unset(consul, relative_path)
|
58
|
+
AppRb::Util.do_it "curl -s -X DELETE #{consul}/v1/kv/#{CONSUL_DIR}/#{relative_path}?recurse"
|
59
|
+
puts ""
|
60
|
+
end
|
61
|
+
|
62
|
+
def self.kv_keys(consul, relative_path)
|
63
|
+
(JSON.load(AppRb::Util.just_cmd "curl -s #{consul}/v1/kv/#{CONSUL_DIR}/#{relative_path}?recurse") || []).map { |v|
|
64
|
+
v["Key"].sub("#{CONSUL_DIR}/#{relative_path}/", "")
|
65
|
+
}
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'open3'
|
2
|
+
|
3
|
+
module AppRb::Util::Docker
|
4
|
+
def self.pull(user, host, image)
|
5
|
+
Open3.popen2e("ssh #{user}@#{host} docker pull #{image}") { |i,o,w|
|
6
|
+
while line = o.gets do
|
7
|
+
puts "[#{host}] " + line
|
8
|
+
end
|
9
|
+
raise "FATAL" unless w.value.success?
|
10
|
+
}
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.run_batch(user, host, name, image, cmd, labels = {}, env = {}, opts = [])
|
14
|
+
AppRb::Util.do_it "ssh #{user}@#{host} docker run " +
|
15
|
+
labels.map { |k, v| "--label #{k}=#{v} " }.join +
|
16
|
+
"--name #{name} " +
|
17
|
+
opts.join(" ") + " " +
|
18
|
+
env.map { |k, v| "-e #{k}='#{v}' " }.join +
|
19
|
+
"#{image} #{cmd}"
|
20
|
+
AppRb::Util.do_it "ssh #{user}@#{host} docker rm #{name}"
|
21
|
+
end
|
22
|
+
|
23
|
+
def self.run_daemon(user, host, name, image, cmd, labels = {}, env = {}, opts = [], ports = {})
|
24
|
+
AppRb::Util.do_it "ssh #{user}@#{host} docker run -d " +
|
25
|
+
"--restart unless-stopped " +
|
26
|
+
labels.map { |k, v| "--label #{k}=#{v} " }.join +
|
27
|
+
"--name #{name} " +
|
28
|
+
opts.join(" ") + " " +
|
29
|
+
env.map { |k, v| "-e #{k}='#{v}' " }.join +
|
30
|
+
ports.map { |k, v| "-p #{k}:#{v} " }.join +
|
31
|
+
"#{image} #{cmd}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.ids(user, host, labels = {})
|
35
|
+
filters = labels.map { |k, v| "-f label=#{k}=#{v} " }.join("")
|
36
|
+
AppRb::Util.just_cmd("ssh #{user}@#{host} docker ps -q #{filters}").split("\n")
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.stop(user, host, labels = {}, except = nil)
|
40
|
+
keep_ids = except ? ids(user, host, labels.merge(except)) : []
|
41
|
+
all_ids = ids(user, host, labels)
|
42
|
+
if (all_ids - keep_ids).length > 0
|
43
|
+
AppRb::Util.do_it("ssh #{user}@#{host} docker stop #{(all_ids - keep_ids).join(" ")}")
|
44
|
+
AppRb::Util.do_it("ssh #{user}@#{host} docker rm #{(all_ids - keep_ids).join(" ")}")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module AppRb::Util::Registry
|
2
|
+
def self.tags_list(registry, image_name)
|
3
|
+
o = JSON.load(`curl -s https://#{registry}/v2/#{image_name}/tags/list`)
|
4
|
+
o.is_a?(Hash) && o["errors"] ? [] : o["tags"]
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.clean(registry, image_name, keep_tags = [])
|
8
|
+
(tags_list(registry, image_name) - keep_tags).each do |hash|
|
9
|
+
digest = AppRb::Util.just_cmd("curl -s --head -H 'Accept: application/vnd.docker.distribution.manifest.v2+json' https://#{registry}/v2/#{image_name}/manifests/#{hash} | grep Docker-Content-Digest | cut -d' ' -f2")
|
10
|
+
AppRb::Util.do_it "curl -s -X DELETE https://#{registry}/v2/#{image_name}/manifests/#{digest}"
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
data/lib/app-rb/util.rb
ADDED
@@ -0,0 +1,48 @@
|
|
1
|
+
module AppRb::Util
|
2
|
+
MIN_PORT = 10_000
|
3
|
+
MAX_PORT = 50_000
|
4
|
+
|
5
|
+
def self.yellow(txt); "\e[0;33m#{txt}\e[0m"; end
|
6
|
+
def self.red(txt); "\e[0;31m#{txt}\e[0m"; end
|
7
|
+
def self.green(txt); "\e[0;32m#{txt}\e[0m"; end
|
8
|
+
def self.blue(txt); "\e[0;34m#{txt}\e[0m"; end
|
9
|
+
|
10
|
+
def self.do_it(cmd)
|
11
|
+
puts "[exec] #{cmd}"
|
12
|
+
system(cmd)
|
13
|
+
unless $?.success?
|
14
|
+
puts red("FATAL :(")
|
15
|
+
exit
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def self.just_cmd(cmd, skip_exit_status = false)
|
20
|
+
puts "[exec] #{cmd}"
|
21
|
+
output = `#{cmd}`
|
22
|
+
if $?.success? || skip_exit_status
|
23
|
+
output.strip
|
24
|
+
else
|
25
|
+
puts red(output)
|
26
|
+
puts red("FATAL :(")
|
27
|
+
exit
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.compare_versions(a, b)
|
32
|
+
parse = proc { |v| v.split(".", 3).map(&:to_i) + [v.index("-dev") ? 1 : 0] }
|
33
|
+
parse.call(a) <=> parse.call(b)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.get_free_port(user, host)
|
37
|
+
port = nil
|
38
|
+
10.times do
|
39
|
+
a = MIN_PORT + rand(MAX_PORT - MIN_PORT)
|
40
|
+
if just_cmd("ssh #{user}@#{host} ss -ln src :#{a} | fgrep -c ':#{a}'", true) == "0"
|
41
|
+
port = a
|
42
|
+
break
|
43
|
+
end
|
44
|
+
end
|
45
|
+
raise "Dont find free port :-((" unless port
|
46
|
+
port
|
47
|
+
end
|
48
|
+
end
|
data/lib/app-rb/version.rb
CHANGED
data/lib/app-rb.rb
CHANGED
@@ -1,5 +1,14 @@
|
|
1
|
-
require "
|
1
|
+
require "pp"
|
2
|
+
require 'json'
|
2
3
|
require "app-rb/cli"
|
4
|
+
require "app-rb/version"
|
5
|
+
require "app-rb/command"
|
6
|
+
require "app-rb/config"
|
7
|
+
require "app-rb/util"
|
8
|
+
require "app-rb/util/build"
|
9
|
+
require "app-rb/util/consul"
|
10
|
+
require "app-rb/util/docker"
|
11
|
+
require "app-rb/util/registry"
|
3
12
|
|
4
13
|
module AppRb
|
5
14
|
def self.get_version
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: app-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexey Vakhov
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-03-
|
11
|
+
date: 2017-03-25 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -73,6 +73,13 @@ files:
|
|
73
73
|
- exe/app-rb
|
74
74
|
- lib/app-rb.rb
|
75
75
|
- lib/app-rb/cli.rb
|
76
|
+
- lib/app-rb/command.rb
|
77
|
+
- lib/app-rb/config.rb
|
78
|
+
- lib/app-rb/util.rb
|
79
|
+
- lib/app-rb/util/build.rb
|
80
|
+
- lib/app-rb/util/consul.rb
|
81
|
+
- lib/app-rb/util/docker.rb
|
82
|
+
- lib/app-rb/util/registry.rb
|
76
83
|
- lib/app-rb/version.rb
|
77
84
|
homepage: https://github.com/uchiru/app-rb
|
78
85
|
licenses:
|