epi 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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