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
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
# TODO: param validation
|
|
2
|
+
class Nodectl::Recipe
|
|
3
|
+
@manager = Nodectl::Manager.new(self)
|
|
4
|
+
|
|
5
|
+
SLOT_NAMES = [:install, :uninstall, :start, :version, :version_list, :version_set]
|
|
6
|
+
|
|
7
|
+
UnsupportedAction = Class.new(StandardError)
|
|
8
|
+
UnsupportedSlot = Class.new(StandardError)
|
|
9
|
+
|
|
10
|
+
LogNotFound = Class.new(Nodectl::NotFound)
|
|
11
|
+
|
|
12
|
+
attr_reader :name
|
|
13
|
+
|
|
14
|
+
def initialize(name, options = {})
|
|
15
|
+
@name = name
|
|
16
|
+
@actions = options[:actions] || {}
|
|
17
|
+
@slots = options[:slots] || {}
|
|
18
|
+
@triggers = options[:triggers] || {}
|
|
19
|
+
@events = options[:events] || {}
|
|
20
|
+
@logs = options[:logs] || {}
|
|
21
|
+
@params = options[:params] || {}
|
|
22
|
+
|
|
23
|
+
self.class[@name.to_s] = self
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def bind(service)
|
|
27
|
+
Nodectl::Binding.new(service, self)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def run_slot(service, slot_name, run_params = nil)
|
|
31
|
+
run_params ||= {}
|
|
32
|
+
|
|
33
|
+
unless @slots.key?(slot_name)
|
|
34
|
+
raise UnsupportedSlot, "slot '#{slot_name}' in not supported by '#{name}' recipe"
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
if slot_name == :install
|
|
38
|
+
install_deps(service)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
run_trigger(service, slot_name, :pre)
|
|
42
|
+
|
|
43
|
+
@params.each do |key, value|
|
|
44
|
+
if !run_params.key?(key) && value.key?(:default)
|
|
45
|
+
run_params[key] = value[:default]
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
run_params.keys.each do |key|
|
|
50
|
+
run_params.delete(key) unless @params.key?(key)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
result = context(service).instance_exec run_params, &@slots[slot_name]
|
|
54
|
+
run_trigger(service, slot_name, :post)
|
|
55
|
+
result
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def run_action(service, action_name, run_params = {})
|
|
59
|
+
unless @actions.key?(action_name)
|
|
60
|
+
raise UnsupportedAction, "action '#{action_name}' in not supported by '#{name}' recipe"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
@params.each do |key, value|
|
|
64
|
+
if !run_params.key?(key) && value.key?(:default)
|
|
65
|
+
run_params[key] = value[:default]
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
Nodectl::Action.run(service, action_name) do
|
|
70
|
+
run_trigger(service, action_name, :pre)
|
|
71
|
+
result = context(service).instance_exec run_params, &@actions[action_name]
|
|
72
|
+
run_trigger(service, action_name, :post)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def run_trigger(service, action_name, trigger_type)
|
|
77
|
+
unless [:pre, :post].include? trigger_type
|
|
78
|
+
raise "incorrect trigger type: expected 'post' or 'pre'"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
trigger = @triggers[:"#{trigger_type}_#{action_name}"]
|
|
82
|
+
context(service).instance_eval &trigger if trigger
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def get_logfile(service, log_name)
|
|
86
|
+
log = @logs.fetch(log_name)
|
|
87
|
+
log.instance_path(service)
|
|
88
|
+
rescue KeyError
|
|
89
|
+
raise LogNotFound, "log '#{log_name}' for recipe '#{name}' not found (service '#{service.name}')"
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def logs
|
|
93
|
+
@logs.keys
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def actions
|
|
97
|
+
@actions.keys
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def triggers
|
|
101
|
+
@triggers.keys
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def supported_actions(service)
|
|
105
|
+
@actions.keys
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def supported_slots(service)
|
|
109
|
+
@slots.keys
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def params
|
|
113
|
+
@params
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def context(service)
|
|
119
|
+
Nodectl::Context.new(self, service)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def install_deps(service)
|
|
123
|
+
service.deps.each do |service|
|
|
124
|
+
if service.status != :installed
|
|
125
|
+
service.recipe_binding.run_slot :install
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
class DSL
|
|
131
|
+
def initialize(name, &blk)
|
|
132
|
+
raise "Cannot create recipe without block" unless block_given?
|
|
133
|
+
|
|
134
|
+
@name = name
|
|
135
|
+
|
|
136
|
+
@actions = {}
|
|
137
|
+
@triggers = {}
|
|
138
|
+
@events = {}
|
|
139
|
+
@logs = {}
|
|
140
|
+
@params = {}
|
|
141
|
+
@slots = {}
|
|
142
|
+
|
|
143
|
+
instance_eval &blk
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def create
|
|
147
|
+
Nodectl::Recipe.new(@name, actions: @actions,
|
|
148
|
+
triggers: @triggers,
|
|
149
|
+
events: @events,
|
|
150
|
+
logs: @logs,
|
|
151
|
+
params: @params,
|
|
152
|
+
slots: @slots)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def action(name, &blk)
|
|
156
|
+
raise "Cannot create action without block" unless block_given?
|
|
157
|
+
@actions[name] = blk
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def slot(name, &blk)
|
|
161
|
+
raise "Cannot create slots without block" unless block_given?
|
|
162
|
+
raise "Unsupported slot name #{name}" unless Nodectl::Recipe::SLOT_NAMES.include?(name)
|
|
163
|
+
@slots[name] = blk
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def event(name, &blk)
|
|
167
|
+
raise "Cannot create event without block" unless block_given?
|
|
168
|
+
@events[name] = blk
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def trigger(name, &blk)
|
|
172
|
+
raise "Cannot create trigger without block" unless block_given?
|
|
173
|
+
@triggers[name] = blk
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
def param(name, options = {})
|
|
177
|
+
@params[name] = options
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
def log(name, path)
|
|
181
|
+
@logs[name.to_s] = Nodectl::Log.new(name, path)
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# Extension for ruby Object class for creating recipes anywhere
|
|
186
|
+
module ObjectMethods
|
|
187
|
+
def declare_recipe(*args, &blk)
|
|
188
|
+
Nodectl::Recipe.declare(*args, &blk)
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
class << self
|
|
193
|
+
def declare(*args, &blk)
|
|
194
|
+
DSL.new(*args, &blk).create
|
|
195
|
+
end
|
|
196
|
+
end
|
|
197
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
class Nodectl::Repository
|
|
2
|
+
VCSError = Class.new(StandardError)
|
|
3
|
+
|
|
4
|
+
def initialize(path)
|
|
5
|
+
@path = path
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def current_version
|
|
9
|
+
ret, out = shell_git("log -1 --pretty=format:%H\\ %ci\\ %s")
|
|
10
|
+
parse_version(out)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def switch_version(version)
|
|
14
|
+
shell_git("checkout -f #{version}")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def versions
|
|
18
|
+
fetch
|
|
19
|
+
|
|
20
|
+
ret, out = shell_git("log --all --pretty=format:%H\\ %ci\\ %s")
|
|
21
|
+
result = []
|
|
22
|
+
|
|
23
|
+
out.each_line do |line|
|
|
24
|
+
result << parse_version(line)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
result
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def fetch
|
|
31
|
+
shell_git("fetch")
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def pull
|
|
35
|
+
shell_git("checkout master")
|
|
36
|
+
shell_git("pull")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def shell_git(command, options = {})
|
|
42
|
+
source_path = options[:source_path] || @path
|
|
43
|
+
gitdir_path = options[:gitdir_path] || File.join(source_path, ".git")
|
|
44
|
+
|
|
45
|
+
self.class.shell_run("git --work-tree=#{source_path} --git-dir=#{gitdir_path} #{command} 2>&1", options)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse_version(line)
|
|
49
|
+
{
|
|
50
|
+
"id" => line[0...40],
|
|
51
|
+
"timestamp" => line[41...66],
|
|
52
|
+
"message" => line[67..-1]
|
|
53
|
+
}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
class << self
|
|
57
|
+
def clone(from, to)
|
|
58
|
+
FileUtils.mkdir_p(to)
|
|
59
|
+
ret, out = shell_run("git clone #{from} #{to}")
|
|
60
|
+
new(to)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def shell_run(command, options = {})
|
|
64
|
+
command = "#{command} 2>&1"
|
|
65
|
+
Nodectl.logger.info "repository: run command '#{command}'"
|
|
66
|
+
|
|
67
|
+
process = IO.popen(command)
|
|
68
|
+
|
|
69
|
+
out = process.read
|
|
70
|
+
process.close
|
|
71
|
+
ret = $?
|
|
72
|
+
|
|
73
|
+
if ret != 0 && !options[:ignore_status]
|
|
74
|
+
raise VCSError, "exit status: #{ret.exitstatus}"
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
[ret, out]
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
class Nodectl::Server
|
|
2
|
+
attr_reader :options
|
|
3
|
+
|
|
4
|
+
def initialize(options = {})
|
|
5
|
+
@options = self.class.default_options.merge(options)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def run
|
|
9
|
+
if @options["daemonize"]
|
|
10
|
+
if Process.fork
|
|
11
|
+
exit
|
|
12
|
+
else
|
|
13
|
+
Nodectl.shut_up!
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
if @options["pidfile"]
|
|
18
|
+
File.open(@options["pidfile"], "w") do |file|
|
|
19
|
+
file.write(Process.pid)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
at_exit { File.unlink @options["pidfile"] }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
Nodectl.logger.info "server with pid #{Process.pid} started"
|
|
26
|
+
at_exit { Nodectl.logger.info "server with pid #{Process.pid} shutting down" }
|
|
27
|
+
|
|
28
|
+
Nodectl::Service.load_register
|
|
29
|
+
Nodectl::Instance.load_register
|
|
30
|
+
|
|
31
|
+
EM.run do
|
|
32
|
+
setup_signals
|
|
33
|
+
rack_run
|
|
34
|
+
watchdog_run
|
|
35
|
+
stream_run
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def stop
|
|
40
|
+
@thin.stop if @thin
|
|
41
|
+
EM.stop
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def stop!
|
|
45
|
+
@thin.stop! if @thin
|
|
46
|
+
EM.stop
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def self.default_options
|
|
50
|
+
@default_options ||= YAML.load_file(Nodectl.options[:config_dir].join('server.yml'))
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def rack_run
|
|
56
|
+
options = @options
|
|
57
|
+
|
|
58
|
+
@thin = Thin::Server.new(@options["http_host"], @options["http_port"], signals: false) do
|
|
59
|
+
map('/instances') { run Nodectl::Webapp::Instances.new }
|
|
60
|
+
map('/services') { run Nodectl::Webapp::Services.new }
|
|
61
|
+
map('/actions') { run Nodectl::Webapp::Actions.new }
|
|
62
|
+
map('/versions') { run Nodectl::Webapp::Versions.new }
|
|
63
|
+
map('/settings') { run Nodectl::Webapp::Settings.new }
|
|
64
|
+
|
|
65
|
+
unless options["ui_disabled"]
|
|
66
|
+
map('/') { run Nodectl::Webapp::UI.new }
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
@thin.start!
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def watchdog_run
|
|
74
|
+
@watchdog = Nodectl::Watchdog.new
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def stream_run
|
|
78
|
+
@websocket = Nodectl::Stream::Websocket.new(@options["websocket_host"],
|
|
79
|
+
@options["websocket_port"])
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def setup_signals
|
|
83
|
+
@signal_queue ||= []
|
|
84
|
+
|
|
85
|
+
%w( INT TERM ).each do |signal|
|
|
86
|
+
trap(signal) { @signal_queue << signal }
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
@signal_timer ||= EM.add_periodic_timer(1) { handle_signals }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def handle_signals
|
|
93
|
+
case @signal_queue.shift
|
|
94
|
+
when 'INT'
|
|
95
|
+
stop!
|
|
96
|
+
when 'TERM', 'QUIT'
|
|
97
|
+
stop
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
EM.next_tick { handle_signals } unless @signal_queue.empty?
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
end
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
class Nodectl::Service
|
|
2
|
+
@manager = Nodectl::Manager.new(self)
|
|
3
|
+
|
|
4
|
+
attr_reader :manifest
|
|
5
|
+
|
|
6
|
+
def initialize(manifest)
|
|
7
|
+
@manifest = manifest
|
|
8
|
+
|
|
9
|
+
Nodectl::Service[name] = self
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def name; @manifest["name"]; end
|
|
13
|
+
def path; @manifest["path"]; end
|
|
14
|
+
def repo; @manifest["repo"]; end
|
|
15
|
+
|
|
16
|
+
def deps
|
|
17
|
+
(@manifest["deps"] || []).map do |service_name|
|
|
18
|
+
self.class.find!(service_name)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def recipe_name
|
|
23
|
+
@manifest["recipe"]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def inspect
|
|
27
|
+
"#<#{self.class} name:#{name}>"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def source_path
|
|
31
|
+
Nodectl.options[:source_dir].join(path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def exist?
|
|
35
|
+
# TODO: think of a better criterion for existing installation
|
|
36
|
+
source_path.exist?
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def status
|
|
40
|
+
exist? ? :installed : :available
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def recipe
|
|
44
|
+
@recipe ||= Nodectl::Recipe.find(recipe_name)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def recipe_binding
|
|
48
|
+
recipe.bind(self) if recipe
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def instances
|
|
52
|
+
Nodectl::Instance.all.select { |i| i.service == self }
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def supported_actions
|
|
56
|
+
binding = recipe_binding
|
|
57
|
+
if binding
|
|
58
|
+
binding.supported_actions
|
|
59
|
+
else
|
|
60
|
+
[]
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def supported_slots
|
|
65
|
+
binding = recipe_binding
|
|
66
|
+
if binding
|
|
67
|
+
binding.supported_slots
|
|
68
|
+
else
|
|
69
|
+
[]
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# TODO: Refactor this method (and may be whole log-related classes):
|
|
74
|
+
# service class should not know about log internals and streaming
|
|
75
|
+
# urls
|
|
76
|
+
def logs
|
|
77
|
+
unless @logs
|
|
78
|
+
if recipe
|
|
79
|
+
binding = recipe_binding
|
|
80
|
+
@logs = recipe.logs.map do |log|
|
|
81
|
+
{
|
|
82
|
+
"id" => "#{name}-#{log}",
|
|
83
|
+
"name" => log,
|
|
84
|
+
"path" => "/logs/#{name}/#{log}",
|
|
85
|
+
}
|
|
86
|
+
end
|
|
87
|
+
else
|
|
88
|
+
@logs = []
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
@logs
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def as_json
|
|
96
|
+
json = {
|
|
97
|
+
"id" => name,
|
|
98
|
+
"status" => status,
|
|
99
|
+
"instance_ids" => instances.map(&:pid),
|
|
100
|
+
"recipe_name" => recipe_name,
|
|
101
|
+
"supported_actions" => supported_actions,
|
|
102
|
+
"supported_slots" => supported_slots,
|
|
103
|
+
"logs" => logs,
|
|
104
|
+
"params" => recipe.params
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
binding = recipe_binding
|
|
108
|
+
|
|
109
|
+
if status == :installed && binding.supported_slots.include?(:version)
|
|
110
|
+
version = binding.run_slot(:version)
|
|
111
|
+
json["version_id"] = version["id"]
|
|
112
|
+
json["version_timestamp"] = version["timestamp"]
|
|
113
|
+
json["version_message"] = version["message"]
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
json
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
class << self
|
|
120
|
+
def load_register
|
|
121
|
+
Nodectl.manifest["services"].each do |service_manifest|
|
|
122
|
+
new(service_manifest)
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
end
|