epi 0.0.1
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 +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
|