nutella_framework 0.3.1 → 0.4.0
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 +4 -4
- data/Gemfile +3 -0
- data/README.md +3 -4
- data/VERSION +1 -1
- data/data/startup +3 -3
- data/framework_components/example_framework_interface/dandelion-flowers-card.jpg +0 -0
- data/framework_components/example_framework_interface/index.html +18 -0
- data/framework_components/main_interface/main_interface_bot.rb +183 -0
- data/framework_components/main_interface/public/index.html +54 -0
- data/framework_components/main_interface/views/index.erb +63 -0
- data/framework_components/order.json.example +6 -0
- data/framework_components/runs_list_bot/app_runs_list_bot.rb +15 -0
- data/lib/{core/commands → commands}/broker.rb +2 -2
- data/lib/{core/commands → commands}/checkup.rb +2 -2
- data/lib/commands/compile.rb +13 -0
- data/lib/commands/dependencies.rb +13 -0
- data/lib/commands/help.rb +35 -0
- data/lib/{core/commands → commands}/install.rb +19 -15
- data/lib/commands/meta/command.rb +19 -0
- data/lib/commands/meta/run_command.rb +114 -0
- data/lib/{core → commands/meta}/template_command.rb +1 -1
- data/lib/commands/new.rb +60 -0
- data/lib/commands/runs.rb +54 -0
- data/lib/commands/start.rb +321 -0
- data/lib/commands/stop.rb +101 -0
- data/lib/{core/commands → commands}/template.rb +59 -39
- data/lib/config/current_app_utils.rb +51 -0
- data/lib/config/persisted_hash.rb +14 -12
- data/lib/config/runlist.rb +116 -16
- data/lib/{cli → core}/nutella_cli.rb +1 -1
- data/lib/core/nutella_core.rb +2 -6
- data/lib/nutella_framework.rb +5 -3
- data/lib/nutella_lib_framework/api.rb +333 -0
- data/lib/tmux/tmux.rb +76 -0
- data/nutella_framework.gemspec +42 -29
- data/test/commands/test_cmd_cli_params_parsing.rb +56 -0
- data/test/commands/test_command_template.rb +31 -0
- data/test/config/test_current_app_utils.rb +34 -0
- data/test/config/test_persisted_hash.rb +48 -0
- data/test/config/test_runlist.rb +15 -23
- data/test/framework_apis/test_framework_api.rb +74 -0
- metadata +74 -27
- data/actors/main_interface/main_interface_bot.rb +0 -163
- data/actors/main_interface/public/index.html +0 -51
- data/actors/main_interface/views/index.erb +0 -45
- data/lib/config/current_project.rb +0 -58
- data/lib/core/command.rb +0 -12
- data/lib/core/commands/compile.rb +0 -21
- data/lib/core/commands/dependencies.rb +0 -21
- data/lib/core/commands/help.rb +0 -28
- data/lib/core/commands/new.rb +0 -60
- data/lib/core/commands/runs.rb +0 -52
- data/lib/core/commands/start.rb +0 -271
- data/lib/core/commands/stop.rb +0 -100
- data/lib/core/run_command.rb +0 -106
- data/lib/core/tmux.rb +0 -38
- data/test/config/test_config.rb +0 -48
- data/test/config/test_project.rb +0 -34
- data/test/test_run_command.rb +0 -54
- /data/{actors → framework_components}/main_interface/startup +0 -0
- /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
|
data/lib/commands/new.rb
ADDED
@@ -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
|