app-rb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +12 -0
- data/.rspec +2 -0
- data/.travis.yml +11 -0
- data/CHANGELOG.md +3 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.md +22 -0
- data/Rakefile +6 -0
- data/app-rb.gemspec +27 -0
- data/bin/release +56 -0
- data/exe/app-rb +313 -0
- data/lib/app-rb/cli.rb +4 -0
- data/lib/app-rb/version.rb +3 -0
- data/lib/app-rb.rb +8 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 23df7f2417cf94b6957cde15260ae950348d8861
|
4
|
+
data.tar.gz: 10198299029a90bada242c624e4bfc59bf3c3e1b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 88b81b190edd57ee17b485e967303421e777f13fa39b22d4d27eed241e86d344ad122abe6db9d1371c8b0f5647ffa87f03550a21837b6ffb3c964bd77f1ba96e
|
7
|
+
data.tar.gz: 780f0b901f9ed60f2194855617ec896cd986d50a9b7eb2577b93e598b538f99846365c5bb51be38f7267d01b6a6a1475a9b3425344a250ede0acb39f49837106
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2017 Alexey Vakhov
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
7
|
+
in the Software without restriction, including without limitation the rights
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
10
|
+
furnished to do so, subject to the following conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
13
|
+
all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# app-rb
|
2
|
+
|
3
|
+
Powerfull docker apps deployer.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
Install last gem version as:
|
8
|
+
|
9
|
+
$ gem install app-rb
|
10
|
+
|
11
|
+
## Usage
|
12
|
+
|
13
|
+
Fire `app-rb` to print help.
|
14
|
+
|
15
|
+
## Contributing
|
16
|
+
|
17
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/uchiru/app-rb.
|
18
|
+
|
19
|
+
## License
|
20
|
+
|
21
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
22
|
+
|
data/Rakefile
ADDED
data/app-rb.gemspec
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'app-rb/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "app-rb"
|
8
|
+
spec.version = AppRb::VERSION
|
9
|
+
spec.authors = ["Alexey Vakhov"]
|
10
|
+
spec.email = ["vakhov@gmail.com"]
|
11
|
+
|
12
|
+
spec.summary = %q{Easy deploy docker apps}
|
13
|
+
spec.description = %q{Deploy docker apps. Nothing else}
|
14
|
+
spec.homepage = "https://github.com/uchiru/app-rb"
|
15
|
+
spec.license = "MIT"
|
16
|
+
|
17
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
18
|
+
f.match(%r{^(test|spec|features)/})
|
19
|
+
end
|
20
|
+
spec.bindir = "exe"
|
21
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
22
|
+
spec.require_paths = ["lib"]
|
23
|
+
|
24
|
+
spec.add_development_dependency "bundler", "~> 1.14"
|
25
|
+
spec.add_development_dependency "rake", "~> 10.0"
|
26
|
+
spec.add_development_dependency "rspec", "~> 3.0"
|
27
|
+
end
|
data/bin/release
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
VERSION_PATH = File.expand_path('../../lib/app-rb/version.rb', __FILE__)
|
4
|
+
CHANGELOG_PATH = File.expand_path('../../CHANGELOG.md', __FILE__)
|
5
|
+
|
6
|
+
current_version = File.read(VERSION_PATH).split("\n").find { |l| l.index("VERSION") }.split('"')[1]
|
7
|
+
major, minor, patch = current_version.split(".", 3).map(&:to_i)
|
8
|
+
dev = !!current_version.index("-dev")
|
9
|
+
|
10
|
+
if ARGV[0] == "patch" && !dev
|
11
|
+
version = "#{major}.#{minor}.#{patch + 1}"
|
12
|
+
next_version = "#{major}.#{minor}.#{patch + 1}"
|
13
|
+
|
14
|
+
elsif ARGV[0] == "minor" && dev && patch == 0
|
15
|
+
version = "#{major}.#{minor}.0"
|
16
|
+
next_version = "#{major}.#{minor + 1}.0-dev"
|
17
|
+
|
18
|
+
elsif ARGV[0] == "major" && dev && patch == 0
|
19
|
+
version = "#{major + 1}.0.0"
|
20
|
+
next_version = "#{major + 1}.1.0-dev"
|
21
|
+
|
22
|
+
else
|
23
|
+
puts "Current version: #{current_version}"
|
24
|
+
unless dev
|
25
|
+
puts "./bin/release patch # => #{major}.#{minor}.#{patch + 1}"
|
26
|
+
end
|
27
|
+
if dev
|
28
|
+
puts "./bin/release minor # => #{major}.#{minor}.0"
|
29
|
+
puts "./bin/release major # => #{major + 1}.0.0"
|
30
|
+
end
|
31
|
+
exit
|
32
|
+
end
|
33
|
+
|
34
|
+
def exec(cmd)
|
35
|
+
puts "[exec] #{cmd}"
|
36
|
+
system cmd
|
37
|
+
end
|
38
|
+
|
39
|
+
puts "releasing version: #{version}"
|
40
|
+
exec %(git add .)
|
41
|
+
exec %(git commit -am"uncommitted changes before release #{version}")
|
42
|
+
exec %(git pull --rebase)
|
43
|
+
File.write(VERSION_PATH, File.read(VERSION_PATH).sub(/VERSION = ".*?"/, %(VERSION = "#{version}")))
|
44
|
+
File.write(CHANGELOG_PATH, "## App-rb #{version} (#{Time.now.strftime("%B %-d, %Y")}) ##\n\n" + File.read(CHANGELOG_PATH))
|
45
|
+
exec %(git commit -am"Release #{version}")
|
46
|
+
exec %(git tag v#{version})
|
47
|
+
exec %(git push)
|
48
|
+
exec %(git push --tags)
|
49
|
+
exec %(gem build app-rb.gemspec)
|
50
|
+
exec %(gem push app-rb-#{version}.gem)
|
51
|
+
exec %(rm -f app-rb-#{version}.gem)
|
52
|
+
File.write(VERSION_PATH, File.read(VERSION_PATH).sub(/VERSION = ".*?"/, %(VERSION = "#{next_version}")))
|
53
|
+
exec %(git commit -am"Start version #{next_version}")
|
54
|
+
exec %(git push)
|
55
|
+
|
56
|
+
puts "Done."
|
data/exe/app-rb
ADDED
@@ -0,0 +1,313 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'json'
|
3
|
+
require 'yaml'
|
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
|
313
|
+
|
data/lib/app-rb/cli.rb
ADDED
data/lib/app-rb.rb
ADDED
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: app-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alexey Vakhov
|
8
|
+
autorequire:
|
9
|
+
bindir: exe
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-03-24 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.14'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.14'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '10.0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '10.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.0'
|
55
|
+
description: Deploy docker apps. Nothing else
|
56
|
+
email:
|
57
|
+
- vakhov@gmail.com
|
58
|
+
executables:
|
59
|
+
- app-rb
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".gitignore"
|
64
|
+
- ".rspec"
|
65
|
+
- ".travis.yml"
|
66
|
+
- CHANGELOG.md
|
67
|
+
- Gemfile
|
68
|
+
- LICENSE.txt
|
69
|
+
- README.md
|
70
|
+
- Rakefile
|
71
|
+
- app-rb.gemspec
|
72
|
+
- bin/release
|
73
|
+
- exe/app-rb
|
74
|
+
- lib/app-rb.rb
|
75
|
+
- lib/app-rb/cli.rb
|
76
|
+
- lib/app-rb/version.rb
|
77
|
+
homepage: https://github.com/uchiru/app-rb
|
78
|
+
licenses:
|
79
|
+
- MIT
|
80
|
+
metadata: {}
|
81
|
+
post_install_message:
|
82
|
+
rdoc_options: []
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '0'
|
95
|
+
requirements: []
|
96
|
+
rubyforge_project:
|
97
|
+
rubygems_version: 2.5.2
|
98
|
+
signing_key:
|
99
|
+
specification_version: 4
|
100
|
+
summary: Easy deploy docker apps
|
101
|
+
test_files: []
|