epi 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/bin/epi +16 -0
- data/lib/epi.rb +52 -0
- data/lib/epi/cli.rb +22 -0
- data/lib/epi/cli/command.rb +27 -0
- data/lib/epi/cli/commands/config.rb +28 -0
- data/lib/epi/cli/commands/job.rb +19 -0
- data/lib/epi/cli/commands/server.rb +38 -0
- data/lib/epi/cli/commands/status.rb +13 -0
- data/lib/epi/configuration_file.rb +56 -0
- data/lib/epi/core_ext.rb +1 -0
- data/lib/epi/core_ext/inflector.rb +11 -0
- data/lib/epi/data.rb +152 -0
- data/lib/epi/exceptions.rb +10 -0
- data/lib/epi/exceptions/base.rb +7 -0
- data/lib/epi/exceptions/fatal.rb +12 -0
- data/lib/epi/exceptions/invalid_configuration_file.rb +14 -0
- data/lib/epi/exceptions/shutdown.rb +7 -0
- data/lib/epi/job.rb +110 -0
- data/lib/epi/job_description.rb +107 -0
- data/lib/epi/jobs.rb +83 -0
- data/lib/epi/launch.rb +59 -0
- data/lib/epi/process_status.rb +71 -0
- data/lib/epi/running_process.rb +159 -0
- data/lib/epi/server.rb +104 -0
- data/lib/epi/server/receiver.rb +46 -0
- data/lib/epi/server/responder.rb +44 -0
- data/lib/epi/server/responders/command.rb +15 -0
- data/lib/epi/server/responders/config.rb +30 -0
- data/lib/epi/server/responders/job.rb +70 -0
- data/lib/epi/server/responders/shutdown.rb +15 -0
- data/lib/epi/server/responders/status.rb +52 -0
- data/lib/epi/server/sender.rb +64 -0
- data/lib/epi/version.rb +3 -0
- metadata +106 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 65b6f4c3b4d118849bb76a9bb617296e7abfc0d0
|
4
|
+
data.tar.gz: 01905d5290fbadfe2ab466a0c6d593c88a3b0a7d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a17b31c2de560b44ff2cc927eaaaee9a987bb828ea526367d8fd57da134f651ca5a99e3c91e7f9b22db80383e3272835bf1a4bc569368741bc74ee95a98ffd6e
|
7
|
+
data.tar.gz: f1c1de71a10027df1c70c137716034eec7a462f729383f81552e4f7132844956bd3a15e20b240d430cff8f6e941b6a0cb5f5da0a11a72f1cc21c1acd59a114df
|
data/bin/epi
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
lib = File.expand_path('../../lib', __FILE__)
|
3
|
+
$: << lib unless $:.include? lib
|
4
|
+
require 'epi'
|
5
|
+
require 'eventmachine'
|
6
|
+
if $0 == __FILE__
|
7
|
+
EventMachine.run do
|
8
|
+
%w[INT TERM].each do |s|
|
9
|
+
Signal.trap(s) do
|
10
|
+
puts " #{s} => bye for now!"
|
11
|
+
EventMachine.stop
|
12
|
+
end
|
13
|
+
end
|
14
|
+
Epi::Cli.run ARGV
|
15
|
+
end
|
16
|
+
end
|
data/lib/epi.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'logger'
|
3
|
+
require 'shellwords'
|
4
|
+
|
5
|
+
require 'epi/core_ext'
|
6
|
+
require 'epi/exceptions'
|
7
|
+
require 'epi/version'
|
8
|
+
require 'epi/cli'
|
9
|
+
require 'epi/server'
|
10
|
+
require 'epi/data'
|
11
|
+
require 'epi/process_status'
|
12
|
+
require 'epi/running_process'
|
13
|
+
require 'epi/job'
|
14
|
+
require 'epi/jobs'
|
15
|
+
require 'epi/configuration_file'
|
16
|
+
require 'epi/job_description'
|
17
|
+
require 'epi/launch'
|
18
|
+
|
19
|
+
module Epi
|
20
|
+
ROOT = Pathname File.expand_path('../..', __FILE__)
|
21
|
+
|
22
|
+
class << self
|
23
|
+
|
24
|
+
def logger
|
25
|
+
@logger ||= make_logger
|
26
|
+
end
|
27
|
+
|
28
|
+
def logger=(value)
|
29
|
+
@logger = Logger === value ? value : make_logger(value)
|
30
|
+
end
|
31
|
+
|
32
|
+
def root?
|
33
|
+
@is_root = `whoami`.chomp == 'root' if @is_root.nil?
|
34
|
+
@is_root
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def make_logger(target = nil)
|
40
|
+
Logger.new(target || ENV['EPI_LOG'] || STDOUT).tap do |logger|
|
41
|
+
logger.level = Logger::Severity::WARN
|
42
|
+
level = ENV['EPI_LOG_LEVEL']
|
43
|
+
if level
|
44
|
+
level = level.upcase
|
45
|
+
logger.level = Logger::Severity.const_get(level) if Logger::Severity.const_defined? level
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|
data/lib/epi/cli.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require_relative 'cli/command'
|
2
|
+
|
3
|
+
module Epi
|
4
|
+
module Cli
|
5
|
+
|
6
|
+
class << self
|
7
|
+
|
8
|
+
def run(args)
|
9
|
+
command = args.shift
|
10
|
+
begin
|
11
|
+
Command.run command, args
|
12
|
+
rescue Exceptions::Fatal => error
|
13
|
+
STDERR << error.message
|
14
|
+
STDERR << "\n"
|
15
|
+
EventMachine.stop_event_loop
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
module Epi
|
2
|
+
module Cli
|
3
|
+
unless defined? Command
|
4
|
+
|
5
|
+
class Command
|
6
|
+
|
7
|
+
def self.run(command, args)
|
8
|
+
const_name = command.camelize.to_sym
|
9
|
+
if Commands.const_defined? const_name
|
10
|
+
klass = Commands.const_get const_name
|
11
|
+
return klass.new(args).run if Class === klass && klass < self
|
12
|
+
end
|
13
|
+
raise Exceptions::Fatal, 'Unknown command'
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :args
|
17
|
+
|
18
|
+
def initialize(args)
|
19
|
+
@args = args
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
|
24
|
+
Dir[File.expand_path '../commands/*.rb', __FILE__].each { |f| require f }
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Epi
|
2
|
+
module Cli
|
3
|
+
module Commands
|
4
|
+
class Config < Command
|
5
|
+
|
6
|
+
def run
|
7
|
+
case args.shift
|
8
|
+
when 'add' then add
|
9
|
+
else raise Exceptions::Fatal, 'Unknown config command, use [ add ]'
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def add
|
16
|
+
raise Exceptions::Fatal, 'No path given' unless args.first
|
17
|
+
paths = args.map do |path|
|
18
|
+
path = Pathname(path)
|
19
|
+
path = Pathname('.').realpath.join(path) unless path.absolute?
|
20
|
+
path.to_s
|
21
|
+
end
|
22
|
+
Epi::Server.send config: {add_paths: paths}
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Epi
|
2
|
+
module Cli
|
3
|
+
module Commands
|
4
|
+
class Job < Command
|
5
|
+
|
6
|
+
def run
|
7
|
+
id = args.shift
|
8
|
+
raise Exceptions::Fatal, 'No job ID given' if id.nil? || id.empty?
|
9
|
+
instruction = args.join(' ').strip
|
10
|
+
raise Exceptions::Fatal, 'No instruction given' if instruction.empty?
|
11
|
+
raise Exceptions::Fatal, 'Invalid instruction' unless
|
12
|
+
instruction =~ /^((\d+ )?(more|less)|\d+|pause|resume|reset|max|min|restart)$/
|
13
|
+
Epi::Server.send job: {id: id, instruction: instruction}
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Epi
|
2
|
+
module Cli
|
3
|
+
module Commands
|
4
|
+
class Server < Command
|
5
|
+
|
6
|
+
def run
|
7
|
+
process = Epi::Server.process
|
8
|
+
raise Exceptions::Fatal, 'You need root privileges to manage this server' if
|
9
|
+
process && process.was_alive? && process.root? && !Epi.root?
|
10
|
+
case args.first
|
11
|
+
when nil, 'start' then startup
|
12
|
+
when 'run' then run_server
|
13
|
+
when 'stop' then shutdown
|
14
|
+
else raise Exceptions::Fatal, 'Unknown server command, use [ start | stop | restart ]'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def startup
|
21
|
+
Epi::Server.ensure_running
|
22
|
+
puts 'Server is running'
|
23
|
+
end
|
24
|
+
|
25
|
+
def shutdown
|
26
|
+
Epi::Server.shutdown
|
27
|
+
puts 'Server has shut down'
|
28
|
+
end
|
29
|
+
|
30
|
+
def run_server
|
31
|
+
Epi::Server.run
|
32
|
+
puts 'Server is running'
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'pathname'
|
3
|
+
require 'digest'
|
4
|
+
|
5
|
+
module Epi
|
6
|
+
class ConfigurationFile
|
7
|
+
extend Forwardable
|
8
|
+
include Exceptions
|
9
|
+
|
10
|
+
attr_reader :path, :job_descriptions
|
11
|
+
|
12
|
+
delegate [:exist?, :binread] => :path
|
13
|
+
|
14
|
+
def initialize(path)
|
15
|
+
@job_descriptions = {}
|
16
|
+
@path = Pathname path
|
17
|
+
end
|
18
|
+
|
19
|
+
def logger
|
20
|
+
Epi.logger
|
21
|
+
end
|
22
|
+
|
23
|
+
def read
|
24
|
+
return unless exist? && changed?
|
25
|
+
logger.info "Reading configuration file #{path}"
|
26
|
+
data = binread
|
27
|
+
begin
|
28
|
+
instance_eval data, path.to_s
|
29
|
+
@last_digest = Digest::MD5.digest(data)
|
30
|
+
rescue => error
|
31
|
+
raise InvalidConfigurationFile.new("Unhandled exception of type #{error.class.name}", error)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def changed?
|
36
|
+
Digest::MD5.digest(binread) != @last_digest
|
37
|
+
end
|
38
|
+
|
39
|
+
def job(id_and_name, &block)
|
40
|
+
raise InvalidConfigurationFile, 'Improper use of "job"' unless
|
41
|
+
Hash === id_and_name &&
|
42
|
+
id_and_name.count == 1 &&
|
43
|
+
Symbol === id_and_name.keys.first &&
|
44
|
+
String === id_and_name.values.first &&
|
45
|
+
block.respond_to?(:call) &&
|
46
|
+
block.respond_to?(:arity) &&
|
47
|
+
block.arity >= 1
|
48
|
+
id, name = id_and_name.first
|
49
|
+
id = id.to_s
|
50
|
+
job_description = @job_descriptions[id] ||= JobDescription.new(id)
|
51
|
+
job_description.name = name
|
52
|
+
job_description.reconfigure &block
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
end
|
data/lib/epi/core_ext.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative 'core_ext/inflector'
|
data/lib/epi/data.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
require 'bson'
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module Epi
|
6
|
+
class Data
|
7
|
+
include Exceptions
|
8
|
+
|
9
|
+
ROOT_HOME = '/etc/epi'
|
10
|
+
|
11
|
+
class << self
|
12
|
+
extend Forwardable
|
13
|
+
|
14
|
+
delegate [:[], :[]=, :read, :write, :root?, :save, :reload, :home] => :default_instance
|
15
|
+
|
16
|
+
%w[server_pid].each do |property|
|
17
|
+
define_method(property) { read property }
|
18
|
+
define_method(property + '=') { |value| write property, value }
|
19
|
+
end
|
20
|
+
|
21
|
+
def configuration_paths
|
22
|
+
self['configuration_paths'] ||= []
|
23
|
+
end
|
24
|
+
|
25
|
+
def jobs
|
26
|
+
self['jobs'] ||= {}
|
27
|
+
end
|
28
|
+
|
29
|
+
def jobs=(value)
|
30
|
+
self['jobs'] = value
|
31
|
+
end
|
32
|
+
|
33
|
+
# Get the default data storage instance
|
34
|
+
# @return [self]
|
35
|
+
def default_instance
|
36
|
+
@default_instance ||= new(detect_home_dir)
|
37
|
+
end
|
38
|
+
|
39
|
+
# Remove the default instance. Useful if the home path changes.
|
40
|
+
def reset!
|
41
|
+
@default_instance = nil
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# Try to guess the Epi home directory, by first looking at the
|
47
|
+
# `EPI_HOME` environment variable, then '/etc/epi', then '~/.epi'
|
48
|
+
# @return [String]
|
49
|
+
def detect_home_dir
|
50
|
+
custom_home_dir || root_home_dir || user_home_dir
|
51
|
+
end
|
52
|
+
|
53
|
+
# The home directory specified in the environment
|
54
|
+
# @return [String|NilClass]
|
55
|
+
def custom_home_dir
|
56
|
+
ENV['EPI_HOME']
|
57
|
+
end
|
58
|
+
|
59
|
+
# The root home directory, if it exists or can be created
|
60
|
+
# @return [String|NilClass]
|
61
|
+
def root_home_dir
|
62
|
+
(Epi.root? || Dir.exist?(ROOT_HOME)) && ROOT_HOME
|
63
|
+
end
|
64
|
+
|
65
|
+
# The user's home directory
|
66
|
+
# @return [String]
|
67
|
+
def user_home_dir
|
68
|
+
"#{ENV['HOME'] || '~'}/.epi"
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
attr_reader :home
|
74
|
+
|
75
|
+
# @param home [String] The directory in which all working files should be stored.
|
76
|
+
def initialize(home)
|
77
|
+
@home = Pathname home
|
78
|
+
prepare_home!
|
79
|
+
end
|
80
|
+
|
81
|
+
# Read a file as UTF-8
|
82
|
+
# @param file_name [String] Name of the file to read
|
83
|
+
# @return [String|NilClass] Contents of the file, or `nil` if the file doesn't exist.
|
84
|
+
def read(file_name)
|
85
|
+
path = home + file_name
|
86
|
+
path.exist? ? path.read : nil
|
87
|
+
end
|
88
|
+
|
89
|
+
# Write a file as UTF-8
|
90
|
+
# @param file_name [String] Name of the file to write
|
91
|
+
# @param data [Object] Data to be written to the file, or `nil` if the file should be deleted.
|
92
|
+
def write(file_name, data)
|
93
|
+
path = home + file_name
|
94
|
+
if data.nil?
|
95
|
+
path.delete if path.exist?
|
96
|
+
nil
|
97
|
+
else
|
98
|
+
data = data.to_s
|
99
|
+
path.parent.mkpath
|
100
|
+
path.write data
|
101
|
+
path.chmod 0644
|
102
|
+
data.length
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def data_file
|
107
|
+
@data_file ||= home + 'data.bson'
|
108
|
+
end
|
109
|
+
|
110
|
+
# Force reload of data from disk
|
111
|
+
def reload
|
112
|
+
@hash = nil
|
113
|
+
end
|
114
|
+
|
115
|
+
# Save data to disk
|
116
|
+
def save
|
117
|
+
data_file.binwrite hash.to_bson
|
118
|
+
data_file.chmod 0644
|
119
|
+
end
|
120
|
+
|
121
|
+
def hash
|
122
|
+
@hash ||= data_file.exist? ? Hash.from_bson(StringIO.new data_file.binread) : {}
|
123
|
+
end
|
124
|
+
|
125
|
+
# Returns true if using root data at /etc/epi, or false if using user data
|
126
|
+
# is at ~/.epi
|
127
|
+
# @return [TrueClass|FalseClass]
|
128
|
+
def root?
|
129
|
+
@is_root
|
130
|
+
end
|
131
|
+
|
132
|
+
def [](key)
|
133
|
+
hash[key.to_s]
|
134
|
+
end
|
135
|
+
|
136
|
+
def []=(key, value)
|
137
|
+
hash[key.to_s] = value
|
138
|
+
end
|
139
|
+
|
140
|
+
private
|
141
|
+
|
142
|
+
# Ensures the home directory exists and is readable
|
143
|
+
# @raise [Fatal]
|
144
|
+
def prepare_home!
|
145
|
+
@is_root = @home.to_s == ROOT_HOME
|
146
|
+
home.mkpath
|
147
|
+
raise Fatal, 'We need write and execute permissions for ' << home.to_s unless
|
148
|
+
home.exist? && home.readable? && home.executable?
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
end
|