nand 0.1.2
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/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +3 -0
- data/CODE_OF_CONDUCT.md +13 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +21 -0
- data/README.ja.md +206 -0
- data/README.md +203 -0
- data/Rakefile +6 -0
- data/bin/console +12 -0
- data/bin/setup +7 -0
- data/exe/nand +19 -0
- data/lib/nand.rb +47 -0
- data/lib/nand/cli.rb +126 -0
- data/lib/nand/daemon.rb +127 -0
- data/lib/nand/launcher.rb +55 -0
- data/lib/nand/launcher/executable_file_launcher.rb +35 -0
- data/lib/nand/launcher/plugin_launcher.rb +29 -0
- data/lib/nand/launcher/rb_file_launcher.rb +63 -0
- data/lib/nand/launcher/shell_launcher.rb +25 -0
- data/lib/nand/logger.rb +42 -0
- data/lib/nand/plugin.rb +22 -0
- data/lib/nand/proc_operation.rb +47 -0
- data/lib/nand/util/object.rb +25 -0
- data/lib/nand/version.rb +5 -0
- data/nand.gemspec +31 -0
- data/samples/spec_mon.rb +67 -0
- data/spec/nand/cli_spec.rb +8 -0
- data/spec/nand/daemon_spec.rb +8 -0
- data/spec/nand/launcher/executable_file_launcher_spec.rb +8 -0
- data/spec/nand/launcher/plugin_launcher_spec.rb +8 -0
- data/spec/nand/launcher/rb_file_launcher_spec.rb +8 -0
- data/spec/nand/launcher/shell_launcher_spec.rb +8 -0
- data/spec/nand/launcher_spec.rb +8 -0
- data/spec/nand/logger_spec.rb +8 -0
- data/spec/nand/plugin_spec.rb +22 -0
- data/spec/nand/proc_operation_spec.rb +44 -0
- data/spec/nand/util/object_spec.rb +8 -0
- data/spec/nand/version_spec.rb +10 -0
- data/spec/nand_spec.rb +28 -0
- data/spec/spec_helper.rb +2 -0
- metadata +198 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'nand/launcher'
|
5
|
+
|
6
|
+
module Nand
|
7
|
+
module Launcher
|
8
|
+
class RbFileLauncher < Base
|
9
|
+
def self.launchable?( target, io, opts )
|
10
|
+
return false unless target.to_s =~/\.rb$/
|
11
|
+
require_rb(target) and
|
12
|
+
true
|
13
|
+
rescue LoadError => e
|
14
|
+
io.puts "\t- " + e.message
|
15
|
+
false
|
16
|
+
rescue => e
|
17
|
+
io.puts "\t- " + e.message
|
18
|
+
false
|
19
|
+
end
|
20
|
+
def self.load( target, opts, *argv )
|
21
|
+
plugin = opts[:plugin] || File.basename(target, ".rb").split(/_/).map{|s| s.capitalize}.join
|
22
|
+
#raise "Option --plugin is Required for #{target}" if plugin.nil?
|
23
|
+
executor = Plugin.plugin!(plugin, *argv)
|
24
|
+
raise "Executor #{plugin} is Not Emplemented exec Method" unless executor.respond_to?(:exec)
|
25
|
+
RbFileLauncher.new(target, executor, opts, *argv)
|
26
|
+
rescue LoadError => e
|
27
|
+
raise "Not Found Plugin #{target}"
|
28
|
+
end
|
29
|
+
def self.require_rb(target)
|
30
|
+
require "#{RbFileLauncher.rb_file(target).to_s}"
|
31
|
+
end
|
32
|
+
def self.rb_file(target)
|
33
|
+
Pathname.new(target).expand_path
|
34
|
+
end
|
35
|
+
def initialize(target, executor, opts, *argv)
|
36
|
+
super(target, opts, *argv)
|
37
|
+
@executor = executor
|
38
|
+
end
|
39
|
+
def launch
|
40
|
+
if child = Process.fork
|
41
|
+
child
|
42
|
+
else
|
43
|
+
begin
|
44
|
+
STDIN.reopen(@exec_stdin)
|
45
|
+
STDOUT.reopen(@exec_stdout)
|
46
|
+
STDERR.reopen(@exec_stderr)
|
47
|
+
Signal.trap(:INT) {exit 0}
|
48
|
+
Signal.trap(:TERM) {exit 0}
|
49
|
+
Process.setpgrp
|
50
|
+
@executor.exec
|
51
|
+
rescue LoadError => e
|
52
|
+
STDERR.puts e.message
|
53
|
+
rescue => e
|
54
|
+
STDERR.puts e.message
|
55
|
+
ensure
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'nand/launcher'
|
4
|
+
|
5
|
+
module Nand
|
6
|
+
module Launcher
|
7
|
+
class ShellLauncher < Base
|
8
|
+
def self.launchable?(target, io, opts)
|
9
|
+
require 'mkmf'
|
10
|
+
raise "Not Executable #{target}" if find_executable0(target).nil?
|
11
|
+
true
|
12
|
+
rescue => e
|
13
|
+
io.puts "\t- " + e.message
|
14
|
+
false
|
15
|
+
end
|
16
|
+
def self.load(target, opts = {}, *argv)
|
17
|
+
new(target, opts, *argv)
|
18
|
+
end
|
19
|
+
def cmd; "#{@progname} #{@argv.join(" ")}" end
|
20
|
+
def launch
|
21
|
+
spawn("#{cmd}", :out => @exec_stdout, :err => @exec_stderr, :in => @exec_stdin, :pgroup => true)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/nand/logger.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'logger'
|
4
|
+
|
5
|
+
module Nand
|
6
|
+
module Logging
|
7
|
+
LOG_FATAL = ::Logger::FATAL
|
8
|
+
LOG_ERROR = ::Logger::ERROR
|
9
|
+
LOG_WARN = ::Logger::WARN
|
10
|
+
LOG_INFO = ::Logger::INFO
|
11
|
+
LOG_DEBUG = ::Logger::DEBUG
|
12
|
+
|
13
|
+
def logger_output_params; [STDOUT] end
|
14
|
+
def logger_formatter; SimpleFormatter.new end
|
15
|
+
def logger_progname ; "" end
|
16
|
+
def log
|
17
|
+
@log ||= begin
|
18
|
+
logger = ::Logger.new(*logger_output_params)
|
19
|
+
logger.formatter = logger_formatter
|
20
|
+
logger.progname = logger_progname
|
21
|
+
logger.level = LOG_WARN
|
22
|
+
logger
|
23
|
+
end
|
24
|
+
end
|
25
|
+
def log_debug!; log.level = LOG_DEBUG end
|
26
|
+
|
27
|
+
class SimpleFormatter < ::Logger::Formatter
|
28
|
+
def call(severity, time, progname, msg)
|
29
|
+
format = "%s\n"
|
30
|
+
format % [msg2str(msg)]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
class TimeFormatter < ::Logger::Formatter
|
34
|
+
def call(severity, time, progname, msg)
|
35
|
+
# time PID ThreadID LEVEL progname message
|
36
|
+
format = "%s #%d[%x] - %s - %s: %s\n"
|
37
|
+
format % ["#{time.strftime('%H:%M:%S.%6N')}",
|
38
|
+
$$, Thread.current.object_id, severity[0...1], progname, msg2str(msg)]
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
data/lib/nand/plugin.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Nand
|
4
|
+
module Plugin
|
5
|
+
def plugin_name; self.name end
|
6
|
+
|
7
|
+
def executor(*argv) ; raise "Not Implemented #{__method__} in #{self}" end
|
8
|
+
def self.extended(klass)
|
9
|
+
raise "Already Registered Name #{klass.plugin_name}" if extended_class_map.include? klass.plugin_name
|
10
|
+
extended_class_map[klass.plugin_name] = klass
|
11
|
+
end
|
12
|
+
def self.plugin!( name, *argv )
|
13
|
+
raise "Unregisterd #{name}" unless extended_class_map.include? name
|
14
|
+
extended_class_map[name].executor(*argv)
|
15
|
+
end
|
16
|
+
private
|
17
|
+
def self.extended_class_map
|
18
|
+
@@extended_class_map ||= {}
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'sys/proctable'
|
4
|
+
|
5
|
+
module Nand
|
6
|
+
module ProcOperation
|
7
|
+
def pid_filename( name ); ".nand_#{name}.pid" end
|
8
|
+
def ps(pid); Sys::ProcTable.ps(pid) end
|
9
|
+
|
10
|
+
def running_ps?(ps, name = nil)
|
11
|
+
ps.cmdline =~ /#{$PROGRAM_NAME}/ and ps.cmdline =~/start/ and ( name.nil? or ps.cmdline.include? name.to_s)
|
12
|
+
end
|
13
|
+
def running_with?(pid, name)
|
14
|
+
proc = ps(pid)
|
15
|
+
!proc.nil? and running_ps?(proc, name)
|
16
|
+
end
|
17
|
+
def pid_from_pidfile(file)
|
18
|
+
raise "File Not Found #{file}" unless FileTest.exist? file
|
19
|
+
raise "File Not Readable #{file}" unless FileTest.readable? file
|
20
|
+
File.open(file, "r"){|fs| fs.read }.strip.to_i
|
21
|
+
end
|
22
|
+
def name_from_pidfile(file)
|
23
|
+
File.basename(file).to_s.scan(/^\.nand_(\S+)\.pid$/).flatten.first
|
24
|
+
end
|
25
|
+
def running_in(dir, pattern = nil)
|
26
|
+
pattern ||= "*"
|
27
|
+
Dir.glob(dir.join(pid_filename(pattern))).
|
28
|
+
map{|f|[pid_from_pidfile(f), name_from_pidfile(f)]}.
|
29
|
+
map{ |pid, name| Daemon.new(dir, name, :uid => ps(pid).euid)}
|
30
|
+
end
|
31
|
+
def all_runnings
|
32
|
+
uid = Process.uid
|
33
|
+
runnings = Sys::ProcTable.ps.select{ |ps| running_ps?(ps) and ps.ppid == 1 and (ps.euid == uid or uid == 0)}
|
34
|
+
dirs = runnings.map do |run|
|
35
|
+
boot_dir = run.environ["PWD"]
|
36
|
+
run_dir = run.cmdline.scan(/--run_dir\s+(\S+)/).flatten.first
|
37
|
+
Pathname.new(run_dir || boot_dir)
|
38
|
+
end.uniq
|
39
|
+
daemons = dirs.map{ |d| running_in(d) }.flatten
|
40
|
+
unless daemons.size == runnings.size
|
41
|
+
unmanageds = runnings.reject{ |ps| daemons.find{|d| d.pid == ps.pid} }.map{ |ps| ps.pid}
|
42
|
+
raise "Found Unmanaged Nand Process [#{unmanageds.join(", ")}], Send Signal Yourself"
|
43
|
+
end
|
44
|
+
daemons
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
module Nand
|
4
|
+
module Util
|
5
|
+
class ::Object
|
6
|
+
def whoami
|
7
|
+
self.is_a?(Class) ? self : self.class
|
8
|
+
end
|
9
|
+
def short_name
|
10
|
+
whoami.to_s.split("::").last
|
11
|
+
end
|
12
|
+
def full_name
|
13
|
+
whoami.to_s
|
14
|
+
end
|
15
|
+
def parent_name
|
16
|
+
self.parent_class.to_s
|
17
|
+
end
|
18
|
+
def parent_class(upper = 1 )
|
19
|
+
whoami.to_s.split("::")[0...-1*upper].inject( ::Object ){ |parent, child| parent.const_get( child ) }
|
20
|
+
end
|
21
|
+
alias :namespace :parent_class
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
data/lib/nand/version.rb
ADDED
data/nand.gemspec
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'nand/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "nand"
|
8
|
+
spec.version = Nand::VERSION
|
9
|
+
spec.authors = ["nstoym"]
|
10
|
+
spec.email = ["nstoym@linkode.co.jp"]
|
11
|
+
spec.description = %q{Nand is a simple CLI tool to make anything daemon by Ruby.}
|
12
|
+
spec.summary = %q{Nandemo Daemon Tool}
|
13
|
+
spec.homepage = "https://github.com/linkodehub/nand"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.bindir = "exe"
|
18
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
19
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
20
|
+
spec.require_paths = ["lib"]
|
21
|
+
|
22
|
+
spec.add_development_dependency "bundler", "~> 1.9"
|
23
|
+
spec.add_development_dependency "rake"
|
24
|
+
spec.add_development_dependency "rspec"
|
25
|
+
spec.add_development_dependency "pry"
|
26
|
+
spec.add_development_dependency "fssm"
|
27
|
+
|
28
|
+
spec.required_ruby_version = '>=2.0'
|
29
|
+
spec.add_dependency "thor"
|
30
|
+
spec.add_dependency "sys-proctable"
|
31
|
+
end
|
data/samples/spec_mon.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'fssm'
|
5
|
+
require 'nand/plugin'
|
6
|
+
|
7
|
+
class SpecMon
|
8
|
+
extend Plugin
|
9
|
+
def self.executor(*argv)
|
10
|
+
top_dir = Pathname.new(argv.shift || Dir.pwd).expand_path
|
11
|
+
lib_dir = top_dir.join("lib")
|
12
|
+
spec_dir = top_dir.join("spec")
|
13
|
+
|
14
|
+
[top_dir, lib_dir, spec_dir].each do |dir|
|
15
|
+
raise "Not Found Directory #{dir}" unless dir.exist?
|
16
|
+
raise "Not Directory #{dir}" unless dir.directory?
|
17
|
+
end
|
18
|
+
|
19
|
+
raise "Not Readable Directory #{lib_dir}" unless lib_dir.readable?
|
20
|
+
raise "Not Writable Directory #{spec_dir}" unless spec_dir.writable?
|
21
|
+
puts "Monitoring #{lib_dir} and Generate to #{spec_dir}"
|
22
|
+
new(lib_dir, spec_dir)
|
23
|
+
end
|
24
|
+
attr_reader :monitoring_dir, :spec_dir
|
25
|
+
def initialize( monitoring_dir, spec_dir )
|
26
|
+
@monitoring_dir = Pathname.new(monitoring_dir).expand_path
|
27
|
+
@spec_dir = Pathname.new(spec_dir).expand_path
|
28
|
+
end
|
29
|
+
def monitor
|
30
|
+
spec_dir = @spec_dir
|
31
|
+
FSSM.monitor(@monitoring_dir, "**/*") do
|
32
|
+
create do |dir, file|
|
33
|
+
if file =~/\.rb$/
|
34
|
+
puts "Created Ruby File #{file} in #{dir}"
|
35
|
+
spec = spec_dir.join(file.gsub(/\.rb$/, "_spec.rb"))
|
36
|
+
puts "Generate Spec file #{spec}"
|
37
|
+
unless spec.dirname.exist?
|
38
|
+
puts "Not Found Dist Path #{spec.dirname}, mkpath!"
|
39
|
+
FileUtils.mkdir_p(spec.dirname, :mode => 0755)
|
40
|
+
end
|
41
|
+
unless spec.exist?
|
42
|
+
File.open(spec, "w") do |fs|
|
43
|
+
code = <<-END_SPEC
|
44
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
45
|
+
require 'spec_helper'
|
46
|
+
require '#{file.gsub(/\.rb$/, "")}'
|
47
|
+
|
48
|
+
describe "#{file} specification" do
|
49
|
+
it "Not Specify"
|
50
|
+
end
|
51
|
+
|
52
|
+
END_SPEC
|
53
|
+
fs.puts code
|
54
|
+
end
|
55
|
+
if spec.exist?
|
56
|
+
puts "Success Genrated #{spec}"
|
57
|
+
else
|
58
|
+
puts "Failed Genrated #{spec}"
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
alias :exec :monitor
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'nand/plugin'
|
4
|
+
|
5
|
+
describe Nand::Plugin do
|
6
|
+
before(:all) do
|
7
|
+
class Hoge ; extend Plugin end
|
8
|
+
class Fuga ; extend Plugin ; def self.executor(*argv); nil end end
|
9
|
+
end
|
10
|
+
it { expect(Hoge).to be_respond_to :executor }
|
11
|
+
it :plugin_name do
|
12
|
+
expect(Hoge.plugin_name).to eq Hoge.to_s
|
13
|
+
end
|
14
|
+
it "implemented executor" do
|
15
|
+
expect(Fuga.executor).to be_nil
|
16
|
+
end
|
17
|
+
it "not implemented executor" do
|
18
|
+
expect{Hoge.executor}.to raise_error RuntimeError
|
19
|
+
end
|
20
|
+
it "#plugin!" do expect(Plugin.plugin!("Fuga")).to be_nil end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,44 @@
|
|
1
|
+
# -*-mode: ruby; coding: utf-8 -*-
|
2
|
+
require 'spec_helper'
|
3
|
+
require 'nand/proc_operation'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
include ProcOperation
|
7
|
+
|
8
|
+
describe "nand/proc_operation.rb specification" do
|
9
|
+
before(:all) do
|
10
|
+
@work_dir = Pathname.new("/tmp")
|
11
|
+
end
|
12
|
+
context :pid_filename do
|
13
|
+
it { expect(pid_filename("abc")).to eq ".nand_abc.pid" }
|
14
|
+
end
|
15
|
+
context :name_from_pidfile do
|
16
|
+
before(:all){ @pidfile = @work_dir.join(".nand_abc.pid"); system("touch #{@pidfile}")}
|
17
|
+
after(:all){ @pidfile.delete if @pidfile.exist? }
|
18
|
+
it { expect(name_from_pidfile(@pidfile)).to eq "abc" }
|
19
|
+
end
|
20
|
+
context :ps do
|
21
|
+
let(:proc){ps(Process.pid)}
|
22
|
+
it { expect(proc).to_not be_nil }
|
23
|
+
it { expect(proc).to be_is_a Struct::ProcTableStruct }
|
24
|
+
it { expect(proc.cmdline).to be_include "rspec" }
|
25
|
+
end
|
26
|
+
context :pid_from_pidfile do
|
27
|
+
before(:all) do
|
28
|
+
@pidfile = @work_dir.join(".nand_abc.pid")
|
29
|
+
@pid = 12345
|
30
|
+
File.open(@pidfile, "w"){|fs| fs.puts @pid }
|
31
|
+
end
|
32
|
+
after(:all){ @pidfile.delete if @pidfile.exist? }
|
33
|
+
it { expect(pid_from_pidfile(@pidfile)).to eq @pid}
|
34
|
+
end
|
35
|
+
context :running_in do
|
36
|
+
end
|
37
|
+
context :running_ps? do
|
38
|
+
end
|
39
|
+
context :running_with? do
|
40
|
+
end
|
41
|
+
context :all_runnings do
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|