luban 0.4.1 → 0.4.2

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: ffaea169e73bcbe91d121057e872f25c28f9aa91
4
- data.tar.gz: 0b0cfbe6d7b224f87f62ec78a2801f0fe970e491
3
+ metadata.gz: 8f634550b34b8380b8a9bb38189ab450e3c50a34
4
+ data.tar.gz: d507cdf9924ca36905c4e8a8585a7835071e1168
5
5
  SHA512:
6
- metadata.gz: 59807ad43e6829f55c512d1d865402710de337424982863330faaa4a86acb16b36921aa3ec5fe438d55399d431988b75b33abdd461e1de85cce15be94596fcc8
7
- data.tar.gz: c2085acfba4d4476242a85904f009afe9c7cbf4ca149f09260cd1972b2b9be3db5f15602ec588fa1cc6089b6ad4dd8d3f02a2e937d14504ffce9128f6d9a55d7
6
+ metadata.gz: 75e17a9e3a96df84cea850e4313060c5cb2a4ca09c2e4274ff75d09704b6deb55a49f6f1e8656c471e7ed51aa883d3a77b179c3a7bdb39a7d1d42b315ceb948b
7
+ data.tar.gz: 246833cd50c99ef0d7d0d847a2a3aa44efec01ef0832df7868ff89ce06f94a7c829fd240a4252c2c22bc458384567d4f8e4967c1366c5f66630fb3542a161b9e
data/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Change log
2
2
 
3
+ ## Version 0.4.2 (Jun 07, 2016)
4
+
5
+ Minor enhancements:
6
+ * Enhanced process management in controller
7
+ * Added parameters #process_monitor to setup process monitor in Luban
8
+ * Added #process_monitor_via convenient method to setup process monitor in Luban
9
+ * Removed #monitor_process and #unmonitor_process from common control tasks
10
+ * Added option #configure_opts for package installation
11
+ * Optimized format result output message
12
+ * Added convenient methods #env_name and #service_name to standardize naming
13
+ * Minor code refactoring
14
+
15
+ Bug fixes:
16
+ * Fixed SSHKit output format to handle :airbrussh properly
17
+
3
18
  ## Version 0.4.1 (May 13, 2016)
4
19
 
5
20
  Minor enhancements:
@@ -120,7 +120,7 @@ module Luban
120
120
 
121
121
  def set_parameters
122
122
  super
123
- copy_parameters_from_parent(:stage, :project)
123
+ copy_parameters_from_parent(:stage, :project, :process_monitor)
124
124
  @packages = {}
125
125
  @services = {}
126
126
  @source = {}
@@ -29,7 +29,7 @@ module Luban
29
29
 
30
30
  def destroy_app
31
31
  rm(etc_path.join('*', "#{stage}.#{project}.#{application}.*"))
32
- rm(app_path)
32
+ rmdir(app_path)
33
33
  update_result "The application environment is destroyed."
34
34
  end
35
35
 
@@ -66,4 +66,4 @@ module Luban
66
66
  end
67
67
  end
68
68
  end
69
- end
69
+ end
@@ -112,7 +112,7 @@ module Luban
112
112
  create_symlinks
113
113
  update_releases_log
114
114
  else
115
- rm(release_path)
115
+ rmdir(release_path)
116
116
  end
117
117
  ensure
118
118
  rm(upload_to)
@@ -176,7 +176,7 @@ module Luban
176
176
  end
177
177
  releases_to_remove = releases - releases_to_keep
178
178
  releases_to_remove.each do |release|
179
- rm(releases_path.join(release))
179
+ rmdir(releases_path.join(release))
180
180
  end
181
181
  info "Removed #{releases_to_remove.count} old releases."
182
182
  else
@@ -36,7 +36,7 @@ module Luban
36
36
  assure_dirs(release_tag)
37
37
  execute(:tar, "-C #{clone_path} -cf - . | tar -C #{release_tag} -xf -")
38
38
  execute(:tar, "-czf", release_package_path, release_tag)
39
- rm(release_tag)
39
+ rmdir(release_tag)
40
40
  end
41
41
  end
42
42
  end
@@ -91,8 +91,7 @@ module Luban
91
91
 
