app-rb 0.4.0 → 0.5.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 +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
|
-
[]()
|
6
6
|
[]()
|
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: {}
|