app-rb 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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:
|