92
92
  module Control
93
93
  Actions = %i(start_process stop_process kill_process
94
- restart_process check_process
95
- monitor_process unmonitor_process)
94
+ restart_process check_process)
96
95
  Actions.each do |action|
97
96
  define_method(action) do |args:, opts:|
98
97
  raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
@@ -101,6 +100,12 @@ module Luban
101
100
 
102
101
  def controllable?; true; end
103
102
 
103
+ def process_monitor_via(monitor, env: "uber/lubmon")
104
+ monitor = monitor.to_s.downcase
105
+ env = env.to_s.downcase
106
+ process_monitor name: monitor, env: env
107
+ end
108
+
104
109
  protected
105
110
 
106
111
  def setup_control_tasks
@@ -128,16 +133,6 @@ module Luban
128
133
  desc "Check process status"
129
134
  action! :check_process
130
135
  end
131
-
132
- task :monitor do
133
- desc "Turn on process monitor"
134
- action! :monitor_process
135
- end
136
-
137
- task :unmonitor do
138
- desc "Turn off process monitor"
139
- action! :unmonitor_process
140
- end
141
136
  end
142
137
  end
143
138
  end
@@ -325,7 +320,9 @@ module Luban
325
320
  def print_task_result(result)
326
321
  result.each do |entry|
327
322
  next if entry[:message].to_s.empty?
328
- puts " [#{entry[:hostname]}] #{entry[:message]}"
323
+ entry[:message].split("\n").each do |msg|
324
+ puts " [#{entry[:hostname]}] #{msg}"
325
+ end
329
326
  end
330
327
  end
331
328
 
@@ -336,7 +333,7 @@ module Luban
336
333
  enable_dry_run if dry_run
337
334
 
338
335
  SSHKit.configure do |sshkit|
339
- sshkit.format = format
336
+ sshkit.format = format unless format == :airbrussh
340
337
  sshkit.output_verbosity = verbosity
341
338
  sshkit.default_env = default_env
342
339
  sshkit.backend = sshkit_backend
@@ -368,4 +365,4 @@ module Luban
368
365
  end
369
366
  end
370
367
  end
371
- end
368
+ end
@@ -68,7 +68,9 @@ module Luban
68
68
  end
69
69
 
70
70
  def configure_build_options
71
- @configure_opts = default_configure_opts | (task.opts.__remaining__ || [])
71
+ @configure_opts = default_configure_opts
72
+ @configure_opts |= task.opts.configure_opts unless task.opts.configure_opts.nil?
73
+ @configure_opts |= task.opts.__remaining__ unless task.opts.__remaining__.nil?
72
74
  @configure_opts.unshift(install_prefix)
73
75
  end
74
76
 
@@ -283,8 +283,7 @@ module Luban
283
283
  end
284
284
 
285
285
  def remove_symlinks!
286
- return unless symlink?(current_path)
287
- execute "[ -h #{current_path} ] && rm -f #{current_path}; true"
286
+ symlink?(current_path) and rm(current_path)
288
287
  end
289
288
 
290
289
  def remove_binstubs!
@@ -292,7 +291,7 @@ module Luban
292
291
  find_cmd = "find #{current_bin_path}/* -type f -print && find #{current_bin_path}/* -type l -print"
293
292
  capture(find_cmd).split("\n").each do |bin|
294
293
  bin_symlink = app_bin_path.join(File.basename(bin))
295
- execute "[ -h #{bin_symlink} ] && rm -f #{bin_symlink}; true"
294
+ symlink?(bin_symlink) and rm(bin_symlink)
296
295
  end
297
296
  end
298
297
 
@@ -20,7 +20,7 @@ module Luban
20
20
  end
21
21
  end
22
22
 
23
- def package_name; task.opts.name; end
23
+ def package_name; File.basename(task.opts.name); end
24
24
  def package_full_name; "#{package_name}-#{package_version}"; end
25
25
 
26
26
  def package_version; task.opts.version; end
@@ -5,18 +5,24 @@ module Luban
5
5
  include Luban::Deployment::Command::Tasks::Deploy
6
6
  include Luban::Deployment::Command::Tasks::Control
