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 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,13 @@
1
+ module Epi
2
+ module Cli
3
+ module Commands
4
+ class Status < Command
5
+
6
+ def run
7
+ Epi::Server.send :status
8
+ end
9
+
10
+ end
11
+ end
12
+ end
13
+ 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
@@ -0,0 +1 @@
1
+ require_relative 'core_ext/inflector'
@@ -0,0 +1,11 @@
1
+ class String
2
+ def camelize
3
+ gsub(/(^|_)[a-z\d]/) { |m| m.upcase }
4
+ end
5
+ end
6
+
7
+ class Symbol
8
+ def camelize
9
+ to_s.camelize.to_sym
10
+ end
11
+ end
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