nutella_framework 0.3.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +3 -0
  3. data/README.md +3 -4
  4. data/VERSION +1 -1
  5. data/data/startup +3 -3
  6. data/framework_components/example_framework_interface/dandelion-flowers-card.jpg +0 -0
  7. data/framework_components/example_framework_interface/index.html +18 -0
  8. data/framework_components/main_interface/main_interface_bot.rb +183 -0
  9. data/framework_components/main_interface/public/index.html +54 -0
  10. data/framework_components/main_interface/views/index.erb +63 -0
  11. data/framework_components/order.json.example +6 -0
  12. data/framework_components/runs_list_bot/app_runs_list_bot.rb +15 -0
  13. data/lib/{core/commands → commands}/broker.rb +2 -2
  14. data/lib/{core/commands → commands}/checkup.rb +2 -2
  15. data/lib/commands/compile.rb +13 -0
  16. data/lib/commands/dependencies.rb +13 -0
  17. data/lib/commands/help.rb +35 -0
  18. data/lib/{core/commands → commands}/install.rb +19 -15
  19. data/lib/commands/meta/command.rb +19 -0
  20. data/lib/commands/meta/run_command.rb +114 -0
  21. data/lib/{core → commands/meta}/template_command.rb +1 -1
  22. data/lib/commands/new.rb +60 -0
  23. data/lib/commands/runs.rb +54 -0
  24. data/lib/commands/start.rb +321 -0
  25. data/lib/commands/stop.rb +101 -0
  26. data/lib/{core/commands → commands}/template.rb +59 -39
  27. data/lib/config/current_app_utils.rb +51 -0
  28. data/lib/config/persisted_hash.rb +14 -12
  29. data/lib/config/runlist.rb +116 -16
  30. data/lib/{cli → core}/nutella_cli.rb +1 -1
  31. data/lib/core/nutella_core.rb +2 -6
  32. data/lib/nutella_framework.rb +5 -3
  33. data/lib/nutella_lib_framework/api.rb +333 -0
  34. data/lib/tmux/tmux.rb +76 -0
  35. data/nutella_framework.gemspec +42 -29
  36. data/test/commands/test_cmd_cli_params_parsing.rb +56 -0
  37. data/test/commands/test_command_template.rb +31 -0
  38. data/test/config/test_current_app_utils.rb +34 -0
  39. data/test/config/test_persisted_hash.rb +48 -0
  40. data/test/config/test_runlist.rb +15 -23
  41. data/test/framework_apis/test_framework_api.rb +74 -0
  42. metadata +74 -27
  43. data/actors/main_interface/main_interface_bot.rb +0 -163
  44. data/actors/main_interface/public/index.html +0 -51
  45. data/actors/main_interface/views/index.erb +0 -45
  46. data/lib/config/current_project.rb +0 -58
  47. data/lib/core/command.rb +0 -12
  48. data/lib/core/commands/compile.rb +0 -21
  49. data/lib/core/commands/dependencies.rb +0 -21
  50. data/lib/core/commands/help.rb +0 -28
  51. data/lib/core/commands/new.rb +0 -60
  52. data/lib/core/commands/runs.rb +0 -52
  53. data/lib/core/commands/start.rb +0 -271
  54. data/lib/core/commands/stop.rb +0 -100
  55. data/lib/core/run_command.rb +0 -106
  56. data/lib/core/tmux.rb +0 -38
  57. data/test/config/test_config.rb +0 -48
  58. data/test/config/test_project.rb +0 -34
  59. data/test/test_run_command.rb +0 -54
  60. /data/{actors → framework_components}/main_interface/startup +0 -0
  61. /data/{actors → framework_components}/main_interface/views/not_found_404.erb +0 -0