7
7
 
8
- ControlActions = Luban::Deployment::Command::Tasks::Control::Actions
9
- ProfileActions = %i(update_profile)
10
-
11
- (ControlActions | ProfileActions).each do |m|
12
- define_method(m) do |args:, opts:|
8
+ def self.service_action(name, dispatch_to: nil, locally: false, &blk)
9
+ define_method(name) do |args:, opts:|
13
10
  if current_version
14
11
  send("#{__method__}!", args: args, opts: opts.merge(version: current_version))
15
12
  else
16
13
  abort "Aborted! No current version of #{display_name} is specified."
17
14
  end
18
15
  end
16
+ unless dispatch_to.nil?
17
+ dispatch_task "#{name}!", to: dispatch_to, as: name, locally: locally, &blk
18
+ protected "#{name}!"
19
+ end
20
+ end
21
+
22
+ Luban::Deployment::Command::Tasks::Control::Actions.each do |action|
23
+ service_action action, dispatch_to: :controller
19
24
  end
25
+ service_action :update_profile, dispatch_to: :configurator, locally: true
20
26
 
21
27
  protected
22
28
 
@@ -37,12 +43,6 @@ module Luban
37
43
  super
38
44
  linked_dirs.push('log', 'pids')
39
45
  end
40
-
41
- ControlActions.each do |task|
42
- dispatch_task "#{task}!", to: :controller, as: task
43
- end
44
-
45
- dispatch_task :update_profile!, to: :configurator, as: :update_profile, locally: true
46
46
  end
47
47
  end
48
48
  end
@@ -2,11 +2,32 @@ module Luban
2
2
  module Deployment
3
3
  module Service
4
4
  class Controller < Worker
5
- %i(process_stopped? process_started?
6
- monitor_process unmonitor_process).each do |m|
7
- define_method(m) do
8
- raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
9
- end
5
+ def pid
6
+ capture(:cat, "#{pid_file_path} 2>/dev/null")
7
+ end
8
+
9
+ def process_started?
10
+ !!process_grep.keys.first
11
+ end
12
+
13
+ def process_stopped?
14
+ !process_started?
15
+ end
16
+
17
+ def pid_file_orphaned?
18
+ process_stopped? and pid_file_exists?
19
+ end
20
+
21
+ def pid_file_missing?
22
+ process_started? and !pid_file_exists?
23
+ end
24
+
25
+ def pid_file_exists?
26
+ file?(pid_file_path, "-s") # file is NOT zero size
27
+ end
28
+
29
+ def process_pattern
30
+ raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
10
31
  end
11
32
 
12
33
  def start_process
@@ -17,9 +38,11 @@ module Luban
17
38
 
18
39
  output = start_process!
19
40
  if check_until { process_started? }
20
- update_result "Successfully started #{package_full_name}: #{output}"
41
+ monitor_process
42
+ update_result "Start #{package_full_name}: [OK] #{output}"
21
43
  else
22
- update_result "Failed to start #{package_full_name}: #{output}",
44
+ remove_orphaned_pid_file
45
+ update_result "Start #{package_full_name}: [FAILED] #{output}",
23
46
  status: :failed, level: :error
24
47
  end
25
48
  end
@@ -30,11 +53,13 @@ module Luban
30
53
  return
31
54
  end
32
55
 
33
- output = stop_process!
56
+ output = stop_process! || 'OK'
34
57
  if check_until { process_stopped? }
35
- update_result "Successfully stopped #{package_full_name}: #{output}"
58
+ unmonitor_process
59
+ update_result "Stop #{package_full_name}: [OK] #{output}"
36
60
  else
37
- update_result "Failed to stop #{package_full_name}: #{output}",
61
+ remove_orphaned_pid_file
62
+ update_result "Stop #{package_full_name}: [FAILED] #{output}",
38
63
  status: :failed, level: :error
39
64
  end
40
65
  end
@@ -42,8 +67,12 @@ module Luban
42
67
  def restart_process
43
68
  if process_started?
44
69
  output = stop_process!
