app-rb 0.4.0 → 0.5.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 +11 -0
- data/README.md +2 -2
- data/app-rb.gemspec +1 -1
- data/lib/app-rb/cli.rb +13 -2
- data/lib/app-rb/command.rb +112 -5
- data/lib/app-rb/config.rb +5 -0
- data/lib/app-rb/util/docker.rb +40 -2
- data/lib/app-rb/version.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 04a7cf3fe51832c039c94e42bb3c01d9b85747f8
|
4
|
+
data.tar.gz: db9d29ba136cdc10b81d3a7efbe3c6523397c0f5
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: edbfa05ea16f99ce0152c370bebe54502e74a9bcb33c1b12d9a2942b243b1471f2b29e41ca780104683ad14333a631fa71da87d1ddc74ad2ec58956ecfc49632
|
7
|
+
data.tar.gz: 0adc9408ade0ea60e65c59827edc311706cf8a441b1bee398980a56dccaba246568585abf58a3b853e009ef6c8b8d3db3b1caf42376aa697bdf6af122de48318
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## App-rb 0.5.0 (April 8, 2017) ##
|
2
|
+
|
3
|
+
* [breaking change] Rename `restart` cli command into `redeploy`.
|
4
|
+
* Check if build and pre deploy node available.
|
5
|
+
* Remove old images after deploy.
|
6
|
+
* Add `run` cmd to start one time jobs.
|
7
|
+
* Add "one-time" work running bash scripts for each run node.
|
8
|
+
* Add `cd` cmd to ssh to some run node.
|
9
|
+
* Deploy slack notifications.
|
10
|
+
* Add cron jobs support.
|
11
|
+
|
1
12
|
## App-rb 0.4.0 (April 8, 2017) ##
|
2
13
|
|
3
14
|
* [breaking change] Remove node role support.
|
data/README.md
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Powerfull docker apps deployer.
|
4
4
|
|
5
|
-
[![Build Status](https://img.shields.io/travis/
|
5
|
+
[![Build Status](https://img.shields.io/travis/smuzitools/app-rb/master.svg)]()
|
6
6
|
[![Gem Version](https://img.shields.io/gem/v/app-rb.svg)]()
|
7
7
|
|
8
8
|
## Installation
|
@@ -17,7 +17,7 @@ Fire `app-rb` to print help.
|
|
17
17
|
|
18
18
|
## Contributing
|
19
19
|
|
20
|
-
Bug reports and pull requests are welcome on GitHub at https://github.com/
|
20
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/smuzitools/app-rb.
|
21
21
|
|
22
22
|
## License
|
23
23
|
|
data/app-rb.gemspec
CHANGED
@@ -11,7 +11,7 @@ Gem::Specification.new do |spec|
|
|
11
11
|
|
12
12
|
spec.summary = %q{Easy deploy docker apps}
|
13
13
|
spec.description = %q{Deploy docker apps. Nothing else}
|
14
|
-
spec.homepage = "https://github.com/
|
14
|
+
spec.homepage = "https://github.com/smuzitools/app-rb"
|
15
15
|
spec.license = "MIT"
|
16
16
|
|
17
17
|
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
data/lib/app-rb/cli.rb
CHANGED
@@ -24,12 +24,16 @@ module AppRb
|
|
24
24
|
Command.new(config).deploy(@args[2])
|
25
25
|
elsif command == "status" || command == "s"
|
26
26
|
Command.new(config).status
|
27
|
-
elsif command == "
|
28
|
-
Command.new(config).
|
27
|
+
elsif command == "redeploy"
|
28
|
+
Command.new(config).redeploy
|
29
29
|
elsif command == "clean"
|
30
30
|
Command.new(config).clean
|
31
31
|
elsif command == "stop"
|
32
32
|
Command.new(config).stop
|
33
|
+
elsif command == "run" || command == "r"
|
34
|
+
Command.new(config).run(@args[2..-1].join(" "))
|
35
|
+
elsif command == "cd"
|
36
|
+
Command.new(config).cd
|
33
37
|
else
|
34
38
|
puts "FATAL: unknown command '#{command}'"
|
35
39
|
exit -1
|
@@ -44,9 +48,16 @@ module AppRb
|
|
44
48
|
puts ""
|
45
49
|
puts " app-rb <yml> <command>"
|
46
50
|
puts ""
|
51
|
+
puts "Usage:"
|
47
52
|
puts " deploy [hash] - deploy new version of app"
|
48
53
|
puts " status - status of app"
|
49
54
|
puts " stop - stop app"
|
55
|
+
puts " run <cmd> [args] - one time command"
|
56
|
+
puts " cd - go to run node"
|
57
|
+
puts ""
|
58
|
+
puts "Advanced:"
|
59
|
+
puts " redeploy - redeploy app"
|
60
|
+
puts " clean - stop and remove not current containers"
|
50
61
|
end
|
51
62
|
end
|
52
63
|
end
|
data/lib/app-rb/command.rb
CHANGED
@@ -6,19 +6,34 @@ module AppRb
|
|
6
6
|
|
7
7
|
def deploy(target)
|
8
8
|
@base = "#{@config.app}-#{Time.now.to_i}"
|
9
|
+
start_at = Time.now
|
10
|
+
user = AppRb::Util.just_cmd("git config user.name")
|
9
11
|
|
10
12
|
# init
|
11
13
|
current_hash = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "hash")
|
12
14
|
build_nodes = @config.nodes(@config.image["constraint"])
|
15
|
+
if build_nodes.empty?
|
16
|
+
puts "FATAL ERROR: no build nodes found"
|
17
|
+
exit -1
|
18
|
+
end
|
13
19
|
pre_payloads = @config.pre_deploy.map do |pre|
|
14
|
-
{
|
20
|
+
payload = {
|
15
21
|
"nodes" => @config.nodes(pre["constraint"]),
|
16
22
|
"cmd" => pre["cmd"],
|
17
23
|
"opts" => pre["opts"] || [],
|
18
24
|
}
|
25
|
+
if payload["nodes"].empty?
|
26
|
+
puts "FATAL ERROR: no pre deploy nodes found"
|
27
|
+
exit -1
|
28
|
+
end
|
29
|
+
payload
|
19
30
|
end
|
20
31
|
deploy_payloads = @config.deploy.map { |key, section|
|
21
32
|
nodes = @config.nodes(section["constraint"])
|
33
|
+
if nodes.empty?
|
34
|
+
puts "FATAL ERROR: no deploy `#{key}` nodes found"
|
35
|
+
exit -1
|
36
|
+
end
|
22
37
|
{
|
23
38
|
"key" => key,
|
24
39
|
"nodes" => nodes,
|
@@ -29,17 +44,44 @@ module AppRb
|
|
29
44
|
"opts" => section["opts"] || [],
|
30
45
|
}
|
31
46
|
}
|
47
|
+
if @config.run["constraint"]
|
48
|
+
run_nodes = @config.nodes(@config.run["constraint"])
|
49
|
+
else
|
50
|
+
run_nodes = deploy_payloads.flat_map { |payload| payload["nodes"] }.uniq
|
51
|
+
end
|
52
|
+
if run_nodes.empty?
|
53
|
+
puts "FATAL ERROR: no run nodes found"
|
54
|
+
exit -1
|
55
|
+
end
|
56
|
+
cron_payloads = @config.cron.map { |key, section|
|
57
|
+
nodes = @config.nodes(section["constraint"])
|
58
|
+
if nodes.empty?
|
59
|
+
puts "FATAL ERROR: no cron `#{key}` nodes found"
|
60
|
+
exit -1
|
61
|
+
end
|
62
|
+
{
|
63
|
+
"key" => key,
|
64
|
+
"nodes" => nodes,
|
65
|
+
"cmd" => section["cmd"],
|
66
|
+
"at" => "#{section["minute"] || "*"} #{section["hour"] || "*"} #{section["day"] || "*"} #{section["month"] || "*"} #{section["weekday"] || "*"}"
|
67
|
+
}
|
68
|
+
}
|
32
69
|
old_ips = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "nodes").split(",")
|
33
70
|
new_ips = (
|
34
71
|
build_nodes.map(&:ip) +
|
35
72
|
pre_payloads.flat_map { |p| p["nodes"].map(&:ip) } +
|
36
|
-
deploy_payloads.flat_map { |p| p["nodes"].map(&:ip) }
|
73
|
+
deploy_payloads.flat_map { |p| p["nodes"].map(&:ip) } +
|
74
|
+
run_nodes.map(&:ip) +
|
75
|
+
cron_payloads.flat_map { |p| p["nodes"].map(&:ip) }
|
37
76
|
).uniq
|
38
77
|
ips = (old_ips + new_ips).uniq
|
39
78
|
AppRb::Util::Consul.kv_set(@config.consul, @config.app, "nodes", ips.join(","))
|
40
79
|
|
41
80
|
# pre
|
42
81
|
new_hash = prepare_image(build_nodes, target)
|
82
|
+
if @config.slack?
|
83
|
+
notify_slack(@config.slack_url, @config.slack_channel, "#{user} start deploy *#{@config.app}* - https://github.com/#{@config.image["repo"]}/compare/#{current_hash.to_s[0..6]}...#{new_hash.to_s[0..6]}")
|
84
|
+
end
|
43
85
|
pre_deploy(pre_payloads, new_hash)
|
44
86
|
stop_bg_jobs(ips)
|
45
87
|
|
@@ -49,12 +91,24 @@ module AppRb
|
|
49
91
|
# switch
|
50
92
|
blue_green(deploy_payloads, new_hash)
|
51
93
|
|
94
|
+
# update one time scripts
|
95
|
+
one_time_scripts(run_nodes, ips, new_hash)
|
96
|
+
|
97
|
+
# update crons
|
98
|
+
set_crons(cron_payloads, ips, new_hash)
|
99
|
+
|
52
100
|
# clean
|
53
101
|
stop_services(ips)
|
54
102
|
clean_registry(current_hash, [current_hash, new_hash].uniq)
|
103
|
+
remove_old_images(ips, [current_hash, new_hash].uniq)
|
55
104
|
|
56
105
|
# finish
|
57
106
|
AppRb::Util::Consul.kv_set(@config.consul, @config.app, "nodes", new_ips.join(","))
|
107
|
+
|
108
|
+
if @config.slack?
|
109
|
+
notify_slack(@config.slack_url, @config.slack_channel, "#{user} finish deploy *#{@config.app}* :cat: :cat: :cat: - #{((Time.now.to_f - start_at.to_f)/60).round(1)} minutes")
|
110
|
+
end
|
111
|
+
|
58
112
|
puts AppRb::Util.green("Done.")
|
59
113
|
if current_hash != "" && !target && current_hash != target
|
60
114
|
puts "to rollback fire: app-rb #{ARGV[0]} deploy #{current_hash}"
|
@@ -90,6 +144,9 @@ module AppRb
|
|
90
144
|
def stop
|
91
145
|
ips = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "nodes").split(",")
|
92
146
|
stop_all(ips)
|
147
|
+
ips.each do |ip|
|
148
|
+
AppRb::Util::Docker.add_cron(@config.user, ip, "#{@config.registry}/#{@config.app}:#{hash}", @config.env, @config.app, [])
|
149
|
+
end
|
93
150
|
end
|
94
151
|
|
95
152
|
def clean
|
@@ -98,15 +155,33 @@ module AppRb
|
|
98
155
|
stop_services(ips, base)
|
99
156
|
end
|
100
157
|
|
101
|
-
def
|
158
|
+
def redeploy
|
102
159
|
hash = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "hash")
|
103
160
|
raise "FATAL: app is not started?" if hash == ""
|
104
161
|
puts "hash=#{hash}"
|
105
162
|
deploy(hash)
|
106
163
|
end
|
107
164
|
|
165
|
+
def run(cmd)
|
166
|
+
hash = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "hash")
|
167
|
+
raise "FATAL: app is not started?" if hash == ""
|
168
|
+
run_nodes = @config.nodes(@config.run["constraint"])
|
169
|
+
AppRb::Util::Docker.run(@config.user, run_nodes.sample.ip, "#{@config.registry}/#{@config.app}:#{hash}", @config.env, cmd)
|
170
|
+
end
|
171
|
+
|
172
|
+
def cd
|
173
|
+
hash = AppRb::Util::Consul.kv_get(@config.consul, @config.app, "hash")
|
174
|
+
raise "FATAL: app is not started?" if hash == ""
|
175
|
+
run_nodes = @config.nodes(@config.run["constraint"])
|
176
|
+
AppRb::Util.do_it "ssh -t #{@config.user}@#{run_nodes.sample.ip} bash --login"
|
177
|
+
end
|
178
|
+
|
108
179
|
private
|
109
180
|
|
181
|
+
def notify_slack(url, channel, msg)
|
182
|
+
AppRb::Util.do_it(%(curl -s -X POST --data-urlencode 'payload={"channel": "#{channel}", "username": "deplobot", "parse": "full", "text": "#{msg}"}' #{url}))
|
183
|
+
end
|
184
|
+
|
110
185
|
def full_image_name(hash)
|
111
186
|
"#{@config.registry}/#{@config.app}:#{hash}"
|
112
187
|
end
|
@@ -154,8 +229,10 @@ module AppRb
|
|
154
229
|
payload["nodes"].map { |node|
|
155
230
|
Thread.new do
|
156
231
|
(plan[node.ip] || []).each do |name|
|
157
|
-
port
|
158
|
-
|
232
|
+
if payload["port"]
|
233
|
+
port = AppRb::Util.get_free_port(@config.user, node.ip)
|
234
|
+
puts "[#{node.name}] port=#{port}"
|
235
|
+
end
|
159
236
|
|
160
237
|
AppRb::Util::Docker.run_daemon(
|
161
238
|
@config.user, node.ip,
|
@@ -226,5 +303,35 @@ module AppRb
|
|
226
303
|
puts AppRb::Util.blue("+++ CLEAN REGISTRY")
|
227
304
|
AppRb::Util::Registry.clean(@config.registry, @config.app, keep_hashes)
|
228
305
|
end
|
306
|
+
|
307
|
+
def remove_old_images(ips, keep_hashes = [])
|
308
|
+
puts AppRb::Util.blue("+++ REMOVE OLD IMAGES")
|
309
|
+
ips.each do |ip|
|
310
|
+
AppRb::Util::Docker.remove_images(@config.user, ip, "#{@config.registry}/#{@config.app}", keep_hashes)
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
def one_time_scripts(run_nodes, ips, hash)
|
315
|
+
puts AppRb::Util.blue("+++ ONE TIME SCRIPTS")
|
316
|
+
run_nodes.each do |n|
|
317
|
+
AppRb::Util::Docker.create_one_time_script(@config.user, n.ip, "#{@config.registry}/#{@config.app}:#{hash}", @config.env, @config.app)
|
318
|
+
end
|
319
|
+
ips.each do |ip|
|
320
|
+
next if run_nodes.map(&:ip).index(ip)
|
321
|
+
AppRb::Util::Docker.remove_one_time_script(@config.user, ip, @config.app)
|
322
|
+
end
|
323
|
+
end
|
324
|
+
|
325
|
+
def set_crons(cron_payloads, ips, hash)
|
326
|
+
puts AppRb::Util.blue("+++ CRONS")
|
327
|
+
ips.each do |ip|
|
328
|
+
crons = cron_payloads.select { |section|
|
329
|
+
section["nodes"].map(&:ip).index(ip)
|
330
|
+
}.map { |section|
|
331
|
+
{"cmd" => section["cmd"], "at" => section["at"], "key" => section["key"]}
|
332
|
+
}
|
333
|
+
AppRb::Util::Docker.add_cron(@config.user, ip, "#{@config.registry}/#{@config.app}:#{hash}", @config.env, @config.app, crons)
|
334
|
+
end
|
335
|
+
end
|
229
336
|
end
|
230
337
|
end
|
data/lib/app-rb/config.rb
CHANGED
@@ -12,6 +12,11 @@ class AppRb::Config
|
|
12
12
|
def env; @body["env"] || {}; end
|
13
13
|
def pre_deploy; @body["pre_deploy"] || []; end
|
14
14
|
def deploy; @body["deploy"] || {}; end
|
15
|
+
def cron; @body["cron"] || {}; end
|
16
|
+
def run; @body["run"] || {}; end
|
17
|
+
def slack_url; @body["slack_url"]; end
|
18
|
+
def slack_channel; @body["slack_channel"]; end
|
19
|
+
def slack?; slack_url && slack_channel; end
|
15
20
|
|
16
21
|
def nodes(constraint = nil)
|
17
22
|
constraint ||= {}
|
data/lib/app-rb/util/docker.rb
CHANGED
@@ -11,13 +11,12 @@ module AppRb::Util::Docker
|
|
11
11
|
end
|
12
12
|
|
13
13
|
def self.run_batch(user, host, name, image, cmd, labels = {}, env = {}, opts = [])
|
14
|
-
AppRb::Util.do_it "ssh #{user}@#{host} docker run " +
|
14
|
+
AppRb::Util.do_it "ssh #{user}@#{host} docker run --rm " +
|
15
15
|
labels.map { |k, v| "--label #{k}=#{v} " }.join +
|
16
16
|
"--name #{name} " +
|
17
17
|
opts.join(" ") + " " +
|
18
18
|
env.map { |k, v| "-e #{k}='#{v}' " }.join +
|
19
19
|
"#{image} #{cmd}"
|
20
|
-
AppRb::Util.do_it "ssh #{user}@#{host} docker rm #{name}"
|
21
20
|
end
|
22
21
|
|
23
22
|
def self.run_daemon(user, host, name, image, cmd, labels = {}, env = {}, opts = [], ports = {})
|
@@ -44,4 +43,43 @@ module AppRb::Util::Docker
|
|
44
43
|
AppRb::Util.do_it("ssh #{user}@#{host} docker rm #{(all_ids - keep_ids).join(" ")}")
|
45
44
|
end
|
46
45
|
end
|
46
|
+
|
47
|
+
def self.remove_images(user, host, image_name, keep_tags = [])
|
48
|
+
all_ids = AppRb::Util.just_cmd("ssh #{user}@#{host} docker images #{image_name} -q").split("\n")
|
49
|
+
keep_ids = keep_tags.map { |tag|
|
50
|
+
AppRb::Util.just_cmd("ssh #{user}@#{host} docker images #{image_name}:#{tag} -q")
|
51
|
+
}
|
52
|
+
if (all_ids - keep_ids).length > 0
|
53
|
+
AppRb::Util.do_it("ssh #{user}@#{host} 'docker rmi #{(all_ids - keep_ids).join(" ")}'; echo ok")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.run(user, host, image, env, cmd)
|
58
|
+
AppRb::Util.do_it "ssh -t #{user}@#{host} docker run --rm -it " +
|
59
|
+
env.map { |k, v| "-e #{k}='#{v}' " }.join +
|
60
|
+
"#{image} #{cmd}"
|
61
|
+
end
|
62
|
+
|
63
|
+
def self.create_one_time_script(user, host, image, env, app)
|
64
|
+
AppRb::Util.do_it "ssh #{user}@#{host} bash <<EOF
|
65
|
+
echo '#/bin/bash' > ~/run_#{app}.sh
|
66
|
+
echo 'docker run -it --rm #{env.map { |k, v| "-e #{k}='#{v}' " }.join} #{image} \"\\$@\"' >> ~/run_#{app}.sh
|
67
|
+
chmod +x ~/run_#{app}.sh
|
68
|
+
\nEOF"
|
69
|
+
end
|
70
|
+
|
71
|
+
def self.remove_one_time_script(user, host, app)
|
72
|
+
AppRb::Util.do_it "ssh #{user}@#{host} bash <<EOF
|
73
|
+
rm -f ~/run_#{app}.sh
|
74
|
+
\nEOF"
|
75
|
+
end
|
76
|
+
|
77
|
+
def self.add_cron(user, host, image, env, app, crons)
|
78
|
+
echos = crons.map { |cron|
|
79
|
+
%(echo "#{cron["at"]} __APP=#{app}; start=\\\\\\$(date); (docker run --rm #{env.map { |k, v| "-e #{k}='#{v}' " }.join}#{image} #{cron["cmd"]}; echo start \\\\\\$start, finish \\\\\\$(date)) >> /home/#{user}/crontab__#{app}__#{cron["key"]}.log 2>&1")
|
80
|
+
}
|
81
|
+
AppRb::Util.do_it %(ssh #{user}@#{host} bash <<EOF
|
82
|
+
(crontab -l | grep -v -F '__APP=#{app};'; #{echos.join("; ")}) | crontab -
|
83
|
+
\nEOF)
|
84
|
+
end
|
47
85
|
end
|
data/lib/app-rb/version.rb
CHANGED
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: app-rb
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.5.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Alexey Vakhov
|
@@ -81,7 +81,7 @@ files:
|
|
81
81
|
- lib/app-rb/util/docker.rb
|
82
82
|
- lib/app-rb/util/registry.rb
|
83
83
|
- lib/app-rb/version.rb
|
84
|
-
homepage: https://github.com/
|
84
|
+
homepage: https://github.com/smuzitools/app-rb
|
85
85
|
licenses:
|
86
86
|
- MIT
|
87
87
|
metadata: {}
|