@@ -0,0 +1,114 @@
1
+ require 'commands/meta/command'
2
+ require 'slop'
3
+
4
+ module Nutella
5
+ # This class describes a run command which can be either start or stop.
6
+ # It is mostly a commodity class for code reuse.
7
+ class RunCommand < Command
8
+
9
+ def run (args=nil)
10
+ console.error 'Running the generic RunCommand!!! WAT? https://www.destroyallsoftware.com/talks/wat'
11
+ end
12
+
13
+
14
+ # Extracts the run_id from the parameters passed to the command line
15
+ # @param [Array<String>] args command line arguments passed to the command
16
+ # @return [String, String ] the run_id
17
+ def parse_run_id_from( args )
18
+ # Simple `nutella start/stop` (no args)
19
+ if args.nil? || args.empty?
20
+ return 'default'
21
+ end
22
+ # If the first argument is a parameter, set the run_id to default
23
+ if args[0].start_with? '-'
24
+ run_id = 'default'
25
+ else
26
+ # If the first argument is a run_id, check that it's not 'default' and return it
27
+ # and shift the arguments so we are left with only the parameters in args
28
+ run_id = args[0]
29
+ raise StandardError.new 'Unfortunately you can\'t use `default` as a run_id because it is reserved :(' if run_id=='default'
30
+ args.shift
31
+ end
32
+ run_id
33
+ end
34
+
35
+
36
+ # Parse the command line parameters
37
+ # @param [Array<String>] args command line arguments passed to the command
38
+ # @return [Hash] an hash containing the parameters
39
+ def parse_cli_parameters( args )
40
+ begin
41
+ opts = Slop::Options.new
42
+ opts.array '-wo', '--without', 'A list of components NOT to start'
43
+ opts.array '-w', '--with', 'A list of components that needs to be started'
44
+ parser = Slop::Parser.new(opts)
45
+ result = parser.parse(args)
46
+ result.to_hash
47
+ rescue
48
+ raise StandardError.new 'The only supported parameters are --with (-w) and --without (-wo)'
49
+ end
50
+ end
51
+
52
+
53
+ # Returns all the components in a certain directory
54
+ def components_in_dir( dir )
55
+ Dir.entries(dir).select {|entry| File.directory?(File.join(dir, entry)) && !(entry =='.' || entry == '..') }
56
+ end
57
+
58
+
59
+ # Executes a code block for each component in a certain directory
60
+ # @param [String] dir directory where we are iterating
61
+ # @yield [component] Passes the component name to the block
62
+ def for_each_component_in_dir( dir, &block )
63
+ components_in_dir(dir).each do |component|
64
+ block.call component
65
+ end
66
+ end
67
+
68
+
69
+ # Runs a script for each bot in a certain directory.
70
+ # Message is displayed in case something goes wrong
71
+ def run_script_for_all_bots_in( dir, script, message )
72
+ for_each_component_in_dir dir do |bot|
73
+ # Skip bot if there is no script
74
+ next unless File.exist? "#{dir}/bots/#{bot}/#{script}"
75
+ # Output message
76
+ console.info "#{message} bot #{bot}."
77
+ # Execute 'script' script
78
+ cur_dir = Dir.pwd
79
+ Dir.chdir "#{dir}/bots/#{bot}"
80
+ system "./#{script}"
81
+ Dir.chdir cur_dir
82
+ end
83
+ true
84
+ end
85
+
86
+
87
+ # Prints a success message if the command completes correctly
88
+ def print_success_message(app_id, run_id, action)
89
+ if run_id == 'default'
90
+ console.success "Application #{app_id} #{action}!"
91
+ else
92
+ console.success "Application #{app_id}, run #{run_id} #{action}!"
93
+ end
94
+ end
95
+
96
+
97
+ def compile_and_dependencies( script , in_progress_message, complete_message)
98
+ # If the current directory is not a nutella application, return
99
+ unless Nutella.current_app.exist?
100
+ console.warn 'The current directory is not a nutella application'
101
+ return
102
+ end
103
+
104
+ # Run script
105
+ return unless run_script_for_all_bots_in( Dir.pwd, script, in_progress_message )
106
+
107
+ # Output success message
108
+ console.success "All #{complete_message} for #{Nutella.current_app.config['name']}"
109
+ end
110
+
111
+
112
+ end
113
+
114
+ end
@@ -1,4 +1,4 @@
1
- require 'core/command'
1
+ require 'commands/meta/command'
2
2
  require 'json'