45
- unless check_until { process_stopped? }
46
- update_result "Failed to stop #{package_full_name}: #{output}",
70
+ if check_until { process_stopped? }
71
+ info "Stop #{package_full_name}: [OK] #{output}"
72
+ unmonitor_process
73
+ else
74
+ remove_orphaned_pid_file
75
+ update_result "Stop #{package_full_name}: [FAILED] #{output}",
47
76
  status: :failed, level: :error
48
77
  return
49
78
  end
@@ -51,9 +80,11 @@ module Luban
51
80
 
52
81
  output = start_process!
53
82
  if check_until { process_started? }
54
- update_result "Successfully restarted #{package_full_name}: #{output}"
83
+ update_result "Restart #{package_full_name}: [OK] #{output}"
84
+ monitor_process
55
85
  else
56
- update_result "Failed to restart #{package_full_name}: #{output}",
86
+ remove_orphaned_pid_file
87
+ update_result "Restart #{package_full_name}: [FAILED] #{output}",
57
88
  status: :failed, level: :error
58
89
  end
59
90
  end
@@ -65,9 +96,43 @@ module Luban
65
96
  def kill_process
66
97
  output = kill_process!
67
98
  if check_until { process_stopped? }
68
- update_result "Successfully kill processs (#{output})."
99
+ unmonitor_process
100
+ remove_orphaned_pid_file
101
+ update_result "Kill #{package_full_name}: [OK] #{output}"
69
102
  else
70
- update_result "Failed to kill process: #{output}"
103
+ update_result "Kill #{package_full_name}: [FAILED] #{output}"
104
+ end
105
+ end
106
+
107
+ def process_monitor_defined?
108
+ !process_monitor[:name].nil?
109
+ end
110
+
111
+ def monitor_process
112
+ if process_monitor_defined?
113
+ if monitor_process!
114
+ info "Turned on process monitor for #{service_entry}"
115
+ else
116
+ info "Failed to turn on process monitor for #{service_entry}"
117
+ end
118
+ end
119
+ end
120
+
121
+ def unmonitor_process
122
+ if process_monitor_defined?
123
+ if unmonitor_process!
124
+ info "Turned off process monitor for #{service_entry}"
125
+ else
126
+ info "Failed to turn off process monitor for #{service_entry}"
127
+ end
128
+ end
129
+ end
130
+
131
+ protected
132
+
133
+ %i(start_process! stop_process!).each do |m|
134
+ define_method(m) do
135
+ raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
71
136
  end
72
137
  end
73
138
 
@@ -80,18 +145,53 @@ module Luban
80
145
  succeeded
81
146
  end
82
147
 
83
- protected
148
+ def check_process!
149
+ if pid_file_missing?
150
+ "#{package_full_name}: started but PID file does NOT exist - #{pid_file_path}"
151
+ elsif process_started?
152
+ "#{package_full_name}: started (PID #{pid})"
153
+ elsif pid_file_orphaned?
154
+ "#{package_full_name}: stopped but PID file exists - #{pid_file_path}"
155
+ else
156
+ "#{package_full_name}: stopped"
157
+ end
158
+ end
84
159
 
85
- %i(start_process! stop_process! check_process!).each do |m|
86
- define_method(m) do
87
- raise NotImplementedError, "#{self.class.name}##{__method__} is an abstract method."
160
+ def kill_process!(pattern = process_pattern)
161
+ capture(:pkill, "-9 -f \"#{pattern}\"")
162
+ end
163
+
164
+ def process_grep(pattern = process_pattern)
165
+ capture(:pgrep, "-l -f \"#{pattern}\" 2>/dev/null").split.inject({}) do |h, p|
166
+ pid, pname = p.split(' ', 2)
167
+ h[pid] = pname
168
+ h
88
169
  end
89
170
  end
90
171
 
