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 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: {}