3
3
 
4
4
  module Nutella
@@ -0,0 +1,60 @@
1
+ require 'commands/meta/command'
2
+ require 'fileutils'
3
+
4
+ module Nutella
5
+ class New < Command
6
+ @description = 'Creates a new nutella application'
7
+
8
+ def run(args=nil)
9
+ app_id = args[0]
10
+
11
+ # If no other arguments, show help and quit here
12
+ if args.empty?
13
+ console.warn 'You need to specify a name for your new application'
14
+ return
15
+ end
16
+
17
+ # Check that a directory (i.e. an app) with the same name doesn't already exist
18
+ # If it does it looks into it to see if there is a nutella.json file and displays
19
+ # the proper error message
20
+ if File.directory? app_id
21
+ if File.exist? "#{app_id}/nutella.json"
22
+ console.warn "An application named #{app_id} already exists"
23
+ return
24
+ else
25
+ console.warn "A directory named #{app_id} already exists"
26
+ return
27
+ end
28
+ end
29
+
30
+ # If all seems good, generate the application skeleton
31
+ create_dir_structure app_id
32
+
33
+ # Display a nice success message and return
34
+ console.success "Your new nutella application #{app_id} is ready!"
35
+ end
36
+
37
+
38
+ private
39
+
40
+
41
+ def create_dir_structure( app_id )
42
+ # Create directories
43
+ FileUtils.mkdir_p("#{app_id}/bots")
44
+ FileUtils.mkdir_p("#{app_id}/interfaces")
45
+ # Create nutella.json hash
46
+ config_file_hash = {
47
+ :name => app_id,
48
+ :version => '0.1.0',
49
+ :nutella_version => File.open("#{Nutella::NUTELLA_HOME}VERSION", 'rb').read,
50
+ :type => 'application',
51
+ :description => 'A quick description of your application'
52
+ }
53
+ # Write nutella.json hash
54
+ File.open("#{app_id}/nutella.json", 'w') do |f|
55
+ f.write(JSON.pretty_generate(config_file_hash))
56
+ end
57
+ end
58
+
59
+ end
60
+ end
@@ -0,0 +1,54 @@
1
+ require 'commands/meta/command'
2
+
3
+
4
+ module Nutella
5
+ class Runs < Command
6
+ @description = 'Displays a list of runs for the current application or all applications'
7
+
8
+ def run(args=nil)
9
+
10
+ # If invoked with "all" it will show all the runs under this instance of nutella
11
+ if args[0]=='--all' || args[0]=='-a'
12
+ display_all_runs
13
+ else
14
+ # If the current directory is not a nutella application, return
15
+ unless Nutella.current_app.exist?
16
+ console.warn 'The current directory is not a nutella application'
17
+ return
18
+ end
19
+ # Display list of runs for current nutella application
20
+ display_app_runs
21
+ end
22
+ end
23
+
24
+
25
+ private
26
+
27
+
28
+ def display_all_runs
29
+ if Nutella.runlist.empty?
30
+ console.info 'You are not running any nutella apps'
31
+ else
32
+ console.info 'Currently running:'
33
+ Nutella.runlist.all_runs.each do |app_id, _|
34
+ console.info "#{app_id}:"
35
+ Nutella.runlist.runs_for_app(app_id).each do |run_id|
36
+ console.info " #{run_id}"
37
+ end
38
+ end
39
+ end
40
+ end
41
+
42
+ def display_app_runs
43
+ app_id = Nutella.current_app.config['name']
44
+ app_runs = Nutella.runlist.runs_for_app app_id
45
+ console.info "Currently running #{app_runs.length} instances of app '#{app_id}':"
46
+ app_runs.each do |run_id|
47
+ console.info " #{run_id}"
48
+ end
49
+ end
50
+
51
+
52
+ end
53
+ end
54
+
@@ -0,0 +1,321 @@
1
+ require 'commands/meta/run_command'
2
+ require 'tmux/tmux'
3
+
4
+ module Nutella
5
+ class Start < RunCommand
6
+ @description = 'Starts all or some of the bots in the current application'
7
+
8
+ def run(args=nil)
9
+
10
+ # If the current directory is not a nutella application, return
11
+ unless Nutella.current_app.exist?
12
+ console.warn 'The current directory is not a nutella application'
13
+ return
14
+ end
15
+
16
+ begin
17
+ run_id, params = parse_cli args
18
+ rescue StandardError => e
19
+ console.error e.message
20
+ return
21
+ end
22
+
23
+ app_id, app_path = fetch_app_details
24
+
25
+ if no_bot_to_start(app_id, app_path, params)
26
+ console.warn "Run #{run} not created: your application bots are already started and you specified no regular bots exclusively for this run"
27
+ return
28
+ end
29
+
30
+ return unless add_to_run_list( app_id, run_id, app_path )
31
+
32
+ return unless start_all_components(app_id, app_path, run_id, params)
33
+
34
+ # Output messages
35
+ print_confirmation(run_id, params, app_id, app_path)
36
+ end
37
+
38
+
39
+
40
+ private
41
+
42
+
43
+ # Parses command line arguments
44
+ def parse_cli( args )
45
+ # Parse run_id
46
+ run_id = parse_run_id_from args
47
+ # Extract parameters
48
+ params = parse_cli_parameters args
49
+ # Check that we are not using 'with' and 'without' options at the same time
50
+ unless params[:with].empty? || params[:without].empty?
51
+ raise StandardError.new 'You can\'t use both --with and --without at the same time'
52
+ end
53
+ return run_id, params
54
+ end
55
+
56
+
57
+ # Fetches the app_id and app_path
58
+ def fetch_app_details
59
+ # Extract app_id
60
+ app_id = Nutella.current_app.config['name']
61
+ return app_id, Dir.pwd
62
+ end
63
+
64
+
65
+ # Returns true if both the list of run level bots is empty and the app bots
66
+ # have been started already
67
+ def no_bot_to_start(app_id, app_path, params)
68
+ return run_level_bots_list(app_path, params).empty? && app_bots_started?(app_id)
69
+ end
70
+
71
+
72
+ # Returns the list of run-level bots for this run
73
+ # Depending on the mode we are in, we want to start only some bots, exclude only some bots or start all bots
74
+ def run_level_bots_list( app_path, params )
75
+ # Fetch the list of all components in the bots dir
76
+ all_bots = components_in_dir "#{app_path}/bots/"
77
+ # Fetch the list of app bots
78
+ app_bots = Nutella.current_app.config['app_bots']
79
+ # Return correct list based on the mode we are in
80
+ case start_mode(params)
81
+ when :WITH
82
+ return get_with_bots_list params[:with], app_bots
83
+ when :WO
84
+ return get_wo_bots_list all_bots, app_bots, params[:without]
85
+ when :ALL
86
+ return get_all_bots_list all_bots, app_bots
87
+ else
88
+ # If we get here it means we are both in with and without mode and something went very wrong...
89
+ raise 'You are using simultaneously with and without modes. This should not happen. Please contact developers.'
90
+ end
91
+ end
92
+
93
+ def start_mode(params)
94
+ return :WITH unless params[:with].empty?
95
+ return :WO unless params[:without].empty?
96
+ :ALL if params[:with].empty? && params[:without].empty?
97
+ end
98
+
99
+ # If we are in "with mode", we want to run only the bots in the "with list" (minus the ones in app_bots_list)
100
+ def get_with_bots_list( incl_bots, app_bots)
101
+ return app_bots.nil? ? incl_bots : incl_bots - app_bots
102
+ end
103
+
104
+ # If we are in "without mode", we want to run all the bots in the bots_list minus the ones in the "without list" and in the "app bots list"
105
+ def get_wo_bots_list( all_bots, app_bots, excl_bots )
106
+ return app_bots.nil? ? all_bots - excl_bots : all_bots - excl_bots - app_bots
107
+ end
108
+
109
+ # If we are in "all mode", we want to run all the bots minus the ones in the "app bots list"
110
+ def get_all_bots_list( all_bots, app_bots )
111
+ return app_bots.nil? ? all_bots : all_bots - app_bots
112
+ end
113
+
114
+
115
+
116
+ # Returns true if the app bots have been started already
117
+ def app_bots_started?( app_id )
118
+ Tmux.session_exist? Tmux.app_bot_session_name app_id
119
+ end
120
+
121
+
122
+ # Check that the run is unique and add it to the list of runs
123
+ # If it's not, return (without adding the run to the list of course)
124
+ def add_to_run_list( app_id, run_id, prj_dir )
125
+ unless Nutella.runlist.add?(app_id, run_id, prj_dir)
126
+ # If the run_id is already in the list, check that it's actually live
127
+ if Tmux.session_exist? Tmux.session_name(app_id, run_id)
128
+ console.error 'Impossible to start nutella app: an instance of this app with the same run_id is already running!'
129
+ console.error "You might want to kill it with 'nutella stop #{run_id}'"
130
+ return false
131
+ end
132
+ end
133
+ true
134
+ end
135
+
136
+
137
+ # Starts all the components at all levels for this run
138
+ def start_all_components( app_id, app_path, run_id, params )
139
+ # Start the internal broker
140
+ return false unless start_internal_broker
141
+ # Start all framework-level components (if needed)
142
+ return false unless start_framework_components
143
+ # Start all app-level bots (if any, if needed)
144
+ return false unless start_app_bots(app_id, app_path)
145
+ # Start all run-level bots
146
+ false unless start_run_bots( app_path, app_id, run_id, params )
147
+ end
148
+
149
+
150
+ def start_internal_broker
151
+ pid_file_path = "#{Nutella.config['broker_dir']}bin/.pid"
152
+ # Check if the process with pid indicated in the pidfile is alive
153
+ return true if sanitize_pid_file pid_file_path
154
+ # Check that broker is not running 'unsupervised' (i.e. check port 1883), if it is, return
155
+ return true unless broker_port_free?
156
+ # Broker is not running and there is no pid file so we try to start
157
+ # the internal broker and create a new pid file. Note that the pid file is created by
158
+ # the `startup` script, not here.
159
+ pid = fork
160
+ exec("#{Nutella.config['broker_dir']}/startup") if pid.nil?
161
+ # Wait a bit to give the chance to the broker to actually start up
162
+ sleep 1
163
+ # All went well so we return true
164
+ true
165
+ end
166
+
167
+
168
+ # Cleans the pid file of a given process
169
+ # @param [String] pid_file_path the file storing the pid file of the process
170
+ # @return [Boolean] true if the pid file exists AND the process with that pid is still alive
171
+ def sanitize_pid_file( pid_file_path )
172
+ # Does the pid file exist?
173
+ # If it does we try to see if the process with that pid is still alive
174
+ if File.exist? pid_file_path
175
+ pid_file = File.open(pid_file_path, 'rb')
176
+ pid = pid_file.read.to_i
177
+ pid_file.close
178
+ begin
179
+ # If this statement doesn't throw an exception then a process with
180
+ # this pid is still alive so we do nothing and just return true
181
+ Process.getpgid pid
182
+ return true
183
+ rescue
184
+ # If there is an exception, there is no process with this pid
185
+ # so we have a stale pid file that we need to remove
186
+ File.delete pid_file_path
187
+ return false
188
+ end
189
+ end
190
+ # If there is no pid file, there is no process running
191
+ false
192
+ end
193
+
194
+
195
+ # Checks if port 1883 (MQTT broker port) is free
196
+ # or some other service is already listening on it
197
+ def broker_port_free?
198
+ begin
199
+ s = TCPServer.new('0.0.0.0', 1883)
200
+ s.close
201
+ rescue
202
+ return false
203
+ end
204
+ true
205
+ end
206
+
207
+
208
+ def start_framework_components
209
+ nutella_components_dir = "#{Nutella::NUTELLA_HOME}framework_components"
210
+ if File.exist? "#{nutella_components_dir}/order.json"
211
+ components_list = JSON.parse IO.read "#{nutella_components_dir}/order.json"
212
+ else
213
+ components_list = components_in_dir(nutella_components_dir)
214
+ end
215
+ components_list.each do |component|
216
+ if File.exist? "#{nutella_components_dir}/#{component}/startup"
217
+ unless start_framework_component "#{nutella_components_dir}/#{component}"
218
+ return false
219
+ end
220
+ end
221
+ end
222
+ true
223
+ end
224
+
225
+
226
+ def start_framework_component( component_dir )
227
+ pid_file_path = "#{component_dir}/.pid"
228
+ return true if sanitize_pid_file pid_file_path
229
+ # Component is not running and there is no pid file so we try to start it
230
+ # and create a new pid file. Note that the pid file is created by
231
+ # the startup script!
232
+ nutella_config_file = "#{Nutella.config['config_dir']}config.json"
233
+ runs_list_file = "#{Nutella.config['config_dir']}runlist.json"
234
+ if nutella_config_file==nil || runs_list_file==nil
235
+ return false
236
+ end
237
+ # We are passing the configuration file and the run list files paths to the framework components
238
+ command = "#{component_dir}/startup #{nutella_config_file} #{runs_list_file}"
239
+ pid = fork
240
+ exec(command) if pid.nil?
241
+ # All went well so we return true
242
+ true
243
+ end
244
+
245
+
246
+ def start_app_bots( app_id, app_path )
247
+ app_bots_list = Nutella.current_app.config['app_bots']
248
+ bots_dir = "#{app_path}/bots/"
249
+ # If app bots have been started already, then do nothing
250
+ unless Tmux.session_exist? Tmux.app_bot_session_name app_id
251
+ # Start all app bots in the list into a new tmux session
252
+ tmux = Tmux.new app_id, nil
253
+ for_each_component_in_dir bots_dir do |bot|
254
+ unless app_bots_list.nil? || !app_bots_list.include?( bot )
255
+ # If there is no 'startup' script output a warning (because
256
+ # startup is mandatory) and skip the bot
257
+ unless File.exist?("#{bots_dir}#{bot}/startup")
258
+ console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
259
+ next
260
+ end
261
+ # Create a new window in the session for this run
262
+ tmux.new_app_bot_window bot
263
+ end
264
+ end
265
+ end
266
+ true
267
+ end
268
+
269
+
270
+ def start_run_bots( app_path, app_id, run_id, params )
271
+ # Create a new tmux instance for this run
272
+ tmux = Tmux.new app_id, run_id
273
+ # Fetch bots dir
274
+ bots_dir = "#{app_path}/bots/"
275
+ # Start the appropriate bots
276
+ run_level_bots_list( app_path, params ).each { |bot| start_run_level_bot(bots_dir, bot, tmux) }
277
+ true
278
+ end
279
+
280
+ # Starts a run level bot
281
+ def start_run_level_bot( bots_dir, bot, tmux )
282
+ # If there is no 'startup' script output a warning (because
283
+ # startup is mandatory) and skip the bot
284
+ unless File.exist?("#{bots_dir}#{bot}/startup")
285
+ console.warn "Impossible to start bot #{bot}. Couldn't locate 'startup' script."
286
+ return
287
+ end
288
+ # Create a new window in the session for this run
289
+ tmux.new_bot_window bot
290
+ end
291
+
292
+
293
+ def print_confirmation( run_id, params, app_id, app_path )
294
+ # If there are no run-level bots to start, do not create the run and error out
295
+ if run_level_bots_list(app_path, params).empty?
296
+ console.warn 'This run doesn\'t seem to have any components. No run was created.'
297
+ return
298
+ end
299
+ print_success_message(app_id, run_id, 'started')
300
+ print_monitoring_details(app_id, run_id)
301
+ end
302
+
303
+
304
+ def print_monitoring_details( app_id, run_id )
305
+ # Output broker info
306
+ console.success "Application is running on broker: #{Nutella.config['broker']}"
307
+ # If some application bots were started, say it
308
+ app_bots_list = Nutella.current_app.config['app_bots']
309
+ unless app_bots_list.nil? || app_bots_list.empty?
310
+ console.success "Do `tmux attach-session -t #{Tmux.app_bot_session_name(app_id)}` to monitor your app bots."
311
+ end
312
+ # Output rest of monitoring info
313
+ console.success "Do `tmux attach-session -t #{Tmux.session_name(app_id,run_id)}` to monitor your bots."
314
+ console.success "Go to http://localhost:#{Nutella.config['main_interface_port']}/#{app_id}/#{run_id} to access your interfaces"
315
+ end
316
+
317
+
318
+ end
319
+
320
+ end
321
+
@@ -0,0 +1,101 @@
1
+ require 'commands/meta/run_command'
2
+ require 'tmux/tmux'
3
+
4
+ module Nutella
5
+ class Stop < RunCommand
6
+ @description = 'Stops all the bots in the current application'
7
+
8
+ def run(args=nil)
9
+
10
+ # If the current directory is not a nutella application, return
11
+ unless Nutella.current_app.exist?
12
+ console.warn 'The current directory is not a nutella application'
13
+ return
14
+ end
15
+
16
+ # Extract run (passed run name) and run_id
17
+ run_id = parse_run_id_from args
18
+ app_id = Nutella.current_app.config['name']
19
+
20
+ # Check that the specified run exists in the list and, if it doesn't, return
21
+ return unless remove_from_run_list app_id, run_id
22
+
23
+ # Stops all run-level bots
24
+ Tmux.kill_run_session app_id, run_id
25
+
26
+ # Stop all app-level bots (if any, if needed)
27
+ stop_app_bots app_id
28
+
29
+ # Stop all framework-level components (if needed)
30
+ if Nutella.runlist.empty?
31
+ stop_framework_components
32
+ end
33
+
34
+ # If running on the internal broker, stop it if needed
35
+ if Nutella.runlist.empty?
36
+ stop_internal_broker
37
+ end
38
+
39
+ # Output success message
40
+ print_success_message(app_id, run_id, 'stopped')
41
+ end
42
+
43
+
44
+ private
45
+
46
+
47
+ def remove_from_run_list( app_id, run_id )
48
+ unless Nutella.runlist.delete? app_id, run_id
49
+ console.warn "Run #{run_id} doesn't exist. Impossible to stop it."
50
+ return false
51
+ end
52
+ true
53
+ end
54
+
55
+
56
+ def stop_app_bots( app_id )
57
+ tmux_session_name = Tmux.app_bot_session_name app_id
58
+ if Tmux.session_exist? tmux_session_name
59
+ # Are there any run of this app hinging on the app bots?
60
+ if Nutella.runlist.runs_for_app(app_id).empty?
61
+ Tmux.kill_app_session app_id
62
+ end
63
+ end
64
+ true
65
+ end
66
+
67
+
68
+ def stop_framework_components
69
+ nutella_components_dir = "#{Nutella::NUTELLA_HOME}framework_components"
70
+ for_each_component_in_dir nutella_components_dir do |component|
71
+ pid_file_path = "#{nutella_components_dir}/#{component}/.pid"
72
+ kill_process_with_pid pid_file_path
73
+ end
74
+ end
75
+
76
+
77
+ def stop_internal_broker
78
+ pid_file_path = "#{Nutella.config['broker_dir']}/bin/.pid"
79
+ kill_process_with_pid pid_file_path
80
+ end
81
+
82
+
83
+ # Does the process pid file exist?
84
+ # If it does we send a SIGKILL to the process with that pid
85
+ # to stop the process and delete the pid file
86
+ def kill_process_with_pid( pid_file_path )
87
+ if File.exist? pid_file_path
88
+ pid_file = File.open( pid_file_path, 'rb' )
89
+ pid = pid_file.read.to_i
90
+ pid_file.close
91
+ begin
92
+ Process.kill( 'SIGKILL', pid )
93
+ rescue
94
+ # Pid file exists but process is dead. Do nothing
95
+ end
96
+ File.delete pid_file_path
97
+ end
98
+ end
99
+
100
+ end
101
+ end