91
- def kill_process!
92
- pid = capture(pid_file_path)
93
- output = capture(:kill, "-9 #{pid}")
94
- output.empty? ? pid : output
172
+ def remove_orphaned_pid_file
173
+ rm(pid_file_path) if pid_file_orphaned?
174
+ end
175
+
176
+ def monitor_process!
177
+ test(process_monitor_command)
178
+ end
179
+
180
+ def unmonitor_process!
181
+ test(process_unmonitor_command)
182
+ end
183
+
184
+ def process_monitor_executable
185
+ @process_monitor_executable ||= env_path.join("#{stage}.#{process_monitor[:env]}").
186
+ join('bin').join(process_monitor[:name])
187
+ end
188
+
189
+ def process_monitor_command
190
+ @process_monitor_command ||= "#{process_monitor_executable} monitor #{service_entry}"
191
+ end
192
+
193
+ def process_unmonitor_command
194
+ @process_unmonitor_command ||= "#{process_monitor_executable} unmonitor #{service_entry}"
95
195
  end
96
196
  end
97
197
  end
@@ -5,7 +5,11 @@ module Luban
5
5
  include Luban::Deployment::Worker::Paths::Remote::Service
6
6
 
7
7
  def service_name
8
- @service_name = package_name.downcase
8
+ @service_name ||= package_name.downcase
9
+ end
10
+
11
+ def service_entry
12
+ @service_entry ||= "#{env_name.gsub('/', '.')}.#{service_name}"
9
13
  end
10
14
 
11
15
  def profile_path
@@ -34,4 +38,4 @@ module Luban
34
38
  end
35
39
  end
36
40
  end
37
- end
41
+ end
@@ -14,8 +14,8 @@ module Luban
14
14
  test "[ -d #{path} ]"
15
15
  end
16
16
 
17
- def file?(path)
18
- test "[ -f #{path} ]"
17
+ def file?(path, test_op = "-f")
18
+ test "[ #{test_op} #{path} ]"
19
19
  end
20
20
 
21
21
  def symlink?(path)
@@ -53,9 +53,12 @@ module Luban
53
53
  end
54
54
 
55
55
  def rm(*opts, path)
56
+ execute(:rm, '-f', *opts, path)
57
+ end
58
+
59
+ def rmdir(*opts, path)
56
60
  execute(:rm, '-fr', *opts, path)
57
61
  end
58
- alias_method :rmdir, :rm
59
62
 
60
63
  def chmod(*opts, path)
61
64
  execute(:chmod, '-R', *opts, path)
@@ -101,35 +104,37 @@ module Luban
101
104
  @user_home ||= capture("eval echo ~")
102
105
  end
103
106
 
104
- def url_exists?(download_url)
107
+ def url_exists?(url)
105
108
  # Sent HEAD request to avoid downloading the file contents
106
- test("curl -s -L -I -o /dev/null -f #{download_url}")
109
+ test("curl -s -L -I -o /dev/null -f #{url}")
107
110
 
108
111
  # Other effective ways to check url existence with curl
109
112
 
110
113
  # In case HEAD request is refused,
111
114
  # only the first byte of the file is requested
112
- # test("curl -s -L -o /dev/null -f -r 0-0 #{download_url}")
115
+ # test("curl -s -L -o /dev/null -f -r 0-0 #{url}")
113
116
 
114
117
  # Alternatively, http code (200) can be validated
115
- # capture("curl -s -L -I -o /dev/null -w '%{http_code}' #{download_url}") == '200'
118
+ # capture("curl -s -L -I -o /dev/null -w '%{http_code}' #{url}") == '200'
116
119
  end
117
120
 
118
121
  def upload_by_template(file_to_upload:, template_file:, auto_revision: false, **opts)
119
- template = File.read(template_file)
120
-
121
122
  if auto_revision
122
123
  require 'digest/md5'
123
- revision = Digest::MD5.hexdigest(template)
124
+ revision = Digest::MD5.file(template_file).hexdigest
124
125
  return if revision_match?(file_to_upload, revision)
125
126
  end
126
127
 
127
- require 'erb'
128
- context = opts[:binding] || binding
129
- upload!(StringIO.new(ERB.new(template, nil, '<>').result(context)), file_to_upload)
128
+ upload!(StringIO.new(render_template(template_file, context: binding)), file_to_upload)
130
129
  yield file_to_upload if block_given?
131
130
  end
132
131
 
132
+ def render_template(template_file, context: binding)
133
+ require 'erb'
134
+ template = File.read(template_file)
135
+ ERB.new(template, nil, '<>').result(context)
136
+ end
137
+
133
138
  def revision_match?(file_to_upload, revision)
