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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: e7742e8a1fcec586fb33ee44414d89ea7ecee87f
4
- data.tar.gz: c9d97cf47d41a5e9b2b22a464b78d3311da7471e
3
+ metadata.gz: 04a7cf3fe51832c039c94e42bb3c01d9b85747f8
4
+ data.tar.gz: db9d29ba136cdc10b81d3a7efbe3c6523397c0f5
5
5
  SHA512:
6
- metadata.gz: 7430044a5e2f07f3a3ef65a4a98561579b0904ae7e842bf686162da33195dd5ab67004a8f4a70c392f48dce721d7a63175e4ce0cc3555f3cf1ddfc81e2343e1c
7
- data.tar.gz: 15c8870993c265004ad98696f9812915e3d7af01a9605ee447354db3efe85598fbf29fa0730c5a5a52192c5f4647ff9e9589399816d4bd3d0851a0fc78b9b073
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/uchiru/app-rb/master.svg)]()
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/uchiru/app-rb.
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/uchiru/app-rb"
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 == "restart"
28
- Command.new(config).restart
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
@@ -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 restart
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 = AppRb::Util.get_free_port(@config.user, node.ip)
158
- puts "[#{node.name}] port=#{port}"
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 ||= {}
@@ -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
@@ -1,3 +1,3 @@
1
1
  module AppRb
2
- VERSION = "0.4.0"
2
+ VERSION = "0.5.0"
3
3
  end
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.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/uchiru/app-rb
84
+ homepage: https://github.com/smuzitools/app-rb
85
85
  licenses:
86
86
  - MIT
87
87
  metadata: {}