zkexec 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: a532a346ae00de20b532490448e745ae1132a57f
4
+ data.tar.gz: 08435fec85406659ec1f6266b598d9af96612c9c
5
+ SHA512:
6
+ metadata.gz: 84d232792462ce12690fb431f57240402e6570e58a5e4cf9c082f329f4211e79c25c9d2c6624d72d0e431db6361c89337fc00f4708bae92a2f3801a73fca6d49
7
+ data.tar.gz: 1067682fb483530f67a73fd071fd0fa9a57e0eb1ab55dca74253c14b2a57b45dff270f91fc9f3c9fa42555c7f60d77a0b0e2bd9bedb9b947bce6b64a799ddcb3
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in zkexec.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014 Kyle Maxwell
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,160 @@
1
+ # zkexec
2
+
3
+ zkexec is a wrapper around an executable that imports configuration from a zookeeper cluster. When zookeeper configuration changes, zkexec syncs the configuration, then restarts the executable. zkexec supports rolling restarts, health checks, and alerts.
4
+
5
+ Warning: this code is not yet being used in production.
6
+
7
+ ## Tutorial
8
+
9
+ ### Ante
10
+
11
+ 1. Get a local zookeeper running on :2181 (the default port).
12
+ 2. Checkout this git repo and cd into it.
13
+ 3. Either `gem install zkexec`, or `bundle; export PATH=$PATH:bin`
14
+
15
+ ### Calling zkexec
16
+
17
+ We'll start with the contrived example of running a script that acts just like cat, but it prefixes each line with the contents of a file:
18
+
19
+ :$ echo -n hello > /tmp/prefix
20
+ :$ echo world | ./test/libexec/prefixed-cat /tmp/prefix
21
+ hello world
22
+
23
+ We want to run this in the console, such that we can type input in stdin, and get our prefixed output. Here's the invokation (feel free to remove the --silent):
24
+
25
+ :$ zkexec --silent --exec "./test/libexec/prefixed-cat /tmp/prefix"
26
+
27
+ This doesn't do anything special--it just wraps the execution. Go ahead and run that, then type a few lines. Ctrl-c away, check the return code. It should behave just like `prefixed-cat`.
28
+
29
+ ### Updating the config files
30
+
31
+ Let's put the prefix in zookeeper. We'll throw it in the root, and use the --mirror flag to let zkexec know about it.
32
+
33
+ :$ zookeeper/bin/zkCli.sh -cmd create /prefix hello
34
+ :$ zkexec --silent \
35
+ --exec "./test/libexec/prefixed-cat /tmp/prefix" \
36
+ --mirror /tmp/prefix=/prefix
37
+ world # typed into stdin
38
+ hello world # stdout
39
+
40
+ So, now that we're tracking the config file, lets change it in another console. Leave the existing terminal running.
41
+
42
+ :$ zookeeper/bin/zkCli.sh -cmd set /prefix goodbye
43
+
44
+ Now, flip back to the original terminal and start typing. You'll note that now the prefix is `goodbye`!
45
+
46
+ ### Edge cases
47
+
48
+ #### What if you run a command that terminates?
49
+
50
+ zkexec will exit with the same error code. Try it with ``/usr/bin/false`! (oh and I'll take this opportunity to show the default logs that show up on stderr when you don't pass the --silent flag)
51
+
52
+ :$ zkexec --exec false # $ zkexec --exec false
53
+ [2014-04-28 16:41:38 -0700] connecting to localhost:2181
54
+ [2014-04-28 16:41:38 -0700] connected
55
+ [2014-04-28 16:41:38 -0700] forking false
56
+ [2014-04-28 16:41:38 -0700] forked 41804
57
+ [2014-04-28 16:41:38 -0700] command failed
58
+ :$ echo $?
59
+ 1
60
+
61
+ This means that you should have something (monit, etc) watching zkexec. zkexec is not trying to be an all purpose monitoring solution.
62
+
63
+ ### Adding health checks
64
+
65
+ A health check is simply an executable that returns 0 when the system is healthy, and non-zero otherwise. For this tutorial, we'll use `./test/libexec/slowcheck`. slowcheck takes two arguments, (1) the tcp port to check, and (2) the number of seconds to sleep before checking it. If and only if the port is open, slowcheck will succeed. The sleep will be useful later.
66
+
67
+ Let's append a health check to the previous command
68
+
69
+ :$ zkexec \
70
+ --exec "./test/libexec/prefixed-cat /tmp/prefix" \
71
+ --mirror /tmp/prefix=/prefix \
72
+ --health "./test/libexec/slowcheck 2181 1" \
73
+ --health-delay 1 \
74
+ --health-interval 1
75
+
76
+ The health-delay is the interval before the first health check (useful if your script is slow to start), and after the service starts, the health check will run every health-interval seconds. It's up to you to make the health check script reasonable. zkexec will happily run absurd scripts without timeouts, etc.
77
+
78
+ In this case, we're actually checking zookeeper's port. There's nothing special or required about that, it just happened to be convenient filler.
79
+
80
+ If you aren't running in silent mode, you'll see output like:
81
+
82
+ [2014-04-28 16:51:07 -0700] connecting to localhost:2181
83
+ [2014-04-28 16:51:07 -0700] connected
84
+ [2014-04-28 16:51:07 -0700] registering callback on /prefix
85
+ [2014-04-28 16:51:07 -0700] forking ./test/libexec/prefixed-cat /tmp/prefix
86
+ [2014-04-28 16:51:07 -0700] forked 42333
87
+ [2014-04-28 16:51:08 -0700] health checking via: ./test/libexec/slowcheck 2181 1
88
+ [2014-04-28 16:51:09 -0700] successful health check
89
+ [2014-04-28 16:51:10 -0700] health checking via: ./test/libexec/slowcheck 2181 1
90
+ [2014-04-28 16:51:12 -0700] successful health check
91
+
92
+
93
+ ### Adding alerts
94
+
95
+ An alert is simply an executable that gets called when a health check fails unexpectedly. Alerts won't trigger within `health-delay` seconds of a restart.
96
+
97
+ Alerts are provided for your convenience. In most cases, it's better to just add a dead man's switch to your health check.
98
+
99
+ #### A better alert scheme
100
+
101
+ Install a dead man's switch via monit via something similar to this sample code:
102
+
103
+ :$ mkdir -p /var/run/foo
104
+ :$ echo "check file succeeded\_at path /var/run/foo/succeeded\_at
105
+ if timestamp < 5 minutes then alert" > /etc/monit/conf.d/foo.conf
106
+ :$ sudo monit reload
107
+ :$
108
+ :$ zkexec \
109
+ ...
110
+ --health "./test/libexec/slowcheck 2181 1 && touch /tmp/succeeded_at"
111
+
112
+ ### Rolling restarts
113
+
114
+ Use `--lock NAME` to establish a rolling restart group. When a config file changes, all wrapped processes sharing the lock name and the zookeeper cluster must acquire the lock before killing the child process. A zkexec only releases the lock when health checks succeed or the zkexec parent process is killed.
115
+
116
+ Give it a try locally. In this case, we'll use the slowness of the health check script to our advantage, because we can watch the restart execute serially via the timestamps of the logs.
117
+
118
+ Run two or more of the following command in separate terminals:
119
+
120
+ :$ zkexec \
121
+ --exec "./test/libexec/prefixed-cat /tmp/prefix" \
122
+ --lock foo \
123
+ --health "test/libexec/slowcheck 2181 10" \
124
+ --mirror /tmp/prefix=/prefix \
125
+ --health-delay 20 \
126
+ --health-interval 1
127
+
128
+ Then, run the following:
129
+
130
+ :$ zookeeper/bin/zkCli.sh -cmd set /prefix woot
131
+
132
+ You can watch the timestamps of the zkexec processes as they log on stdout, noting that they take turns restarting.
133
+
134
+ #### Config failures in rolling restart
135
+
136
+ A config failure during a restart is defined as either (1) the restarted process exiting non-zero, or (2) the restarted process failing health checks for `health-delay` seconds.
137
+
138
+ A config failure pauses the restart. If this happens, you should push new config, and if it doesn't fail, the restart will pick up where it left off.
139
+
140
+ ### All options
141
+
142
+ zkexec doesn't use config files. All options are documented on the command line `--help`:
143
+
144
+ :$ zkexec -h
145
+ Usage: zkexec [options]
146
+
147
+ Run a command, and restart if the config files change on the remote zookeeper.
148
+
149
+ -e, --exec COMMAND Run this command
150
+ -c, --cluster HOST:PORT,... Comma-delimited list of zookeeper hosts
151
+ -H, --health COMMAND Run this command to health-check
152
+ -i, --health-interval INTERVAL Health-check every INTERVAL seconds
153
+ -d, --health-delay INTERVAL Wait before starting health checks
154
+ -m, --mirror LOCAL_PATH=ZK_PATH Mirror a config file from zookeeper to localhost
155
+ -l, --lock NAME Name of a zk lockfile, used to enforce rolling restarts
156
+ -a, --alert COMMAND Run this command if the primary command returns
157
+ falsey or health checks fail for too long
158
+ -v, --verbose
159
+ -s, --silent
160
+ -h, --help Show this message
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+ $LOAD_PATH.unshift File.join(File.dirname(__FILE__), "..", "lib")
3
+ Thread.abort_on_exception = true
4
+ require "zkexec"
5
+
6
+ runner = ZkExec::Runner.new
7
+ runner.run(ARGV)
@@ -0,0 +1,15 @@
1
+ require "zkexec/version"
2
+ require "zkexec/executor"
3
+ require "zkexec/runner"
4
+
5
+ require "zk"
6
+
7
+ module ZkExec
8
+ def log(s)
9
+ STDERR.puts("[#{Time.now}] #{s}") unless $silent
10
+ end
11
+
12
+ def silence!
13
+ $silent = true
14
+ end
15
+ end
@@ -0,0 +1,182 @@
1
+ require "zk"
2
+ require "thread"
3
+
4
+ module ZkExec
5
+ class Executor
6
+ include ZkExec
7
+ include Process
8
+
9
+ def initialize(options)
10
+ @cmd = options[:exec]
11
+ @cluster = options[:cluster]
12
+ @health = options[:health]
13
+ @health_interval = options[:health_interval]
14
+ @health_delay = options[:health_delay]
15
+ @mirrors = options[:mirrors]
16
+ @alert = options[:alert]
17
+ @lock_name = options[:lock]
18
+
19
+ log "connecting to #{@cluster}"
20
+ @zk = ZK.new(@cluster, :thread => :per_callback)
21
+ raise "timeout connecting to #{@cluster}" unless @zk.connected?
22
+ log "connected"
23
+
24
+ # re-establish watches
25
+ @on_connected ||= @zk.on_connected do
26
+ @mirrors.each do |(local, remote)|
27
+ watch(remote)
28
+ end
29
+ end
30
+
31
+ @restart_lock = @lock_name && @zk.exclusive_locker(@lock_name)
32
+ @local_lock = Mutex.new
33
+
34
+ @mirrors.each do |(local, remote)|
35
+ log "registering callback on #{remote}"
36
+ @zk.register(remote) do |event|
37
+ if event.changed?
38
+ log "#{remote} changed"
39
+ copy(local, remote)
40
+ kill_to_refork
41
+ else
42
+ watch(remote)
43
+ end
44
+ end
45
+ watch(remote)
46
+ end
47
+ end
48
+
49
+ private
50
+ def copy(local, remote)
51
+ data = watch(remote)
52
+ File.open(local, "w") {|f| f.print(data) }
53
+ rescue ZK::Exceptions::NoNode => e
54
+ raise "node not found in #{e.message}"
55
+ end
56
+
57
+ private
58
+ def watch(remote)
59
+ data, stat = *@zk.get(remote, :watch => true)
60
+ return data
61
+ rescue ZK::Exceptions::NoNode => e
62
+ raise "node not found in #{e.message}"
63
+ end
64
+
65
+ private
66
+ def with_restart_lock
67
+ if @restart_lock
68
+ begin
69
+ log "waiting on lock: #{@lock_name}"
70
+ @restart_lock.lock(:wait => true)
71
+ log "acquired lock: #{@lock_name}"
72
+ yield
73
+ ensure
74
+ @restart_lock.unlock
75
+ log "released lock: #{@lock_name}"
76
+ end
77
+ else
78
+ yield
79
+ end
80
+ end
81
+
82
+ private
83
+ def pid_exists?(pid)
84
+ begin
85
+ Process.getpgid(pid)
86
+ true
87
+ rescue Errno::ESRCH
88
+ false
89
+ end
90
+ end
91
+
92
+ private
93
+ def kill_to_refork
94
+ if @child
95
+ with_restart_lock do
96
+ if @health_checks
97
+ Thread.kill(@health_checks)
98
+ @health_checks = nil
99
+ end
100
+ @should_refork = true
101
+ child = @child
102
+ @child = nil
103
+
104
+ log "killing #{child}"
105
+ Process.kill("TERM", child)
106
+ checks_started = Time.now
107
+ while pid_exists?(child) && Time.now - checks_started < 30
108
+ sleep 1
109
+ end
110
+ if pid_exists?(child)
111
+ log "force killing #{child}"
112
+ Process.kill("KILL", child)
113
+ end
114
+
115
+ log "#{child} terminated"
116
+
117
+ # This intentionally infinite loops on failure, so that we don't propagate bad config
118
+ health_checks_started = Time.now
119
+ loop do
120
+ log "waiting for health check success"
121
+ if system(@health)
122
+ log "health checks succeeding"
123
+ return
124
+ end
125
+ if Time.now - health_checks_started > @health_delay
126
+ log "health checks failing"
127
+ alert
128
+ end
129
+ sleep @health_interval
130
+ end
131
+ end
132
+ end
133
+ end
134
+
135
+ def alert
136
+ if @alert
137
+ fork { exec(@alert) }
138
+ end
139
+ end
140
+
141
+ def start_health_thread
142
+ @health_checks ||= @health && Thread.new {
143
+ sleep @health_delay
144
+ loop do
145
+ log "health checking via: #{@health}"
146
+ pid = fork { exec(@health) }
147
+ wait pid
148
+ if $?.exitstatus == 0
149
+ log "successful health check"
150
+ else
151
+ log "failed health check, alerting"
152
+ alert
153
+ end
154
+ sleep @health_interval
155
+ end
156
+ }
157
+ end
158
+
159
+ public
160
+ def run
161
+ Thread.new { execute }
162
+ end
163
+
164
+ def execute
165
+ @should_refork = true
166
+
167
+ while @should_refork
168
+ start_health_thread
169
+ log "forking #{@cmd}"
170
+ @child = fork { exec @cmd }
171
+ log "forked #{@child}"
172
+ @should_refork = false
173
+ wait @child
174
+ end
175
+
176
+ if $?.exitstatus != 0
177
+ alert
178
+ raise "command failed"
179
+ end
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,82 @@
1
+ require "optparse"
2
+
3
+ module ZkExec
4
+ class Runner
5
+ include ZkExec
6
+
7
+ def initialize
8
+ end
9
+
10
+ def run(args)
11
+ options = { :mirrors => [], :cluster => "localhost:2181", :health_interval => 30, :health_delay => 30 }
12
+ opts = OptionParser.new do |opts|
13
+ opts.banner = "Usage: zkexec [options]\n\nRun a command, and restart if the config files change on the remote zookeeper.\n\n"
14
+
15
+ opts.on("-e", "--exec COMMAND", "Run this command") do |s|
16
+ options[:exec] = s
17
+ end
18
+
19
+ opts.on("-c", "--cluster HOST:PORT,...", "Comma-delimited list of zookeeper hosts") do |s|
20
+ options[:cluster] = s
21
+ end
22
+
23
+ opts.on("-H", "--health COMMAND", "Run this command to health-check") do |s|
24
+ options[:health] = s
25
+ end
26
+
27
+ opts.on("-i", "--health-interval INTERVAL", "Health-check every INTERVAL seconds") do |s|
28
+ options[:health_interval] = s.to_f
29
+ end
30
+
31
+ opts.on("-d", "--health-delay INTERVAL", "Wait before starting health checks") do |s|
32
+ options[:health_delay] = s.to_f
33
+ end
34
+
35
+ opts.on("-m", "--mirror LOCAL_PATH=ZK_PATH", "Mirror a config file from zookeeper to localhost") do |s|
36
+ options[:mirrors] << s.split("=")
37
+ end
38
+
39
+ opts.on("-l", "--lock NAME", "Name of a zk lockfile, used to enforce rolling restarts") do |s|
40
+ options[:lock] = s
41
+ end
42
+
43
+ opts.on("-a", "--alert COMMAND", "Run this command if the primary command returns","falsey or health checks fail for too long") do |s|
44
+ options[:alert] = s
45
+ end
46
+
47
+ opts.on("-v", "--verbose") do
48
+ options[:verbose] = true
49
+ end
50
+
51
+ opts.on("-s", "--silent") do
52
+ options[:silent] = true
53
+ end
54
+
55
+ opts.on_tail("-h", "--help", "Show this message") do
56
+ puts opts
57
+ exit
58
+ end
59
+ end
60
+
61
+ opts.parse!
62
+
63
+ unless options[:exec]
64
+ puts "Missing required option --exec. See --help for usage."
65
+ exit 1
66
+ end
67
+
68
+ silence! if options[:silent]
69
+
70
+ begin
71
+ Executor.new(options).execute
72
+ rescue => e
73
+ if options[:verbose]
74
+ raise
75
+ else
76
+ log(e.message)
77
+ exit 1
78
+ end
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,3 @@
1
+ module ZkExec
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ prefixes = ARGV.map{ |arg| File.read(arg) }
4
+
5
+ while line = STDIN.gets
6
+ puts [prefixes + [line]].join(" ")
7
+ end
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ if ARGV.length != 2
4
+ abort "usage: #{$0} PORT SLEEP"
5
+ end
6
+
7
+ require "socket"
8
+
9
+ sleep ARGV[1].to_f
10
+ TCPSocket.new("localhost", ARGV[0].to_i).close
11
+
@@ -0,0 +1,27 @@
1
+ gem "minitest"
2
+ require "minitest/autorun"
3
+ require "minitest/spec"
4
+ require "socket"
5
+
6
+ TCPSocket.new("localhost", 2181).close \
7
+ rescue abort "No zookeeper running, please start a local one!"
8
+
9
+ ZKEXEC = File.expand_path(File.join(File.dirname(__FILE__), "..", "bin", "zkexec"))
10
+
11
+ describe "zkexec" do
12
+ it "should fail when run without args" do
13
+ system("#{ZKEXEC}").must_equal(false)
14
+ end
15
+
16
+ it "should run with exec" do # note this is /usr/bin/true or equivalent
17
+ system("#{ZKEXEC} --exec true").must_equal(true)
18
+ end
19
+
20
+ it "should keep the inner response code" do # note this is /usr/bin/false or equivalent
21
+ system("#{ZKEXEC} --exec false").must_equal(false)
22
+ end
23
+
24
+ it "should alert on failure" do
25
+ `#{ZKEXEC} --exec false --alert 'echo FOO'`.must_match(/FOO/)
26
+ end
27
+ end
@@ -0,0 +1,25 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'zkexec/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "zkexec"
8
+ spec.version = ZkExec::VERSION
9
+ spec.authors = ["Kyle Maxwell"]
10
+ spec.email = ["kyle@kylemaxwell.com"]
11
+ spec.summary = %q{Run a process in a wrapper that manages config files from zookeeper}
12
+ spec.description = %q{Run a process in a wrapper that manages config files from zookeeper}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_runtime_dependency "zk", "1.9.4"
22
+
23
+ spec.add_development_dependency "bundler", "~> 1.5"
24
+ spec.add_development_dependency "rake"
25
+ end
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: zkexec
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Kyle Maxwell
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2014-04-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: zk
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '='
18
+ - !ruby/object:Gem::Version
19
+ version: 1.9.4
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '='
25
+ - !ruby/object:Gem::Version
26
+ version: 1.9.4
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.5'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.5'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ description: Run a process in a wrapper that manages config files from zookeeper
56
+ email:
57
+ - kyle@kylemaxwell.com
58
+ executables:
59
+ - zkexec
60
+ extensions: []
61
+ extra_rdoc_files: []
62
+ files:
63
+ - ".gitignore"
64
+ - Gemfile
65
+ - LICENSE.txt
66
+ - README.md
67
+ - Rakefile
68
+ - bin/zkexec
69
+ - lib/zkexec.rb
70
+ - lib/zkexec/executor.rb
71
+ - lib/zkexec/runner.rb
72
+ - lib/zkexec/version.rb
73
+ - test/libexec/prefixed-cat
74
+ - test/libexec/slowcheck
75
+ - test/zk_exec_test.rb
76
+ - zkexec.gemspec
77
+ homepage: ''
78
+ licenses:
79
+ - MIT
80
+ metadata: {}
81
+ post_install_message:
82
+ rdoc_options: []
83
+ require_paths:
84
+ - lib
85
+ required_ruby_version: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: '0'
95
+ requirements: []
96
+ rubyforge_project:
97
+ rubygems_version: 2.2.2
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Run a process in a wrapper that manages config files from zookeeper
101
+ test_files:
102
+ - test/libexec/prefixed-cat
103
+ - test/libexec/slowcheck
104
+ - test/zk_exec_test.rb