134
139
  file?(file_to_upload) and match?("grep \"Revision: \" #{file_to_upload}; true", revision)
135
140
  end
@@ -170,6 +175,10 @@ module Luban
170
175
  @hostname ||= host.hostname
171
176
  end
172
177
 
178
+ def now
179
+ Time.now().strftime("%d/%m/%Y %H:%M:%S")
180
+ end
181
+
173
182
  def method_missing(sym, *args, &blk)
174
183
  backend.respond_to?(sym) ? backend.send(sym, *args, &blk) : super
175
184
  end
@@ -64,8 +64,6 @@ module Luban
64
64
  parameter :stage
65
65
 
66
66
  parameter :process_monitor
67
- parameter :process_monitor_env
68
-
69
67
  parameter :sshkit_backend
70
68
  parameter :authen_key_type
71
69
  parameter :default_env
@@ -79,6 +77,7 @@ module Luban
79
77
  protected
80
78
 
81
79
  def set_default_project_parameters
80
+ set_default :process_monitor, {}
82
81
  set_default :sshkit_backend, SSHKit::Backend::Netssh
83
82
  set_default :authen_key_type, 'rsa'
84
83
  set_default :default_env, { path: '$PATH:/usr/local/bin' }
@@ -97,7 +96,16 @@ module Luban
97
96
  Luban::Deployment::Helpers::Configuration::Finder.project(self)
98
97
  end
99
98
 
100
- def validate_project_parameters; end
99
+ def validate_project_parameters
100
+ unless process_monitor.empty?
101
+ if process_monitor[:name].nil?
102
+ abort "Aborted! Please specify the process monitor."
103
+ end
104
+ if process_monitor[:env].nil?
105
+ abort "Aborted! Please specify the process monitor environment."
106
+ end
107
+ end
108
+ end
101
109
  end
102
110
 
103
111
  module Application
@@ -1,7 +1,7 @@
1
1
  # Luban environment activation resource file
2
- # Environment: <%= env_name = "#{stage}.#{project}/#{application}" %>
2
+ # Environment: <%= env_name %>
3
3
  # Revision: <%= revision %>
4
- # Created at <%= Time.now().strftime("%d/%m/%Y %H:%M:%S") %>
4
+ # Created at <%= now %>
5
5
 
6
6
  echo_line() {
7
7
  if [ "$PS1" ]; then
@@ -1,7 +1,7 @@
1
1
  # Environment de-activation resource file
2
- # Environment: <%= env_name = "#{stage}.#{project}/#{application}" %>
2
+ # Environment: <%= env_name %>
3
3
  # Revision: <%= revision %>
4
- # Created at <%= Time.now().strftime("%d/%m/%Y %H:%M:%S") %>
4
+ # Created at <%= now %>
5
5
 
6
6
  echo_line() {
7
7
  if [ "$PS1" ]; then
@@ -1,5 +1,5 @@
1
1
  module Luban
2
2
  module Deployment
3
- VERSION = "0.4.1"
3
+ VERSION = "0.4.2"
4
4
  end
5
5
  end
@@ -25,6 +25,10 @@ module Luban
25
25
  def osx?; os_name == 'Darwin'; end
26
26
  def linux?; os_name == 'Linux'; end
27
27
 
28
+ def env_name
29
+ @env_name ||= "#{stage}.#{project}/#{application}"
30
+ end
31
+
28
32
  def run
29
33
  update_result(__return__: @run_blk ? run_with_block : run_with_command).to_h
30
34
  end
@@ -62,7 +66,9 @@ module Luban
62
66
  r.level = level
63
67
  r.message = message unless message.nil? or !r.message.nil?
64
68
  attrs.each_pair { |k, v| r.send("#{k}=", v) }
65
- send(level, message) unless message.nil? or message.empty?
69
+ unless message.nil? or message.empty?
70
+ message.split("\n").each { |msg| send(level, msg) }
71
+ end
66
72
  end
67
73
  end
68
74
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: luban
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.1
4
+ version: 0.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Rubyist Lei
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2016-05-13 00:00:00.000000000 Z
11
+ date: 2016-06-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: luban-cli