nodectl 0.2.4
Sign up to get free protection for your applications and to get access to all the features.
- 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,44 @@
|
|
1
|
+
class Nodectl::Stream::Buffer
|
2
|
+
def initialize
|
3
|
+
@buffer = ""
|
4
|
+
@valid = ""
|
5
|
+
end
|
6
|
+
|
7
|
+
def <<(string)
|
8
|
+
string = @buffer + string
|
9
|
+
@buffer = ""
|
10
|
+
@buffer.force_encoding(Encoding::ASCII_8BIT)
|
11
|
+
|
12
|
+
string.force_encoding(Encoding::UTF_8)
|
13
|
+
|
14
|
+
16.times do
|
15
|
+
if string.valid_encoding?
|
16
|
+
break
|
17
|
+
end
|
18
|
+
|
19
|
+
string, byte = byte_rotate(string)
|
20
|
+
@buffer << byte
|
21
|
+
end
|
22
|
+
|
23
|
+
if string.valid_encoding?
|
24
|
+
@valid << string
|
25
|
+
else
|
26
|
+
Nodectl.logger.error("buffer: cannot convert string to valid UTF-8, ignore it")
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def read
|
31
|
+
ret = @valid
|
32
|
+
@valid = ""
|
33
|
+
ret
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def byte_rotate(string)
|
39
|
+
byte = string.byteslice(-1)
|
40
|
+
string = string.byteslice(0...-1)
|
41
|
+
byte.force_encoding(Encoding::ASCII_8BIT)
|
42
|
+
[string, byte]
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Nodectl::Stream::EventsSession
|
2
|
+
@manager = Nodectl::Manager.new(self)
|
3
|
+
|
4
|
+
attr_reader :websocket
|
5
|
+
attr_reader :file
|
6
|
+
|
7
|
+
def initialize(websocket, handshake)
|
8
|
+
@websocket = websocket
|
9
|
+
@handshake = handshake
|
10
|
+
|
11
|
+
self.class[self.class.next_id] = self
|
12
|
+
end
|
13
|
+
|
14
|
+
def publish(resource, id, event_name, payload = {})
|
15
|
+
message = MultiJson.dump({resource: resource,
|
16
|
+
id: id,
|
17
|
+
event_name: event_name,
|
18
|
+
payload: payload})
|
19
|
+
|
20
|
+
@websocket.send(message)
|
21
|
+
|
22
|
+
Nodectl.logger.debug("events_session: sent 'message'")
|
23
|
+
end
|
24
|
+
|
25
|
+
def close
|
26
|
+
self.class.delete(self)
|
27
|
+
@websocket.close unless @websocket.closed?
|
28
|
+
end
|
29
|
+
|
30
|
+
def onclose(&block)
|
31
|
+
@websocket.onclose(&block)
|
32
|
+
end
|
33
|
+
|
34
|
+
class << self
|
35
|
+
def publish(resource, id, event_name, payload = {})
|
36
|
+
all.each { |session| session.publish(resource, id, event_name, payload) }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
class Nodectl::Stream::File
|
2
|
+
def initialize(file, options = {})
|
3
|
+
@onread = []
|
4
|
+
@onclose = []
|
5
|
+
|
6
|
+
if file.respond_to? :read_nonblock
|
7
|
+
@file = file
|
8
|
+
else
|
9
|
+
begin
|
10
|
+
@file = File.new(file)
|
11
|
+
rescue Errno::ENOENT
|
12
|
+
raise Nodectl::NotFound, "file not found"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
@options = options
|
17
|
+
|
18
|
+
if @options[:end_offset]
|
19
|
+
offset = @file.size - @options[:end_offset]
|
20
|
+
if offset > 0
|
21
|
+
@file.seek(offset)
|
22
|
+
@options[:end_offset].times do
|
23
|
+
break if @file.readchar == "\n"
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def onread(&blk)
|
30
|
+
@onread << blk
|
31
|
+
end
|
32
|
+
|
33
|
+
def onclose(&blk)
|
34
|
+
@onclose << blk
|
35
|
+
end
|
36
|
+
|
37
|
+
def read_chunk
|
38
|
+
chunk = @file.read_nonblock(4096)
|
39
|
+
|
40
|
+
if chunk.size > 0
|
41
|
+
onread_call(chunk)
|
42
|
+
end
|
43
|
+
rescue Errno::EAGAIN, EOFError
|
44
|
+
# Nothing to do
|
45
|
+
end
|
46
|
+
|
47
|
+
def close
|
48
|
+
onclose_call
|
49
|
+
@file.close
|
50
|
+
end
|
51
|
+
|
52
|
+
def closed?
|
53
|
+
@file.closed?
|
54
|
+
end
|
55
|
+
|
56
|
+
private
|
57
|
+
|
58
|
+
def file
|
59
|
+
@file
|
60
|
+
end
|
61
|
+
|
62
|
+
def onread_call(chunk)
|
63
|
+
@onread.each { |blk| blk.call(chunk) }
|
64
|
+
end
|
65
|
+
|
66
|
+
def onclose_call
|
67
|
+
@onclose.each { |blk| blk.call }
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
class Nodectl::Stream::FileSession
|
2
|
+
@manager = Nodectl::Manager.new(self)
|
3
|
+
|
4
|
+
attr_reader :websocket
|
5
|
+
attr_reader :file
|
6
|
+
|
7
|
+
def initialize(websocket, handshake, file)
|
8
|
+
@websocket = websocket
|
9
|
+
@handshake = handshake
|
10
|
+
@file = file
|
11
|
+
@buffer = Nodectl::Stream::Buffer.new
|
12
|
+
|
13
|
+
@file.onread do |chunk|
|
14
|
+
@buffer << chunk
|
15
|
+
publish(@buffer.read)
|
16
|
+
end
|
17
|
+
|
18
|
+
self.class[self.class.next_id] = self
|
19
|
+
end
|
20
|
+
|
21
|
+
def publish(message)
|
22
|
+
@websocket.send(message)
|
23
|
+
Nodectl.logger.debug("file_session: sent #{message.bytesize} bytes")
|
24
|
+
end
|
25
|
+
|
26
|
+
def close
|
27
|
+
self.class.delete(self)
|
28
|
+
@file.close
|
29
|
+
@websocket.close unless @websocket.closed?
|
30
|
+
end
|
31
|
+
|
32
|
+
def onclose(&block)
|
33
|
+
@websocket.onclose(&block)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Nodectl::Stream::FileWithMemory < Nodectl::Stream::File
|
2
|
+
def initialize(file, options = {})
|
3
|
+
super(file, options)
|
4
|
+
|
5
|
+
@memory = ""
|
6
|
+
end
|
7
|
+
|
8
|
+
def onread(&blk)
|
9
|
+
blk.call(@memory)
|
10
|
+
@onread << blk
|
11
|
+
end
|
12
|
+
|
13
|
+
def onread_call(chunk)
|
14
|
+
@memory << chunk
|
15
|
+
super(chunk)
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,90 @@
|
|
1
|
+
class Nodectl::Stream::Websocket
|
2
|
+
URI_LOG = /\A\/logs\/(?<service_name>[\w_-]+)\/(?<log_name>\w+)\Z/
|
3
|
+
URI_ACTION = /\A\/actions\/(?<action_id>\w+)\Z/
|
4
|
+
URI_EVENTS = /\A\/events\Z/
|
5
|
+
|
6
|
+
def initialize(host, port)
|
7
|
+
EM::WebSocket.run(host: host, port: port) do |ws|
|
8
|
+
Nodectl.logger.info("websocket: socket created")
|
9
|
+
ws.onopen { |handshake| connection_open(ws, handshake) }
|
10
|
+
end
|
11
|
+
|
12
|
+
callbacks_init
|
13
|
+
end
|
14
|
+
|
15
|
+
def connection_open(ws, handshake)
|
16
|
+
Nodectl.logger.info("websocket: connection opened with url '#{handshake.path}'")
|
17
|
+
|
18
|
+
case handshake.path
|
19
|
+
when URI_LOG
|
20
|
+
create_log_session(ws, handshake)
|
21
|
+
when URI_ACTION
|
22
|
+
create_action_session(ws, handshake)
|
23
|
+
when URI_EVENTS
|
24
|
+
create_events_session(ws, handshake)
|
25
|
+
else
|
26
|
+
ws.close
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def callbacks_init
|
31
|
+
EM.add_periodic_timer(0.5, method(:tick))
|
32
|
+
|
33
|
+
Nodectl::Instance.onadd do |instance|
|
34
|
+
Nodectl::Stream::EventsSession.publish('instance', instance.pid, 'started')
|
35
|
+
end
|
36
|
+
|
37
|
+
Nodectl::Instance.ondelete do |instance, options|
|
38
|
+
Nodectl::Stream::EventsSession.publish('instance', instance.pid, 'stopped', options)
|
39
|
+
end
|
40
|
+
|
41
|
+
Nodectl::Action.onadd do |action|
|
42
|
+
Nodectl::Stream::EventsSession.publish('action', action.id, 'started')
|
43
|
+
end
|
44
|
+
|
45
|
+
Nodectl::Action.onkill do |action, options|
|
46
|
+
Nodectl::Stream::EventsSession.publish('action', action.id, 'stopped', options)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def create_log_session(ws, handshake)
|
53
|
+
match = URI_LOG.match(handshake.path)
|
54
|
+
service = Nodectl::Service.find!(match[:service_name])
|
55
|
+
logfile = service.recipe_binding.get_logfile(match[:log_name])
|
56
|
+
file = Nodectl::PromisedFile.new(logfile, Nodectl::Stream::File, end_offset: 5000, infinity: true)
|
57
|
+
session = Nodectl::Stream::FileSession.new(ws, handshake, file)
|
58
|
+
|
59
|
+
Nodectl.logger.info("websocket: log session open succeed: #{logfile}")
|
60
|
+
rescue StandardError => e
|
61
|
+
Nodectl.logger.info("websocket: log session open failed: #{e.message}")
|
62
|
+
ws.close
|
63
|
+
end
|
64
|
+
|
65
|
+
def create_action_session(ws, handshake)
|
66
|
+
match = URI_ACTION.match(handshake.path)
|
67
|
+
action = Nodectl::Action.find!(match[:action_id].to_i)
|
68
|
+
session = Nodectl::Stream::FileSession.new(ws, handshake, action.output)
|
69
|
+
|
70
|
+
Nodectl.logger.info("websocket: action session open succeed: action ##{action.id}")
|
71
|
+
rescue StandardError => e
|
72
|
+
Nodectl.logger.info("websocket: action session open failed: #{e.message}")
|
73
|
+
ws.close
|
74
|
+
end
|
75
|
+
|
76
|
+
def create_events_session(ws, handshake)
|
77
|
+
session = Nodectl::Stream::EventsSession.new(ws, handshake)
|
78
|
+
|
79
|
+
Nodectl.logger.info("websocket: events session open succeed")
|
80
|
+
rescue StandardError => e
|
81
|
+
Nodectl.logger.info("websocket: events session open failed: #{e.message}")
|
82
|
+
ws.close
|
83
|
+
end
|
84
|
+
|
85
|
+
def tick
|
86
|
+
Nodectl::Stream::FileSession.all.each do |session|
|
87
|
+
session.file.read_chunk
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Nodectl::Watchdog
|
2
|
+
TICK_INTERVAL = 2
|
3
|
+
|
4
|
+
def initialize
|
5
|
+
@timer = EM.add_periodic_timer(TICK_INTERVAL, method(:tick))
|
6
|
+
end
|
7
|
+
|
8
|
+
def tick
|
9
|
+
Nodectl::Instance.all.each do |instance|
|
10
|
+
instance.alive?
|
11
|
+
end
|
12
|
+
|
13
|
+
Nodectl::Process.all.each do |process|
|
14
|
+
process.alive?
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Nodectl::Webapp::Actions < Nodectl::Webapp::Base
|
2
|
+
get '/' do
|
3
|
+
json actions: Nodectl::Action.all.map(&:as_json)
|
4
|
+
end
|
5
|
+
|
6
|
+
get '/:id' do
|
7
|
+
json action: Nodectl::Action.find!(params[:id]).as_json
|
8
|
+
end
|
9
|
+
|
10
|
+
post '/' do
|
11
|
+
service = Nodectl::Service.find(params[:action][:service_id])
|
12
|
+
|
13
|
+
if service
|
14
|
+
action = service.recipe_binding.run_action(params[:action][:name].to_sym, params[:action][:params] || {})
|
15
|
+
json action: action.as_json
|
16
|
+
else
|
17
|
+
json errors: {service_id: "service not found"}
|
18
|
+
status 422
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class Nodectl::Webapp::Base < Sinatra::Base
|
2
|
+
helpers Sinatra::JSON
|
3
|
+
|
4
|
+
before do
|
5
|
+
params.merge! json_body_params
|
6
|
+
end
|
7
|
+
|
8
|
+
helpers do
|
9
|
+
def json_body_params
|
10
|
+
@json_body_params ||= begin
|
11
|
+
MultiJson.load(request.body.read.to_s, symbolize_keys: true)
|
12
|
+
rescue MultiJson::LoadError
|
13
|
+
{}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
configure do
|
19
|
+
set :threaded, false
|
20
|
+
set :show_exceptions, :after_handler
|
21
|
+
enable :logging
|
22
|
+
end
|
23
|
+
|
24
|
+
error StandardError do
|
25
|
+
exception = env['sinatra.error']
|
26
|
+
backtrace = [exception.inspect] + exception.backtrace
|
27
|
+
Nodectl.logger.error('webapp error - ' + backtrace.join("\n from"))
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
class Nodectl::Webapp::Instances < Nodectl::Webapp::Base
|
2
|
+
get '/' do
|
3
|
+
if params[:ids]
|
4
|
+
select_id = params[:ids].map(&:to_i)
|
5
|
+
instances = Nodectl::Instance.all.select { |i| select_id.include?(i.pid) }
|
6
|
+
else
|
7
|
+
instances = Nodectl::Instance.all
|
8
|
+
end
|
9
|
+
|
10
|
+
json instances: instances.map(&:as_json)
|
11
|
+
end
|
12
|
+
|
13
|
+
get '/:pid' do
|
14
|
+
instance = Nodectl::Instance.find!(params[:pid].to_i)
|
15
|
+
json instance: instance.as_json
|
16
|
+
end
|
17
|
+
|
18
|
+
post '/' do
|
19
|
+
service = Nodectl::Service.find(params[:instance][:service_id])
|
20
|
+
|
21
|
+
if service
|
22
|
+
instance = service.recipe_binding.run_slot :start, params[:instance][:options]
|
23
|
+
status 201
|
24
|
+
json instance: instance.as_json
|
25
|
+
else
|
26
|
+
json errors: {service_name: "service not found"}
|
27
|
+
status 422
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
put '/:pid' do
|
32
|
+
instance = Nodectl::Instance.find!(params[:pid].to_i)
|
33
|
+
|
34
|
+
if params[:instance][:status] == "stopping"
|
35
|
+
if params[:instance][:force_stop]
|
36
|
+
instance.stop(force: true)
|
37
|
+
else
|
38
|
+
instance.stop
|
39
|
+
end
|
40
|
+
elsif params[:instance][:status] == "restarting"
|
41
|
+
instance.restart(force: true)
|
42
|
+
end
|
43
|
+
|
44
|
+
json instance: instance.as_json
|
45
|
+
end
|
46
|
+
end
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,39 @@
|
|
1
|
+
class Nodectl::Webapp::Services < Nodectl::Webapp::Base
|
2
|
+
get '/' do
|
3
|
+
json services: Nodectl::Service.all.map(&:as_json)
|
4
|
+
end
|
5
|
+
|
6
|
+
get '/:name' do
|
7
|
+
service = Nodectl::Service.find!(params[:name])
|
8
|
+
json service: service.as_json
|
9
|
+
end
|
10
|
+
|
11
|
+
put '/:name' do
|
12
|
+
service = Nodectl::Service.find!(params[:name])
|
13
|
+
|
14
|
+
if params[:service][:slot]
|
15
|
+
service.recipe_binding.run_slot params[:service][:slot].to_sym, params[:service][:slot_params]
|
16
|
+
end
|
17
|
+
|
18
|
+
binding = service.recipe_binding
|
19
|
+
|
20
|
+
if !params[:service][:version_id].nil? &&
|
21
|
+
binding.supported_slots.include?(:version_set) &&
|
22
|
+
binding.supported_slots.include?(:version)
|
23
|
+
current = binding.run_slot(:version)["id"]
|
24
|
+
target = params[:service][:version_id]
|
25
|
+
|
26
|
+
if current != target
|
27
|
+
binding.run_slot(:version_set, {id: target})
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
json service: service.as_json
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def service_params
|
37
|
+
params[:service] || {}
|
38
|
+
end
|
39
|
+
end
|