nodectl 0.2.4
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 +18 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +24 -0
- data/Gemfile +4 -0
- data/README.md +3 -0
- data/Rakefile +1 -0
- data/TODO.md +7 -0
- data/assets/javascript/application.coffee +22 -0
- data/assets/javascript/bootstrap.js +2006 -0
- data/assets/javascript/controllers/application_controller.coffee +73 -0
- data/assets/javascript/controllers/service_controller.coffee +62 -0
- data/assets/javascript/event_handler.coffee +51 -0
- data/assets/javascript/models/action.coffee +28 -0
- data/assets/javascript/models/instance.coffee +7 -0
- data/assets/javascript/models/service.coffee +75 -0
- data/assets/javascript/models/version.coffee +3 -0
- data/assets/javascript/notifier.coffee +64 -0
- data/assets/javascript/router.coffee +3 -0
- data/assets/javascript/routes/application.coffee +6 -0
- data/assets/javascript/routes/log.coffee +4 -0
- data/assets/javascript/routes/service.coffee +3 -0
- data/assets/javascript/store.coffee +32 -0
- data/assets/javascript/templates/application.hbs +82 -0
- data/assets/javascript/templates/index.hbs +1 -0
- data/assets/javascript/templates/log.hbs +1 -0
- data/assets/javascript/templates/service.hbs +110 -0
- data/assets/javascript/templates/views/action.hbs +20 -0
- data/assets/javascript/templates/views/actions_list.hbs +26 -0
- data/assets/javascript/templates/views/environments_list.hbs +9 -0
- data/assets/javascript/templates/views/install_button.hbs +1 -0
- data/assets/javascript/templates/views/mass_run.hbs +30 -0
- data/assets/javascript/templates/views/notifier_control.hbs +7 -0
- data/assets/javascript/templates/views/param.hbs +6 -0
- data/assets/javascript/templates/views/terminal.hbs +8 -0
- data/assets/javascript/ui.coffee +24 -0
- data/assets/javascript/vendor/ansiparse.js +186 -0
- data/assets/javascript/vendor/audio.min.js +24 -0
- data/assets/javascript/vendor/audiojs.swf +0 -0
- data/assets/javascript/vendor/ember-data.js +10528 -0
- data/assets/javascript/vendor/ember.js +40583 -0
- data/assets/javascript/vendor/handlebars.js +2746 -0
- data/assets/javascript/vendor/jquery.js +8829 -0
- data/assets/javascript/vendor/ladda.js +157 -0
- data/assets/javascript/vendor/moment.min.js +6 -0
- data/assets/javascript/vendor/notify-osd.js +319 -0
- data/assets/javascript/vendor/player-graphics.gif +0 -0
- data/assets/javascript/vendor/spin.js +218 -0
- data/assets/javascript/views/action_view.coffee +18 -0
- data/assets/javascript/views/actions_list_view.coffee +2 -0
- data/assets/javascript/views/environments_list_view.coffee +2 -0
- data/assets/javascript/views/install_button_view.coffee +34 -0
- data/assets/javascript/views/mass_run_view.coffee +107 -0
- data/assets/javascript/views/notifier_control_view.coffee +29 -0
- data/assets/javascript/views/terminal_view.coffee +56 -0
- data/assets/stylesheets/application.css +137 -0
- data/assets/stylesheets/bootstrap-theme.css +347 -0
- data/assets/stylesheets/bootstrap.css +5785 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.eot +0 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.svg +229 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.ttf +0 -0
- data/assets/stylesheets/fonts/glyphicons-halflings-regular.woff +0 -0
- data/assets/stylesheets/ladda-themeless.css +330 -0
- data/assets/stylesheets/ladda.css +392 -0
- data/assets/stylesheets/notify-osd.css +49 -0
- data/assets/stylesheets/terminal.css +12 -0
- data/bin/nodectl +4 -0
- data/lib/nodectl.rb +137 -0
- data/lib/nodectl/action.rb +80 -0
- data/lib/nodectl/binding.rb +13 -0
- data/lib/nodectl/cli.rb +123 -0
- data/lib/nodectl/context.rb +34 -0
- data/lib/nodectl/database.rb +55 -0
- data/lib/nodectl/generators/init.rb +22 -0
- data/lib/nodectl/generators/templates/init/.gitignore +5 -0
- data/lib/nodectl/generators/templates/init/config/manifest.yml +1 -0
- data/lib/nodectl/generators/templates/init/config/server.yml +7 -0
- data/lib/nodectl/instance.rb +100 -0
- data/lib/nodectl/log.rb +13 -0
- data/lib/nodectl/manager.rb +71 -0
- data/lib/nodectl/multi_io.rb +16 -0
- data/lib/nodectl/options.rb +47 -0
- data/lib/nodectl/process.rb +145 -0
- data/lib/nodectl/promised_file.rb +37 -0
- data/lib/nodectl/recipe.rb +197 -0
- data/lib/nodectl/repository.rb +80 -0
- data/lib/nodectl/server.rb +103 -0
- data/lib/nodectl/service.rb +126 -0
- data/lib/nodectl/stream/buffer.rb +44 -0
- data/lib/nodectl/stream/events_session.rb +39 -0
- data/lib/nodectl/stream/file.rb +69 -0
- data/lib/nodectl/stream/file_session.rb +35 -0
- data/lib/nodectl/stream/file_with_memory.rb +17 -0
- data/lib/nodectl/stream/websocket.rb +90 -0
- data/lib/nodectl/version.rb +3 -0
- data/lib/nodectl/watchdog.rb +17 -0
- data/lib/nodectl/webapp/actions.rb +21 -0
- data/lib/nodectl/webapp/base.rb +29 -0
- data/lib/nodectl/webapp/instances.rb +46 -0
- data/lib/nodectl/webapp/public/sounds/alert.mp3 +0 -0
- data/lib/nodectl/webapp/public/sounds/error.mp3 +0 -0
- data/lib/nodectl/webapp/public/sounds/question.mp3 +0 -0
- data/lib/nodectl/webapp/services.rb +39 -0
- data/lib/nodectl/webapp/settings.rb +7 -0
- data/lib/nodectl/webapp/ui.rb +19 -0
- data/lib/nodectl/webapp/versions.rb +12 -0
- data/lib/nodectl/webapp/views/ui.haml +20 -0
- data/nodectl.gemspec +35 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/stream/buffer_spec.rb +33 -0
- metadata +365 -0
data/bin/nodectl
ADDED
data/lib/nodectl.rb
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
require "nodectl/version"
|
|
2
|
+
|
|
3
|
+
require "thor"
|
|
4
|
+
require "thin"
|
|
5
|
+
require "eventmachine"
|
|
6
|
+
require "em-websocket"
|
|
7
|
+
require 'sinatra/base'
|
|
8
|
+
require 'sinatra/json'
|
|
9
|
+
require "logger"
|
|
10
|
+
require 'stringio'
|
|
11
|
+
require "yaml"
|
|
12
|
+
require "time"
|
|
13
|
+
require "dbm"
|
|
14
|
+
require 'multi_json'
|
|
15
|
+
|
|
16
|
+
module Nodectl
|
|
17
|
+
autoload :Action, "nodectl/action.rb"
|
|
18
|
+
autoload :Binding, "nodectl/binding.rb"
|
|
19
|
+
autoload :CLI, "nodectl/cli.rb"
|
|
20
|
+
autoload :Context, "nodectl/context.rb"
|
|
21
|
+
autoload :Database, "nodectl/database.rb"
|
|
22
|
+
autoload :Log, "nodectl/log.rb"
|
|
23
|
+
autoload :Manager, "nodectl/manager.rb"
|
|
24
|
+
autoload :MultiIO, "nodectl/multi_io.rb"
|
|
25
|
+
autoload :Instance, "nodectl/instance.rb"
|
|
26
|
+
autoload :Options, "nodectl/options.rb"
|
|
27
|
+
autoload :PromisedFile, "nodectl/promised_file.rb"
|
|
28
|
+
autoload :Repository, "nodectl/repository.rb"
|
|
29
|
+
autoload :Recipe, "nodectl/recipe.rb"
|
|
30
|
+
autoload :Process, "nodectl/process.rb"
|
|
31
|
+
autoload :Server, "nodectl/server.rb"
|
|
32
|
+
autoload :Service, "nodectl/service.rb"
|
|
33
|
+
autoload :Watchdog, "nodectl/watchdog.rb"
|
|
34
|
+
|
|
35
|
+
module Generators
|
|
36
|
+
autoload :Init, "nodectl/generators/init.rb"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
module Webapp
|
|
40
|
+
autoload :Actions, "nodectl/webapp/actions.rb"
|
|
41
|
+
autoload :Base, "nodectl/webapp/base.rb"
|
|
42
|
+
autoload :Instances, "nodectl/webapp/instances.rb"
|
|
43
|
+
autoload :Services, "nodectl/webapp/services.rb"
|
|
44
|
+
autoload :Settings, "nodectl/webapp/settings.rb"
|
|
45
|
+
autoload :UI, "nodectl/webapp/ui.rb"
|
|
46
|
+
autoload :Versions, "nodectl/webapp/versions.rb"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
module Stream
|
|
50
|
+
autoload :Buffer, "nodectl/stream/buffer.rb"
|
|
51
|
+
autoload :EventsSession, "nodectl/stream/events_session.rb"
|
|
52
|
+
autoload :File, "nodectl/stream/file.rb"
|
|
53
|
+
autoload :FileSession, "nodectl/stream/file_session.rb"
|
|
54
|
+
autoload :FileWithMemory, "nodectl/stream/file_with_memory.rb"
|
|
55
|
+
autoload :Websocket, "nodectl/stream/websocket.rb"
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
NotFound = Class.new(Sinatra::NotFound)
|
|
59
|
+
|
|
60
|
+
class << self
|
|
61
|
+
attr_reader :options
|
|
62
|
+
|
|
63
|
+
# Node initialization: here default global options could be overriden
|
|
64
|
+
#
|
|
65
|
+
# @param options [Hash] overrides for default options
|
|
66
|
+
def boot(options = {})
|
|
67
|
+
@options = Options.new(options)
|
|
68
|
+
|
|
69
|
+
# Routing targets
|
|
70
|
+
Dir["#{@options[:recipe_dir]}/**/*.rb"].each do |path|
|
|
71
|
+
require path
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# Key-value store for node
|
|
76
|
+
#
|
|
77
|
+
# @return [Nodectl::Database]
|
|
78
|
+
def database
|
|
79
|
+
@database ||= Nodectl::Database.new(options[:database])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def server
|
|
83
|
+
@server ||= Nodectl::Server.new(options)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Get node manifest: list of hashes with service definitions. It looks like:
|
|
87
|
+
#
|
|
88
|
+
# [
|
|
89
|
+
# {
|
|
90
|
+
# "name" => "hub",
|
|
91
|
+
# "repo" => "git://example.com:service.git",
|
|
92
|
+
# "path" => "hub"
|
|
93
|
+
# },
|
|
94
|
+
# ...
|
|
95
|
+
# ]
|
|
96
|
+
#
|
|
97
|
+
# @return [Array] node manifest
|
|
98
|
+
def manifest
|
|
99
|
+
@manifest ||= YAML.load_file(@options[:manifest]) || []
|
|
100
|
+
rescue SystemCallError => e
|
|
101
|
+
$stderr.puts "Manifest loading error: #{e.message}"
|
|
102
|
+
abort
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# Get logger instance
|
|
106
|
+
#
|
|
107
|
+
# @return [Logger] logger instance
|
|
108
|
+
def logger
|
|
109
|
+
unless defined? @logger
|
|
110
|
+
file = File.open(options[:logger_dir].join("nodectl.log"), "a")
|
|
111
|
+
file.sync = true
|
|
112
|
+
io = MultiIO.new(STDOUT, file)
|
|
113
|
+
|
|
114
|
+
@logger ||= Logger.new(io)
|
|
115
|
+
@logger.level = Logger.const_get(options[:log_level])
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
@logger
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Redirect all standard streams to null device
|
|
122
|
+
def shut_up!
|
|
123
|
+
$stdout.reopen(File.open(File::NULL, "w"))
|
|
124
|
+
$stderr.reopen(File.open(File::NULL, "w"))
|
|
125
|
+
$stdin.reopen(File.open(File::NULL, "r"))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# Base path of assets folder
|
|
129
|
+
#
|
|
130
|
+
# @return [String]
|
|
131
|
+
def assets_path
|
|
132
|
+
File.absolute_path(File.join(__FILE__, "../../assets"))
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
include Nodectl::Recipe::ObjectMethods
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
class Nodectl::Action < Nodectl::Process
|
|
2
|
+
@manager = Nodectl::Manager.new(self)
|
|
3
|
+
|
|
4
|
+
attr_reader :id
|
|
5
|
+
attr_reader :service
|
|
6
|
+
attr_reader :name
|
|
7
|
+
attr_reader :output
|
|
8
|
+
|
|
9
|
+
def initialize(service, name, options = {}, &blk)
|
|
10
|
+
super(options, &blk)
|
|
11
|
+
|
|
12
|
+
@id = Nodectl::Action.next_id
|
|
13
|
+
@service = service
|
|
14
|
+
@name = name
|
|
15
|
+
|
|
16
|
+
onkill do
|
|
17
|
+
result = if @exit_status == 0
|
|
18
|
+
"succeed"
|
|
19
|
+
else
|
|
20
|
+
"failed"
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
self.class.onkill_call(self, reason: result)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def run
|
|
28
|
+
rout, @wout = IO.pipe
|
|
29
|
+
|
|
30
|
+
@output = Nodectl::Stream::FileWithMemory.new(rout)
|
|
31
|
+
|
|
32
|
+
super
|
|
33
|
+
|
|
34
|
+
@started_at = Time.now
|
|
35
|
+
|
|
36
|
+
Nodectl::Action[@id] = self
|
|
37
|
+
Nodectl.logger.info("action: [#{pid}] started: #{service.name}##{name}")
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def as_json
|
|
41
|
+
{
|
|
42
|
+
"id" => @id,
|
|
43
|
+
"pid" => @pid,
|
|
44
|
+
"name" => @name,
|
|
45
|
+
"service_id" => @service.name,
|
|
46
|
+
"ws" => "/actions/#{id}",
|
|
47
|
+
"exit_status" => @exit_status,
|
|
48
|
+
"started_at" => @started_at.iso8601
|
|
49
|
+
}
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
private
|
|
53
|
+
|
|
54
|
+
def pre_fork_block
|
|
55
|
+
$stdout.reopen(@wout)
|
|
56
|
+
$stderr.reopen(@wout)
|
|
57
|
+
|
|
58
|
+
$stdout.sync = true
|
|
59
|
+
$stderr.sync = true
|
|
60
|
+
|
|
61
|
+
@wout.close
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
def onkill(&blk)
|
|
66
|
+
@onkill ||= []
|
|
67
|
+
@onkill << blk
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def onkill_call(action, options = {})
|
|
71
|
+
@onkill.each { |b| b.call(action, options) }
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def run(service, name, *args, &blk)
|
|
75
|
+
action = new(service, name, *args, &blk)
|
|
76
|
+
action.run
|
|
77
|
+
action
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
class Nodectl::Binding
|
|
2
|
+
attr_reader :service
|
|
3
|
+
attr_reader :recipe
|
|
4
|
+
|
|
5
|
+
def initialize(service, recipe)
|
|
6
|
+
@service = service
|
|
7
|
+
@recipe = recipe
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def method_missing(name, *args, &blk)
|
|
11
|
+
@recipe.public_send(name, @service, *args, &blk)
|
|
12
|
+
end
|
|
13
|
+
end
|
data/lib/nodectl/cli.rb
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
class Nodectl::CLI < Thor
|
|
2
|
+
class_option :root, desc: "Root directory path"
|
|
3
|
+
class_option :config_dir, desc: "Config directory path"
|
|
4
|
+
class_option :source_dir, desc: "Source directory path"
|
|
5
|
+
class_option :logger_dir, desc: "Logger directory path"
|
|
6
|
+
class_option :manifest, desc: "Manifest file path"
|
|
7
|
+
class_option :database, desc: "Nodectl database path"
|
|
8
|
+
class_option :log_level, desc: "Log level", enum: ["DEBUG", "INFO", "WARN", "ERROR", "FATAL"]
|
|
9
|
+
|
|
10
|
+
desc "init [HOST_PATH]", "Initialize new node"
|
|
11
|
+
def init(host_path = "./")
|
|
12
|
+
Nodectl::Generators::Init.start [host_path]
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
desc "recipes", "Show available recipes list"
|
|
16
|
+
def recipes
|
|
17
|
+
boot
|
|
18
|
+
|
|
19
|
+
Nodectl::Recipe.all.each do |recipe|
|
|
20
|
+
puts recipe.name
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
desc "recipe RECIPE_NAME", "Show recipe info"
|
|
25
|
+
def recipe(recipe_name)
|
|
26
|
+
boot
|
|
27
|
+
|
|
28
|
+
recipe = Nodectl::Recipe.find!(recipe_name)
|
|
29
|
+
|
|
30
|
+
print "Logs: "
|
|
31
|
+
puts recipe.logs.map(&:to_s).join(", ")
|
|
32
|
+
|
|
33
|
+
print "Actions: "
|
|
34
|
+
puts recipe.actions.map(&:to_s).join(", ")
|
|
35
|
+
|
|
36
|
+
print "Triggers: "
|
|
37
|
+
puts recipe.triggers.map(&:to_s).join(", ")
|
|
38
|
+
|
|
39
|
+
rescue Nodectl::NotFound
|
|
40
|
+
$stderr.puts "recipe '#{recipe_name}' not found"
|
|
41
|
+
abort
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
desc "services", "Show services list"
|
|
45
|
+
def services
|
|
46
|
+
boot
|
|
47
|
+
|
|
48
|
+
Nodectl::Service.load_register
|
|
49
|
+
|
|
50
|
+
Nodectl::Service.all.each do |service|
|
|
51
|
+
puts "#{service.name} #{service.status}"
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
desc "install SERVICE_NAME", "Install specified service"
|
|
56
|
+
def install(service_name)
|
|
57
|
+
boot
|
|
58
|
+
|
|
59
|
+
service = find_service!(service_name)
|
|
60
|
+
recipe = find_recipe!(service)
|
|
61
|
+
|
|
62
|
+
recipe.run_action :install
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
option :ui_disabled, desc: "Disable UI and only API", type: :boolean
|
|
66
|
+
option :daemonize, desc: "Daemonize server", type: :boolean, default: false
|
|
67
|
+
option :pidfile, desc: "File to save server PID"
|
|
68
|
+
|
|
69
|
+
option :http_host, desc: "Host to bind webserver"
|
|
70
|
+
option :http_port, desc: "Port to bind webserver"
|
|
71
|
+
|
|
72
|
+
option :websocket_host, desc: "Host to bind websocket server"
|
|
73
|
+
option :websocket_port, desc: "Port to bind websocket server"
|
|
74
|
+
|
|
75
|
+
# AMPQ support is not implemented yet
|
|
76
|
+
#
|
|
77
|
+
# option :amqp_host, desc: "Host of AMQP broker"
|
|
78
|
+
# option :amqp_port, desc: "Port of AMQP broker"
|
|
79
|
+
# option :amqp_username, desc: "AMQP username"
|
|
80
|
+
# option :amqp_password, desc: "AMQP password"
|
|
81
|
+
# option :amqp_vhost, desc: "AMQP vhost"
|
|
82
|
+
|
|
83
|
+
desc 'server', 'Run node'
|
|
84
|
+
def server
|
|
85
|
+
boot
|
|
86
|
+
|
|
87
|
+
Nodectl::server.run
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
desc 'version', 'Show nodectl version'
|
|
91
|
+
def version
|
|
92
|
+
puts "Nodectl #{Nodectl::VERSION}"
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
private
|
|
96
|
+
|
|
97
|
+
def boot
|
|
98
|
+
Nodectl.boot(options)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def find_service!(service_name)
|
|
102
|
+
service = Nodectl::Service.find(service_name)
|
|
103
|
+
|
|
104
|
+
unless service
|
|
105
|
+
$stderr.puts "service '#{service_name}' not found"
|
|
106
|
+
abort
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
service
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def find_recipe!(service)
|
|
113
|
+
recipe = service.recipe_binding
|
|
114
|
+
|
|
115
|
+
unless recipe
|
|
116
|
+
$stderr.puts "recipe '#{service.recipe_name}' for service '#{service.name}' not found"
|
|
117
|
+
abort
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
recipe
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
class Nodectl::Context
|
|
2
|
+
attr_reader :recipe
|
|
3
|
+
attr_reader :service
|
|
4
|
+
|
|
5
|
+
def initialize(recipe, service, methods = nil)
|
|
6
|
+
@recipe = recipe
|
|
7
|
+
@service = service
|
|
8
|
+
|
|
9
|
+
if methods
|
|
10
|
+
extend methods
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def exec(*args)
|
|
15
|
+
Process.exec(*args)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def chdir(dir)
|
|
19
|
+
Dir.chdir(dir)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def run(command, options = {}, &blk)
|
|
23
|
+
instance = Nodectl::Instance.new(service, command, options) do
|
|
24
|
+
instance_eval &blk if blk
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
instance.run
|
|
28
|
+
instance
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def run_action(action_name, options = {})
|
|
32
|
+
service.recipe_binding.run_action(action_name, options)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
class Nodectl::Database
|
|
2
|
+
LoadError = Class.new(StandardError)
|
|
3
|
+
DumpError = Class.new(StandardError)
|
|
4
|
+
|
|
5
|
+
def initialize(file_path)
|
|
6
|
+
@db = DBM.open(file_path, 0666, DBM::WRCREAT)
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def reset
|
|
10
|
+
@db.clear
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def fetch(key, default = nil)
|
|
14
|
+
blob = @db[key]
|
|
15
|
+
|
|
16
|
+
if blob
|
|
17
|
+
load(blob)
|
|
18
|
+
elsif default
|
|
19
|
+
default
|
|
20
|
+
else
|
|
21
|
+
raise LoadError, "key not found: '#{key}'"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def [](key)
|
|
26
|
+
blob = @db[key]
|
|
27
|
+
|
|
28
|
+
if blob
|
|
29
|
+
load(blob)
|
|
30
|
+
else
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def []=(key, value)
|
|
36
|
+
@db[key] = Marshal.dump(value)
|
|
37
|
+
rescue TypeError => e
|
|
38
|
+
raise LoadError, "marshal dump error for key '#{key}' and value '#{value}': #{e.message}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def dump(value)
|
|
44
|
+
Marshal.dump(value)
|
|
45
|
+
rescue TypeError => e
|
|
46
|
+
raise LoadError, "marshal dump error for key '#{key}' and value '#{value}': #{e.message}"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def load(value)
|
|
50
|
+
Marshal.load(value)
|
|
51
|
+
rescue TypeError => e
|
|
52
|
+
raise LoadError, "marshal load error for key '#{key}': #{e.message}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
end
|